diff --git a/packages/eslint-plugin/tests/eslint-rules/prefer-const.test.ts b/packages/eslint-plugin/tests/eslint-rules/prefer-const.test.ts index 9bd707aedf20..574c0707c064 100644 --- a/packages/eslint-plugin/tests/eslint-rules/prefer-const.test.ts +++ b/packages/eslint-plugin/tests/eslint-rules/prefer-const.test.ts @@ -21,5 +21,17 @@ let x: number | undefined = 1; let x: number | undefined = 1; (x as number) += 1; `, + ` +let x: number | undefined = 1; +x!++; + `, + ` +let x: number | undefined = 1; +(x)++; + `, + ` +let x: number | undefined = 1; +(x as number)++; + `, ], }); diff --git a/packages/scope-manager/src/referencer/Referencer.ts b/packages/scope-manager/src/referencer/Referencer.ts index 1cf0868c7452..06c19b67e455 100644 --- a/packages/scope-manager/src/referencer/Referencer.ts +++ b/packages/scope-manager/src/referencer/Referencer.ts @@ -325,17 +325,7 @@ class Referencer extends Visitor { } protected AssignmentExpression(node: TSESTree.AssignmentExpression): void { - let left = node.left; - switch (left.type) { - case AST_NODE_TYPES.TSAsExpression: - case AST_NODE_TYPES.TSTypeAssertion: - // explicitly visit the type annotation - this.visitType(left.typeAnnotation); - // intentional fallthrough - case AST_NODE_TYPES.TSNonNullExpression: - // unwrap the expression - left = left.expression; - } + const left = this.visitExpressionTarget(node.left); if (PatternVisitor.isPattern(left)) { if (node.operator === '=') { @@ -752,8 +742,10 @@ class Referencer extends Visitor { } protected UpdateExpression(node: TSESTree.UpdateExpression): void { - if (PatternVisitor.isPattern(node.argument)) { - this.visitPattern(node.argument, pattern => { + const argument = this.visitExpressionTarget(node.argument); + + if (PatternVisitor.isPattern(argument)) { + this.visitPattern(argument, pattern => { this.currentScope().referenceValue( pattern, ReferenceFlag.ReadWrite, @@ -811,6 +803,21 @@ class Referencer extends Visitor { this.close(node); } + + private visitExpressionTarget(left: TSESTree.Node) { + switch (left.type) { + case AST_NODE_TYPES.TSAsExpression: + case AST_NODE_TYPES.TSTypeAssertion: + // explicitly visit the type annotation + this.visitType(left.typeAnnotation); + // intentional fallthrough + case AST_NODE_TYPES.TSNonNullExpression: + // unwrap the expression + left = left.expression; + } + + return left; + } } export { Referencer, type ReferencerOptions }; diff --git a/packages/scope-manager/tests/fixtures/type-assertion/increment/angle-bracket-increment.ts b/packages/scope-manager/tests/fixtures/type-assertion/increment/angle-bracket-increment.ts new file mode 100644 index 000000000000..a1227c1c1aad --- /dev/null +++ b/packages/scope-manager/tests/fixtures/type-assertion/increment/angle-bracket-increment.ts @@ -0,0 +1,2 @@ +let x: number | undefined = 1; +(x)++; diff --git a/packages/scope-manager/tests/fixtures/type-assertion/increment/angle-bracket-increment.ts.shot b/packages/scope-manager/tests/fixtures/type-assertion/increment/angle-bracket-increment.ts.shot new file mode 100644 index 000000000000..c85d3ddb0422 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/type-assertion/increment/angle-bracket-increment.ts.shot @@ -0,0 +1,62 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`type-assertion increment angle-bracket-increment 1`] = ` +ScopeManager { + variables: [ + ImplicitGlobalConstTypeVariable, + Variable$2 { + defs: [ + VariableDefinition$1 { + name: Identifier<"x">, + node: VariableDeclarator$1, + }, + ], + name: "x", + references: [ + Reference$1 { + identifier: Identifier<"x">, + init: true, + isRead: false, + isTypeReference: false, + isValueReference: true, + isWrite: true, + resolved: Variable$2, + writeExpr: Literal$2, + }, + Reference$2 { + identifier: Identifier<"x">, + init: false, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: true, + resolved: Variable$2, + writeExpr: null, + }, + ], + isValueVariable: true, + isTypeVariable: false, + }, + ], + scopes: [ + GlobalScope$1 { + block: Program$3, + isStrict: false, + references: [ + Reference$1, + Reference$2, + ], + set: Map { + "const" => ImplicitGlobalConstTypeVariable, + "x" => Variable$2, + }, + type: "global", + upper: null, + variables: [ + ImplicitGlobalConstTypeVariable, + Variable$2, + ], + }, + ], +} +`; diff --git a/packages/scope-manager/tests/fixtures/type-assertion/increment/as-increment.ts b/packages/scope-manager/tests/fixtures/type-assertion/increment/as-increment.ts new file mode 100644 index 000000000000..4f19cee47715 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/type-assertion/increment/as-increment.ts @@ -0,0 +1,2 @@ +let x: number | undefined = 1; +(x as number)++; diff --git a/packages/scope-manager/tests/fixtures/type-assertion/increment/as-increment.ts.shot b/packages/scope-manager/tests/fixtures/type-assertion/increment/as-increment.ts.shot new file mode 100644 index 000000000000..f3437982f7d6 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/type-assertion/increment/as-increment.ts.shot @@ -0,0 +1,62 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`type-assertion increment as-increment 1`] = ` +ScopeManager { + variables: [ + ImplicitGlobalConstTypeVariable, + Variable$2 { + defs: [ + VariableDefinition$1 { + name: Identifier<"x">, + node: VariableDeclarator$1, + }, + ], + name: "x", + references: [ + Reference$1 { + identifier: Identifier<"x">, + init: true, + isRead: false, + isTypeReference: false, + isValueReference: true, + isWrite: true, + resolved: Variable$2, + writeExpr: Literal$2, + }, + Reference$2 { + identifier: Identifier<"x">, + init: false, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: true, + resolved: Variable$2, + writeExpr: null, + }, + ], + isValueVariable: true, + isTypeVariable: false, + }, + ], + scopes: [ + GlobalScope$1 { + block: Program$3, + isStrict: false, + references: [ + Reference$1, + Reference$2, + ], + set: Map { + "const" => ImplicitGlobalConstTypeVariable, + "x" => Variable$2, + }, + type: "global", + upper: null, + variables: [ + ImplicitGlobalConstTypeVariable, + Variable$2, + ], + }, + ], +} +`; diff --git a/packages/scope-manager/tests/fixtures/type-assertion/increment/non-null-increment.ts b/packages/scope-manager/tests/fixtures/type-assertion/increment/non-null-increment.ts new file mode 100644 index 000000000000..3b088705a342 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/type-assertion/increment/non-null-increment.ts @@ -0,0 +1,2 @@ +let x: number | undefined = 1; +x!++; diff --git a/packages/scope-manager/tests/fixtures/type-assertion/increment/non-null-increment.ts.shot b/packages/scope-manager/tests/fixtures/type-assertion/increment/non-null-increment.ts.shot new file mode 100644 index 000000000000..e37ac428db14 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/type-assertion/increment/non-null-increment.ts.shot @@ -0,0 +1,62 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`type-assertion increment non-null-increment 1`] = ` +ScopeManager { + variables: [ + ImplicitGlobalConstTypeVariable, + Variable$2 { + defs: [ + VariableDefinition$1 { + name: Identifier<"x">, + node: VariableDeclarator$1, + }, + ], + name: "x", + references: [ + Reference$1 { + identifier: Identifier<"x">, + init: true, + isRead: false, + isTypeReference: false, + isValueReference: true, + isWrite: true, + resolved: Variable$2, + writeExpr: Literal$2, + }, + Reference$2 { + identifier: Identifier<"x">, + init: false, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: true, + resolved: Variable$2, + writeExpr: null, + }, + ], + isValueVariable: true, + isTypeVariable: false, + }, + ], + scopes: [ + GlobalScope$1 { + block: Program$3, + isStrict: false, + references: [ + Reference$1, + Reference$2, + ], + set: Map { + "const" => ImplicitGlobalConstTypeVariable, + "x" => Variable$2, + }, + type: "global", + upper: null, + variables: [ + ImplicitGlobalConstTypeVariable, + Variable$2, + ], + }, + ], +} +`;