Skip to content

Commit

Permalink
fix(immutable-data): ignore casting when evaluating the expressions (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaStevens authored Mar 25, 2024
1 parent 1c1f086 commit 50e789a
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 19 deletions.
41 changes: 23 additions & 18 deletions src/rules/immutable-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
isMemberExpression,
isNewExpression,
isObjectConstructorType,
isTSAsExpression,
} from "#eslint-plugin-functional/utils/type-guards";

/**
Expand Down Expand Up @@ -361,62 +362,66 @@ function checkUpdateExpression(
* a mutator method call.
*/
function isInChainCallAndFollowsNew(
node: TSESTree.MemberExpression,
node: TSESTree.Expression,
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
): boolean {
if (isMemberExpression(node)) {
return isInChainCallAndFollowsNew(node.object, context);
}

if (isTSAsExpression(node)) {
return isInChainCallAndFollowsNew(node.expression, context);
}

// Check for: [0, 1, 2]
if (isArrayExpression(node.object)) {
if (isArrayExpression(node)) {
return true;
}

// Check for: new Array()
if (
isNewExpression(node.object) &&
isArrayConstructorType(getTypeOfNode(node.object.callee, context))
isNewExpression(node) &&
isArrayConstructorType(getTypeOfNode(node.callee, context))
) {
return true;
}

if (
isCallExpression(node.object) &&
isMemberExpression(node.object.callee) &&
isIdentifier(node.object.callee.property)
isCallExpression(node) &&
isMemberExpression(node.callee) &&
isIdentifier(node.callee.property)
) {
// Check for: Array.from(iterable)
if (
arrayConstructorFunctions.some(
isExpected(node.object.callee.property.name),
) &&
isArrayConstructorType(getTypeOfNode(node.object.callee.object, context))
arrayConstructorFunctions.some(isExpected(node.callee.property.name)) &&
isArrayConstructorType(getTypeOfNode(node.callee.object, context))
) {
return true;
}

// Check for: array.slice(0)
if (
arrayNewObjectReturningMethods.some(
isExpected(node.object.callee.property.name),
)
arrayNewObjectReturningMethods.some(isExpected(node.callee.property.name))
) {
return true;
}

// Check for: Object.entries(object)
if (
objectConstructorNewObjectReturningMethods.some(
isExpected(node.object.callee.property.name),
isExpected(node.callee.property.name),
) &&
isObjectConstructorType(getTypeOfNode(node.object.callee.object, context))
isObjectConstructorType(getTypeOfNode(node.callee.object, context))
) {
return true;
}

// Check for: "".split("")
if (
stringConstructorNewObjectReturningMethods.some(
isExpected(node.object.callee.property.name),
isExpected(node.callee.property.name),
) &&
getTypeOfNode(node.object.callee.object, context).isStringLiteral()
getTypeOfNode(node.callee.object, context).isStringLiteral()
) {
return true;
}
Expand Down
5 changes: 4 additions & 1 deletion src/utils/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
isMemberExpression,
isPrivateIdentifier,
isThisExpression,
isTSAsExpression,
isTSTypeAnnotation,
isUnaryExpression,
isVariableDeclaration,
Expand Down Expand Up @@ -72,7 +73,9 @@ function getNodeIdentifierText(
? context.sourceCode
.getText(node.typeAnnotation as TSESTree.Node)
.replaceAll(/\s+/gmu, "")
: null;
: isTSAsExpression(node)
? getNodeIdentifierText(node.expression, context)
: null;

if (identifierText !== null) {
return identifierText;
Expand Down
6 changes: 6 additions & 0 deletions src/utils/type-guards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ export function isTSArrayType(
return node.type === AST_NODE_TYPES.TSArrayType;
}

export function isTSAsExpression(
node: TSESTree.Node,
): node is TSESTree.TSAsExpression {
return node.type === AST_NODE_TYPES.TSAsExpression;
}

export function isTSFunctionType(
node: TSESTree.Node,
): node is TSESTree.TSFunctionType {
Expand Down
14 changes: 14 additions & 0 deletions tests/rules/immutable-data/ts/array/invalid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,20 @@ const tests: Array<
},
],
},
{
code: dedent`
(mutable_foo as string[]).sort();
`,
optionsSet: [[]],
errors: [
{
messageId: "array",
type: AST_NODE_TYPES.CallExpression,
line: 1,
column: 1,
},
],
},
];

export default tests;
12 changes: 12 additions & 0 deletions tests/rules/immutable-data/ts/array/valid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,18 @@ const tests: Array<ValidTestCaseSet<OptionsOf<typeof rule>>> = [
`,
optionsSet: [[{ ignoreImmediateMutation: true }]],
},
{
code: dedent`
(mutable_foo as string[]).sort();
`,
optionsSet: [[{ ignoreAccessorPattern: "mutable*" }]],
},
{
code: dedent`
([a, b, c] as string[]).sort();
`,
optionsSet: [[{ ignoreImmediateMutation: true }]],
},
];

export default tests;
14 changes: 14 additions & 0 deletions tests/rules/immutable-data/ts/object/invalid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,20 @@ const tests: Array<
},
],
},
{
code: dedent`
(mutable_foo as Bar).baz = "hello world";
`,
optionsSet: [[]],
errors: [
{
messageId: "generic",
type: AST_NODE_TYPES.AssignmentExpression,
line: 1,
column: 1,
},
],
},
];

export default tests;
6 changes: 6 additions & 0 deletions tests/rules/immutable-data/ts/object/valid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@ const tests: Array<ValidTestCaseSet<OptionsOf<typeof rule>>> = [
`,
optionsSet: [[{ ignoreNonConstDeclarations: true }]],
},
{
code: dedent`
(mutable_foo as Bar).baz = "hello world";
`,
optionsSet: [[{ ignoreAccessorPattern: "mutable*.*" }]],
},
];

export default tests;

0 comments on commit 50e789a

Please sign in to comment.