From 2a471c6f607276e1639ef5d7be02fe811450fc95 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Sat, 17 Nov 2018 18:27:51 +0100 Subject: [PATCH 01/32] wip --- package.json | 1 + tsconfig.json | 3 +- util/resolver.ts | 624 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 627 insertions(+), 1 deletion(-) create mode 100644 util/resolver.ts diff --git a/package.json b/package.json index 4f87376..ddfaf2d 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev" }, "dependencies": { + "bind-decorator": "^1.0.11", "tslib": "^1.8.1" }, "engines": { diff --git a/tsconfig.json b/tsconfig.json index cb7cd27..62aaf0d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,8 @@ "lib": ["es2016"], "skipLibCheck": true, "declaration": true, - "importHelpers": true + "importHelpers": true, + "experimentalDecorators": true }, "exclude": [ "node_modules", diff --git a/util/resolver.ts b/util/resolver.ts new file mode 100644 index 0000000..78f3a98 --- /dev/null +++ b/util/resolver.ts @@ -0,0 +1,624 @@ +import * as ts from 'typescript'; +import { ScopeBoundarySelector, isScopeBoundary, isBlockScopedVariableDeclarationList, isThisParameter, getPropertyName, getDeclarationOfBindingElement, ScopeBoundary, isBlockScopeBoundary, isNodeKind } from './util'; +import { getUsageDomain } from './usage'; +import bind from 'bind-decorator'; + +export enum Domain { + None = 0, + Namespace = 1 << 0, + Type = 1 << 1, + Value = 1 << 2, + Any = Type | Value | Namespace, + ValueOrNamespace = Value | Namespace, + // @internal + Lazy = 1 << 3, // TODO handle Lazy Domain everywhere +} + +interface Declaration { + name: string; + node: ts.NamedDeclaration; + domain: Domain; + selector: ScopeBoundarySelector; +} +interface Symbol { + name: string; + domain: Domain; + declarations: Declaration[]; +} +export interface Use { + location: ts.Identifier; + domain: Domain; +} + +type TypeCheckerFactory = () => ts.TypeChecker; +export type TypeCheckerOrFactory = ts.TypeChecker | TypeCheckerFactory; + +export interface Resolver { + findReferences(declaration: ts.Identifier, domain: Domain | undefined, getChecker: TypeCheckerOrFactory): Use[]; + findReferences(declaration: ts.Identifier, domain?: Domain, getChecker?: TypeCheckerOrFactory): Use[] | undefined; +} + +export function createResolver(): Resolver { + return new ResolverImpl(); +} + +function makeCheckerFactory(checkerOrFactory: TypeCheckerOrFactory): TypeCheckerFactory { + let checker = typeof checkerOrFactory === 'function' ? undefined : checkerOrFactory; + return getChecker; + function getChecker() { + if (checker === undefined) + checker = (>checkerOrFactory)(); + return checker; + } +} + +const SENTINEL_USE: Use = {}; + +class ResolverImpl implements Resolver { + private _scopeMap = new WeakMap(); + + public findReferences(declaration: ts.Identifier, domain: Domain | undefined, getChecker: TypeCheckerOrFactory): Use[]; + public findReferences(declaration: ts.Identifier, domain?: Domain, getChecker?: TypeCheckerOrFactory): Use[] | undefined; + public findReferences(declaration: ts.Identifier, domain = Domain.Any, getChecker?: TypeCheckerOrFactory): Use[] | undefined { + const selector = getScopeBoundarySelector(declaration); + if (selector === undefined) + return; // not a declaration name + let scopeNode = findScopeBoundary(declaration.parent!, selector.selector); + if (selector.outer) + scopeNode = findScopeBoundary(scopeNode.parent!, selector.selector); + const scope = this.getOrCreateScope(scopeNode); + const result = []; + for (const use of scope.getUses(scope.getSymbol(declaration), domain, getChecker && makeCheckerFactory(getChecker))) { + if (use === SENTINEL_USE) + return; + result.push(use); + } + return result; + } + + public getOrCreateScope(node: ts.Node) { + let scope = this._scopeMap.get(node); + if (scope === undefined) { + scope = this._createScope(node); + this._scopeMap.set(node, scope); + } + return scope; + } + + private _createScope(node: ts.Node): Scope { + switch (node.kind) { + case ts.SyntaxKind.SourceFile: + case ts.SyntaxKind.CallSignature: + case ts.SyntaxKind.ConstructSignature: + case ts.SyntaxKind.MethodSignature: + case ts.SyntaxKind.FunctionType: + case ts.SyntaxKind.ConstructorType: + return new BaseScope(node, ScopeBoundary.Function, this); + case ts.SyntaxKind.MappedType: + return new BaseScope(node, ScopeBoundary.Type, this); + case ts.SyntaxKind.InterfaceDeclaration: + case ts.SyntaxKind.TypeAliasDeclaration: + return new DeclarationScope( + node, + ScopeBoundary.Type, + this, + { + name: (node).name.text, + domain: Domain.Type, + node: node, + selector: ScopeBoundarySelector.Type, + }, + ); + case ts.SyntaxKind.EnumDeclaration: + return new NamespaceScope( + node, + ScopeBoundary.Function, + this, + { + name: (node).name.text, + domain: Domain.ValueOrNamespace, + node: node, + selector: ScopeBoundarySelector.Function, + }, + ); + case ts.SyntaxKind.ModuleDeclaration: + return new NamespaceScope( + node, + ScopeBoundary.Function, + this, + (node).name.kind === ts.SyntaxKind.StringLiteral || node.flags & ts.NodeFlags.GlobalAugmentation + ? undefined + : { + name: (node).name.text, + domain: Domain.ValueOrNamespace | Domain.Lazy, + node: node, + selector: ScopeBoundarySelector.Function, + }, + ); + case ts.SyntaxKind.ConditionalType: + return new ConditionalTypeScope(node, ScopeBoundary.ConditionalType, this); + // TODO handling of ClassLikeDeclaration might need change when https://github.com/Microsoft/TypeScript/issues/28472 is resolved + case ts.SyntaxKind.ClassDeclaration: + return new DeclarationScope( + node, + ScopeBoundary.Function, + this, + (node).name === undefined + ? undefined + : { + name: (node).name!.text, + domain: Domain.Type | Domain.Value, + node: node, + selector: ScopeBoundarySelector.Block, + }, + ); + case ts.SyntaxKind.ClassExpression: + if ((node).name === undefined) + return new DeclarationScope(node, ScopeBoundary.Function, this); + return new NamedDeclarationExpressionScope(node, this, new DeclarationScope( + node, + ScopeBoundary.Function, + this, + { + name: (node).name!.text, + domain: Domain.Type | Domain.Value, + node: node, + selector: ScopeBoundarySelector.Block, + }, + )); + case ts.SyntaxKind.FunctionExpression: + if ((node).name !== undefined) + return new NamedDeclarationExpressionScope(node, this, new FunctionLikeScope(node, this)); + // falls through + case ts.SyntaxKind.MethodDeclaration: + case ts.SyntaxKind.Constructor: + case ts.SyntaxKind.GetAccessor: + case ts.SyntaxKind.SetAccessor: + case ts.SyntaxKind.FunctionDeclaration: + case ts.SyntaxKind.ArrowFunction: + return new FunctionLikeScope(node, this); + default: + if (isBlockScopeBoundary(node)) + return new BaseScope(node, ScopeBoundary.Block, this); + throw new Error(`unhandled Scope ${ts.SyntaxKind[node.kind]}`); + } + } +} + +function findScopeBoundary(node: ts.Node, selector: ScopeBoundarySelector): ts.Node { + while ((isScopeBoundary(node) & selector) === 0 && node.parent !== undefined) + node = node.parent; + return node; +} + +interface DeclarationBoundary { + selector: ScopeBoundarySelector; + outer: boolean; +} + +function getScopeBoundarySelector(node: ts.Identifier): DeclarationBoundary | undefined { + switch (node.parent!.kind) { + case ts.SyntaxKind.ClassDeclaration: + case ts.SyntaxKind.InterfaceDeclaration: + case ts.SyntaxKind.TypeAliasDeclaration: + case ts.SyntaxKind.EnumDeclaration: + return {selector: ScopeBoundarySelector.Block, outer: true}; + case ts.SyntaxKind.EnumMember: + if ((node.parent).name === node) + return {selector: ScopeBoundarySelector.Block, outer: false}; + return; + case ts.SyntaxKind.FunctionDeclaration: + case ts.SyntaxKind.ModuleDeclaration: + return {selector: ScopeBoundarySelector.Function, outer: true}; + case ts.SyntaxKind.FunctionExpression: + case ts.SyntaxKind.ClassExpression: + return {selector: ScopeBoundarySelector.Block, outer: false}; // this is not entirely correct, but works for our purpose + case ts.SyntaxKind.Parameter: + if (node.originalKeywordKind === ts.SyntaxKind.ThisKeyword || node.parent!.parent!.kind === ts.SyntaxKind.IndexSignature) + return; + return {selector: ScopeBoundarySelector.Function, outer: false}; + case ts.SyntaxKind.VariableDeclaration: + return { + selector: isBlockScopedVariableDeclarationList(node.parent!.parent) + ? ScopeBoundarySelector.Block + : ScopeBoundarySelector.Function, + outer: false, + }; + case ts.SyntaxKind.BindingElement: { + const declaration = getDeclarationOfBindingElement(node.parent); + const blockScoped = declaration.kind === ts.SyntaxKind.Parameter || + declaration.parent!.kind === ts.SyntaxKind.CatchClause || + isBlockScopedVariableDeclarationList(declaration.parent); + return {selector: blockScoped ? ScopeBoundarySelector.Block : ScopeBoundarySelector.Function, outer: false}; + } + case ts.SyntaxKind.TypeParameter: + return { + selector: node.parent!.parent!.kind === ts.SyntaxKind.InferType + ? ScopeBoundarySelector.InferType + : ScopeBoundarySelector.Type, + outer: false, + }; + case ts.SyntaxKind.ImportEqualsDeclaration: + case ts.SyntaxKind.ImportSpecifier: + if ((node.parent).name !== node) + return; + // falls through + case ts.SyntaxKind.ImportClause: + case ts.SyntaxKind.NamespaceImport: + return {selector: ScopeBoundarySelector.Function, outer: false}; + default: + return; + } +} + +function getLazyDeclarationDomain(declaration: ts.NamedDeclaration, checker: ts.TypeChecker): Domain { + let symbol = checker.getSymbolAtLocation(declaration)!; + if (symbol.flags & ts.SymbolFlags.Alias) + symbol = checker.getAliasedSymbol(symbol); + return getDomainOfSymbol(symbol); +} + +function getDomainOfSymbol(symbol: ts.Symbol) { + let domain = Domain.None; + if (symbol.flags & ts.SymbolFlags.Type) + domain |= Domain.Type; + if (symbol.flags & (ts.SymbolFlags.Value | ts.SymbolFlags.ValueModule)) + domain |= Domain.Value; + if (symbol.flags & ts.SymbolFlags.Namespace) + domain |= Domain.Namespace; + return domain; +} + +interface Scope { + resolver: ResolverImpl; + getDeclarationsForParent(): Iterable; + getUses(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory): Iterable; + getUsesInScope(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory): Iterable; + getSymbol(declaration: ts.Identifier): Symbol; + addUse(use: Use): void; + addDeclaration(declaration: Declaration): void; + addChildScope(scope: Scope): void; +} + +class BaseScope implements Scope { + protected _initial = true; + protected _uses: Use[] = []; + protected _symbols = new Map(); + protected _scopes: Scope[] = []; + protected _declarationsForParent: Declaration[] = []; + + constructor(protected _node: T, protected _boundary: ScopeBoundary, public resolver: ResolverImpl) {} + + public getDeclarationsForParent() { + this._initialize(); + return this._declarationsForParent; + } + + public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory): Iterable { + this._initialize(); + const ownSymbol = this._symbols.get(symbol.name); + if (ownSymbol !== undefined && ownSymbol.domain & domain) { + const resolvedOwnSymbol = this._resolveSymbol(ownSymbol, domain, getChecker); + if (resolvedOwnSymbol === undefined) { + yield SENTINEL_USE; + return; + } + symbol = this._resolveSymbol(symbol, domain & ~resolvedOwnSymbol.domain, getChecker)!; + domain &= symbol.domain; + } + yield* this._matchUses(symbol, domain, getChecker); + } + + protected* _matchUses(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory) { + if (domain === Domain.None) + return; + for (const use of this._uses) + if (use.domain & domain && use.location.text === symbol.name) + yield use; + for (const scope of this._scopes) + yield* scope.getUsesInScope(symbol, domain, getChecker); + } + + public* getUses(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory): Iterable { + const resolvedSymbol = this._resolveSymbol(symbol, domain, getChecker); + if (resolvedSymbol === undefined) { + yield SENTINEL_USE; + return; + } + domain &= resolvedSymbol.domain; + yield* this._matchUses(resolvedSymbol, domain, getChecker); + } + + protected _resolveSymbol(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory): Symbol | undefined { + const result: Symbol = { + name: symbol.name, + domain: Domain.None, + declarations: [], + }; + for (let declaration of symbol.declarations) { + if ((declaration.domain & domain) === 0) + continue; + if (declaration.domain & Domain.Lazy) { + if (getChecker === undefined) + return; + const newDomain = getLazyDeclarationDomain(declaration.node, getChecker()); + if ((newDomain & domain) === 0) + continue; + declaration = {...declaration, domain: newDomain}; + } + result.declarations.push(declaration); + result.domain |= declaration.domain; + } + return result; + } + + public getSymbol(declaration: ts.Identifier) { + this._initialize(); + return this._symbols.get(declaration.text)!; + } + + public addUse(use: Use) { + this._uses.push(use); + } + + public addDeclaration(declaration: Declaration) { + if (this._isOwnDeclaration(declaration)) { + this._addOwnDeclaration(declaration); + } else { + this._declarationsForParent.push(declaration); + } + } + + protected _addOwnDeclaration(declaration: Declaration) { + const symbol = this._symbols.get(declaration.name); + if (symbol !== undefined) { + symbol.domain |= declaration.domain; + symbol.declarations.push(declaration); + } else { + this._symbols.set(declaration.name, { + name: declaration.name, + domain: declaration.domain, + declarations: [declaration], + }); + } + } + + public addChildScope(scope: Scope) { + this._scopes.push(scope); + } + + protected _initialize() { + if (this._initial) { + this._analyze(); + for (const scope of this._scopes) + for (const decl of scope.getDeclarationsForParent()) + this.addDeclaration(decl); + this._initial = false; + } + } + + protected _analyze() { + ts.forEachChild(this._node, this._analyzeNode); + } + + protected _isOwnDeclaration(declaration: Declaration) { + return (declaration.selector & this._boundary) !== 0; + } + + @bind + protected _analyzeNode(node: ts.Node): void { + if (isScopeBoundary(node)) { + this.addChildScope(this.resolver.getOrCreateScope(node)); + return; + } + switch (node.kind) { + case ts.SyntaxKind.VariableDeclarationList: + return this._handleVariableDeclarationList(node); + case ts.SyntaxKind.VariableDeclaration: + // catch binding + return this._handleBindingName((node).name, true); + case ts.SyntaxKind.Parameter: + if (node.parent!.kind === ts.SyntaxKind.IndexSignature || isThisParameter(node)) + return (node).type && this._analyzeNode((node).type!); + return this._handleVariableLikeDeclaration(node, false); + case ts.SyntaxKind.EnumMember: + this.addDeclaration({ + name: getPropertyName((node).name)!, + domain: Domain.Value, + node: node, + selector: ScopeBoundarySelector.Block, + }); + if ((node).initializer !== undefined) + this._analyzeNode((node).initializer!); + return; + case ts.SyntaxKind.ImportEqualsDeclaration: + this._analyzeNode((node).moduleReference); + // falls through + case ts.SyntaxKind.ImportClause: + case ts.SyntaxKind.ImportSpecifier: + case ts.SyntaxKind.NamespaceImport: + this.addDeclaration({ + name: ((node).name).text, + domain: Domain.Any | Domain.Lazy, + node: node, + selector: ScopeBoundarySelector.Function, + }); + return; + case ts.SyntaxKind.TypeParameter: + this.addDeclaration({ + name: (node).name.text, + domain: Domain.Type, + node: (node).name, + selector: node.parent!.kind === ts.SyntaxKind.InferType ? ScopeBoundarySelector.InferType : ScopeBoundarySelector.Type, + }); + if ((node).constraint !== undefined) + this._analyzeNode((node).constraint!); + if ((node).decorators !== undefined) + this._analyzeNode((node).default!); + return; + case ts.SyntaxKind.Identifier: { + const domain = getUsageDomain(node); + if (domain !== undefined) // TODO + this.addUse({location: node, domain: domain | 0}); + return; + } + } + if (isNodeKind(node.kind)) + return ts.forEachChild(node, this._analyzeNode); + } + + private _handleVariableDeclarationList(list: ts.VariableDeclarationList) { + const blockScoped = isBlockScopedVariableDeclarationList(list); + for (const declaration of list.declarations) + this._handleVariableLikeDeclaration(declaration, blockScoped); + } + + private _handleVariableLikeDeclaration(declaration: ts.VariableDeclaration | ts.ParameterDeclaration, blockScoped: boolean) { + this._handleBindingName(declaration.name, blockScoped); + if (declaration.type !== undefined) + this._analyzeNode(declaration.type); + if (declaration.initializer !== undefined) + this._analyzeNode(declaration.initializer); + } + + private _handleBindingName(name: ts.BindingName, blockScoped: boolean) { + const selector = blockScoped ? ScopeBoundarySelector.Block : ScopeBoundarySelector.Function; + if (name.kind === ts.SyntaxKind.Identifier) + return this.addDeclaration({name: name.text, domain: Domain.Value, node: name, selector}); + + for (const element of name.elements) { + if (element.kind === ts.SyntaxKind.OmittedExpression) + break; + if (element.propertyName !== undefined && element.propertyName.kind === ts.SyntaxKind.ComputedPropertyName) + this._analyzeNode(element.propertyName); + this._handleBindingName(element.name, blockScoped); + if (element.initializer !== undefined) + this._analyzeNode(element.initializer); + } + } +} + +class DeclarationScope extends BaseScope { + constructor(node: T, boundary: ScopeBoundary, resolver: ResolverImpl, declaration?: Declaration) { + super(node, boundary, resolver); + if (declaration) + this._declarationsForParent.push(declaration); + } + + public getDeclarationsForParent() { + return this._declarationsForParent; + } +} + +class NamespaceScope extends DeclarationScope { + public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory) { + const isEnum = this._node.kind === ts.SyntaxKind.EnumDeclaration; + if (isEnum && (domain & Domain.ValueOrNamespace) === 0) + return; // if we are only looking for type uses, we won't find them in an enum + if (getChecker === undefined) { + yield SENTINEL_USE; + return; + } + const namespaceSymbol = getChecker().getSymbolAtLocation(this._node)!; + const exportedSymbol = namespaceSymbol.exports!.get(ts.escapeLeadingUnderscores(symbol.name)); + if (exportedSymbol !== undefined) { + const exportedSymbolDomain = isEnum ? Domain.Value : getDomainOfSymbol(exportedSymbol); + symbol = this._resolveSymbol(symbol, exportedSymbolDomain & ~exportedSymbolDomain, getChecker)!; + domain &= symbol.domain; + if (domain === Domain.None) + return; + } + yield* super.getUsesInScope(symbol, domain, getChecker); + } +} + +class ConditionalTypeThenScope extends BaseScope { + public addOwnDeclaration(declaration: Declaration) { + this._addOwnDeclaration(declaration); + } + + protected _analyze() { + this._analyzeNode(this._node); + } +} + +class ConditionalTypeScope extends BaseScope { + private _then: ConditionalTypeThenScope; + + protected _analyze() { + this._analyzeNode(this._node.checkType); + this._analyzeNode(this._node.extendsType); + this.addChildScope(this._then = new ConditionalTypeThenScope(this._node.trueType, 0, this.resolver)); + this._analyzeNode(this._node.falseType); + } + + protected _addOwnDeclaration(declaration: Declaration) { + this._then.addDeclaration(declaration); + } + + protected _isOwnDeclaration(declaration: Declaration) { + return super._isOwnDeclaration(declaration) && + declaration.node.pos > this._node.extendsType.pos && + declaration.node.pos < this._node.extendsType.end; + } +} + +class NamedDeclarationExpressionScope extends BaseScope { + constructor(node: ts.Node, resolver: ResolverImpl, childScope: Scope) { + super(node, ScopeBoundary.Function, resolver); + this._scopes.push(childScope); + } + + public getDeclarationsForParent() { + return []; + } + + protected _analyze() { + // do nothing + } +} + +class FunctionLikeInnerScope extends BaseScope { + protected _analyze() { + if (this._node.type !== undefined) + this._analyzeNode(this._node.type); + if (this._node.body !== undefined) + this._analyzeNode(this._node.body); + } +} + +class FunctionLikeScope extends DeclarationScope { + constructor(node: ts.FunctionLikeDeclaration, resolver: ResolverImpl) { + super( + node, + ScopeBoundary.Function, + resolver, + node.kind !== ts.SyntaxKind.FunctionDeclaration && node.kind !== ts.SyntaxKind.FunctionExpression || node.name === undefined + ? undefined + : { + name: node.name.text, + domain: Domain.Value, + node, + selector: ScopeBoundarySelector.Function, + }, + ); + } + + protected _analyze() { + this.addChildScope(new FunctionLikeInnerScope(this._node, ScopeBoundary.Function, this.resolver)); + if (this._node.typeParameters !== undefined) + for (const typeParameter of this._node.typeParameters) + this._analyzeNode(typeParameter); + for (const parameter of this._node.parameters) + this._analyzeNode(parameter); + } +} + +// TODO decorators !!!!! ARGH +// function/class decorated with itself +// type parmeters shadowing declaration name +// type parameter cannot reference parameter +// * member decorator accessing class generics +// * MappedType type parameter referencing itself in its constraint +// exporting partially shadowed declaration (SourceFile and Namespace) +// domain of 'export import = ' in namespace From bce0e168662e3b1c28e7df0b64065229cf42a2e8 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Sat, 17 Nov 2018 19:33:12 +0100 Subject: [PATCH 02/32] handle uses in decorators, type parameter extends and conditional type --- util/resolver.ts | 109 ++++++++++++++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 39 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index 78f3a98..831899c 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -139,7 +139,7 @@ class ResolverImpl implements Resolver { return new ConditionalTypeScope(node, ScopeBoundary.ConditionalType, this); // TODO handling of ClassLikeDeclaration might need change when https://github.com/Microsoft/TypeScript/issues/28472 is resolved case ts.SyntaxKind.ClassDeclaration: - return new DeclarationScope( + return new DecoratableDeclarationScope( node, ScopeBoundary.Function, this, @@ -272,6 +272,7 @@ function getDomainOfSymbol(symbol: ts.Symbol) { interface Scope { resolver: ResolverImpl; getDeclarationsForParent(): Iterable; + getUsesForParent(): Iterable; getUses(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory): Iterable; getUsesInScope(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory): Iterable; getSymbol(declaration: ts.Identifier): Symbol; @@ -281,10 +282,10 @@ interface Scope { } class BaseScope implements Scope { - protected _initial = true; - protected _uses: Use[] = []; - protected _symbols = new Map(); - protected _scopes: Scope[] = []; + private _initial = true; + private _uses: Use[] = []; + private _symbols = new Map(); + private _scopes: Scope[] = []; protected _declarationsForParent: Declaration[] = []; constructor(protected _node: T, protected _boundary: ScopeBoundary, public resolver: ResolverImpl) {} @@ -294,6 +295,10 @@ class BaseScope implements Scope { return this._declarationsForParent; } + public getUsesForParent(): Iterable { + return []; // overridden by scopes that really need this + } + public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory): Iterable { this._initialize(); const ownSymbol = this._symbols.get(symbol.name); @@ -362,14 +367,10 @@ class BaseScope implements Scope { } public addDeclaration(declaration: Declaration) { - if (this._isOwnDeclaration(declaration)) { - this._addOwnDeclaration(declaration); - } else { + if (!this._isOwnDeclaration(declaration)) { this._declarationsForParent.push(declaration); + return; } - } - - protected _addOwnDeclaration(declaration: Declaration) { const symbol = this._symbols.get(declaration.name); if (symbol !== undefined) { symbol.domain |= declaration.domain; @@ -390,9 +391,12 @@ class BaseScope implements Scope { protected _initialize() { if (this._initial) { this._analyze(); - for (const scope of this._scopes) + for (const scope of this._scopes) { for (const decl of scope.getDeclarationsForParent()) this.addDeclaration(decl); + for (const use of scope.getUsesForParent()) + this.addUse(use); + } this._initial = false; } } @@ -510,6 +514,30 @@ class DeclarationScope exte } } +class DecoratableDeclarationScope< + T extends ts.ClassDeclaration | ts.FunctionLikeDeclaration = ts.ClassDeclaration | ts.FunctionLikeDeclaration, +> extends DeclarationScope { + protected _usesForParent: Use[] = []; + + public getUsesForParent() { + this._initialize(); + return this._usesForParent; + } + + public addUse(use: Use) { + if (this._isOwnUse(use)) { + super.addUse(use); + } else { + this._usesForParent.push(use); + } + } + + protected _isOwnUse(use: Use) { + // decorators cannot access parameters and type parameters of the declaration they decorate + return this._node.decorators === undefined || use.location.end > this._node.decorators.end; + } +} + class NamespaceScope extends DeclarationScope { public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory) { const isEnum = this._node.kind === ts.SyntaxKind.EnumDeclaration; @@ -532,41 +560,34 @@ class NamespaceScope extends DeclarationScope { - private _then: ConditionalTypeThenScope; - - protected _analyze() { - this._analyzeNode(this._node.checkType); - this._analyzeNode(this._node.extendsType); - this.addChildScope(this._then = new ConditionalTypeThenScope(this._node.trueType, 0, this.resolver)); - this._analyzeNode(this._node.falseType); - } - - protected _addOwnDeclaration(declaration: Declaration) { - this._then.addDeclaration(declaration); - } + private _usesForParent: Use[] = []; protected _isOwnDeclaration(declaration: Declaration) { return super._isOwnDeclaration(declaration) && declaration.node.pos > this._node.extendsType.pos && declaration.node.pos < this._node.extendsType.end; } + + public getUsesForParent() { + this._initialize(); + return this._usesForParent; + } + + public addUse(use: Use) { + // only 'trueType' can access InferTypes of a ConditionalType + if (use.location.pos < this._node.trueType.pos || use.location.pos > this._node.trueType.end) { + this._usesForParent.push(use); + } else { + super.addUse(use); + } + } } class NamedDeclarationExpressionScope extends BaseScope { constructor(node: ts.Node, resolver: ResolverImpl, childScope: Scope) { super(node, ScopeBoundary.Function, resolver); - this._scopes.push(childScope); + this.addChildScope(childScope); } public getDeclarationsForParent() { @@ -587,7 +608,7 @@ class FunctionLikeInnerScope extends BaseScope { } } -class FunctionLikeScope extends DeclarationScope { +class FunctionLikeScope extends DecoratableDeclarationScope { constructor(node: ts.FunctionLikeDeclaration, resolver: ResolverImpl) { super( node, @@ -612,12 +633,22 @@ class FunctionLikeScope extends DeclarationScope { for (const parameter of this._node.parameters) this._analyzeNode(parameter); } + + protected _isOwnUse(use: Use) { + return super._isOwnUse(use) && + (// 'typeof' in TypeParameters has no access to parameters + (use.domain & Domain.Type) !== 0 || + this._node.typeParameters === undefined || + use.location.pos < this._node.typeParameters.pos || + use.location.pos > this._node.typeParameters.end + ); + } } // TODO decorators !!!!! ARGH -// function/class decorated with itself -// type parmeters shadowing declaration name -// type parameter cannot reference parameter +// * function/class decorated with itself +// * type parmeters shadowing declaration name +// * type parameter cannot reference parameter // * member decorator accessing class generics // * MappedType type parameter referencing itself in its constraint // exporting partially shadowed declaration (SourceFile and Namespace) From b77b9082d6d1ee4d41e57698a661e72d28a74ac7 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Sat, 17 Nov 2018 19:40:48 +0100 Subject: [PATCH 03/32] don't analyze function body if not necessary --- util/resolver.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/util/resolver.ts b/util/resolver.ts index 831899c..412f17e 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -600,6 +600,10 @@ class NamedDeclarationExpressionScope extends BaseScope { } class FunctionLikeInnerScope extends BaseScope { + public getDeclarationsForParent() { + return []; + } + protected _analyze() { if (this._node.type !== undefined) this._analyzeNode(this._node.type); @@ -645,11 +649,11 @@ class FunctionLikeScope extends DecoratableDeclarationScopenode).name === undefined) return new DeclarationScope(node, ScopeBoundary.Function, this); - return new NamedDeclarationExpressionScope(node, this, new DeclarationScope( + return new NamedDeclarationExpressionScope(node, this, new DeclarationScope( node, ScopeBoundary.Function, this, @@ -168,7 +172,11 @@ class ResolverImpl implements Resolver { )); case ts.SyntaxKind.FunctionExpression: if ((node).name !== undefined) - return new NamedDeclarationExpressionScope(node, this, new FunctionLikeScope(node, this)); + return new NamedDeclarationExpressionScope( + node, + this, + new FunctionLikeScope(node, this), + ); // falls through case ts.SyntaxKind.MethodDeclaration: case ts.SyntaxKind.Constructor: @@ -199,31 +207,36 @@ interface DeclarationBoundary { function getScopeBoundarySelector(node: ts.Identifier): DeclarationBoundary | undefined { switch (node.parent!.kind) { case ts.SyntaxKind.ClassDeclaration: - case ts.SyntaxKind.InterfaceDeclaration: - case ts.SyntaxKind.TypeAliasDeclaration: case ts.SyntaxKind.EnumDeclaration: return {selector: ScopeBoundarySelector.Block, outer: true}; case ts.SyntaxKind.EnumMember: - if ((node.parent).name === node) - return {selector: ScopeBoundarySelector.Block, outer: false}; - return; - case ts.SyntaxKind.FunctionDeclaration: + if ((node.parent).name !== node) + return; + // falls through + case ts.SyntaxKind.InterfaceDeclaration: + case ts.SyntaxKind.TypeAliasDeclaration: + case ts.SyntaxKind.FunctionExpression: // this is not entirely correct, but works for our purpose + case ts.SyntaxKind.ClassExpression: + return {selector: ScopeBoundarySelector.Block, outer: false}; case ts.SyntaxKind.ModuleDeclaration: + if (node.parent.flags & ts.NodeFlags.GlobalAugmentation) + return; + // falls through + case ts.SyntaxKind.FunctionDeclaration: return {selector: ScopeBoundarySelector.Function, outer: true}; - case ts.SyntaxKind.FunctionExpression: - case ts.SyntaxKind.ClassExpression: - return {selector: ScopeBoundarySelector.Block, outer: false}; // this is not entirely correct, but works for our purpose case ts.SyntaxKind.Parameter: if (node.originalKeywordKind === ts.SyntaxKind.ThisKeyword || node.parent!.parent!.kind === ts.SyntaxKind.IndexSignature) return; return {selector: ScopeBoundarySelector.Function, outer: false}; - case ts.SyntaxKind.VariableDeclaration: + case ts.SyntaxKind.VariableDeclaration: { + const parent = (node.parent).parent!; return { - selector: isBlockScopedVariableDeclarationList(node.parent!.parent) + selector: parent.kind === ts.SyntaxKind.CatchClause || isBlockScopedVariableDeclarationList(parent) ? ScopeBoundarySelector.Block : ScopeBoundarySelector.Function, outer: false, }; + } case ts.SyntaxKind.BindingElement: { const declaration = getDeclarationOfBindingElement(node.parent); const blockScoped = declaration.kind === ts.SyntaxKind.Parameter || @@ -262,10 +275,10 @@ function getDomainOfSymbol(symbol: ts.Symbol) { let domain = Domain.None; if (symbol.flags & ts.SymbolFlags.Type) domain |= Domain.Type; - if (symbol.flags & (ts.SymbolFlags.Value | ts.SymbolFlags.ValueModule)) + if (symbol.flags & ts.SymbolFlags.Value) domain |= Domain.Value; if (symbol.flags & ts.SymbolFlags.Namespace) - domain |= Domain.Namespace; + domain |= Domain.ValueOrNamespace; return domain; } @@ -275,10 +288,11 @@ interface Scope { getUsesForParent(): Iterable; getUses(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory): Iterable; getUsesInScope(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory): Iterable; - getSymbol(declaration: ts.Identifier): Symbol; + getSymbol(declaration: ts.Identifier): Symbol | undefined; addUse(use: Use): void; addDeclaration(declaration: Declaration): void; addChildScope(scope: Scope): void; + getDelegateScope(location: ts.Node): Scope; } class BaseScope implements Scope { @@ -290,6 +304,10 @@ class BaseScope implements Scope { constructor(protected _node: T, protected _boundary: ScopeBoundary, public resolver: ResolverImpl) {} + public getDelegateScope(_location: ts.Node): Scope { + return this; + } + public getDeclarationsForParent() { this._initialize(); return this._declarationsForParent; @@ -327,6 +345,7 @@ class BaseScope implements Scope { public* getUses(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory): Iterable { const resolvedSymbol = this._resolveSymbol(symbol, domain, getChecker); if (resolvedSymbol === undefined) { + // TODO maybe resolve all references and abort if there is a match yield SENTINEL_USE; return; } @@ -359,7 +378,7 @@ class BaseScope implements Scope { public getSymbol(declaration: ts.Identifier) { this._initialize(); - return this._symbols.get(declaration.text)!; + return this._symbols.get(declaration.text); } public addUse(use: Use) { @@ -435,10 +454,20 @@ class BaseScope implements Scope { if ((node).initializer !== undefined) this._analyzeNode((node).initializer!); return; + case ts.SyntaxKind.ImportClause: + if ((node).name !== undefined) + this.addDeclaration({ + name: (node).name!.text, + domain: Domain.Any | Domain.Lazy, + node: node, + selector: ScopeBoundarySelector.Function, + }); + if ((node).namedBindings !== undefined) + this._analyzeNode((node).namedBindings!); + return; case ts.SyntaxKind.ImportEqualsDeclaration: this._analyzeNode((node).moduleReference); // falls through - case ts.SyntaxKind.ImportClause: case ts.SyntaxKind.ImportSpecifier: case ts.SyntaxKind.NamespaceImport: this.addDeclaration({ @@ -540,17 +569,21 @@ class DecoratableDeclarationScope< class NamespaceScope extends DeclarationScope { public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory) { - const isEnum = this._node.kind === ts.SyntaxKind.EnumDeclaration; - if (isEnum && (domain & Domain.ValueOrNamespace) === 0) - return; // if we are only looking for type uses, we won't find them in an enum if (getChecker === undefined) { - yield SENTINEL_USE; + // we cannot know for sure if a merged namespace has an export that shadows the outer declaration + // instead of aborting immediately, analyze the scope and abort if there is a match + for (const _ of super.getUsesInScope(symbol, domain, getChecker)) { + yield SENTINEL_USE; + return; + } return; } const namespaceSymbol = getChecker().getSymbolAtLocation(this._node)!; const exportedSymbol = namespaceSymbol.exports!.get(ts.escapeLeadingUnderscores(symbol.name)); if (exportedSymbol !== undefined) { - const exportedSymbolDomain = isEnum ? Domain.Value : getDomainOfSymbol(exportedSymbol); + const exportedSymbolDomain = this._node.kind === ts.SyntaxKind.EnumDeclaration + ? Domain.Value + : getDomainOfSymbol(exportedSymbol); symbol = this._resolveSymbol(symbol, exportedSymbolDomain & ~exportedSymbolDomain, getChecker)!; domain &= symbol.domain; if (domain === Domain.None) @@ -584,10 +617,9 @@ class ConditionalTypeScope extends BaseScope { } } -class NamedDeclarationExpressionScope extends BaseScope { - constructor(node: ts.Node, resolver: ResolverImpl, childScope: Scope) { +class NamedDeclarationExpressionScope extends BaseScope { + constructor(node: ts.NamedDeclaration, resolver: ResolverImpl, private _childScope: Scope) { super(node, ScopeBoundary.Function, resolver); - this.addChildScope(childScope); } public getDeclarationsForParent() { @@ -595,7 +627,13 @@ class NamedDeclarationExpressionScope extends BaseScope { } protected _analyze() { - // do nothing + this.addChildScope(this._childScope); + } + + public getDelegateScope(location: ts.Node): Scope { + return location === this._node.name + ? this + : this._childScope.getDelegateScope(location); } } @@ -613,6 +651,8 @@ class FunctionLikeInnerScope extends BaseScope { } class FunctionLikeScope extends DecoratableDeclarationScope { + private _innerScope = new FunctionLikeInnerScope(this._node, ScopeBoundary.Function, this.resolver); + constructor(node: ts.FunctionLikeDeclaration, resolver: ResolverImpl) { super( node, @@ -629,8 +669,14 @@ class FunctionLikeScope extends DecoratableDeclarationScope>checkerOrFactory)(); + if (checker === null) + checker = createChecker(checkerOrFactory); return checker; } } +function createChecker(checkerOrFactory: TypeCheckerOrFactory | undefined): ts.TypeChecker | undefined { + if (checkerOrFactory === undefined) + return; + let result: {getTypeChecker(): ts.TypeChecker | undefined} | ts.TypeChecker | undefined; + if (typeof checkerOrFactory === 'function') { + result = checkerOrFactory(); + } else if ('program' in checkerOrFactory) { + result = checkerOrFactory.program; + } else { + result = checkerOrFactory; + } + if (result !== undefined && 'getTypeChecker' in result) + result = result.getTypeChecker(); + return result; +} + const SENTINEL_USE: Use = {}; class ResolverImpl implements Resolver { private _scopeMap = new WeakMap(); - public findReferences(declaration: ts.Identifier, domain: Domain | undefined, getChecker: TypeCheckerOrFactory): Use[]; - public findReferences(declaration: ts.Identifier, domain?: Domain, getChecker?: TypeCheckerOrFactory): Use[] | undefined; public findReferences(declaration: ts.Identifier, domain = Domain.Any, getChecker?: TypeCheckerOrFactory): Use[] | undefined { const selector = getScopeBoundarySelector(declaration); if (selector === undefined) @@ -286,8 +303,8 @@ interface Scope { resolver: ResolverImpl; getDeclarationsForParent(): Iterable; getUsesForParent(): Iterable; - getUses(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory): Iterable; - getUsesInScope(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory): Iterable; + getUses(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable; + getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable; getSymbol(declaration: ts.Identifier): Symbol | undefined; addUse(use: Use): void; addDeclaration(declaration: Declaration): void; @@ -317,7 +334,7 @@ class BaseScope implements Scope { return []; // overridden by scopes that really need this } - public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory): Iterable { + public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { this._initialize(); const ownSymbol = this._symbols.get(symbol.name); if (ownSymbol !== undefined && ownSymbol.domain & domain) { @@ -332,7 +349,7 @@ class BaseScope implements Scope { yield* this._matchUses(symbol, domain, getChecker); } - protected* _matchUses(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory) { + protected* _matchUses(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { if (domain === Domain.None) return; for (const use of this._uses) @@ -342,7 +359,7 @@ class BaseScope implements Scope { yield* scope.getUsesInScope(symbol, domain, getChecker); } - public* getUses(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory): Iterable { + public* getUses(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { const resolvedSymbol = this._resolveSymbol(symbol, domain, getChecker); if (resolvedSymbol === undefined) { // TODO maybe resolve all references and abort if there is a match @@ -353,7 +370,7 @@ class BaseScope implements Scope { yield* this._matchUses(resolvedSymbol, domain, getChecker); } - protected _resolveSymbol(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory): Symbol | undefined { + protected _resolveSymbol(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Symbol | undefined { const result: Symbol = { name: symbol.name, domain: Domain.None, @@ -363,9 +380,10 @@ class BaseScope implements Scope { if ((declaration.domain & domain) === 0) continue; if (declaration.domain & Domain.Lazy) { - if (getChecker === undefined) + const checker = getChecker(); + if (checker === undefined) return; - const newDomain = getLazyDeclarationDomain(declaration.node, getChecker()); + const newDomain = getLazyDeclarationDomain(declaration.node, checker); if ((newDomain & domain) === 0) continue; declaration = {...declaration, domain: newDomain}; @@ -568,8 +586,9 @@ class DecoratableDeclarationScope< } class NamespaceScope extends DeclarationScope { - public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker?: TypeCheckerFactory) { - if (getChecker === undefined) { + public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { + const checker = getChecker(); + if (checker === undefined) { // we cannot know for sure if a merged namespace has an export that shadows the outer declaration // instead of aborting immediately, analyze the scope and abort if there is a match for (const _ of super.getUsesInScope(symbol, domain, getChecker)) { @@ -578,7 +597,7 @@ class NamespaceScope extends DeclarationScope Date: Sun, 18 Nov 2018 19:52:24 +0100 Subject: [PATCH 07/32] don't fail fast on lazy symbols --- tslint.json | 3 +- util/resolver.ts | 124 ++++++++++++++++++++++++++--------------------- 2 files changed, 71 insertions(+), 56 deletions(-) diff --git a/tslint.json b/tslint.json index a4b97ff..367396b 100644 --- a/tslint.json +++ b/tslint.json @@ -113,7 +113,8 @@ "switch-final-break": true, "trailing-comma": [true, { "singleline": "never", - "multiline": "always" + "multiline": "always", + "esSpecCompliant": true }], "triple-equals": [true, "allow-null-check"], "typedef-whitespace": [true, { diff --git a/util/resolver.ts b/util/resolver.ts index 658c50f..624e0de 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -71,7 +71,7 @@ function createChecker(checkerOrFactory: TypeCheckerOrFactory | undefined): ts.T return result; } -const SENTINEL_USE: Use = {}; +const SENTINEL_USE: Use = {location: undefined!, domain: Domain.Any}; class ResolverImpl implements Resolver { private _scopeMap = new WeakMap(); @@ -88,8 +88,9 @@ class ResolverImpl implements Resolver { const symbol = scope.getSymbol(declaration); if (symbol === undefined) return; // something went wrong, possibly a syntax error + // TODO move this up front to avoid unnecessary work domain &= getDeclarationDomain(declaration)!; // TODO - for (const use of scope.getUses(symbol, domain, getChecker && makeCheckerFactory(getChecker))) { + for (const use of scope.getUses(symbol, domain, makeCheckerFactory(getChecker))) { if (use === SENTINEL_USE) return; result.push(use); @@ -299,6 +300,35 @@ function getDomainOfSymbol(symbol: ts.Symbol) { return domain; } +function* lazyFilterUses( + uses: Iterable, + getChecker: TypeCheckerFactory, + inclusive: boolean, + resolveDomain: (checker: ts.TypeChecker, ...args: T) => Domain, + ...args: T +) { + let resolvedDomain: Domain | undefined; + for (const use of uses) { + if (resolvedDomain === undefined) { + const checker = getChecker(); + if (checker === undefined) { + yield SENTINEL_USE; + return; + } + resolvedDomain = resolveDomain(checker, ...args); + } + if (((use.domain & resolvedDomain) !== 0) === inclusive) + yield use; + } +} + +function resolveLazySymbolDomain(checker: ts.TypeChecker, symbol: Symbol): Domain { + let result: Domain = Domain.None; + for (const declaration of symbol.declarations) + result |= declaration.domain & Domain.Lazy ? getLazyDeclarationDomain(declaration.node, checker) : declaration.domain; + return result; +} + interface Scope { resolver: ResolverImpl; getDeclarationsForParent(): Iterable; @@ -334,19 +364,18 @@ class BaseScope implements Scope { return []; // overridden by scopes that really need this } - public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { + public getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { this._initialize(); - const ownSymbol = this._symbols.get(symbol.name); - if (ownSymbol !== undefined && ownSymbol.domain & domain) { - const resolvedOwnSymbol = this._resolveSymbol(ownSymbol, domain, getChecker); - if (resolvedOwnSymbol === undefined) { - yield SENTINEL_USE; - return; - } - symbol = this._resolveSymbol(symbol, domain & ~resolvedOwnSymbol.domain, getChecker)!; + let ownSymbol = this._symbols.get(symbol.name); + if (ownSymbol !== undefined) { + ownSymbol = this._resolveSymbol(ownSymbol, domain); + if (ownSymbol.domain & Domain.Lazy) + // we don't know exactly what we have to deal with -> search uses syntactically and filter or abort later + return lazyFilterUses(this._matchUses(symbol, domain, getChecker), getChecker, false, resolveLazySymbolDomain, ownSymbol); + symbol = this._resolveSymbol(symbol, domain & ~ownSymbol.domain); domain &= symbol.domain; } - yield* this._matchUses(symbol, domain, getChecker); + return this._matchUses(symbol, domain, getChecker); } protected* _matchUses(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { @@ -355,39 +384,29 @@ class BaseScope implements Scope { for (const use of this._uses) if (use.domain & domain && use.location.text === symbol.name) yield use; + for (const scope of this._scopes) yield* scope.getUsesInScope(symbol, domain, getChecker); } - public* getUses(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { - const resolvedSymbol = this._resolveSymbol(symbol, domain, getChecker); - if (resolvedSymbol === undefined) { - // TODO maybe resolve all references and abort if there is a match - yield SENTINEL_USE; - return; - } - domain &= resolvedSymbol.domain; - yield* this._matchUses(resolvedSymbol, domain, getChecker); + public getUses(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { + symbol = this._resolveSymbol(symbol, domain); + domain &= symbol.domain; + let uses = this._matchUses(symbol, domain, getChecker); + if (symbol.domain & Domain.Lazy) + uses = lazyFilterUses(uses, getChecker, true, resolveLazySymbolDomain, symbol); + return uses; } - protected _resolveSymbol(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Symbol | undefined { + protected _resolveSymbol(symbol: Symbol, domain: Domain): Symbol { const result: Symbol = { name: symbol.name, domain: Domain.None, declarations: [], }; - for (let declaration of symbol.declarations) { + for (const declaration of symbol.declarations) { if ((declaration.domain & domain) === 0) continue; - if (declaration.domain & Domain.Lazy) { - const checker = getChecker(); - if (checker === undefined) - return; - const newDomain = getLazyDeclarationDomain(declaration.node, checker); - if ((newDomain & domain) === 0) - continue; - declaration = {...declaration, domain: newDomain}; - } result.declarations.push(declaration); result.domain |= declaration.domain; } @@ -585,30 +604,25 @@ class DecoratableDeclarationScope< } } +function resolveNamespaceExportDomain(checker: ts.TypeChecker, node: ts.ModuleDeclaration | ts.EnumDeclaration, name: string) { + const exportedSymbol = checker.getSymbolAtLocation(node)!.exports!.get(ts.escapeLeadingUnderscores(name)); + if (exportedSymbol === undefined) + return Domain.None; + return node.kind === ts.SyntaxKind.EnumDeclaration + ? Domain.Value + : getDomainOfSymbol(exportedSymbol); +} + class NamespaceScope extends DeclarationScope { - public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { - const checker = getChecker(); - if (checker === undefined) { - // we cannot know for sure if a merged namespace has an export that shadows the outer declaration - // instead of aborting immediately, analyze the scope and abort if there is a match - for (const _ of super.getUsesInScope(symbol, domain, getChecker)) { - yield SENTINEL_USE; - return; - } - return; - } - const namespaceSymbol = checker.getSymbolAtLocation(this._node)!; - const exportedSymbol = namespaceSymbol.exports!.get(ts.escapeLeadingUnderscores(symbol.name)); - if (exportedSymbol !== undefined) { - const exportedSymbolDomain = this._node.kind === ts.SyntaxKind.EnumDeclaration - ? Domain.Value - : getDomainOfSymbol(exportedSymbol); - symbol = this._resolveSymbol(symbol, exportedSymbolDomain & ~exportedSymbolDomain, getChecker)!; - domain &= symbol.domain; - if (domain === Domain.None) - return; - } - yield* super.getUsesInScope(symbol, domain, getChecker); + public getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { + return lazyFilterUses( + super.getUsesInScope(symbol, domain, getChecker), + getChecker, + false, + resolveNamespaceExportDomain, + this._node, + symbol.name, + ); } } From 3a49249bc59040147cd6d9f6a47a14c0bbed27cd Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Sun, 18 Nov 2018 19:59:07 +0100 Subject: [PATCH 08/32] correctly visit TypeParameter.default --- util/resolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/resolver.ts b/util/resolver.ts index 624e0de..a2baa8a 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -523,7 +523,7 @@ class BaseScope implements Scope { }); if ((node).constraint !== undefined) this._analyzeNode((node).constraint!); - if ((node).decorators !== undefined) + if ((node).default !== undefined) this._analyzeNode((node).default!); return; case ts.SyntaxKind.Identifier: { From ef45125a009a9bc299419eeeea1b3cbb0312a173 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Mon, 19 Nov 2018 15:07:30 +0100 Subject: [PATCH 09/32] fix shadowing of symbols --- util/resolver.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index a2baa8a..659c40d 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -372,8 +372,8 @@ class BaseScope implements Scope { if (ownSymbol.domain & Domain.Lazy) // we don't know exactly what we have to deal with -> search uses syntactically and filter or abort later return lazyFilterUses(this._matchUses(symbol, domain, getChecker), getChecker, false, resolveLazySymbolDomain, ownSymbol); - symbol = this._resolveSymbol(symbol, domain & ~ownSymbol.domain); - domain &= symbol.domain; + domain &= ~ownSymbol.domain; + symbol = this._resolveSymbol(symbol, domain); } return this._matchUses(symbol, domain, getChecker); } @@ -737,3 +737,7 @@ class FunctionLikeScope extends DecoratableDeclarationScope Date: Mon, 19 Nov 2018 15:25:40 +0100 Subject: [PATCH 10/32] handle WithStatement --- util/resolver.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/util/resolver.ts b/util/resolver.ts index 659c40d..8a95d5c 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -203,6 +203,8 @@ class ResolverImpl implements Resolver { case ts.SyntaxKind.FunctionDeclaration: case ts.SyntaxKind.ArrowFunction: return new FunctionLikeScope(node, this); + case ts.SyntaxKind.WithStatement: + return new WithStatementScope(node, ScopeBoundary.Block, this); default: if (isBlockScopeBoundary(node)) return new BaseScope(node, ScopeBoundary.Block, this); @@ -568,6 +570,19 @@ class BaseScope implements Scope { } } +class WithStatementScope extends BaseScope { + public getDeclarationsForParent() { + return []; // nothing to do here + } + public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { + // we don't know what could be in scope here + for (const _ of super.getUsesInScope(symbol, domain, getChecker)) { + yield SENTINEL_USE; // TODO make SENTINEL a flag instead of a constant object + return; + } + } +} + class DeclarationScope extends BaseScope { constructor(node: T, boundary: ScopeBoundary, resolver: ResolverImpl, declaration?: Declaration) { super(node, boundary, resolver); @@ -740,4 +755,4 @@ class FunctionLikeScope extends DecoratableDeclarationScope Date: Mon, 19 Nov 2018 18:22:27 +0100 Subject: [PATCH 11/32] handle callable signature the same as functions --- util/resolver.ts | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index 8a95d5c..0085fff 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -11,7 +11,9 @@ export enum Domain { Any = Type | Value | Namespace, ValueOrNamespace = Value | Namespace, // @internal - Lazy = 1 << 3, // TODO handle Lazy Domain everywhere + Lazy = 1 << 3, + // @internal + DoNotUse = 1 << 4, } interface Declaration { @@ -71,8 +73,6 @@ function createChecker(checkerOrFactory: TypeCheckerOrFactory | undefined): ts.T return result; } -const SENTINEL_USE: Use = {location: undefined!, domain: Domain.Any}; - class ResolverImpl implements Resolver { private _scopeMap = new WeakMap(); @@ -91,7 +91,7 @@ class ResolverImpl implements Resolver { // TODO move this up front to avoid unnecessary work domain &= getDeclarationDomain(declaration)!; // TODO for (const use of scope.getUses(symbol, domain, makeCheckerFactory(getChecker))) { - if (use === SENTINEL_USE) + if (use.domain & Domain.DoNotUse) return; result.push(use); } @@ -110,11 +110,6 @@ class ResolverImpl implements Resolver { private _createScope(node: ts.Node): Scope { switch (node.kind) { case ts.SyntaxKind.SourceFile: - case ts.SyntaxKind.CallSignature: - case ts.SyntaxKind.ConstructSignature: - case ts.SyntaxKind.MethodSignature: - case ts.SyntaxKind.FunctionType: - case ts.SyntaxKind.ConstructorType: return new BaseScope(node, ScopeBoundary.Function, this); case ts.SyntaxKind.MappedType: return new BaseScope(node, ScopeBoundary.Type, this); @@ -202,7 +197,12 @@ class ResolverImpl implements Resolver { case ts.SyntaxKind.SetAccessor: case ts.SyntaxKind.FunctionDeclaration: case ts.SyntaxKind.ArrowFunction: - return new FunctionLikeScope(node, this); + case ts.SyntaxKind.CallSignature: + case ts.SyntaxKind.ConstructSignature: + case ts.SyntaxKind.MethodSignature: + case ts.SyntaxKind.FunctionType: + case ts.SyntaxKind.ConstructorType: + return new FunctionLikeScope(node, this); case ts.SyntaxKind.WithStatement: return new WithStatementScope(node, ScopeBoundary.Block, this); default: @@ -314,8 +314,8 @@ function* lazyFilterUses( if (resolvedDomain === undefined) { const checker = getChecker(); if (checker === undefined) { - yield SENTINEL_USE; - return; + yield {location: use.location, domain: use.domain | Domain.DoNotUse}; + continue; } resolvedDomain = resolveDomain(checker, ...args); } @@ -576,10 +576,8 @@ class WithStatementScope extends BaseScope { } public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { // we don't know what could be in scope here - for (const _ of super.getUsesInScope(symbol, domain, getChecker)) { - yield SENTINEL_USE; // TODO make SENTINEL a flag instead of a constant object - return; - } + for (const use of super.getUsesInScope(symbol, domain, getChecker)) + yield {location: use.location, domain: use.domain | Domain.DoNotUse}; } } @@ -596,7 +594,7 @@ class DeclarationScope exte } class DecoratableDeclarationScope< - T extends ts.ClassDeclaration | ts.FunctionLikeDeclaration = ts.ClassDeclaration | ts.FunctionLikeDeclaration, + T extends ts.ClassDeclaration | ts.SignatureDeclaration = ts.ClassDeclaration | ts.SignatureDeclaration, > extends DeclarationScope { protected _usesForParent: Use[] = []; @@ -630,6 +628,7 @@ function resolveNamespaceExportDomain(checker: ts.TypeChecker, node: ts.ModuleDe class NamespaceScope extends DeclarationScope { public getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { + // TODO allow type uses in Enum even without the checker return lazyFilterUses( super.getUsesInScope(symbol, domain, getChecker), getChecker, @@ -685,7 +684,7 @@ class NamedDeclarationExpressionScope extends BaseScope { } } -class FunctionLikeInnerScope extends BaseScope { +class FunctionLikeInnerScope extends BaseScope { public getDeclarationsForParent() { return []; } @@ -693,15 +692,15 @@ class FunctionLikeInnerScope extends BaseScope { protected _analyze() { if (this._node.type !== undefined) this._analyzeNode(this._node.type); - if (this._node.body !== undefined) + if ('body' in this._node && this._node.body !== undefined) this._analyzeNode(this._node.body); } } -class FunctionLikeScope extends DecoratableDeclarationScope { +class FunctionLikeScope extends DecoratableDeclarationScope { private _innerScope = new FunctionLikeInnerScope(this._node, ScopeBoundary.Function, this.resolver); - constructor(node: ts.FunctionLikeDeclaration, resolver: ResolverImpl) { + constructor(node: ts.SignatureDeclaration, resolver: ResolverImpl) { super( node, ScopeBoundary.Function, From 496d7581b279401328fcb0c32c142210c3eee405 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Mon, 19 Nov 2018 20:23:57 +0100 Subject: [PATCH 12/32] refactor uses for parent, decorator can no longer access type parameter --- util/resolver.ts | 152 +++++++++++++++++++++++------------------------ 1 file changed, 73 insertions(+), 79 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index 0085fff..4c21732 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -332,9 +332,9 @@ function resolveLazySymbolDomain(checker: ts.TypeChecker, symbol: Symbol): Domai } interface Scope { + node: ts.Node; resolver: ResolverImpl; getDeclarationsForParent(): Iterable; - getUsesForParent(): Iterable; getUses(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable; getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable; getSymbol(declaration: ts.Identifier): Symbol | undefined; @@ -348,10 +348,10 @@ class BaseScope implements Scope { private _initial = true; private _uses: Use[] = []; private _symbols = new Map(); - private _scopes: Scope[] = []; + protected _scopes: Scope[] = []; protected _declarationsForParent: Declaration[] = []; - constructor(protected _node: T, protected _boundary: ScopeBoundary, public resolver: ResolverImpl) {} + constructor(public node: T, protected _boundary: ScopeBoundary, public resolver: ResolverImpl) {} public getDelegateScope(_location: ts.Node): Scope { return this; @@ -362,39 +362,55 @@ class BaseScope implements Scope { return this._declarationsForParent; } - public getUsesForParent(): Iterable { - return []; // overridden by scopes that really need this - } - - public getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { + public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { this._initialize(); + yield* this._matchUsesBeforeShadowing(symbol, domain, getChecker); let ownSymbol = this._symbols.get(symbol.name); if (ownSymbol !== undefined) { ownSymbol = this._resolveSymbol(ownSymbol, domain); if (ownSymbol.domain & Domain.Lazy) // we don't know exactly what we have to deal with -> search uses syntactically and filter or abort later - return lazyFilterUses(this._matchUses(symbol, domain, getChecker), getChecker, false, resolveLazySymbolDomain, ownSymbol); + yield* lazyFilterUses( + this._matchUsesAfterShadowing(symbol, domain, getChecker), + getChecker, + false, + resolveLazySymbolDomain, + ownSymbol, + ); domain &= ~ownSymbol.domain; symbol = this._resolveSymbol(symbol, domain); } - return this._matchUses(symbol, domain, getChecker); + yield* this._matchUsesAfterShadowing(symbol, domain, getChecker); + } + + protected* _matchUsesBeforeShadowing(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { + for (const use of this._uses) + if (use.domain & domain && use.location.text === symbol.name && use.domain & this._propagateUsesToParent(use.location)) + yield use; + for (const scope of this._scopes) { + const propagateDomain = this._propagateUsesToParent(scope.node) & domain; + if (propagateDomain !== Domain.None) + yield* scope.getUsesInScope(this._resolveSymbol(symbol, propagateDomain), propagateDomain, getChecker); + } } - protected* _matchUses(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { + protected* _matchUsesAfterShadowing(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { if (domain === Domain.None) return; for (const use of this._uses) - if (use.domain & domain && use.location.text === symbol.name) + if (use.domain & domain && use.location.text === symbol.name && (use.domain & this._propagateUsesToParent(use.location)) === 0) yield use; - - for (const scope of this._scopes) - yield* scope.getUsesInScope(symbol, domain, getChecker); + for (const scope of this._scopes) { + const nonPropagatedDomain = domain & ~this._propagateUsesToParent(scope.node); + if (nonPropagatedDomain !== Domain.None) + yield* scope.getUsesInScope(this._resolveSymbol(symbol, nonPropagatedDomain), nonPropagatedDomain, getChecker); + } } public getUses(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { symbol = this._resolveSymbol(symbol, domain); domain &= symbol.domain; - let uses = this._matchUses(symbol, domain, getChecker); + let uses = this._matchUsesAfterShadowing(symbol, domain, getChecker); if (symbol.domain & Domain.Lazy) uses = lazyFilterUses(uses, getChecker, true, resolveLazySymbolDomain, symbol); return uses; @@ -449,24 +465,25 @@ class BaseScope implements Scope { protected _initialize() { if (this._initial) { this._analyze(); - for (const scope of this._scopes) { + for (const scope of this._scopes) for (const decl of scope.getDeclarationsForParent()) this.addDeclaration(decl); - for (const use of scope.getUsesForParent()) - this.addUse(use); - } this._initial = false; } } protected _analyze() { - ts.forEachChild(this._node, this._analyzeNode); + ts.forEachChild(this.node, this._analyzeNode); } protected _isOwnDeclaration(declaration: Declaration) { return (declaration.selector & this._boundary) !== 0; } + protected _propagateUsesToParent(_location: ts.Node): Domain { + return Domain.None; + } + @bind protected _analyzeNode(node: ts.Node): void { if (isScopeBoundary(node)) { @@ -596,24 +613,11 @@ class DeclarationScope exte class DecoratableDeclarationScope< T extends ts.ClassDeclaration | ts.SignatureDeclaration = ts.ClassDeclaration | ts.SignatureDeclaration, > extends DeclarationScope { - protected _usesForParent: Use[] = []; - - public getUsesForParent() { - this._initialize(); - return this._usesForParent; - } - - public addUse(use: Use) { - if (this._isOwnUse(use)) { - super.addUse(use); - } else { - this._usesForParent.push(use); - } - } - - protected _isOwnUse(use: Use) { + protected _propagateUsesToParent(location: ts.Node) { // decorators cannot access parameters and type parameters of the declaration they decorate - return this._node.decorators === undefined || use.location.end > this._node.decorators.end; + return this.node.decorators !== undefined && location.pos < this.node.decorators.end + ? Domain.Any + : Domain.None; } } @@ -634,33 +638,23 @@ class NamespaceScope extends DeclarationScope { - private _usesForParent: Use[] = []; - protected _isOwnDeclaration(declaration: Declaration) { return super._isOwnDeclaration(declaration) && - declaration.node.pos > this._node.extendsType.pos && - declaration.node.pos < this._node.extendsType.end; - } - - public getUsesForParent() { - this._initialize(); - return this._usesForParent; + declaration.node.pos > this.node.extendsType.pos && + declaration.node.pos < this.node.extendsType.end; } - public addUse(use: Use) { - // only 'trueType' can access InferTypes of a ConditionalType - if (use.location.pos < this._node.trueType.pos || use.location.pos > this._node.trueType.end) { - this._usesForParent.push(use); - } else { - super.addUse(use); - } + public _propagateUsesToParent(location: ts.Node) { + return location.pos < this.node.trueType.pos || location.pos > this.node.trueType.end + ? Domain.Any + : Domain.None; } } @@ -678,7 +672,7 @@ class NamedDeclarationExpressionScope extends BaseScope { } public getDelegateScope(location: ts.Node): Scope { - return location === this._node.name + return location === this.node.name ? this : this._childScope.getDelegateScope(location); } @@ -690,15 +684,15 @@ class FunctionLikeInnerScope extends BaseScope { } protected _analyze() { - if (this._node.type !== undefined) - this._analyzeNode(this._node.type); - if ('body' in this._node && this._node.body !== undefined) - this._analyzeNode(this._node.body); + if (this.node.type !== undefined) + this._analyzeNode(this.node.type); + if ('body' in this.node && this.node.body !== undefined) + this._analyzeNode(this.node.body); } } class FunctionLikeScope extends DecoratableDeclarationScope { - private _innerScope = new FunctionLikeInnerScope(this._node, ScopeBoundary.Function, this.resolver); + private _innerScope = new FunctionLikeInnerScope(this.node, ScopeBoundary.Function, this.resolver); constructor(node: ts.SignatureDeclaration, resolver: ResolverImpl) { super( @@ -717,27 +711,28 @@ class FunctionLikeScope extends DecoratableDeclarationScope this._node.typeParameters.end + if (this.node.decorators !== undefined) + this.node.decorators.forEach(this._analyzeNode); + if (this.node.typeParameters !== undefined) + this.node.typeParameters.forEach(this._analyzeNode); + this.node.parameters.forEach(this._analyzeNode); + } + + protected _propagateUsesToParent(location: ts.Node) { + return super._propagateUsesToParent(location) | + ( + this.node.typeParameters !== undefined && + location.pos > this.node.typeParameters.pos && + location.pos < this.node.typeParameters.end + ? Domain.Type + : Domain.None ); } } @@ -751,7 +746,6 @@ class FunctionLikeScope extends DecoratableDeclarationScope Date: Mon, 19 Nov 2018 21:59:04 +0100 Subject: [PATCH 13/32] make stuff protected --- util/resolver.ts | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index 4c21732..e6fdbe7 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -333,14 +333,10 @@ function resolveLazySymbolDomain(checker: ts.TypeChecker, symbol: Symbol): Domai interface Scope { node: ts.Node; - resolver: ResolverImpl; getDeclarationsForParent(): Iterable; getUses(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable; getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable; getSymbol(declaration: ts.Identifier): Symbol | undefined; - addUse(use: Use): void; - addDeclaration(declaration: Declaration): void; - addChildScope(scope: Scope): void; getDelegateScope(location: ts.Node): Scope; } @@ -348,10 +344,10 @@ class BaseScope implements Scope { private _initial = true; private _uses: Use[] = []; private _symbols = new Map(); - protected _scopes: Scope[] = []; + private _scopes: Scope[] = []; protected _declarationsForParent: Declaration[] = []; - constructor(public node: T, protected _boundary: ScopeBoundary, public resolver: ResolverImpl) {} + constructor(public node: T, protected _boundary: ScopeBoundary, protected _resolver: ResolverImpl) {} public getDelegateScope(_location: ts.Node): Scope { return this; @@ -436,11 +432,11 @@ class BaseScope implements Scope { return this._symbols.get(declaration.text); } - public addUse(use: Use) { + protected _addUse(use: Use) { this._uses.push(use); } - public addDeclaration(declaration: Declaration) { + protected _addDeclaration(declaration: Declaration) { if (!this._isOwnDeclaration(declaration)) { this._declarationsForParent.push(declaration); return; @@ -458,7 +454,7 @@ class BaseScope implements Scope { } } - public addChildScope(scope: Scope) { + protected _addChildScope(scope: Scope) { this._scopes.push(scope); } @@ -467,7 +463,7 @@ class BaseScope implements Scope { this._analyze(); for (const scope of this._scopes) for (const decl of scope.getDeclarationsForParent()) - this.addDeclaration(decl); + this._addDeclaration(decl); this._initial = false; } } @@ -487,7 +483,7 @@ class BaseScope implements Scope { @bind protected _analyzeNode(node: ts.Node): void { if (isScopeBoundary(node)) { - this.addChildScope(this.resolver.getOrCreateScope(node)); + this._addChildScope(this._resolver.getOrCreateScope(node)); return; } switch (node.kind) { @@ -501,7 +497,7 @@ class BaseScope implements Scope { return (node).type && this._analyzeNode((node).type!); return this._handleVariableLikeDeclaration(node, false); case ts.SyntaxKind.EnumMember: - this.addDeclaration({ + this._addDeclaration({ name: getPropertyName((node).name)!, domain: Domain.Value, node: node, @@ -512,7 +508,7 @@ class BaseScope implements Scope { return; case ts.SyntaxKind.ImportClause: if ((node).name !== undefined) - this.addDeclaration({ + this._addDeclaration({ name: (node).name!.text, domain: Domain.Any | Domain.Lazy, node: node, @@ -526,7 +522,7 @@ class BaseScope implements Scope { // falls through case ts.SyntaxKind.ImportSpecifier: case ts.SyntaxKind.NamespaceImport: - this.addDeclaration({ + this._addDeclaration({ name: ((node).name).text, domain: Domain.Any | Domain.Lazy, node: node, @@ -534,7 +530,7 @@ class BaseScope implements Scope { }); return; case ts.SyntaxKind.TypeParameter: - this.addDeclaration({ + this._addDeclaration({ name: (node).name.text, domain: Domain.Type, node: (node).name, @@ -548,7 +544,7 @@ class BaseScope implements Scope { case ts.SyntaxKind.Identifier: { const domain = getUsageDomain(node); if (domain !== undefined) // TODO - this.addUse({location: node, domain: domain | 0}); + this._addUse({location: node, domain: domain | 0}); return; } } @@ -573,7 +569,7 @@ class BaseScope implements Scope { private _handleBindingName(name: ts.BindingName, blockScoped: boolean) { const selector = blockScoped ? ScopeBoundarySelector.Block : ScopeBoundarySelector.Function; if (name.kind === ts.SyntaxKind.Identifier) - return this.addDeclaration({name: name.text, domain: Domain.Value, node: name, selector}); + return this._addDeclaration({name: name.text, domain: Domain.Value, node: name, selector}); for (const element of name.elements) { if (element.kind === ts.SyntaxKind.OmittedExpression) @@ -668,7 +664,7 @@ class NamedDeclarationExpressionScope extends BaseScope { } protected _analyze() { - this.addChildScope(this._childScope); + this._addChildScope(this._childScope); } public getDelegateScope(location: ts.Node): Scope { @@ -692,7 +688,7 @@ class FunctionLikeInnerScope extends BaseScope { } class FunctionLikeScope extends DecoratableDeclarationScope { - private _innerScope = new FunctionLikeInnerScope(this.node, ScopeBoundary.Function, this.resolver); + private _innerScope = new FunctionLikeInnerScope(this.node, ScopeBoundary.Function, this._resolver); constructor(node: ts.SignatureDeclaration, resolver: ResolverImpl) { super( @@ -717,7 +713,7 @@ class FunctionLikeScope extends DecoratableDeclarationScope Date: Mon, 19 Nov 2018 22:50:14 +0100 Subject: [PATCH 14/32] parameter decorator cannot reference decorator --- util/resolver.ts | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index e6fdbe7..4799add 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -12,6 +12,7 @@ export enum Domain { ValueOrNamespace = Value | Namespace, // @internal Lazy = 1 << 3, + /** Mark this use as unsafe as we cannot know for sure if it really resolves to a given declaration. */ // @internal DoNotUse = 1 << 4, } @@ -642,15 +643,11 @@ class NamespaceScope extends DeclarationScope { protected _isOwnDeclaration(declaration: Declaration) { - return super._isOwnDeclaration(declaration) && - declaration.node.pos > this.node.extendsType.pos && - declaration.node.pos < this.node.extendsType.end; + return super._isOwnDeclaration(declaration) && isInRange(declaration.node.pos, this.node.extendsType); } - public _propagateUsesToParent(location: ts.Node) { - return location.pos < this.node.trueType.pos || location.pos > this.node.trueType.end - ? Domain.Any - : Domain.None; + protected _propagateUsesToParent(location: ts.Node) { + return isInRange(location.pos, this.node.trueType) ? Domain.None : Domain.Any; } } @@ -722,15 +719,28 @@ class FunctionLikeScope extends DecoratableDeclarationScope this.node.typeParameters.pos && - location.pos < this.node.typeParameters.end - ? Domain.Type - : Domain.None - ); + return super._propagateUsesToParent(location) || // method decorators + // 'typeof' in type parameters cannot access parameters of that function + (isInRange(location.pos, this.node.typeParameters) ? Domain.Type : Domain.None) || + // property decorators cannot access parameters + (isInParameterDecorator(location.pos, this.node) ? Domain.Any : Domain.None); + } +} + +function isInParameterDecorator(pos: number, {parameters}: ts.SignatureDeclaration): boolean { + if (!isInRange(pos, parameters)) + return false; + for (const param of parameters) { + if (isInRange(pos, param.decorators)) + return true; + if (pos < param.end) + break; } + return false; +} + +function isInRange(pos: number, range?: ts.TextRange): boolean { + return range !== undefined && pos > range.pos && pos < range.end; } // * function/class decorated with itself @@ -745,3 +755,4 @@ class FunctionLikeScope extends DecoratableDeclarationScope Date: Tue, 20 Nov 2018 22:39:01 +0100 Subject: [PATCH 15/32] handle computed property name and heritage clauses --- util/resolver.ts | 89 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 66 insertions(+), 23 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index 4799add..1b13719 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -157,7 +157,7 @@ class ResolverImpl implements Resolver { return new ConditionalTypeScope(node, ScopeBoundary.ConditionalType, this); // TODO handling of ClassLikeDeclaration might need change when https://github.com/Microsoft/TypeScript/issues/28472 is resolved case ts.SyntaxKind.ClassDeclaration: - return new DecoratableDeclarationScope( + return new ClassScope( node, ScopeBoundary.Function, this, @@ -172,8 +172,8 @@ class ResolverImpl implements Resolver { ); case ts.SyntaxKind.ClassExpression: if ((node).name === undefined) - return new DeclarationScope(node, ScopeBoundary.Function, this); - return new NamedDeclarationExpressionScope(node, this, new DeclarationScope( + return new ClassScope(node, ScopeBoundary.Function, this); + return new NamedDeclarationExpressionScope(node, this, new ClassScope( node, ScopeBoundary.Function, this, @@ -339,6 +339,8 @@ interface Scope { getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable; getSymbol(declaration: ts.Identifier): Symbol | undefined; getDelegateScope(location: ts.Node): Scope; + matchUsesBeforeShadowing(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable; + matchUsesAfterShadowing(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable; } class BaseScope implements Scope { @@ -361,14 +363,14 @@ class BaseScope implements Scope { public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { this._initialize(); - yield* this._matchUsesBeforeShadowing(symbol, domain, getChecker); + yield* this.matchUsesBeforeShadowing(symbol, domain, getChecker); let ownSymbol = this._symbols.get(symbol.name); if (ownSymbol !== undefined) { ownSymbol = this._resolveSymbol(ownSymbol, domain); if (ownSymbol.domain & Domain.Lazy) // we don't know exactly what we have to deal with -> search uses syntactically and filter or abort later yield* lazyFilterUses( - this._matchUsesAfterShadowing(symbol, domain, getChecker), + this.matchUsesAfterShadowing(symbol, domain, getChecker), getChecker, false, resolveLazySymbolDomain, @@ -377,37 +379,38 @@ class BaseScope implements Scope { domain &= ~ownSymbol.domain; symbol = this._resolveSymbol(symbol, domain); } - yield* this._matchUsesAfterShadowing(symbol, domain, getChecker); + yield* this.matchUsesAfterShadowing(symbol, domain, getChecker); } - protected* _matchUsesBeforeShadowing(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { + public* matchUsesBeforeShadowing(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { for (const use of this._uses) if (use.domain & domain && use.location.text === symbol.name && use.domain & this._propagateUsesToParent(use.location)) yield use; - for (const scope of this._scopes) { - const propagateDomain = this._propagateUsesToParent(scope.node) & domain; - if (propagateDomain !== Domain.None) - yield* scope.getUsesInScope(this._resolveSymbol(symbol, propagateDomain), propagateDomain, getChecker); - } + for (const scope of this._scopes) + yield* this._matchChildScope(scope, true, symbol, domain, getChecker); } - protected* _matchUsesAfterShadowing(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { + protected _matchChildScope(scope: Scope, beforeShadowing: boolean, symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { + domain &= beforeShadowing ? this._propagateUsesToParent(scope.node) : ~this._propagateUsesToParent(scope.node); + if (domain !== Domain.None) + return scope.getUsesInScope(this._resolveSymbol(symbol, domain), domain, getChecker); + return []; + } + + public* matchUsesAfterShadowing(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { if (domain === Domain.None) return; for (const use of this._uses) if (use.domain & domain && use.location.text === symbol.name && (use.domain & this._propagateUsesToParent(use.location)) === 0) yield use; - for (const scope of this._scopes) { - const nonPropagatedDomain = domain & ~this._propagateUsesToParent(scope.node); - if (nonPropagatedDomain !== Domain.None) - yield* scope.getUsesInScope(this._resolveSymbol(symbol, nonPropagatedDomain), nonPropagatedDomain, getChecker); - } + for (const scope of this._scopes) + yield* this._matchChildScope(scope, false, symbol, domain, getChecker); } public getUses(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { symbol = this._resolveSymbol(symbol, domain); domain &= symbol.domain; - let uses = this._matchUsesAfterShadowing(symbol, domain, getChecker); + let uses = this.matchUsesAfterShadowing(symbol, domain, getChecker); if (symbol.domain & Domain.Lazy) uses = lazyFilterUses(uses, getChecker, true, resolveLazySymbolDomain, symbol); return uses; @@ -608,7 +611,7 @@ class DeclarationScope exte } class DecoratableDeclarationScope< - T extends ts.ClassDeclaration | ts.SignatureDeclaration = ts.ClassDeclaration | ts.SignatureDeclaration, + T extends ts.ClassLikeDeclaration | ts.SignatureDeclaration = ts.ClassLikeDeclaration | ts.SignatureDeclaration, > extends DeclarationScope { protected _propagateUsesToParent(location: ts.Node) { // decorators cannot access parameters and type parameters of the declaration they decorate @@ -618,6 +621,33 @@ class DecoratableDeclarationScope< } } +class ClassScope extends DecoratableDeclarationScope { + protected _propagateUsesToParent(location: ts.Node) { + return super._propagateUsesToParent(location) || // decorators + // computed property names cannot access type parameters of class + // TODO this won't work for method scopes + (isInComputedNameOfMember(location.pos, this.node) ? Domain.Type : Domain.None) || + // expression in 'extends' clause cannot access type parameters of class + (isInHeritageClauseExpression(location.pos, this.node) ? Domain.Type : Domain.None); + } +} + +function isInHeritageClauseExpression(pos: number, {heritageClauses}: ts.ClassLikeDeclaration): boolean { + if (heritageClauses === undefined || heritageClauses[0].token !== ts.SyntaxKind.ExtendsKeyword || heritageClauses[0].types.length !== 1) + return false; + return isInRange(pos, heritageClauses[0].types[0].expression); +} + +function isInComputedNameOfMember(pos: number, node: ts.ClassLikeDeclaration): boolean { + for (const member of node.members) { + if (pos < member.pos) + break; + if (isInComputedPropertyName(pos, member)) + return true; + } + return false; +} + function resolveNamespaceExportDomain(checker: ts.TypeChecker, node: ts.ModuleDeclaration | ts.EnumDeclaration, name: string) { const exportedSymbol = checker.getSymbolAtLocation(node)!.exports!.get(ts.escapeLeadingUnderscores(name)); if (exportedSymbol === undefined) @@ -723,10 +753,16 @@ class FunctionLikeScope extends DecoratableDeclarationScope range.pos && pos < range.end; +function isInRange(pos: number, range: ts.TextRange | undefined): boolean { + return range !== undefined && pos >= range.pos && pos < range.end; } // * function/class decorated with itself @@ -756,3 +792,10 @@ function isInRange(pos: number, range?: ts.TextRange): boolean { // * FunctionScope in Decorator has no access to parameters and generics // * with statement // * parameter decorator cannot access parameters +// handle arguments +// * computed property names of methods cannot access parameters and generics +// * computed property names cannot access class generics +// computed method names cannot access class generics +// * ExpressionWithTypeArguments.expression in class extends clause cannot reference class generics +// in ambient namespace exclude alias exports 'export {T as V}' +// make sure namespace import is treated as namespace From d8cccd35a6fbf0888382baae78af04e816b9b72f Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Wed, 21 Nov 2018 20:52:47 +0100 Subject: [PATCH 16/32] unify code paths --- util/resolver.ts | 154 ++++++++++++++++++++--------------------------- 1 file changed, 64 insertions(+), 90 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index 1b13719..848ddf3 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -157,7 +157,7 @@ class ResolverImpl implements Resolver { return new ConditionalTypeScope(node, ScopeBoundary.ConditionalType, this); // TODO handling of ClassLikeDeclaration might need change when https://github.com/Microsoft/TypeScript/issues/28472 is resolved case ts.SyntaxKind.ClassDeclaration: - return new ClassScope( + return new DecoratableDeclarationScope( node, ScopeBoundary.Function, this, @@ -172,8 +172,8 @@ class ResolverImpl implements Resolver { ); case ts.SyntaxKind.ClassExpression: if ((node).name === undefined) - return new ClassScope(node, ScopeBoundary.Function, this); - return new NamedDeclarationExpressionScope(node, this, new ClassScope( + return new DeclarationScope(node, ScopeBoundary.Function, this); + return new NamedDeclarationExpressionScope(node, this, new DeclarationScope( node, ScopeBoundary.Function, this, @@ -339,8 +339,23 @@ interface Scope { getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable; getSymbol(declaration: ts.Identifier): Symbol | undefined; getDelegateScope(location: ts.Node): Scope; - matchUsesBeforeShadowing(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable; - matchUsesAfterShadowing(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable; +} + +interface MatchRange extends ts.TextRange { + domain: Domain; +} + +namespace MatchRange { + export function create(domain: Domain, {pos, end}: ts.TextRange): MatchRange { + return {domain, pos, end}; + } +} + +function getDomainOfMatchingRange(pos: number, ranges: ReadonlyArray) { + for (const range of ranges) + if (isInRange(pos, range)) + return range.domain; + return Domain.None; } class BaseScope implements Scope { @@ -348,6 +363,7 @@ class BaseScope implements Scope { private _uses: Use[] = []; private _symbols = new Map(); private _scopes: Scope[] = []; + private _propagatedRanges: ReadonlyArray = this._collectPropagatedRanges(); protected _declarationsForParent: Declaration[] = []; constructor(public node: T, protected _boundary: ScopeBoundary, protected _resolver: ResolverImpl) {} @@ -363,14 +379,16 @@ class BaseScope implements Scope { public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { this._initialize(); - yield* this.matchUsesBeforeShadowing(symbol, domain, getChecker); + if (this._propagatedRanges.length !== 0) + yield* this._match(symbol, domain, getChecker, this._propagatedRanges, true); + let ownSymbol = this._symbols.get(symbol.name); if (ownSymbol !== undefined) { ownSymbol = this._resolveSymbol(ownSymbol, domain); if (ownSymbol.domain & Domain.Lazy) // we don't know exactly what we have to deal with -> search uses syntactically and filter or abort later yield* lazyFilterUses( - this.matchUsesAfterShadowing(symbol, domain, getChecker), + this._match(symbol, domain, getChecker, this._propagatedRanges, false), getChecker, false, resolveLazySymbolDomain, @@ -379,38 +397,31 @@ class BaseScope implements Scope { domain &= ~ownSymbol.domain; symbol = this._resolveSymbol(symbol, domain); } - yield* this.matchUsesAfterShadowing(symbol, domain, getChecker); + yield* this._match(symbol, domain, getChecker, this._propagatedRanges, false); } - public* matchUsesBeforeShadowing(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { + private* _match(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory, ranges: ReadonlyArray, include: boolean) { for (const use of this._uses) - if (use.domain & domain && use.location.text === symbol.name && use.domain & this._propagateUsesToParent(use.location)) + if ( + use.domain & domain && + use.location.text === symbol.name && + ((use.domain & getDomainOfMatchingRange(use.location.pos, ranges)) !== 0) === include + ) yield use; - for (const scope of this._scopes) - yield* this._matchChildScope(scope, true, symbol, domain, getChecker); - } - - protected _matchChildScope(scope: Scope, beforeShadowing: boolean, symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { - domain &= beforeShadowing ? this._propagateUsesToParent(scope.node) : ~this._propagateUsesToParent(scope.node); - if (domain !== Domain.None) - return scope.getUsesInScope(this._resolveSymbol(symbol, domain), domain, getChecker); - return []; - } - - public* matchUsesAfterShadowing(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { - if (domain === Domain.None) - return; - for (const use of this._uses) - if (use.domain & domain && use.location.text === symbol.name && (use.domain & this._propagateUsesToParent(use.location)) === 0) - yield use; - for (const scope of this._scopes) - yield* this._matchChildScope(scope, false, symbol, domain, getChecker); + for (const scope of this._scopes) { + let propagatedDomain = getDomainOfMatchingRange(scope.node.pos, ranges); + if (!include) + propagatedDomain = ~propagatedDomain; + const d = domain & propagatedDomain; + if (d !== 0) + yield* scope.getUsesInScope(this._resolveSymbol(symbol, d), d, getChecker); + } } public getUses(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { symbol = this._resolveSymbol(symbol, domain); - domain &= symbol.domain; - let uses = this.matchUsesAfterShadowing(symbol, domain, getChecker); + domain &= symbol.domain; // TODO kann weg + let uses = this._match(symbol, domain, getChecker, this._propagatedRanges, false); if (symbol.domain & Domain.Lazy) uses = lazyFilterUses(uses, getChecker, true, resolveLazySymbolDomain, symbol); return uses; @@ -480,8 +491,8 @@ class BaseScope implements Scope { return (declaration.selector & this._boundary) !== 0; } - protected _propagateUsesToParent(_location: ts.Node): Domain { - return Domain.None; + protected _collectPropagatedRanges(): MatchRange[] { + return []; } @bind @@ -611,41 +622,15 @@ class DeclarationScope exte } class DecoratableDeclarationScope< - T extends ts.ClassLikeDeclaration | ts.SignatureDeclaration = ts.ClassLikeDeclaration | ts.SignatureDeclaration, + T extends ts.ClassDeclaration | ts.SignatureDeclaration = ts.ClassDeclaration | ts.SignatureDeclaration, > extends DeclarationScope { - protected _propagateUsesToParent(location: ts.Node) { + protected _collectPropagatedRanges() { // decorators cannot access parameters and type parameters of the declaration they decorate - return this.node.decorators !== undefined && location.pos < this.node.decorators.end - ? Domain.Any - : Domain.None; - } -} - -class ClassScope extends DecoratableDeclarationScope { - protected _propagateUsesToParent(location: ts.Node) { - return super._propagateUsesToParent(location) || // decorators - // computed property names cannot access type parameters of class - // TODO this won't work for method scopes - (isInComputedNameOfMember(location.pos, this.node) ? Domain.Type : Domain.None) || - // expression in 'extends' clause cannot access type parameters of class - (isInHeritageClauseExpression(location.pos, this.node) ? Domain.Type : Domain.None); - } -} - -function isInHeritageClauseExpression(pos: number, {heritageClauses}: ts.ClassLikeDeclaration): boolean { - if (heritageClauses === undefined || heritageClauses[0].token !== ts.SyntaxKind.ExtendsKeyword || heritageClauses[0].types.length !== 1) - return false; - return isInRange(pos, heritageClauses[0].types[0].expression); -} + return this.node.decorators === undefined + ? [] + : [MatchRange.create(Domain.Any, this.node.decorators)]; -function isInComputedNameOfMember(pos: number, node: ts.ClassLikeDeclaration): boolean { - for (const member of node.members) { - if (pos < member.pos) - break; - if (isInComputedPropertyName(pos, member)) - return true; } - return false; } function resolveNamespaceExportDomain(checker: ts.TypeChecker, node: ts.ModuleDeclaration | ts.EnumDeclaration, name: string) { @@ -676,8 +661,8 @@ class ConditionalTypeScope extends BaseScope { return super._isOwnDeclaration(declaration) && isInRange(declaration.node.pos, this.node.extendsType); } - protected _propagateUsesToParent(location: ts.Node) { - return isInRange(location.pos, this.node.trueType) ? Domain.None : Domain.Any; + protected _collectPropagatedRanges() { + return [MatchRange.create(Domain.Type, this.node.trueType)]; } } @@ -743,36 +728,26 @@ class FunctionLikeScope extends DecoratableDeclarationScope Date: Wed, 21 Nov 2018 21:02:03 +0100 Subject: [PATCH 17/32] add special handling for arguments --- util/resolver.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index 848ddf3..6256bdb 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -19,7 +19,7 @@ export enum Domain { interface Declaration { name: string; - node: ts.NamedDeclaration; + node: ts.NamedDeclaration | undefined; domain: Domain; selector: ScopeBoundarySelector; } @@ -328,7 +328,7 @@ function* lazyFilterUses( function resolveLazySymbolDomain(checker: ts.TypeChecker, symbol: Symbol): Domain { let result: Domain = Domain.None; for (const declaration of symbol.declarations) - result |= declaration.domain & Domain.Lazy ? getLazyDeclarationDomain(declaration.node, checker) : declaration.domain; + result |= declaration.domain & Domain.Lazy ? getLazyDeclarationDomain(declaration.node!, checker) : declaration.domain; return result; } @@ -658,7 +658,7 @@ class NamespaceScope extends DeclarationScope { protected _isOwnDeclaration(declaration: Declaration) { - return super._isOwnDeclaration(declaration) && isInRange(declaration.node.pos, this.node.extendsType); + return super._isOwnDeclaration(declaration) && isInRange(declaration.node!.pos, this.node.extendsType); } protected _collectPropagatedRanges() { @@ -716,6 +716,15 @@ class FunctionLikeScope extends DecoratableDeclarationScope Date: Wed, 21 Nov 2018 21:45:26 +0100 Subject: [PATCH 18/32] fix lint and refactoring --- util/resolver.ts | 87 ++++++++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index 6256bdb..bbf34aa 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -1,5 +1,15 @@ import * as ts from 'typescript'; -import { ScopeBoundarySelector, isScopeBoundary, isBlockScopedVariableDeclarationList, isThisParameter, getPropertyName, getDeclarationOfBindingElement, ScopeBoundary, isBlockScopeBoundary, isNodeKind } from './util'; +import { + ScopeBoundarySelector, + isScopeBoundary, + isBlockScopedVariableDeclarationList, + isThisParameter, + getPropertyName, + getDeclarationOfBindingElement, + ScopeBoundary, + isBlockScopeBoundary, + isNodeKind, +} from './util'; import { getUsageDomain, getDeclarationDomain } from './usage'; import bind from 'bind-decorator'; @@ -49,7 +59,7 @@ export function createResolver(): Resolver { } function makeCheckerFactory(checkerOrFactory: TypeCheckerOrFactory | undefined): TypeCheckerFactory { - let checker: ts.TypeChecker | undefined | null = null; + let checker: ts.TypeChecker | undefined | null = null; // tslint:disable-line:no-null-keyword return getChecker; function getChecker() { if (checker === null) @@ -78,19 +88,20 @@ class ResolverImpl implements Resolver { private _scopeMap = new WeakMap(); public findReferences(declaration: ts.Identifier, domain = Domain.Any, getChecker?: TypeCheckerOrFactory): Use[] | undefined { + domain &= getDeclarationDomain(declaration)!; // TODO + if (domain === 0) + return; // not a declaration const selector = getScopeBoundarySelector(declaration); if (selector === undefined) - return; // not a declaration name + throw new Error(`unhandled declaration '${ts.SyntaxKind[declaration.parent!.kind]}'`); // shouldn't happen let scopeNode = findScopeBoundary(declaration.parent!, selector.selector); if (selector.outer) scopeNode = findScopeBoundary(scopeNode.parent!, selector.selector); const scope = this.getOrCreateScope(scopeNode).getDelegateScope(declaration); - const result = []; const symbol = scope.getSymbol(declaration); if (symbol === undefined) return; // something went wrong, possibly a syntax error - // TODO move this up front to avoid unnecessary work - domain &= getDeclarationDomain(declaration)!; // TODO + const result = []; for (const use of scope.getUses(symbol, domain, makeCheckerFactory(getChecker))) { if (use.domain & Domain.DoNotUse) return; @@ -303,7 +314,7 @@ function getDomainOfSymbol(symbol: ts.Symbol) { return domain; } -function* lazyFilterUses( +function* lazyFilterUses>( uses: Iterable, getChecker: TypeCheckerFactory, inclusive: boolean, @@ -326,12 +337,27 @@ function* lazyFilterUses( } function resolveLazySymbolDomain(checker: ts.TypeChecker, symbol: Symbol): Domain { - let result: Domain = Domain.None; + let result = Domain.None; for (const declaration of symbol.declarations) result |= declaration.domain & Domain.Lazy ? getLazyDeclarationDomain(declaration.node!, checker) : declaration.domain; return result; } +function filterSymbol(symbol: Symbol, domain: Domain): Symbol { + const result: Symbol = { + name: symbol.name, + domain: Domain.None, + declarations: [], + }; + for (const declaration of symbol.declarations) { + if ((declaration.domain & domain) === 0) + continue; + result.declarations.push(declaration); + result.domain |= declaration.domain; + } + return result; +} + interface Scope { node: ts.Node; getDeclarationsForParent(): Iterable; @@ -358,6 +384,10 @@ function getDomainOfMatchingRange(pos: number, ranges: ReadonlyArray return Domain.None; } +function isInRange(pos: number, range: ts.TextRange): boolean { + return pos >= range.pos && pos < range.end; +} + class BaseScope implements Scope { private _initial = true; private _uses: Use[] = []; @@ -384,7 +414,7 @@ class BaseScope implements Scope { let ownSymbol = this._symbols.get(symbol.name); if (ownSymbol !== undefined) { - ownSymbol = this._resolveSymbol(ownSymbol, domain); + ownSymbol = filterSymbol(ownSymbol, domain); if (ownSymbol.domain & Domain.Lazy) // we don't know exactly what we have to deal with -> search uses syntactically and filter or abort later yield* lazyFilterUses( @@ -395,7 +425,7 @@ class BaseScope implements Scope { ownSymbol, ); domain &= ~ownSymbol.domain; - symbol = this._resolveSymbol(symbol, domain); + symbol = filterSymbol(symbol, domain); } yield* this._match(symbol, domain, getChecker, this._propagatedRanges, false); } @@ -414,34 +444,18 @@ class BaseScope implements Scope { propagatedDomain = ~propagatedDomain; const d = domain & propagatedDomain; if (d !== 0) - yield* scope.getUsesInScope(this._resolveSymbol(symbol, d), d, getChecker); + yield* scope.getUsesInScope(filterSymbol(symbol, d), d, getChecker); } } public getUses(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable { - symbol = this._resolveSymbol(symbol, domain); - domain &= symbol.domain; // TODO kann weg + symbol = filterSymbol(symbol, domain); let uses = this._match(symbol, domain, getChecker, this._propagatedRanges, false); if (symbol.domain & Domain.Lazy) uses = lazyFilterUses(uses, getChecker, true, resolveLazySymbolDomain, symbol); return uses; } - protected _resolveSymbol(symbol: Symbol, domain: Domain): Symbol { - const result: Symbol = { - name: symbol.name, - domain: Domain.None, - declarations: [], - }; - for (const declaration of symbol.declarations) { - if ((declaration.domain & domain) === 0) - continue; - result.declarations.push(declaration); - result.domain |= declaration.domain; - } - return result; - } - public getSymbol(declaration: ts.Identifier) { this._initialize(); return this._symbols.get(declaration.text); @@ -491,6 +505,7 @@ class BaseScope implements Scope { return (declaration.selector & this._boundary) !== 0; } + // tslint:disable-next-line:prefer-function-over-method protected _collectPropagatedRanges(): MatchRange[] { return []; } @@ -584,6 +599,7 @@ class BaseScope implements Scope { private _handleBindingName(name: ts.BindingName, blockScoped: boolean) { const selector = blockScoped ? ScopeBoundarySelector.Block : ScopeBoundarySelector.Function; if (name.kind === ts.SyntaxKind.Identifier) + // tslint:disable-next-line:object-shorthand-properties-first return this._addDeclaration({name: name.text, domain: Domain.Value, node: name, selector}); for (const element of name.elements) { @@ -599,6 +615,7 @@ class BaseScope implements Scope { } class WithStatementScope extends BaseScope { + // tslint:disable-next-line:prefer-function-over-method public getDeclarationsForParent() { return []; // nothing to do here } @@ -667,10 +684,12 @@ class ConditionalTypeScope extends BaseScope { } class NamedDeclarationExpressionScope extends BaseScope { + // tslint:disable-next-line:parameter-properties constructor(node: ts.NamedDeclaration, resolver: ResolverImpl, private _childScope: Scope) { super(node, ScopeBoundary.Function, resolver); } + // tslint:disable-next-line:prefer-function-over-method public getDeclarationsForParent() { return []; } @@ -687,6 +706,7 @@ class NamedDeclarationExpressionScope extends BaseScope { } class FunctionLikeInnerScope extends BaseScope { + // tslint:disable-next-line:prefer-function-over-method public getDeclarationsForParent() { return []; } @@ -712,7 +732,7 @@ class FunctionLikeScope extends DecoratableDeclarationScope= range.pos && pos < range.end; -} - // * function/class decorated with itself // * type parmeters shadowing declaration name // * type parameter cannot reference parameter @@ -782,3 +798,8 @@ function isInRange(pos: number, range: ts.TextRange | undefined): boolean { // * ExpressionWithTypeArguments.expression in class extends clause cannot reference class generics // in ambient namespace exclude alias exports 'export {T as V}' // make sure namespace import is treated as namespace + +// statically analyze merged namespaces and enums +// statically etermine if namespace or enum can merge with something else +// add function to determine if symbol is exported or global +// add function to get declarations at use site From f1e8afdb70a243e7256772e77715feb189bfb0dd Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Thu, 22 Nov 2018 14:46:00 +0100 Subject: [PATCH 19/32] optimize FunctionLikeScope --- util/resolver.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index bbf34aa..f94ad0b 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -720,7 +720,7 @@ class FunctionLikeInnerScope extends BaseScope { } class FunctionLikeScope extends DecoratableDeclarationScope { - private _innerScope = new FunctionLikeInnerScope(this.node, ScopeBoundary.Function, this._resolver); + private _innerScope: FunctionLikeInnerScope | undefined = undefined; constructor(node: ts.SignatureDeclaration, resolver: ResolverImpl) { super( @@ -745,16 +745,17 @@ class FunctionLikeScope extends DecoratableDeclarationScope Date: Thu, 22 Nov 2018 20:27:39 +0100 Subject: [PATCH 20/32] fix bug with sparse array destructuring --- util/index.ts | 1 + util/resolver.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/util/index.ts b/util/index.ts index 9e93ff6..eab7c99 100644 --- a/util/index.ts +++ b/util/index.ts @@ -3,3 +3,4 @@ export * from './usage'; export * from './control-flow'; export * from './type'; export * from './convert-ast'; +export * from './resolver'; diff --git a/util/resolver.ts b/util/resolver.ts index f94ad0b..b3beb7c 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -604,7 +604,7 @@ class BaseScope implements Scope { for (const element of name.elements) { if (element.kind === ts.SyntaxKind.OmittedExpression) - break; + continue; if (element.propertyName !== undefined && element.propertyName.kind === ts.SyntaxKind.ComputedPropertyName) this._analyzeNode(element.propertyName); this._handleBindingName(element.name, blockScoped); From 5bed3dd967527a89e9d5063d22efc8bdbcc9ee52 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Thu, 22 Nov 2018 20:42:44 +0100 Subject: [PATCH 21/32] micro optimization --- util/resolver.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index b3beb7c..9717e50 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -705,7 +705,7 @@ class NamedDeclarationExpressionScope extends BaseScope { } } -class FunctionLikeInnerScope extends BaseScope { +class FunctionLikeInnerScope extends BaseScope { // tslint:disable-next-line:prefer-function-over-method public getDeclarationsForParent() { return []; @@ -714,8 +714,7 @@ class FunctionLikeInnerScope extends BaseScope { protected _analyze() { if (this.node.type !== undefined) this._analyzeNode(this.node.type); - if ('body' in this.node && this.node.body !== undefined) - this._analyzeNode(this.node.body); + this._analyzeNode(this.node.body); } } @@ -746,7 +745,11 @@ class FunctionLikeScope extends DecoratableDeclarationScopethis.node, + ScopeBoundary.Function, + this._resolver, + ); } public getDelegateScope(location: ts.Node): Scope { From a88388fb52187a9b5f2ddd6bf967173fd7315607 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Sun, 25 Nov 2018 22:53:32 +0100 Subject: [PATCH 22/32] added isAmbientModule --- util/util.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/util/util.ts b/util/util.ts index a5c9a97..43890a9 100644 --- a/util/util.ts +++ b/util/util.ts @@ -1297,3 +1297,7 @@ export function isCompilerOptionEnabled(options: ts.CompilerOptions, option: Boo } return options[option] === true; } + +export function isAmbientModule(node: ts.ModuleDeclaration): boolean { + return node.name.kind === ts.SyntaxKind.StringLiteral || (node.flags & ts.NodeFlags.GlobalAugmentation) !== 0; +} From 767023aad041ead853ea3f102ca04be5ef0ee683 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Sun, 25 Nov 2018 22:54:18 +0100 Subject: [PATCH 23/32] update lookup in merged ambient modules --- util/resolver.ts | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index 9717e50..b7a6a22 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -9,9 +9,11 @@ import { ScopeBoundary, isBlockScopeBoundary, isNodeKind, + isAmbientModule, } from './util'; import { getUsageDomain, getDeclarationDomain } from './usage'; import bind from 'bind-decorator'; +import { isExportSpecifier, isInterfaceDeclaration, isClassDeclaration, isFunctionDeclaration } from '../typeguard'; export enum Domain { None = 0, @@ -651,9 +653,33 @@ class DecoratableDeclarationScope< } function resolveNamespaceExportDomain(checker: ts.TypeChecker, node: ts.ModuleDeclaration | ts.EnumDeclaration, name: string) { - const exportedSymbol = checker.getSymbolAtLocation(node)!.exports!.get(ts.escapeLeadingUnderscores(name)); + const exports = checker.getSymbolAtLocation(node)!.exports!; + const ambientModule = node.kind === ts.SyntaxKind.ModuleDeclaration && isAmbientModule(node); + if (ambientModule) { + // 'export default class C' is visible as 'C' in merged ambient modules + // it shadows named exports of the same name in that ambient module + const exportDefault = exports.get(ts.InternalSymbolName.Default); + if (exportDefault !== undefined) { + const declaration = exportDefault.declarations![0]; + if ( + (isInterfaceDeclaration(declaration) || isClassDeclaration(declaration) || isFunctionDeclaration(declaration)) && + declaration.name !== undefined && + declaration.name.text === name + ) + return getDomainOfSymbol(exportDefault); + } + + } + const exportedSymbol = exports.get(ts.escapeLeadingUnderscores(name)); if (exportedSymbol === undefined) return Domain.None; + if (ambientModule && + exportedSymbol.flags === ts.SymbolFlags.Alias && + exportedSymbol.declarations !== undefined && + exportedSymbol.declarations.some(isExportSpecifier) + ) + // 'export {Foo as Bar}' of ambient module is not in scope + return Domain.None; return node.kind === ts.SyntaxKind.EnumDeclaration ? Domain.Value : getDomainOfSymbol(exportedSymbol); @@ -795,7 +821,7 @@ class FunctionLikeScope extends DecoratableDeclarationScope Date: Mon, 26 Nov 2018 19:51:55 +0100 Subject: [PATCH 24/32] implement declaration lookup, fix conditional type propagation --- util/resolver.ts | 87 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 4 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index b7a6a22..6d92e84 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -54,6 +54,7 @@ export type TypeCheckerOrFactory = export interface Resolver { findReferences(declaration: ts.Identifier, domain?: Domain, getChecker?: TypeCheckerOrFactory): Use[] | undefined; + findDeclarations(use: ts.Identifier, getChecker?: TypeCheckerOrFactory): ts.NamedDeclaration[] | undefined; } export function createResolver(): Resolver { @@ -89,6 +90,12 @@ function createChecker(checkerOrFactory: TypeCheckerOrFactory | undefined): ts.T class ResolverImpl implements Resolver { private _scopeMap = new WeakMap(); + public findParentScope(node: ts.Node): Scope | undefined { + if (node.kind === ts.SyntaxKind.SourceFile) + return; + return this.getOrCreateScope(findScopeBoundary(node.parent!, -1)).getDelegateScope(node); + } + public findReferences(declaration: ts.Identifier, domain = Domain.Any, getChecker?: TypeCheckerOrFactory): Use[] | undefined { domain &= getDeclarationDomain(declaration)!; // TODO if (domain === 0) @@ -112,6 +119,22 @@ class ResolverImpl implements Resolver { return result; } + public findDeclarations(use: ts.Identifier, getChecker?: TypeCheckerOrFactory) { + const domain = getUsageDomain(use)! & Domain.Any; // TODO + if (domain === 0) + return; + const symbol = this.getOrCreateScope(findScopeBoundary(use.parent!, -1)).lookupSymbol(use, domain, makeCheckerFactory(getChecker)); + if (symbol === undefined) + return []; + if (symbol.domain & Domain.DoNotUse) + return; + const result: ts.NamedDeclaration[] = []; + for (const declaration of symbol.declarations) + if (declaration.node !== undefined) // TODO filter by domain? + result.push(declaration.node); + return result; + } + public getOrCreateScope(node: ts.Node) { let scope = this._scopeMap.get(node); if (scope === undefined) { @@ -362,11 +385,13 @@ function filterSymbol(symbol: Symbol, domain: Domain): Symbol { interface Scope { node: ts.Node; + parent?: Scope; getDeclarationsForParent(): Iterable; getUses(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable; getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable; getSymbol(declaration: ts.Identifier): Symbol | undefined; getDelegateScope(location: ts.Node): Scope; + lookupSymbol(use: ts.Identifier, domain: Domain, getChecker: TypeCheckerFactory): Symbol | undefined; } interface MatchRange extends ts.TextRange { @@ -391,6 +416,7 @@ function isInRange(pos: number, range: ts.TextRange): boolean { } class BaseScope implements Scope { + public parent: Scope | undefined = undefined; private _initial = true; private _uses: Use[] = []; private _symbols = new Map(); @@ -463,6 +489,32 @@ class BaseScope implements Scope { return this._symbols.get(declaration.text); } + public lookupSymbol(location: ts.Identifier, domain: Domain, getChecker: TypeCheckerFactory) { + if ((domain & getDomainOfMatchingRange(location.pos, this._propagatedRanges)) === 0) { + const ownSymbol = this._getOwnSymbol(location, domain, getChecker); + if (ownSymbol !== undefined) + return ownSymbol; + } + const parent = this.parent || this._resolver.findParentScope(this.node); + return parent && parent.lookupSymbol(location, domain, getChecker); + } + + protected _getOwnSymbol(location: ts.Identifier, domain: Domain, getChecker: TypeCheckerFactory) { + this._initialize(); + const ownSymbol = this._symbols.get(location.text); + if (ownSymbol === undefined || (ownSymbol.domain & domain) === 0) + return; + if (ownSymbol.domain & Domain.Lazy) { + const checker = getChecker(); + if (checker === undefined) + return {...ownSymbol, domain: ownSymbol.domain | Domain.DoNotUse}; + const resolvedDomain = resolveLazySymbolDomain(checker, ownSymbol); + if (resolvedDomain & domain) + return {...ownSymbol, domain: resolvedDomain}; + } + return ownSymbol; + } + protected _addUse(use: Use) { this._uses.push(use); } @@ -626,6 +678,11 @@ class WithStatementScope extends BaseScope { for (const use of super.getUsesInScope(symbol, domain, getChecker)) yield {location: use.location, domain: use.domain | Domain.DoNotUse}; } + + // tslint:disable-next-line:prefer-function-over-method + protected _getOwnSymbol(location: ts.Identifier, domain: Domain): Symbol { + return {name: location.text, domain: domain | Domain.DoNotUse, declarations: []}; + } } class DeclarationScope extends BaseScope { @@ -697,6 +754,23 @@ class NamespaceScope extends DeclarationScope { @@ -705,7 +779,10 @@ class ConditionalTypeScope extends BaseScope { } protected _collectPropagatedRanges() { - return [MatchRange.create(Domain.Type, this.node.trueType)]; + return [ + {domain: Domain.Any, pos: this.node.pos, end: this.node.extendsType.end}, + MatchRange.create(Domain.Any, this.node.falseType), + ]; } } @@ -713,6 +790,7 @@ class NamedDeclarationExpressionScope extends BaseScope { // tslint:disable-next-line:parameter-properties constructor(node: ts.NamedDeclaration, resolver: ResolverImpl, private _childScope: Scope) { super(node, ScopeBoundary.Function, resolver); + this._childScope.parent = this; } // tslint:disable-next-line:prefer-function-over-method @@ -770,12 +848,14 @@ class FunctionLikeScope extends DecoratableDeclarationScopethis.node, ScopeBoundary.Function, this._resolver, ); + this._innerScope.parent = this; + } } public getDelegateScope(location: ts.Node): Scope { @@ -835,9 +915,8 @@ class FunctionLikeScope extends DecoratableDeclarationScope Date: Mon, 26 Nov 2018 20:33:46 +0100 Subject: [PATCH 25/32] optimize lookup where possible --- util/resolver.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/util/resolver.ts b/util/resolver.ts index 6d92e84..3397d08 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -415,6 +415,20 @@ function isInRange(pos: number, range: ts.TextRange): boolean { return pos >= range.pos && pos < range.end; } +function scopeBoundaryToDomain(boundary: ScopeBoundary): Domain { + switch (boundary) { + case ScopeBoundary.Block: + return Domain.Type | Domain.Value; + case ScopeBoundary.Function: + return Domain.Any; + case ScopeBoundary.Type: + case ScopeBoundary.ConditionalType: + return Domain.Type; + default: + return Domain.None; + } +} + class BaseScope implements Scope { public parent: Scope | undefined = undefined; private _initial = true; @@ -490,7 +504,10 @@ class BaseScope implements Scope { } public lookupSymbol(location: ts.Identifier, domain: Domain, getChecker: TypeCheckerFactory) { - if ((domain & getDomainOfMatchingRange(location.pos, this._propagatedRanges)) === 0) { + if ( + (domain & scopeBoundaryToDomain(this._boundary)) !== 0 && + (domain & getDomainOfMatchingRange(location.pos, this._propagatedRanges)) === 0 + ) { const ownSymbol = this._getOwnSymbol(location, domain, getChecker); if (ownSymbol !== undefined) return ownSymbol; From a1c97fadb702d5fdfb6217c79d880021982b4f40 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Mon, 26 Nov 2018 21:37:17 +0100 Subject: [PATCH 26/32] filter declarations on lookup --- util/resolver.ts | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index 3397d08..e35f62c 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -130,7 +130,7 @@ class ResolverImpl implements Resolver { return; const result: ts.NamedDeclaration[] = []; for (const declaration of symbol.declarations) - if (declaration.node !== undefined) // TODO filter by domain? + if (declaration.node !== undefined) result.push(declaration.node); return result; } @@ -362,9 +362,25 @@ function* lazyFilterUses>( } function resolveLazySymbolDomain(checker: ts.TypeChecker, symbol: Symbol): Domain { - let result = Domain.None; - for (const declaration of symbol.declarations) - result |= declaration.domain & Domain.Lazy ? getLazyDeclarationDomain(declaration.node!, checker) : declaration.domain; + return resolveLazySymbol(checker, symbol).domain; +} + +function resolveLazySymbol(checker: ts.TypeChecker, symbol: Symbol): Symbol { + const result: Symbol = { + name: symbol.name, + domain: Domain.None, + declarations: [], + }; + for (const declaration of symbol.declarations) { + if (declaration.domain & Domain.Lazy) { + const domain = getLazyDeclarationDomain(declaration.node!, checker); + result.declarations.push({...declaration, domain}); + result.domain |= domain; + } else { + result.declarations.push(declaration); + result.domain |= declaration.domain; + } + } return result; } @@ -518,18 +534,18 @@ class BaseScope implements Scope { protected _getOwnSymbol(location: ts.Identifier, domain: Domain, getChecker: TypeCheckerFactory) { this._initialize(); - const ownSymbol = this._symbols.get(location.text); + let ownSymbol = this._symbols.get(location.text); if (ownSymbol === undefined || (ownSymbol.domain & domain) === 0) return; if (ownSymbol.domain & Domain.Lazy) { const checker = getChecker(); if (checker === undefined) return {...ownSymbol, domain: ownSymbol.domain | Domain.DoNotUse}; - const resolvedDomain = resolveLazySymbolDomain(checker, ownSymbol); - if (resolvedDomain & domain) - return {...ownSymbol, domain: resolvedDomain}; + ownSymbol = resolveLazySymbol(checker, ownSymbol); + if ((ownSymbol.domain & domain) === 0) + return; } - return ownSymbol; + return filterSymbol(ownSymbol, domain); } protected _addUse(use: Use) { From e68e4e6326c02879fb4816a1fc5bee0f816d5021 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Mon, 26 Nov 2018 22:06:47 +0100 Subject: [PATCH 27/32] allow type and namespace uses inside WithStatement --- util/resolver.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index e35f62c..86387a6 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -166,7 +166,7 @@ class ResolverImpl implements Resolver { case ts.SyntaxKind.EnumDeclaration: return new NamespaceScope( node, - ScopeBoundary.Function, + ScopeBoundary.Block, this, { name: (node).name.text, @@ -708,13 +708,21 @@ class WithStatementScope extends BaseScope { } public* getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { // we don't know what could be in scope here - for (const use of super.getUsesInScope(symbol, domain, getChecker)) - yield {location: use.location, domain: use.domain | Domain.DoNotUse}; + for (const use of super.getUsesInScope(symbol, domain, getChecker)) { + if (use.domain & Domain.Value) { + yield {location: use.location, domain: use.domain | Domain.DoNotUse}; + } else { + yield use; + } + } } // tslint:disable-next-line:prefer-function-over-method - protected _getOwnSymbol(location: ts.Identifier, domain: Domain): Symbol { - return {name: location.text, domain: domain | Domain.DoNotUse, declarations: []}; + protected _getOwnSymbol(location: ts.Identifier, domain: Domain) { + // we don't need to call super here, as a WithStatement should never have any own declaration + return domain & Domain.Value + ? {name: location.text, domain: domain | Domain.DoNotUse, declarations: []} + : undefined; } } @@ -777,7 +785,7 @@ function resolveNamespaceExportDomain(checker: ts.TypeChecker, node: ts.ModuleDe class NamespaceScope extends DeclarationScope { public getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory) { - // TODO allow type uses in Enum even without the checker + // TODO allow non-value uses in Enum even without the checker return lazyFilterUses( super.getUsesInScope(symbol, domain, getChecker), getChecker, From d499ca91564c24510af82a8e8a9468b890a125b5 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Tue, 27 Nov 2018 21:10:18 +0100 Subject: [PATCH 28/32] fix lookup of InferType --- util/resolver.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index 86387a6..2109327 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -251,8 +251,18 @@ class ResolverImpl implements Resolver { } function findScopeBoundary(node: ts.Node, selector: ScopeBoundarySelector): ts.Node { - while ((isScopeBoundary(node) & selector) === 0 && node.parent !== undefined) + let prev = node; + while ( + ( + (isScopeBoundary(node) & selector) === 0 || + // InferTypes belong to the ConditionalType where they occur in the `extendsType` + selector === ScopeBoundarySelector.InferType && (node).extendsType !== prev + ) && + node.parent !== undefined + ) { + prev = node; node = node.parent; + } return node; } @@ -577,6 +587,7 @@ class BaseScope implements Scope { protected _initialize() { if (this._initial) { this._analyze(); + // TODO Only ConditionalType and Function can get declarations from a child scope for (const scope of this._scopes) for (const decl of scope.getDeclarationsForParent()) this._addDeclaration(decl); @@ -935,12 +946,14 @@ class FunctionLikeScope extends DecoratableDeclarationScope Date: Tue, 27 Nov 2018 21:33:21 +0100 Subject: [PATCH 29/32] don't eagerly initialize child scopes of BlockScope --- util/resolver.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index 2109327..0818516 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -463,6 +463,7 @@ class BaseScope implements Scope { private _scopes: Scope[] = []; private _propagatedRanges: ReadonlyArray = this._collectPropagatedRanges(); protected _declarationsForParent: Declaration[] = []; + private _declarationsForParentInitialized = false; constructor(public node: T, protected _boundary: ScopeBoundary, protected _resolver: ResolverImpl) {} @@ -472,6 +473,11 @@ class BaseScope implements Scope { public getDeclarationsForParent() { this._initialize(); + if (!this._declarationsForParentInitialized) { + for (const scope of this._scopes) + this._declarationsForParent.push(...scope.getDeclarationsForParent()); + this._declarationsForParentInitialized = true; + } return this._declarationsForParent; } @@ -587,10 +593,13 @@ class BaseScope implements Scope { protected _initialize() { if (this._initial) { this._analyze(); - // TODO Only ConditionalType and Function can get declarations from a child scope - for (const scope of this._scopes) - for (const decl of scope.getDeclarationsForParent()) - this._addDeclaration(decl); + if (this._boundary === ScopeBoundary.Function || this._boundary === ScopeBoundary.ConditionalType) { + // only ConditionalType and Function can get declarations from a child scope + for (const scope of this._scopes) + for (const decl of scope.getDeclarationsForParent()) + this._addDeclaration(decl); + this._declarationsForParentInitialized = true; + } this._initial = false; } } From a28a9eb93b4736c9a7caf1f7c3ea39326c18c86f Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Tue, 29 Jan 2019 20:15:25 +0100 Subject: [PATCH 30/32] handle class extends and parameter decorators --- util/resolver.ts | 66 +++++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index 0818516..80ab864 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -192,34 +192,34 @@ class ResolverImpl implements Resolver { case ts.SyntaxKind.ConditionalType: return new ConditionalTypeScope(node, ScopeBoundary.ConditionalType, this); // TODO handling of ClassLikeDeclaration might need change when https://github.com/Microsoft/TypeScript/issues/28472 is resolved + case ts.SyntaxKind.ClassExpression: + if ((node).name !== undefined) + return new NamedDeclarationExpressionScope(node, this, new ClassLikeScope( + node, + ScopeBoundary.Function, + this, + { + name: (node).name!.text, + domain: Domain.Type | Domain.Value, + node: node, + selector: ScopeBoundarySelector.Block, + }, + )); + // falls through case ts.SyntaxKind.ClassDeclaration: - return new DecoratableDeclarationScope( - node, + return new ClassLikeScope( + node, ScopeBoundary.Function, this, - (node).name === undefined + (node).name === undefined ? undefined : { - name: (node).name!.text, + name: (node).name!.text, domain: Domain.Type | Domain.Value, - node: node, + node: node, selector: ScopeBoundarySelector.Block, }, ); - case ts.SyntaxKind.ClassExpression: - if ((node).name === undefined) - return new DeclarationScope(node, ScopeBoundary.Function, this); - return new NamedDeclarationExpressionScope(node, this, new DeclarationScope( - node, - ScopeBoundary.Function, - this, - { - name: (node).name!.text, - domain: Domain.Type | Domain.Value, - node: node, - selector: ScopeBoundarySelector.Block, - }, - )); case ts.SyntaxKind.FunctionExpression: if ((node).name !== undefined) return new NamedDeclarationExpressionScope( @@ -759,7 +759,7 @@ class DeclarationScope exte } class DecoratableDeclarationScope< - T extends ts.ClassDeclaration | ts.SignatureDeclaration = ts.ClassDeclaration | ts.SignatureDeclaration, + T extends ts.ClassLikeDeclaration | ts.SignatureDeclaration = ts.ClassLikeDeclaration | ts.SignatureDeclaration, > extends DeclarationScope { protected _collectPropagatedRanges() { // decorators cannot access parameters and type parameters of the declaration they decorate @@ -770,6 +770,20 @@ class DecoratableDeclarationScope< } } +class ClassLikeScope extends DecoratableDeclarationScope { + protected _collectPropagatedRanges() { + const result = super._collectPropagatedRanges(); // decorators + if (this.node.heritageClauses !== undefined) { + const [clause] = this.node.heritageClauses; + if (clause.token === ts.SyntaxKind.ExtendsKeyword && clause.types.length !== 0) + // expression in 'extends' clause cannot access type parameters and 'this' of the class + // this slightly deviates from TypeScript's behavior, but it's an error to reference class generics inside 'extends' anyway + result.push(MatchRange.create(Domain.Any, clause.types[0].expression)); + } + return result; + } +} + function resolveNamespaceExportDomain(checker: ts.TypeChecker, node: ts.ModuleDeclaration | ts.EnumDeclaration, name: string) { const exports = checker.getSymbolAtLocation(node)!.exports!; const ambientModule = node.kind === ts.SyntaxKind.ModuleDeclaration && isAmbientModule(node); @@ -945,12 +959,13 @@ class FunctionLikeScope extends DecoratableDeclarationScope Date: Wed, 30 Jan 2019 19:46:44 +0100 Subject: [PATCH 31/32] track references of this and super --- util/resolver.ts | 72 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 11 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index 80ab864..f0e29fc 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -3,7 +3,6 @@ import { ScopeBoundarySelector, isScopeBoundary, isBlockScopedVariableDeclarationList, - isThisParameter, getPropertyName, getDeclarationOfBindingElement, ScopeBoundary, @@ -41,7 +40,7 @@ interface Symbol { declarations: Declaration[]; } export interface Use { - location: ts.Identifier; + location: ts.Identifier | ts.SuperExpression | ts.ThisExpression | ts.ThisTypeNode; domain: Domain; } @@ -151,15 +150,26 @@ class ResolverImpl implements Resolver { case ts.SyntaxKind.MappedType: return new BaseScope(node, ScopeBoundary.Type, this); case ts.SyntaxKind.InterfaceDeclaration: + return new InterfaceScope( + node, + ScopeBoundary.Type, + this, + { + name: (node).name.text, + domain: Domain.Type, + node: node, + selector: ScopeBoundarySelector.Type, + }, + ); case ts.SyntaxKind.TypeAliasDeclaration: return new DeclarationScope( - node, + node, ScopeBoundary.Type, this, { - name: (node).name.text, + name: (node).name.text, domain: Domain.Type, - node: node, + node: node, selector: ScopeBoundarySelector.Type, }, ); @@ -455,6 +465,18 @@ function scopeBoundaryToDomain(boundary: ScopeBoundary): Domain { } } +function getUseName(node: Use['location']) { + switch (node.kind) { + case ts.SyntaxKind.ThisKeyword: + case ts.SyntaxKind.ThisType: + return 'this'; + case ts.SyntaxKind.SuperKeyword: + return 'super'; + default: + return node.text; + } +} + class BaseScope implements Scope { public parent: Scope | undefined = undefined; private _initial = true; @@ -508,7 +530,7 @@ class BaseScope implements Scope { for (const use of this._uses) if ( use.domain & domain && - use.location.text === symbol.name && + getUseName(use.location) === symbol.name && ((use.domain & getDomainOfMatchingRange(use.location.pos, ranges)) !== 0) === include ) yield use; @@ -630,7 +652,7 @@ class BaseScope implements Scope { // catch binding return this._handleBindingName((node).name, true); case ts.SyntaxKind.Parameter: - if (node.parent!.kind === ts.SyntaxKind.IndexSignature || isThisParameter(node)) + if (node.parent!.kind === ts.SyntaxKind.IndexSignature) return (node).type && this._analyzeNode((node).type!); return this._handleVariableLikeDeclaration(node, false); case ts.SyntaxKind.EnumMember: @@ -678,6 +700,15 @@ class BaseScope implements Scope { if ((node).default !== undefined) this._analyzeNode((node).default!); return; + case ts.SyntaxKind.ThisType: + this._addUse({location: node, domain: Domain.Type}); + return; + case ts.SyntaxKind.ThisKeyword: + this._addUse({location: node, domain: Domain.Value}); + return; + case ts.SyntaxKind.SuperKeyword: + this._addUse({location: node, domain: Domain.Value}); + return; case ts.SyntaxKind.Identifier: { const domain = getUsageDomain(node); if (domain !== undefined) // TODO @@ -770,18 +801,32 @@ class DecoratableDeclarationScope< } } +class InterfaceScope extends DeclarationScope { + protected _analyze() { + this._addDeclaration({name: 'this', domain: Domain.Type, node: undefined, selector: ScopeBoundarySelector.Type}); + super._analyze(); + } +} + class ClassLikeScope extends DecoratableDeclarationScope { protected _collectPropagatedRanges() { const result = super._collectPropagatedRanges(); // decorators if (this.node.heritageClauses !== undefined) { const [clause] = this.node.heritageClauses; if (clause.token === ts.SyntaxKind.ExtendsKeyword && clause.types.length !== 0) - // expression in 'extends' clause cannot access type parameters and 'this' of the class - // this slightly deviates from TypeScript's behavior, but it's an error to reference class generics inside 'extends' anyway - result.push(MatchRange.create(Domain.Any, clause.types[0].expression)); + // expression in 'extends' cannot access 'this' and 'super' of the class + result.push(MatchRange.create(Domain.ValueOrNamespace, clause.types[0].expression)); } return result; } + + protected _analyze() { + this._addDeclaration( + {name: 'this', domain: Domain.Type | Domain.Value, node: undefined, selector: ScopeBoundarySelector.Function}, + ); + this._addDeclaration({name: 'super', domain: Domain.Value, node: undefined, selector: ScopeBoundarySelector.Function}); + super._analyze(); + } } function resolveNamespaceExportDomain(checker: ts.TypeChecker, node: ts.ModuleDeclaration | ts.EnumDeclaration, name: string) { @@ -922,6 +967,10 @@ class FunctionLikeScope extends DecoratableDeclarationScope Date: Wed, 30 Jan 2019 19:57:43 +0100 Subject: [PATCH 32/32] allow findDeclarations of this and super --- util/resolver.ts | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/util/resolver.ts b/util/resolver.ts index f0e29fc..cfde8c2 100644 --- a/util/resolver.ts +++ b/util/resolver.ts @@ -86,6 +86,18 @@ function createChecker(checkerOrFactory: TypeCheckerOrFactory | undefined): ts.T return result; } +function getUseDomain(node: Use['location']): Domain { + switch (node.kind) { + case ts.SyntaxKind.ThisKeyword: + case ts.SyntaxKind.SuperKeyword: + return Domain.Value; + case ts.SyntaxKind.ThisType: + return Domain.Type; + default: + return getUsageDomain(node)! & Domain.Any; + } +} + class ResolverImpl implements Resolver { private _scopeMap = new WeakMap(); @@ -96,6 +108,7 @@ class ResolverImpl implements Resolver { } public findReferences(declaration: ts.Identifier, domain = Domain.Any, getChecker?: TypeCheckerOrFactory): Use[] | undefined { + // TODO allow 'this'-parameter domain &= getDeclarationDomain(declaration)!; // TODO if (domain === 0) return; // not a declaration @@ -118,8 +131,8 @@ class ResolverImpl implements Resolver { return result; } - public findDeclarations(use: ts.Identifier, getChecker?: TypeCheckerOrFactory) { - const domain = getUsageDomain(use)! & Domain.Any; // TODO + public findDeclarations(use: Use['location'], getChecker?: TypeCheckerOrFactory) { + const domain = getUseDomain(use); if (domain === 0) return; const symbol = this.getOrCreateScope(findScopeBoundary(use.parent!, -1)).lookupSymbol(use, domain, makeCheckerFactory(getChecker)); @@ -427,7 +440,7 @@ interface Scope { getUsesInScope(symbol: Symbol, domain: Domain, getChecker: TypeCheckerFactory): Iterable; getSymbol(declaration: ts.Identifier): Symbol | undefined; getDelegateScope(location: ts.Node): Scope; - lookupSymbol(use: ts.Identifier, domain: Domain, getChecker: TypeCheckerFactory): Symbol | undefined; + lookupSymbol(use: Use['location'], domain: Domain, getChecker: TypeCheckerFactory): Symbol | undefined; } interface MatchRange extends ts.TextRange { @@ -557,12 +570,12 @@ class BaseScope implements Scope { return this._symbols.get(declaration.text); } - public lookupSymbol(location: ts.Identifier, domain: Domain, getChecker: TypeCheckerFactory) { + public lookupSymbol(location: Use['location'], domain: Domain, getChecker: TypeCheckerFactory) { if ( (domain & scopeBoundaryToDomain(this._boundary)) !== 0 && (domain & getDomainOfMatchingRange(location.pos, this._propagatedRanges)) === 0 ) { - const ownSymbol = this._getOwnSymbol(location, domain, getChecker); + const ownSymbol = this._getOwnSymbol(getUseName(location), domain, getChecker); if (ownSymbol !== undefined) return ownSymbol; } @@ -570,9 +583,9 @@ class BaseScope implements Scope { return parent && parent.lookupSymbol(location, domain, getChecker); } - protected _getOwnSymbol(location: ts.Identifier, domain: Domain, getChecker: TypeCheckerFactory) { + protected _getOwnSymbol(name: string, domain: Domain, getChecker: TypeCheckerFactory) { this._initialize(); - let ownSymbol = this._symbols.get(location.text); + let ownSymbol = this._symbols.get(name); if (ownSymbol === undefined || (ownSymbol.domain & domain) === 0) return; if (ownSymbol.domain & Domain.Lazy) { @@ -769,10 +782,10 @@ class WithStatementScope extends BaseScope { } // tslint:disable-next-line:prefer-function-over-method - protected _getOwnSymbol(location: ts.Identifier, domain: Domain) { + protected _getOwnSymbol(name: string, domain: Domain) { // we don't need to call super here, as a WithStatement should never have any own declaration return domain & Domain.Value - ? {name: location.text, domain: domain | Domain.DoNotUse, declarations: []} + ? {name, domain: domain | Domain.DoNotUse, declarations: []} : undefined; } } @@ -875,18 +888,18 @@ class NamespaceScope extends DeclarationScope