From e1eac8312268d1855a2ed7784b4d190ecb9c9fa4 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 18 Jan 2021 22:45:11 -0800 Subject: [PATCH] fix(scope-manager): fix incorrect handling of class decorators and class method default params (#2943) Fixes #2941 Fixes #2942 This was a regression introduced by #2751 --- .../tests/eslint-rules/no-undef.test.ts | 11 ++ .../tests/rules/no-use-before-define.test.ts | 14 ++ .../src/referencer/ClassVisitor.ts | 17 +- .../class/declaration/method-param-default.ts | 13 ++ .../declaration/method-param-default.ts.shot | 164 ++++++++++++++++++ .../class-deco-with-object-param.ts | 10 ++ .../class-deco-with-object-param.ts.shot | 147 ++++++++++++++++ 7 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 packages/scope-manager/tests/fixtures/class/declaration/method-param-default.ts create mode 100644 packages/scope-manager/tests/fixtures/class/declaration/method-param-default.ts.shot create mode 100644 packages/scope-manager/tests/fixtures/decorators/class-deco-with-object-param.ts create mode 100644 packages/scope-manager/tests/fixtures/decorators/class-deco-with-object-param.ts.shot diff --git a/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts b/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts index 58baeeeaeac..c7cd5aeb399 100644 --- a/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts +++ b/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts @@ -224,6 +224,17 @@ function Foo() {} ` const x = 1 as const; `, + // https://github.com/typescript-eslint/typescript-eslint/issues/2942 + ` +declare function deco(...param: any): (...param: any) => any; + +@deco({ + components: { + val: true, + }, +}) +class Foo {} + `, ], invalid: [ { diff --git a/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts b/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts index 8fb4d076602..e585b66e3ac 100644 --- a/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts +++ b/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts @@ -428,6 +428,20 @@ export class CidrIpPatternDirective implements Validator {} }, ], }, + // https://github.com/typescript-eslint/typescript-eslint/issues/2941 + ` +class A { + constructor(printName) { + this.printName = printName; + } + + openPort(printerName = this.printerName) { + this.tscOcx.ActiveXopenport(printerName); + + return this; + } +} + `, ], invalid: [ { diff --git a/packages/scope-manager/src/referencer/ClassVisitor.ts b/packages/scope-manager/src/referencer/ClassVisitor.ts index ff758a3ae8f..9f9315fa411 100644 --- a/packages/scope-manager/src/referencer/ClassVisitor.ts +++ b/packages/scope-manager/src/referencer/ClassVisitor.ts @@ -33,6 +33,15 @@ class ClassVisitor extends Visitor { classVisitor.visitClass(node); } + visit(node: TSESTree.Node | null | undefined): void { + // make sure we only handle the nodes we are designed to handle + if (node && node.type in this) { + super.visit(node); + } else { + this.#referencer.visit(node); + } + } + /////////////////// // Visit helpers // /////////////////// @@ -46,7 +55,7 @@ class ClassVisitor extends Visitor { .defineIdentifier(node.id, new ClassNameDefinition(node.id, node)); } - node.decorators?.forEach(d => this.visit(d)); + node.decorators?.forEach(d => this.#referencer.visit(d)); this.#referencer.scopeManager.nestClassScope(node); @@ -298,6 +307,12 @@ class ClassVisitor extends Visitor { // Visit selectors // ///////////////////// + protected ClassBody(node: TSESTree.ClassBody): void { + // this is here on purpose so that this visitor explicitly declares visitors + // for all nodes it cares about (see the instance visit method above) + this.visitChildren(node); + } + protected ClassProperty(node: TSESTree.ClassProperty): void { this.visitClassProperty(node); } diff --git a/packages/scope-manager/tests/fixtures/class/declaration/method-param-default.ts b/packages/scope-manager/tests/fixtures/class/declaration/method-param-default.ts new file mode 100644 index 00000000000..55ec4d36c9b --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/declaration/method-param-default.ts @@ -0,0 +1,13 @@ +// https://github.com/typescript-eslint/typescript-eslint/issues/2941 + +class A { + constructor(printName) { + this.printName = printName; + } + + openPort(printerName = this.printerName) { + this.tscOcx.ActiveXopenport(printerName); + + return this; + } +} diff --git a/packages/scope-manager/tests/fixtures/class/declaration/method-param-default.ts.shot b/packages/scope-manager/tests/fixtures/class/declaration/method-param-default.ts.shot new file mode 100644 index 00000000000..9b1cfa22790 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/declaration/method-param-default.ts.shot @@ -0,0 +1,164 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`class declaration method-param-default 1`] = ` +ScopeManager { + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2 { + defs: Array [ + ClassNameDefinition$1 { + name: Identifier<"A">, + node: ClassDeclaration$1, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$3 { + defs: Array [ + ClassNameDefinition$2 { + name: Identifier<"A">, + node: ClassDeclaration$1, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$4 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$5 { + defs: Array [ + ParameterDefinition$3 { + name: Identifier<"printName">, + node: FunctionExpression$2, + }, + ], + name: "printName", + references: Array [ + Reference$1 { + identifier: Identifier<"printName">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$5, + }, + ], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$6 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$7 { + defs: Array [ + ParameterDefinition$4 { + name: Identifier<"printerName">, + node: FunctionExpression$3, + }, + ], + name: "printerName", + references: Array [ + Reference$2 { + identifier: Identifier<"printerName">, + init: true, + isRead: false, + isTypeReference: false, + isValueReference: true, + isWrite: true, + resolved: Variable$7, + writeExpr: MemberExpression$4, + }, + Reference$3 { + identifier: Identifier<"printerName">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$7, + }, + ], + isValueVariable: true, + isTypeVariable: false, + }, + ], + scopes: Array [ + GlobalScope$1 { + block: Program$5, + isStrict: false, + references: Array [], + set: Map { + "const" => ImplicitGlobalConstTypeVariable, + "A" => Variable$2, + }, + type: "global", + upper: null, + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2, + ], + }, + ClassScope$2 { + block: ClassDeclaration$1, + isStrict: true, + references: Array [], + set: Map { + "A" => Variable$3, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$3, + ], + }, + FunctionScope$3 { + block: FunctionExpression$2, + isStrict: true, + references: Array [ + Reference$1, + ], + set: Map { + "arguments" => Variable$4, + "printName" => Variable$5, + }, + type: "function", + upper: ClassScope$2, + variables: Array [ + Variable$4, + Variable$5, + ], + }, + FunctionScope$4 { + block: FunctionExpression$3, + isStrict: true, + references: Array [ + Reference$2, + Reference$3, + ], + set: Map { + "arguments" => Variable$6, + "printerName" => Variable$7, + }, + type: "function", + upper: ClassScope$2, + variables: Array [ + Variable$6, + Variable$7, + ], + }, + ], +} +`; diff --git a/packages/scope-manager/tests/fixtures/decorators/class-deco-with-object-param.ts b/packages/scope-manager/tests/fixtures/decorators/class-deco-with-object-param.ts new file mode 100644 index 00000000000..492104207e2 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/decorators/class-deco-with-object-param.ts @@ -0,0 +1,10 @@ +// https://github.com/typescript-eslint/typescript-eslint/issues/2942 + +declare function deco(...param: any): (...param: any) => any; + +@deco({ + components: { + val: true, + }, +}) +class Foo {} diff --git a/packages/scope-manager/tests/fixtures/decorators/class-deco-with-object-param.ts.shot b/packages/scope-manager/tests/fixtures/decorators/class-deco-with-object-param.ts.shot new file mode 100644 index 00000000000..144c0baf14f --- /dev/null +++ b/packages/scope-manager/tests/fixtures/decorators/class-deco-with-object-param.ts.shot @@ -0,0 +1,147 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`decorators class-deco-with-object-param 1`] = ` +ScopeManager { + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2 { + defs: Array [ + FunctionNameDefinition$1 { + name: Identifier<"deco">, + node: TSDeclareFunction$1, + }, + ], + name: "deco", + references: Array [ + Reference$1 { + identifier: Identifier<"deco">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + ], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$3 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$4 { + defs: Array [ + ParameterDefinition$2 { + name: Identifier<"param">, + node: TSDeclareFunction$1, + }, + ], + name: "param", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$5 { + defs: Array [ + ParameterDefinition$3 { + name: Identifier<"param">, + node: TSFunctionType$2, + }, + ], + name: "param", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$6 { + defs: Array [ + ClassNameDefinition$4 { + name: Identifier<"Foo">, + node: ClassDeclaration$3, + }, + ], + name: "Foo", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$7 { + defs: Array [ + ClassNameDefinition$5 { + name: Identifier<"Foo">, + node: ClassDeclaration$3, + }, + ], + name: "Foo", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + ], + scopes: Array [ + GlobalScope$1 { + block: Program$4, + isStrict: false, + references: Array [ + Reference$1, + ], + set: Map { + "const" => ImplicitGlobalConstTypeVariable, + "deco" => Variable$2, + "Foo" => Variable$6, + }, + type: "global", + upper: null, + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2, + Variable$6, + ], + }, + FunctionScope$2 { + block: TSDeclareFunction$1, + isStrict: false, + references: Array [], + set: Map { + "arguments" => Variable$3, + "param" => Variable$4, + }, + type: "function", + upper: GlobalScope$1, + variables: Array [ + Variable$3, + Variable$4, + ], + }, + FunctionTypeScope$3 { + block: TSFunctionType$2, + isStrict: true, + references: Array [], + set: Map { + "param" => Variable$5, + }, + type: "functionType", + upper: FunctionScope$2, + variables: Array [ + Variable$5, + ], + }, + ClassScope$4 { + block: ClassDeclaration$3, + isStrict: true, + references: Array [], + set: Map { + "Foo" => Variable$7, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$7, + ], + }, + ], +} +`;