From da0883a5668f9e8064c03587190061e24859ffde Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Sat, 2 May 2020 19:43:37 -0700 Subject: [PATCH] feat: add a scope when a type has generics --- packages/scope-manager/src/ScopeManager.ts | 56 ++- .../src/referencer/Referencer.ts | 467 +++++++++--------- .../src/referencer/TypeReferencer.ts | 23 +- .../scope-manager/src/referencer/Visitor.ts | 2 +- packages/scope-manager/src/scope/Scope.ts | 2 + packages/scope-manager/src/scope/ScopeBase.ts | 5 +- packages/scope-manager/src/scope/ScopeType.ts | 1 + packages/scope-manager/src/scope/TypeScope.ts | 21 + packages/scope-manager/src/scope/index.ts | 1 + .../tests/types/reference-type.test.ts | 66 +-- .../tests/types/variable-definition.test.ts | 31 +- 11 files changed, 353 insertions(+), 322 deletions(-) create mode 100644 packages/scope-manager/src/scope/TypeScope.ts diff --git a/packages/scope-manager/src/ScopeManager.ts b/packages/scope-manager/src/ScopeManager.ts index d128d339cada..dbd5f405c7b4 100644 --- a/packages/scope-manager/src/ScopeManager.ts +++ b/packages/scope-manager/src/ScopeManager.ts @@ -11,6 +11,7 @@ import { ModuleScope, Scope, SwitchScope, + TypeScope, WithScope, } from './scope'; @@ -144,23 +145,19 @@ class ScopeManager { return scope; } - public nestGlobalScope(node: GlobalScope['block']): Scope { - return this.nestScope(new GlobalScope(this, node)); - } - public nestBlockScope(node: BlockScope['block']): Scope { assert(this.currentScope); return this.nestScope(new BlockScope(this, this.currentScope, node)); } - public nestFunctionScope( - node: FunctionScope['block'], - isMethodDefinition: boolean, - ): Scope { + public nestCatchScope(node: CatchScope['block']): Scope { assert(this.currentScope); - return this.nestScope( - new FunctionScope(this, this.currentScope, node, isMethodDefinition), - ); + return this.nestScope(new CatchScope(this, this.currentScope, node)); + } + + public nestClassScope(node: ClassScope['block']): Scope { + assert(this.currentScope); + return this.nestScope(new ClassScope(this, this.currentScope, node)); } public nestForScope(node: ForScope['block']): Scope { @@ -168,19 +165,32 @@ class ScopeManager { return this.nestScope(new ForScope(this, this.currentScope, node)); } - public nestCatchScope(node: CatchScope['block']): Scope { + public nestFunctionExpressionNameScope( + node: FunctionExpressionNameScope['block'], + ): Scope { assert(this.currentScope); - return this.nestScope(new CatchScope(this, this.currentScope, node)); + return this.nestScope( + new FunctionExpressionNameScope(this, this.currentScope, node), + ); } - public nestWithScope(node: WithScope['block']): Scope { + public nestFunctionScope( + node: FunctionScope['block'], + isMethodDefinition: boolean, + ): Scope { assert(this.currentScope); - return this.nestScope(new WithScope(this, this.currentScope, node)); + return this.nestScope( + new FunctionScope(this, this.currentScope, node, isMethodDefinition), + ); } - public nestClassScope(node: ClassScope['block']): Scope { + public nestGlobalScope(node: GlobalScope['block']): Scope { + return this.nestScope(new GlobalScope(this, node)); + } + + public nestModuleScope(node: ModuleScope['block']): Scope { assert(this.currentScope); - return this.nestScope(new ClassScope(this, this.currentScope, node)); + return this.nestScope(new ModuleScope(this, this.currentScope, node)); } public nestSwitchScope(node: SwitchScope['block']): Scope { @@ -188,18 +198,14 @@ class ScopeManager { return this.nestScope(new SwitchScope(this, this.currentScope, node)); } - public nestModuleScope(node: ModuleScope['block']): Scope { + public nestTypeScope(node: TypeScope['block']): Scope { assert(this.currentScope); - return this.nestScope(new ModuleScope(this, this.currentScope, node)); + return this.nestScope(new TypeScope(this, this.currentScope, node)); } - public nestFunctionExpressionNameScope( - node: FunctionExpressionNameScope['block'], - ): Scope { + public nestWithScope(node: WithScope['block']): Scope { assert(this.currentScope); - return this.nestScope( - new FunctionExpressionNameScope(this, this.currentScope, node), - ); + return this.nestScope(new WithScope(this, this.currentScope, node)); } } diff --git a/packages/scope-manager/src/referencer/Referencer.ts b/packages/scope-manager/src/referencer/Referencer.ts index 89a04e6fe752..77e3f5cc0395 100644 --- a/packages/scope-manager/src/referencer/Referencer.ts +++ b/packages/scope-manager/src/referencer/Referencer.ts @@ -45,29 +45,27 @@ function traverseIdentifierInPattern( type ReferencerOptions = VisitorOptions; // Referencing variables and creating bindings. class Referencer extends Visitor { - isInnerMethodDefinition: boolean; - options: ReferencerOptions; - scopeManager: ScopeManager; - parent: TSESTree.Node | null; + private isInnerMethodDefinition: boolean; + public readonly options: ReferencerOptions; + public readonly scopeManager: ScopeManager; constructor(options: ReferencerOptions, scopeManager: ScopeManager) { super(null, options); this.options = options; this.scopeManager = scopeManager; - this.parent = null; this.isInnerMethodDefinition = false; } - currentScope(): Scope; - currentScope(throwOnNull: true): Scope | null; - currentScope(dontThrowOnNull?: true): Scope | null { + public currentScope(): Scope; + public currentScope(throwOnNull: true): Scope | null; + public currentScope(dontThrowOnNull?: true): Scope | null { if (!dontThrowOnNull) { assert(this.scopeManager.currentScope); } return this.scopeManager.currentScope; } - close(node: TSESTree.Node): void { + public close(node: TSESTree.Node): void { while (this.currentScope(true) && node === this.currentScope().block) { this.scopeManager.currentScope = this.currentScope().close( this.scopeManager, @@ -75,18 +73,20 @@ class Referencer extends Visitor { } } - pushInnerMethodDefinition(isInnerMethodDefinition: boolean): boolean { + private pushInnerMethodDefinition(isInnerMethodDefinition: boolean): boolean { const previous = this.isInnerMethodDefinition; this.isInnerMethodDefinition = isInnerMethodDefinition; return previous; } - popInnerMethodDefinition(isInnerMethodDefinition: boolean | undefined): void { + private popInnerMethodDefinition( + isInnerMethodDefinition: boolean | undefined, + ): void { this.isInnerMethodDefinition = !!isInnerMethodDefinition; } - referencingDefaultValue( + private referencingDefaultValue( pattern: TSESTree.Identifier, assignments: (TSESTree.AssignmentExpression | TSESTree.AssignmentPattern)[], maybeImplicitGlobal: ReferenceImplicitGlobal | null, @@ -103,38 +103,90 @@ class Referencer extends Visitor { }); } - visitPattern(node: TSESTree.Node, callback: PatternVisitorCallback): void; - visitPattern( - node: TSESTree.Node, - options: PatternVisitorOptions, - callback: PatternVisitorCallback, - ): void; - visitPattern( - node: TSESTree.Node, - optionsOrCallback: PatternVisitorCallback | PatternVisitorOptions, - callback?: PatternVisitorCallback, + /////////////////// + // Visit helpers // + /////////////////// + + protected visitClass( + node: TSESTree.ClassDeclaration | TSESTree.ClassExpression, ): void { - let visitPatternOptions: PatternVisitorOptions; - let visitPatternCallback: PatternVisitorCallback; + if (node.type === AST_NODE_TYPES.ClassDeclaration && node.id) { + this.currentScope().defineIdentifier( + node.id, + new ClassNameDefinition(node.id, node), + ); + } - if (typeof optionsOrCallback === 'function') { - visitPatternCallback = optionsOrCallback; - visitPatternOptions = { processRightHandNodes: false }; + this.visit(node.superClass); + + this.scopeManager.nestClassScope(node); + + if (node.id) { + this.currentScope().defineIdentifier( + node.id, + new ClassNameDefinition(node.id, node), + ); + } + this.visit(node.body); + + this.close(node); + } + + protected visitForIn( + node: TSESTree.ForInStatement | TSESTree.ForOfStatement, + ): void { + if ( + node.left.type === AST_NODE_TYPES.VariableDeclaration && + node.left.kind !== 'var' + ) { + this.scopeManager.nestForScope(node); + } + + if (node.left.type === AST_NODE_TYPES.VariableDeclaration) { + this.visit(node.left); + this.visitPattern(node.left.declarations[0].id, pattern => { + this.currentScope().referenceValue( + pattern, + ReferenceFlag.WRITE, + node.right, + null, + true, + ); + }); } else { - assert(callback); - visitPatternCallback = callback; - visitPatternOptions = optionsOrCallback; + this.visitPattern( + node.left, + { processRightHandNodes: true }, + (pattern, info) => { + const maybeImplicitGlobal = !this.currentScope().isStrict + ? { + pattern, + node, + } + : null; + this.referencingDefaultValue( + pattern, + info.assignments, + maybeImplicitGlobal, + false, + ); + this.currentScope().referenceValue( + pattern, + ReferenceFlag.WRITE, + node.right, + maybeImplicitGlobal, + false, + ); + }, + ); } + this.visit(node.right); + this.visit(node.body); - traverseIdentifierInPattern( - this.options, - node, - visitPatternOptions.processRightHandNodes ? this : null, - visitPatternCallback, - ); + this.close(node); } - visitFunction( + protected visitFunction( node: | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression @@ -195,30 +247,41 @@ class Referencer extends Visitor { this.close(node); } - visitClass(node: TSESTree.ClassDeclaration | TSESTree.ClassExpression): void { - if (node.type === AST_NODE_TYPES.ClassDeclaration && node.id) { - this.currentScope().defineIdentifier( - node.id, - new ClassNameDefinition(node.id, node), - ); - } - - this.visit(node.superClass); - - this.scopeManager.nestClassScope(node); + protected visitPattern( + node: TSESTree.Node, + callback: PatternVisitorCallback, + ): void; + protected visitPattern( + node: TSESTree.Node, + options: PatternVisitorOptions, + callback: PatternVisitorCallback, + ): void; + protected visitPattern( + node: TSESTree.Node, + optionsOrCallback: PatternVisitorCallback | PatternVisitorOptions, + callback?: PatternVisitorCallback, + ): void { + let visitPatternOptions: PatternVisitorOptions; + let visitPatternCallback: PatternVisitorCallback; - if (node.id) { - this.currentScope().defineIdentifier( - node.id, - new ClassNameDefinition(node.id, node), - ); + if (typeof optionsOrCallback === 'function') { + visitPatternCallback = optionsOrCallback; + visitPatternOptions = { processRightHandNodes: false }; + } else { + assert(callback); + visitPatternCallback = callback; + visitPatternOptions = optionsOrCallback; } - this.visit(node.body); - this.close(node); + traverseIdentifierInPattern( + this.options, + node, + visitPatternOptions.processRightHandNodes ? this : null, + visitPatternCallback, + ); } - visitProperty( + protected visitProperty( node: | TSESTree.MethodDefinition | TSESTree.TSAbstractMethodDefinition @@ -241,59 +304,17 @@ class Referencer extends Visitor { } } - visitForIn(node: TSESTree.ForInStatement | TSESTree.ForOfStatement): void { - if ( - node.left.type === AST_NODE_TYPES.VariableDeclaration && - node.left.kind !== 'var' - ) { - this.scopeManager.nestForScope(node); - } + ///////////////////// + // Visit selectors // + ///////////////////// - if (node.left.type === AST_NODE_TYPES.VariableDeclaration) { - this.visit(node.left); - this.visitPattern(node.left.declarations[0].id, pattern => { - this.currentScope().referenceValue( - pattern, - ReferenceFlag.WRITE, - node.right, - null, - true, - ); - }); - } else { - this.visitPattern( - node.left, - { processRightHandNodes: true }, - (pattern, info) => { - const maybeImplicitGlobal = !this.currentScope().isStrict - ? { - pattern, - node, - } - : null; - this.referencingDefaultValue( - pattern, - info.assignments, - maybeImplicitGlobal, - false, - ); - this.currentScope().referenceValue( - pattern, - ReferenceFlag.WRITE, - node.right, - maybeImplicitGlobal, - false, - ); - }, - ); - } - this.visit(node.right); - this.visit(node.body); - - this.close(node); + protected ArrowFunctionExpression( + node: TSESTree.ArrowFunctionExpression, + ): void { + this.visitFunction(node); } - AssignmentExpression(node: TSESTree.AssignmentExpression): void { + protected AssignmentExpression(node: TSESTree.AssignmentExpression): void { if (PatternVisitor.isPattern(node.left)) { if (node.operator === '=') { this.visitPattern( @@ -337,7 +358,21 @@ class Referencer extends Visitor { this.visit(node.right); } - CatchClause(node: TSESTree.CatchClause): void { + protected BlockStatement(node: TSESTree.BlockStatement): void { + if (this.scopeManager.isES6()) { + this.scopeManager.nestBlockScope(node); + } + + this.visitChildren(node); + + this.close(node); + } + + protected CallExpression(node: TSESTree.CallExpression): void { + this.visitChildren(node); + } + + protected CatchClause(node: TSESTree.CatchClause): void { this.scopeManager.nestCatchScope(node); if (node.param) { @@ -359,61 +394,45 @@ class Referencer extends Visitor { this.close(node); } - Program(node: TSESTree.Program): void { - this.scopeManager.nestGlobalScope(node); - - if (this.scopeManager.isGlobalReturn()) { - // Force strictness of GlobalScope to false when using node.js scope. - this.currentScope().isStrict = false; - this.scopeManager.nestFunctionScope(node, false); - } - - if (this.scopeManager.isES6() && this.scopeManager.isModule()) { - this.scopeManager.nestModuleScope(node); - } - - if (this.scopeManager.isStrict()) { - this.currentScope().isStrict = true; - } + protected ClassExpression(node: TSESTree.ClassExpression): void { + this.visitClass(node); + } - this.visitChildren(node); - this.close(node); + protected ClassDeclaration(node: TSESTree.ClassDeclaration): void { + this.visitClass(node); } - Identifier(node: TSESTree.Identifier): void { - this.currentScope().referenceValue(node); + protected ExportDefaultDeclaration( + node: TSESTree.ExportDefaultDeclaration, + ): void { + this.visit(node.declaration); } - UpdateExpression(node: TSESTree.UpdateExpression): void { - if (PatternVisitor.isPattern(node.argument)) { - this.visitPattern(node.argument, pattern => { - this.currentScope().referenceValue(pattern, ReferenceFlag.RW, null); - }); + protected ExportNamedDeclaration( + node: TSESTree.ExportNamedDeclaration, + ): void { + if (node.declaration) { + // export const x = 1; + this.visit(node.declaration); } else { + // export { x }; this.visitChildren(node); } } - MemberExpression(node: TSESTree.MemberExpression): void { - this.visit(node.object); - if (node.computed) { - this.visit(node.property); - } - } - - Property(node: TSESTree.Property): void { - this.visitProperty(node); + protected ExportSpecifier(node: TSESTree.ExportSpecifier): void { + this.visit(node.local); } - MethodDefinition(node: TSESTree.MethodDefinition): void { - this.visitProperty(node); + protected ForInStatement(node: TSESTree.ForInStatement): void { + this.visitForIn(node); } - LabeledStatement(node: TSESTree.LabeledStatement): void { - this.visit(node.body); + protected ForOfStatement(node: TSESTree.ForOfStatement): void { + this.visitForIn(node); } - ForStatement(node: TSESTree.ForStatement): void { + protected ForStatement(node: TSESTree.ForStatement): void { // Create ForStatement declaration. // NOTE: In ES6, ForStatement dynamically generates per iteration environment. However, this is // a static analyzer, we only generate one scope for ForStatement. @@ -430,40 +449,93 @@ class Referencer extends Visitor { this.close(node); } - ClassExpression(node: TSESTree.ClassExpression): void { - this.visitClass(node); + protected FunctionDeclaration(node: TSESTree.FunctionDeclaration): void { + this.visitFunction(node); } - ClassDeclaration(node: TSESTree.ClassDeclaration): void { - this.visitClass(node); + protected FunctionExpression(node: TSESTree.FunctionExpression): void { + this.visitFunction(node); } - CallExpression(node: TSESTree.CallExpression): void { - this.visitChildren(node); + protected Identifier(node: TSESTree.Identifier): void { + this.currentScope().referenceValue(node); } - BlockStatement(node: TSESTree.BlockStatement): void { - if (this.scopeManager.isES6()) { - this.scopeManager.nestBlockScope(node); + protected ImportDeclaration(node: TSESTree.ImportDeclaration): void { + assert( + this.scopeManager.isES6() && this.scopeManager.isModule(), + 'ImportDeclaration should appear when the mode is ES6 and in the module context.', + ); + + const importer = new ImportReferencer(node, this); + importer.visit(node); + } + + protected LabeledStatement(node: TSESTree.LabeledStatement): void { + this.visit(node.body); + } + + protected MemberExpression(node: TSESTree.MemberExpression): void { + this.visit(node.object); + if (node.computed) { + this.visit(node.property); } + } - this.visitChildren(node); + protected MethodDefinition(node: TSESTree.MethodDefinition): void { + this.visitProperty(node); + } + protected Program(node: TSESTree.Program): void { + this.scopeManager.nestGlobalScope(node); + + if (this.scopeManager.isGlobalReturn()) { + // Force strictness of GlobalScope to false when using node.js scope. + this.currentScope().isStrict = false; + this.scopeManager.nestFunctionScope(node, false); + } + + if (this.scopeManager.isES6() && this.scopeManager.isModule()) { + this.scopeManager.nestModuleScope(node); + } + + if (this.scopeManager.isStrict()) { + this.currentScope().isStrict = true; + } + + this.visitChildren(node); this.close(node); } - WithStatement(node: TSESTree.WithStatement): void { - this.visit(node.object); + protected Property(node: TSESTree.Property): void { + this.visitProperty(node); + } - // Then nest scope for WithStatement. - this.scopeManager.nestWithScope(node); + protected SwitchStatement(node: TSESTree.SwitchStatement): void { + this.visit(node.discriminant); - this.visit(node.body); + if (this.scopeManager.isES6()) { + this.scopeManager.nestSwitchScope(node); + } + + for (let i = 0, iz = node.cases.length; i < iz; ++i) { + this.visit(node.cases[i]); + } this.close(node); } - VariableDeclaration(node: TSESTree.VariableDeclaration): void { + protected UpdateExpression(node: TSESTree.UpdateExpression): void { + if (PatternVisitor.isPattern(node.argument)) { + this.visitPattern(node.argument, pattern => { + this.currentScope().referenceValue(pattern, ReferenceFlag.RW, null); + }); + } else { + this.visitChildren(node); + } + } + + protected VariableDeclaration(node: TSESTree.VariableDeclaration): void { const variableTargetScope = node.kind === 'var' ? this.currentScope().variableScope @@ -501,73 +573,22 @@ class Referencer extends Visitor { } } - SwitchStatement(node: TSESTree.SwitchStatement): void { - this.visit(node.discriminant); + protected WithStatement(node: TSESTree.WithStatement): void { + this.visit(node.object); - if (this.scopeManager.isES6()) { - this.scopeManager.nestSwitchScope(node); - } + // Then nest scope for WithStatement. + this.scopeManager.nestWithScope(node); - for (let i = 0, iz = node.cases.length; i < iz; ++i) { - this.visit(node.cases[i]); - } + this.visit(node.body); this.close(node); } - FunctionDeclaration(node: TSESTree.FunctionDeclaration): void { - this.visitFunction(node); - } - - FunctionExpression(node: TSESTree.FunctionExpression): void { - this.visitFunction(node); - } - - ForOfStatement(node: TSESTree.ForOfStatement): void { - this.visitForIn(node); - } - - ForInStatement(node: TSESTree.ForInStatement): void { - this.visitForIn(node); - } - - ArrowFunctionExpression(node: TSESTree.ArrowFunctionExpression): void { - this.visitFunction(node); - } - - ImportDeclaration(node: TSESTree.ImportDeclaration): void { - assert( - this.scopeManager.isES6() && this.scopeManager.isModule(), - 'ImportDeclaration should appear when the mode is ES6 and in the module context.', - ); - - const importer = new ImportReferencer(node, this); - importer.visit(node); - } - - ExportDefaultDeclaration(node: TSESTree.ExportDefaultDeclaration): void { - this.visit(node.declaration); - } - - ExportNamedDeclaration(node: TSESTree.ExportNamedDeclaration): void { - if (node.declaration) { - // export const x = 1; - this.visit(node.declaration); - } else { - // export { x }; - this.visitChildren(node); - } - } - - ExportSpecifier(node: TSESTree.ExportSpecifier): void { - this.visit(node.local); - } - /////////////////////////// // TypeScript type nodes // /////////////////////////// - visitTypeDeclaration( + protected visitTypeDeclaration( node: | TSESTree.TSTypeParameter | TSESTree.TSInterfaceDeclaration @@ -577,10 +598,14 @@ class Referencer extends Visitor { typeReferencer.visit(node); } - TSTypeAliasDeclaration(node: TSESTree.TSTypeAliasDeclaration): void { + protected TSTypeAliasDeclaration( + node: TSESTree.TSTypeAliasDeclaration, + ): void { this.visitTypeDeclaration(node); } - TSInterfaceDeclaration(node: TSESTree.TSInterfaceDeclaration): void { + protected TSInterfaceDeclaration( + node: TSESTree.TSInterfaceDeclaration, + ): void { this.visitTypeDeclaration(node); } } diff --git a/packages/scope-manager/src/referencer/TypeReferencer.ts b/packages/scope-manager/src/referencer/TypeReferencer.ts index fe647649a7cd..938b3346c52c 100644 --- a/packages/scope-manager/src/referencer/TypeReferencer.ts +++ b/packages/scope-manager/src/referencer/TypeReferencer.ts @@ -16,27 +16,36 @@ class TypeReferencer extends Visitor { visitTypeDeclaration( name: TSESTree.Identifier, - node: - | TSESTree.TSTypeParameter - | TSESTree.TSInterfaceDeclaration - | TSESTree.TSTypeAliasDeclaration, + node: TSESTree.TSInterfaceDeclaration | TSESTree.TSTypeAliasDeclaration, ): void { this.referencer .currentScope() .defineIdentifier(name, new TypeDefinition(name, node)); - this.visitChildren(node); + if (node.typeParameters) { + // type parameters cannot be referenced from outside their current scope + this.referencer.scopeManager.nestTypeScope(node); + } + + this.visit(node.typeParameters); } TSTypeParameter(node: TSESTree.TSTypeParameter): void { - // generic type parameter decls, and inferred generic type parameters - this.visitTypeDeclaration(node.name, node); + this.referencer + .currentScope() + .defineIdentifier(node.name, new TypeDefinition(node.name, node)); } TSTypeAliasDeclaration(node: TSESTree.TSTypeAliasDeclaration): void { this.visitTypeDeclaration(node.id, node); + this.visit(node.typeAnnotation); + this.referencer.close(node); } TSInterfaceDeclaration(node: TSESTree.TSInterfaceDeclaration): void { this.visitTypeDeclaration(node.id, node); + node.extends?.forEach(this.visit, this); + node.implements?.forEach(this.visit, this); + this.visit(node.body); + this.referencer.close(node); } Identifier(node: TSESTree.Identifier): void { diff --git a/packages/scope-manager/src/referencer/Visitor.ts b/packages/scope-manager/src/referencer/Visitor.ts index d9a5330744c3..fe9008b3caab 100644 --- a/packages/scope-manager/src/referencer/Visitor.ts +++ b/packages/scope-manager/src/referencer/Visitor.ts @@ -24,7 +24,7 @@ declare class ESRecurseVisitorType { /** * Dispatching node. */ - visit(node: TSESTree.Node | null): void; + visit(node: TSESTree.Node | null | undefined): void; } class Visitor extends (ESRecurseVisitor as typeof ESRecurseVisitorType) {} diff --git a/packages/scope-manager/src/scope/Scope.ts b/packages/scope-manager/src/scope/Scope.ts index 402b6a06d896..81af3ffac2f7 100644 --- a/packages/scope-manager/src/scope/Scope.ts +++ b/packages/scope-manager/src/scope/Scope.ts @@ -7,6 +7,7 @@ import { FunctionScope } from './FunctionScope'; import { GlobalScope } from './GlobalScope'; import { ModuleScope } from './ModuleScope'; import { SwitchScope } from './SwitchScope'; +import { TypeScope } from './TypeScope'; import { WithScope } from './WithScope'; type Scope = @@ -19,6 +20,7 @@ type Scope = | GlobalScope | ModuleScope | SwitchScope + | TypeScope | WithScope; type BlockNode = Scope['block']; diff --git a/packages/scope-manager/src/scope/ScopeBase.ts b/packages/scope-manager/src/scope/ScopeBase.ts index 7a8785aa8d11..bb9db497458a 100644 --- a/packages/scope-manager/src/scope/ScopeBase.ts +++ b/packages/scope-manager/src/scope/ScopeBase.ts @@ -283,7 +283,10 @@ class ScopeBase< } // make sure we don't match a type reference to a value variable - if (ref.isTypeReference && !variable.isTypeVariable()) { + if ( + (ref.isTypeReference && !variable.isTypeVariable()) || + (!ref.isTypeReference && !variable.isValueVariable()) + ) { return false; } diff --git a/packages/scope-manager/src/scope/ScopeType.ts b/packages/scope-manager/src/scope/ScopeType.ts index 00ec03eccfb5..2c7ade50e355 100644 --- a/packages/scope-manager/src/scope/ScopeType.ts +++ b/packages/scope-manager/src/scope/ScopeType.ts @@ -8,6 +8,7 @@ enum ScopeType { global = 'global', module = 'module', switch = 'switch', + type = 'type', with = 'with', } diff --git a/packages/scope-manager/src/scope/TypeScope.ts b/packages/scope-manager/src/scope/TypeScope.ts new file mode 100644 index 000000000000..33a6352bff29 --- /dev/null +++ b/packages/scope-manager/src/scope/TypeScope.ts @@ -0,0 +1,21 @@ +import { TSESTree } from '@typescript-eslint/experimental-utils'; +import { Scope } from './Scope'; +import { ScopeBase } from './ScopeBase'; +import { ScopeType } from './ScopeType'; +import { ScopeManager } from '../ScopeManager'; + +class TypeScope extends ScopeBase< + ScopeType.type, + TSESTree.TSTypeAliasDeclaration | TSESTree.TSInterfaceDeclaration, + Scope +> { + constructor( + scopeManager: ScopeManager, + upperScope: TypeScope['upper'], + block: TypeScope['block'], + ) { + super(scopeManager, ScopeType.type, upperScope, block, false); + } +} + +export { TypeScope }; diff --git a/packages/scope-manager/src/scope/index.ts b/packages/scope-manager/src/scope/index.ts index a0f88f003362..d4d1069938ef 100644 --- a/packages/scope-manager/src/scope/index.ts +++ b/packages/scope-manager/src/scope/index.ts @@ -9,4 +9,5 @@ export * from './ModuleScope'; export * from './Scope'; export * from './ScopeType'; export * from './SwitchScope'; +export * from './TypeScope'; export * from './WithScope'; diff --git a/packages/scope-manager/tests/types/reference-type.test.ts b/packages/scope-manager/tests/types/reference-type.test.ts index 957f6a58dac6..d3aacecda804 100644 --- a/packages/scope-manager/tests/types/reference-type.test.ts +++ b/packages/scope-manager/tests/types/reference-type.test.ts @@ -14,18 +14,14 @@ describe('referencing a type - positive', () => { ); const variable = scopeManager.getDeclaredVariables(node)[0]; - // there should be one reference from the declaration itself - expect(variable.references).toHaveLength(2); - expect(variable.references[0].identifier.parent).toBe(node); - - // and one reference from the usage - const otherTypeDecl = getSpecificNode( + expect(variable.references).toHaveLength(1); + const referencingNode = getSpecificNode( ast, AST_NODE_TYPES.TSTypeAliasDeclaration, n => (n.id.name === 'OtherType' ? n : null), ); - expect(variable.references[1].identifier.parent?.parent).toBe( - otherTypeDecl, + expect(variable.references[0].identifier.parent?.parent).toBe( + referencingNode, ); }); @@ -37,14 +33,13 @@ describe('referencing a type - positive', () => { const node = getSpecificNode(ast, AST_NODE_TYPES.ClassDeclaration); const variable = scopeManager.getDeclaredVariables(node)[0]; - // there should be one reference from the type declaration expect(variable.references).toHaveLength(1); - const otherTypeDecl = getSpecificNode( + const referencingNode = getSpecificNode( ast, AST_NODE_TYPES.TSTypeAliasDeclaration, ); expect(variable.references[0].identifier.parent?.parent).toBe( - otherTypeDecl, + referencingNode, ); }); @@ -55,13 +50,12 @@ describe('referencing a type - positive', () => { const node = getSpecificNode(ast, AST_NODE_TYPES.TSTypeParameter); const variable = scopeManager.getDeclaredVariables(node)[0]; - // there should be one reference from the declaration itself - expect(variable.references).toHaveLength(2); - expect(variable.references[0].identifier.parent).toBe(node); - - // and one reference from the usage - const usage = getSpecificNode(ast, AST_NODE_TYPES.TSTypeReference); - expect(variable.references[1].identifier.parent).toBe(usage); + expect(variable.references).toHaveLength(1); + const referencingNode = getSpecificNode( + ast, + AST_NODE_TYPES.TSTypeReference, + ); + expect(variable.references[0].identifier.parent).toBe(referencingNode); }); }); @@ -74,37 +68,29 @@ describe('referencing a type - negative', () => { const node = getSpecificNode(ast, AST_NODE_TYPES.VariableDeclarator); const variable = scopeManager.getDeclaredVariables(node)[0]; - // there should be one reference from the declaration itself + // variables declare a reference to themselves if they have an initialization + // so there should be one reference from the declaration itself expect(variable.references).toHaveLength(1); expect(variable.references[0].identifier.parent).toBe(node); }); it('does not record a reference when a type is referenced from a value', () => { const { ast, scopeManager } = parseAndAnalyze(` - type Type = value; - const value = 1; + type Type = 1; + const value = Type; `); const node = getSpecificNode(ast, AST_NODE_TYPES.TSTypeAliasDeclaration); const variable = scopeManager.getDeclaredVariables(node)[0]; - - // there should be one reference from the declaration itself - expect(variable.references).toHaveLength(1); - expect(variable.references[0].identifier.parent).toBe(node); + expect(variable.references).toHaveLength(0); }); - it.todo( - 'does not record a reference when a type is referenced from outside its declaring type', - // () => { - // const { ast, scopeManager } = parseAndAnalyze(` - // type TypeDecl = T; - // type Other = TypeParam; - // `); - // const node = getSpecificNode(ast, AST_NODE_TYPES.TSTypeParameter); - // const variable = scopeManager.getDeclaredVariables(node)[0]; - - // // there should be one reference from the declaration itself - // expect(variable.references).toHaveLength(1); - // expect(variable.references[0].identifier.parent).toBe(node); - // }, - ); + it('does not record a reference when a type is referenced from outside its declaring type', () => { + const { ast, scopeManager } = parseAndAnalyze(` + type TypeDecl = T; + type Other = TypeParam; + `); + const node = getSpecificNode(ast, AST_NODE_TYPES.TSTypeParameter); + const variable = scopeManager.getDeclaredVariables(node)[0]; + expect(variable.references).toHaveLength(0); + }); }); diff --git a/packages/scope-manager/tests/types/variable-definition.test.ts b/packages/scope-manager/tests/types/variable-definition.test.ts index 5fca43738e59..c585f53a03c6 100644 --- a/packages/scope-manager/tests/types/variable-definition.test.ts +++ b/packages/scope-manager/tests/types/variable-definition.test.ts @@ -23,13 +23,7 @@ describe('variable definition', () => { Identifier<"TypeDecl">, ], name: "TypeDecl", - references: Array [ - Reference$1 { - identifier: Identifier<"TypeDecl">, - isTypeReference: true, - resolved: Variable$1, - }, - ], + references: Array [], }, ] `); @@ -68,13 +62,7 @@ describe('variable definition', () => { Identifier<"InterfaceDecl">, ], name: "InterfaceDecl", - references: Array [ - Reference$1 { - identifier: Identifier<"InterfaceDecl">, - isTypeReference: true, - resolved: Variable$1, - }, - ], + references: Array [], }, ] `); @@ -100,13 +88,7 @@ describe('variable definition', () => { Identifier<"TypeParam">, ], name: "TypeParam", - references: Array [ - Reference$2 { - identifier: Identifier<"TypeParam">, - isTypeReference: true, - resolved: Variable$2, - }, - ], + references: Array [], }, ] `); @@ -137,12 +119,7 @@ describe('variable definition', () => { ], name: "Inferred", references: Array [ - Reference$5 { - identifier: Identifier<"Inferred">, - isTypeReference: true, - resolved: Variable$3, - }, - Reference$6 { + Reference$3 { identifier: Identifier<"Inferred">, isTypeReference: true, resolved: Variable$3,