Skip to content

Commit fa68492

Browse files
authored
fix(scope-manager): fix assertion assignments not being marked as write references (typescript-eslint#2809)
Fixes typescript-eslint#2804
1 parent 14bdc2e commit fa68492

File tree

9 files changed

+253
-6
lines changed

9 files changed

+253
-6
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import rule from 'eslint/lib/rules/prefer-const';
2+
import { RuleTester } from '../RuleTester';
3+
4+
const ruleTester = new RuleTester({
5+
parser: '@typescript-eslint/parser',
6+
});
7+
8+
ruleTester.run('prefer-const', rule, {
9+
valid: [
10+
`
11+
let x: number | undefined = 1;
12+
x! += 1;
13+
`,
14+
`
15+
let x: number | undefined = 1;
16+
(<number>x) += 1;
17+
`,
18+
`
19+
let x: number | undefined = 1;
20+
(x as number) += 1;
21+
`,
22+
],
23+
invalid: [],
24+
});

packages/eslint-plugin/typings/eslint-rules.d.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ declare module 'eslint/lib/rules/space-infix-ops' {
797797
'missingSpace',
798798
[
799799
{
800-
int32Hint: boolean;
800+
int32Hint?: boolean;
801801
},
802802
],
803803
{
@@ -812,6 +812,25 @@ declare module 'eslint/lib/rules/space-infix-ops' {
812812
export = rule;
813813
}
814814

815+
declare module 'eslint/lib/rules/prefer-const' {
816+
import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
817+
818+
const rule: TSESLint.RuleModule<
819+
'useConst',
820+
[
821+
{
822+
destructuring?: 'any' | 'all';
823+
ignoreReadBeforeAssign?: boolean;
824+
},
825+
],
826+
{
827+
'Program:exit'(node: TSESTree.Program): void;
828+
VariableDeclaration(node: TSESTree.VariableDeclaration): void;
829+
}
830+
>;
831+
export = rule;
832+
}
833+
815834
declare module 'eslint/lib/rules/utils/ast-utils' {
816835
import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
817836

packages/scope-manager/src/referencer/Referencer.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -386,10 +386,22 @@ class Referencer extends Visitor {
386386
}
387387

388388
protected AssignmentExpression(node: TSESTree.AssignmentExpression): void {
389-
if (PatternVisitor.isPattern(node.left)) {
389+
let left = node.left;
390+
switch (left.type) {
391+
case AST_NODE_TYPES.TSAsExpression:
392+
case AST_NODE_TYPES.TSTypeAssertion:
393+
// explicitly visit the type annotation
394+
this.visit(left.typeAnnotation);
395+
// intentional fallthrough
396+
case AST_NODE_TYPES.TSNonNullExpression:
397+
// unwrap the expression
398+
left = left.expression;
399+
}
400+
401+
if (PatternVisitor.isPattern(left)) {
390402
if (node.operator === '=') {
391403
this.visitPattern(
392-
node.left,
404+
left,
393405
(pattern, info) => {
394406
const maybeImplicitGlobal = !this.currentScope().isStrict
395407
? {
@@ -413,15 +425,15 @@ class Referencer extends Visitor {
413425
},
414426
{ processRightHandNodes: true },
415427
);
416-
} else if (node.left.type === AST_NODE_TYPES.Identifier) {
428+
} else if (left.type === AST_NODE_TYPES.Identifier) {
417429
this.currentScope().referenceValue(
418-
node.left,
430+
left,
419431
ReferenceFlag.ReadWrite,
420432
node.right,
421433
);
422434
}
423435
} else {
424-
this.visit(node.left);
436+
this.visit(left);
425437
}
426438
this.visit(node.right);
427439
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
let x: number | undefined = 1;
2+
(<number>x) += 1;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`type-assertion assignment angle-bracket-assignment 1`] = `
4+
ScopeManager {
5+
variables: Array [
6+
ImplicitGlobalConstTypeVariable,
7+
Variable$2 {
8+
defs: Array [
9+
VariableDefinition$1 {
10+
name: Identifier<"x">,
11+
node: VariableDeclarator$1,
12+
},
13+
],
14+
name: "x",
15+
references: Array [
16+
Reference$1 {
17+
identifier: Identifier<"x">,
18+
init: true,
19+
isRead: false,
20+
isTypeReference: false,
21+
isValueReference: true,
22+
isWrite: true,
23+
resolved: Variable$2,
24+
writeExpr: Literal$2,
25+
},
26+
Reference$2 {
27+
identifier: Identifier<"x">,
28+
init: false,
29+
isRead: true,
30+
isTypeReference: false,
31+
isValueReference: true,
32+
isWrite: true,
33+
resolved: Variable$2,
34+
writeExpr: Literal$3,
35+
},
36+
],
37+
isValueVariable: true,
38+
isTypeVariable: false,
39+
},
40+
],
41+
scopes: Array [
42+
GlobalScope$1 {
43+
block: Program$4,
44+
isStrict: false,
45+
references: Array [
46+
Reference$1,
47+
Reference$2,
48+
],
49+
set: Map {
50+
"const" => ImplicitGlobalConstTypeVariable,
51+
"x" => Variable$2,
52+
},
53+
type: "global",
54+
upper: null,
55+
variables: Array [
56+
ImplicitGlobalConstTypeVariable,
57+
Variable$2,
58+
],
59+
},
60+
],
61+
}
62+
`;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
let x: number | undefined = 1;
2+
(x as number) += 1;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`type-assertion assignment as-assignment 1`] = `
4+
ScopeManager {
5+
variables: Array [
6+
ImplicitGlobalConstTypeVariable,
7+
Variable$2 {
8+
defs: Array [
9+
VariableDefinition$1 {
10+
name: Identifier<"x">,
11+
node: VariableDeclarator$1,
12+
},
13+
],
14+
name: "x",
15+
references: Array [
16+
Reference$1 {
17+
identifier: Identifier<"x">,
18+
init: true,
19+
isRead: false,
20+
isTypeReference: false,
21+
isValueReference: true,
22+
isWrite: true,
23+
resolved: Variable$2,
24+
writeExpr: Literal$2,
25+
},
26+
Reference$2 {
27+
identifier: Identifier<"x">,
28+
init: false,
29+
isRead: true,
30+
isTypeReference: false,
31+
isValueReference: true,
32+
isWrite: true,
33+
resolved: Variable$2,
34+
writeExpr: Literal$3,
35+
},
36+
],
37+
isValueVariable: true,
38+
isTypeVariable: false,
39+
},
40+
],
41+
scopes: Array [
42+
GlobalScope$1 {
43+
block: Program$4,
44+
isStrict: false,
45+
references: Array [
46+
Reference$1,
47+
Reference$2,
48+
],
49+
set: Map {
50+
"const" => ImplicitGlobalConstTypeVariable,
51+
"x" => Variable$2,
52+
},
53+
type: "global",
54+
upper: null,
55+
variables: Array [
56+
ImplicitGlobalConstTypeVariable,
57+
Variable$2,
58+
],
59+
},
60+
],
61+
}
62+
`;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
let x: number | undefined = 1;
2+
x! += 1;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`type-assertion assignment non-null-assignment 1`] = `
4+
ScopeManager {
5+
variables: Array [
6+
ImplicitGlobalConstTypeVariable,
7+
Variable$2 {
8+
defs: Array [
9+
VariableDefinition$1 {
10+
name: Identifier<"x">,
11+
node: VariableDeclarator$1,
12+
},
13+
],
14+
name: "x",
15+
references: Array [
16+
Reference$1 {
17+
identifier: Identifier<"x">,
18+
init: true,
19+
isRead: false,
20+
isTypeReference: false,
21+
isValueReference: true,
22+
isWrite: true,
23+
resolved: Variable$2,
24+
writeExpr: Literal$2,
25+
},
26+
Reference$2 {
27+
identifier: Identifier<"x">,
28+
init: false,
29+
isRead: true,
30+
isTypeReference: false,
31+
isValueReference: true,
32+
isWrite: true,
33+
resolved: Variable$2,
34+
writeExpr: Literal$3,
35+
},
36+
],
37+
isValueVariable: true,
38+
isTypeVariable: false,
39+
},
40+
],
41+
scopes: Array [
42+
GlobalScope$1 {
43+
block: Program$4,
44+
isStrict: false,
45+
references: Array [
46+
Reference$1,
47+
Reference$2,
48+
],
49+
set: Map {
50+
"const" => ImplicitGlobalConstTypeVariable,
51+
"x" => Variable$2,
52+
},
53+
type: "global",
54+
upper: null,
55+
variables: Array [
56+
ImplicitGlobalConstTypeVariable,
57+
Variable$2,
58+
],
59+
},
60+
],
61+
}
62+
`;

0 commit comments

Comments
 (0)