From 7325053dfa9df13e9e54f4854d6e2346c3c77c1c Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 26 Dec 2019 15:51:49 -0600 Subject: [PATCH] refactor(language-service): adds Symbol#typeArguments and does cleanup (#34571) Adds a `typeArguments` method to the `Symbol` interface, cleaning up how type parameters of a TypeScript type are currently found. This will be necessary for providing completions for `$event` variables' properties (#34570). This commit also performs some fly-by cleanups seen while implementing the `typeArguments` methods. There is more clean up to do in the `typescript_symbols` file, but the scope of this commit didn't need to get larger. PR Close #34571 --- .../language-service/src/expression_type.ts | 3 +- .../language-service/src/global_symbols.ts | 1 + .../language-service/src/locate_symbol.ts | 2 + packages/language-service/src/symbols.ts | 5 + .../src/typescript_symbols.ts | 110 +++++++++--------- 5 files changed, 63 insertions(+), 58 deletions(-) diff --git a/packages/language-service/src/expression_type.ts b/packages/language-service/src/expression_type.ts index fc47f6a875..a6795a14c6 100644 --- a/packages/language-service/src/expression_type.ts +++ b/packages/language-service/src/expression_type.ts @@ -223,7 +223,8 @@ export class AstType implements AstVisitor { members(): SymbolTable{return _this.scope;}, signatures(): Signature[]{return [];}, selectSignature(types): Signature | undefined{return undefined;}, - indexed(argument): Symbol | undefined{return undefined;} + indexed(argument): Symbol | undefined{return undefined;}, + typeArguments(): Symbol[] | undefined{return undefined;}, }; } diff --git a/packages/language-service/src/global_symbols.ts b/packages/language-service/src/global_symbols.ts index 2f2c46aba0..4c0325ab62 100644 --- a/packages/language-service/src/global_symbols.ts +++ b/packages/language-service/src/global_symbols.ts @@ -59,6 +59,7 @@ export const createGlobalSymbolTable: (query: ng.SymbolQuery) => ng.SymbolTable }; }, indexed: () => undefined, + typeArguments: () => undefined, }, }, ]); diff --git a/packages/language-service/src/locate_symbol.ts b/packages/language-service/src/locate_symbol.ts index 20e66e4645..dba92ab926 100644 --- a/packages/language-service/src/locate_symbol.ts +++ b/packages/language-service/src/locate_symbol.ts @@ -223,4 +223,6 @@ class OverrideKindSymbol implements Symbol { selectSignature(types: Symbol[]) { return this.sym.selectSignature(types); } indexed(argument: Symbol) { return this.sym.indexed(argument); } + + typeArguments(): Symbol[]|undefined { return this.sym.typeArguments(); } } diff --git a/packages/language-service/src/symbols.ts b/packages/language-service/src/symbols.ts index 2b88deab11..da3f851e24 100644 --- a/packages/language-service/src/symbols.ts +++ b/packages/language-service/src/symbols.ts @@ -129,6 +129,11 @@ export interface Symbol { * If the symbol cannot be indexed, this method should return `undefined`. */ indexed(argument: Symbol, key?: any): Symbol|undefined; + + /** + * Returns the type arguments of a Symbol, if any. + */ + typeArguments(): Symbol[]|undefined; } /** diff --git a/packages/language-service/src/typescript_symbols.ts b/packages/language-service/src/typescript_symbols.ts index 834d242b7f..88b21d5d21 100644 --- a/packages/language-service/src/typescript_symbols.ts +++ b/packages/language-service/src/typescript_symbols.ts @@ -125,10 +125,10 @@ class TypeScriptSymbolQuery implements SymbolQuery { getElementType(type: Symbol): Symbol|undefined { if (type instanceof TypeWrapper) { - const elementType = getTypeParameterOf(type.tsType, 'Array'); - if (elementType) { - return new TypeWrapper(elementType, type.context); - } + const tSymbol = type.tsType.symbol; + const tArgs = type.typeArguments(); + if (!tSymbol || tSymbol.name !== 'Array' || !tArgs || tArgs.length != 1) return; + return tArgs[0]; } } @@ -206,19 +206,19 @@ class TypeScriptSymbolQuery implements SymbolQuery { } private getTsTypeOf(symbol: Symbol): ts.Type|undefined { - const type = this.getTypeWrapper(symbol); + const type = getTypeWrapper(symbol); return type && type.tsType; } +} - private getTypeWrapper(symbol: Symbol): TypeWrapper|undefined { - let type: TypeWrapper|undefined = undefined; - if (symbol instanceof TypeWrapper) { - type = symbol; - } else if (symbol.type instanceof TypeWrapper) { - type = symbol.type; - } - return type; +function getTypeWrapper(symbol: Symbol): TypeWrapper|undefined { + let type: TypeWrapper|undefined = undefined; + if (symbol instanceof TypeWrapper) { + type = symbol; + } else if (symbol.type instanceof TypeWrapper) { + type = symbol.type; } + return type; } function typeCallable(type: ts.Type): boolean { @@ -290,8 +290,8 @@ class TypeWrapper implements Symbol { } indexed(argument: Symbol, value: any): Symbol|undefined { - const type = argument instanceof TypeWrapper ? argument : argument.type; - if (!(type instanceof TypeWrapper)) return; + const type = getTypeWrapper(argument); + if (!type) return; const typeKind = typeKindOf(type.tsType); switch (typeKind) { @@ -311,6 +311,13 @@ class TypeWrapper implements Symbol { return sType && new TypeWrapper(sType, this.context); } } + + typeArguments(): Symbol[]|undefined { + // TODO: use checker.getTypeArguments when TS 3.7 lands in the monorepo. + const typeArguments: ReadonlyArray = (this.tsType as any).typeArguments; + if (!typeArguments) return undefined; + return typeArguments.map(ta => new TypeWrapper(ta, this.context)); + } } // If stringIndexType a primitive type(e.g. 'string'), the Symbol is undefined; @@ -342,7 +349,7 @@ class SymbolWrapper implements Symbol { get kind(): DeclarationKind { return this.callable ? 'method' : 'property'; } - get type(): Symbol|undefined { return new TypeWrapper(this.tsType, this.context); } + get type(): TypeWrapper { return new TypeWrapper(this.tsType, this.context); } get container(): Symbol|undefined { return getContainerOf(this.symbol, this.context); } @@ -380,6 +387,8 @@ class SymbolWrapper implements Symbol { indexed(argument: Symbol): Symbol|undefined { return undefined; } + typeArguments(): Symbol[]|undefined { return this.type.typeArguments(); } + private get tsType(): ts.Type { let type = this._tsType; if (!type) { @@ -405,21 +414,21 @@ class DeclaredSymbol implements Symbol { get container(): Symbol|undefined { return undefined; } - get type() { return this.declaration.type; } + get type(): Symbol { return this.declaration.type; } - get callable(): boolean { return this.declaration.type.callable; } + get callable(): boolean { return this.type.callable; } get definition(): Definition { return this.declaration.definition; } get documentation(): ts.SymbolDisplayPart[] { return this.declaration.type.documentation; } - members(): SymbolTable { return this.declaration.type.members(); } + members(): SymbolTable { return this.type.members(); } - signatures(): Signature[] { return this.declaration.type.signatures(); } + signatures(): Signature[] { return this.type.signatures(); } - selectSignature(types: Symbol[]): Signature|undefined { - return this.declaration.type.selectSignature(types); - } + selectSignature(types: Symbol[]): Signature|undefined { return this.type.selectSignature(types); } + + typeArguments(): Symbol[]|undefined { return this.type.typeArguments(); } indexed(argument: Symbol): Symbol|undefined { return undefined; } } @@ -442,17 +451,14 @@ class SignatureResultOverride implements Signature { get result(): Symbol { return this.resultType; } } -export function toSymbolTableFactory(symbols: ts.Symbol[]) { +export function toSymbolTableFactory(symbols: ts.Symbol[]): ts.SymbolTable { // ∀ Typescript version >= 2.2, `SymbolTable` is implemented as an ES6 `Map` const result = new Map(); for (const symbol of symbols) { result.set(symbol.name, symbol); } - // First, tell the compiler that `result` is of type `any`. Then, use a second type assertion - // to `ts.SymbolTable`. - // Otherwise, `Map` and `ts.SymbolTable` will be considered as incompatible - // types by the compiler - return (result); + + return result as ts.SymbolTable; } function toSymbols(symbolTable: ts.SymbolTable | undefined): ts.Symbol[] { @@ -602,7 +608,7 @@ class PipeSymbol implements Symbol { get name(): string { return this.pipe.name; } - get type(): Symbol|undefined { return new TypeWrapper(this.tsType, this.context); } + get type(): TypeWrapper { return new TypeWrapper(this.tsType, this.context); } get definition(): Definition|undefined { const symbol = this.tsType.getSymbol(); @@ -625,24 +631,21 @@ class PipeSymbol implements Symbol { let signature = selectSignature(this.tsType, this.context, types) !; if (types.length > 0) { const parameterType = types[0]; - if (parameterType instanceof TypeWrapper) { - let resultType: ts.Type|undefined = undefined; - switch (this.name) { - case 'async': - // Get symbol of 'Observable', 'Promise', or 'EventEmitter' type. - const symbol = parameterType.tsType.symbol; - if (symbol) { - resultType = getTypeParameterOf(parameterType.tsType, symbol.name); - } - break; - case 'slice': - resultType = parameterType.tsType; - break; - } - if (resultType) { - signature = new SignatureResultOverride( - signature, new TypeWrapper(resultType, parameterType.context)); - } + let resultType: Symbol|undefined = undefined; + switch (this.name) { + case 'async': + // Get type argument of 'Observable', 'Promise', or 'EventEmitter'. + const tArgs = parameterType.typeArguments(); + if (tArgs && tArgs.length === 1) { + resultType = tArgs[0]; + } + break; + case 'slice': + resultType = parameterType; + break; + } + if (resultType) { + signature = new SignatureResultOverride(signature, resultType); } } return signature; @@ -650,6 +653,8 @@ class PipeSymbol implements Symbol { indexed(argument: Symbol): Symbol|undefined { return undefined; } + typeArguments(): Symbol[]|undefined { return this.type.typeArguments(); } + private get tsType(): ts.Type { let type = this._tsType; if (!type) { @@ -798,15 +803,6 @@ function getContainerOf(symbol: ts.Symbol, context: TypeContext): Symbol|undefin } } -function getTypeParameterOf(type: ts.Type, name: string): ts.Type|undefined { - if (type && type.symbol && type.symbol.name == name) { - const typeArguments: ts.Type[] = (type as any).typeArguments; - if (typeArguments && typeArguments.length <= 1) { - return typeArguments[0]; - } - } -} - function typeKindOf(type: ts.Type | undefined): BuiltinType { if (type) { if (type.flags & ts.TypeFlags.Any) {