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
This commit is contained in:
ayazhafiz 2019-12-26 15:51:49 -06:00 committed by atscott
parent 15b4173a76
commit 7325053dfa
5 changed files with 63 additions and 58 deletions

View File

@ -223,7 +223,8 @@ export class AstType implements AstVisitor {
members(): SymbolTable{return _this.scope;}, members(): SymbolTable{return _this.scope;},
signatures(): Signature[]{return [];}, signatures(): Signature[]{return [];},
selectSignature(types): Signature | undefined{return undefined;}, selectSignature(types): Signature | undefined{return undefined;},
indexed(argument): Symbol | undefined{return undefined;} indexed(argument): Symbol | undefined{return undefined;},
typeArguments(): Symbol[] | undefined{return undefined;},
}; };
} }

View File

@ -59,6 +59,7 @@ export const createGlobalSymbolTable: (query: ng.SymbolQuery) => ng.SymbolTable
}; };
}, },
indexed: () => undefined, indexed: () => undefined,
typeArguments: () => undefined,
}, },
}, },
]); ]);

View File

@ -223,4 +223,6 @@ class OverrideKindSymbol implements Symbol {
selectSignature(types: Symbol[]) { return this.sym.selectSignature(types); } selectSignature(types: Symbol[]) { return this.sym.selectSignature(types); }
indexed(argument: Symbol) { return this.sym.indexed(argument); } indexed(argument: Symbol) { return this.sym.indexed(argument); }
typeArguments(): Symbol[]|undefined { return this.sym.typeArguments(); }
} }

View File

@ -129,6 +129,11 @@ export interface Symbol {
* If the symbol cannot be indexed, this method should return `undefined`. * If the symbol cannot be indexed, this method should return `undefined`.
*/ */
indexed(argument: Symbol, key?: any): Symbol|undefined; indexed(argument: Symbol, key?: any): Symbol|undefined;
/**
* Returns the type arguments of a Symbol, if any.
*/
typeArguments(): Symbol[]|undefined;
} }
/** /**

View File

@ -125,10 +125,10 @@ class TypeScriptSymbolQuery implements SymbolQuery {
getElementType(type: Symbol): Symbol|undefined { getElementType(type: Symbol): Symbol|undefined {
if (type instanceof TypeWrapper) { if (type instanceof TypeWrapper) {
const elementType = getTypeParameterOf(type.tsType, 'Array'); const tSymbol = type.tsType.symbol;
if (elementType) { const tArgs = type.typeArguments();
return new TypeWrapper(elementType, type.context); if (!tSymbol || tSymbol.name !== 'Array' || !tArgs || tArgs.length != 1) return;
} return tArgs[0];
} }
} }
@ -206,11 +206,12 @@ class TypeScriptSymbolQuery implements SymbolQuery {
} }
private getTsTypeOf(symbol: Symbol): ts.Type|undefined { private getTsTypeOf(symbol: Symbol): ts.Type|undefined {
const type = this.getTypeWrapper(symbol); const type = getTypeWrapper(symbol);
return type && type.tsType; return type && type.tsType;
} }
}
private getTypeWrapper(symbol: Symbol): TypeWrapper|undefined { function getTypeWrapper(symbol: Symbol): TypeWrapper|undefined {
let type: TypeWrapper|undefined = undefined; let type: TypeWrapper|undefined = undefined;
if (symbol instanceof TypeWrapper) { if (symbol instanceof TypeWrapper) {
type = symbol; type = symbol;
@ -218,7 +219,6 @@ class TypeScriptSymbolQuery implements SymbolQuery {
type = symbol.type; type = symbol.type;
} }
return type; return type;
}
} }
function typeCallable(type: ts.Type): boolean { function typeCallable(type: ts.Type): boolean {
@ -290,8 +290,8 @@ class TypeWrapper implements Symbol {
} }
indexed(argument: Symbol, value: any): Symbol|undefined { indexed(argument: Symbol, value: any): Symbol|undefined {
const type = argument instanceof TypeWrapper ? argument : argument.type; const type = getTypeWrapper(argument);
if (!(type instanceof TypeWrapper)) return; if (!type) return;
const typeKind = typeKindOf(type.tsType); const typeKind = typeKindOf(type.tsType);
switch (typeKind) { switch (typeKind) {
@ -311,6 +311,13 @@ class TypeWrapper implements Symbol {
return sType && new TypeWrapper(sType, this.context); 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<ts.Type> = (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; // 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 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); } 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; } indexed(argument: Symbol): Symbol|undefined { return undefined; }
typeArguments(): Symbol[]|undefined { return this.type.typeArguments(); }
private get tsType(): ts.Type { private get tsType(): ts.Type {
let type = this._tsType; let type = this._tsType;
if (!type) { if (!type) {
@ -405,21 +414,21 @@ class DeclaredSymbol implements Symbol {
get container(): Symbol|undefined { return undefined; } 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 definition(): Definition { return this.declaration.definition; }
get documentation(): ts.SymbolDisplayPart[] { return this.declaration.type.documentation; } 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 { selectSignature(types: Symbol[]): Signature|undefined { return this.type.selectSignature(types); }
return this.declaration.type.selectSignature(types);
} typeArguments(): Symbol[]|undefined { return this.type.typeArguments(); }
indexed(argument: Symbol): Symbol|undefined { return undefined; } indexed(argument: Symbol): Symbol|undefined { return undefined; }
} }
@ -442,17 +451,14 @@ class SignatureResultOverride implements Signature {
get result(): Symbol { return this.resultType; } 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` // ∀ Typescript version >= 2.2, `SymbolTable` is implemented as an ES6 `Map`
const result = new Map<string, ts.Symbol>(); const result = new Map<string, ts.Symbol>();
for (const symbol of symbols) { for (const symbol of symbols) {
result.set(symbol.name, symbol); result.set(symbol.name, symbol);
} }
// First, tell the compiler that `result` is of type `any`. Then, use a second type assertion
// to `ts.SymbolTable`. return result as ts.SymbolTable;
// Otherwise, `Map<string, ts.Symbol>` and `ts.SymbolTable` will be considered as incompatible
// types by the compiler
return <ts.SymbolTable>(<any>result);
} }
function toSymbols(symbolTable: ts.SymbolTable | undefined): ts.Symbol[] { function toSymbols(symbolTable: ts.SymbolTable | undefined): ts.Symbol[] {
@ -602,7 +608,7 @@ class PipeSymbol implements Symbol {
get name(): string { return this.pipe.name; } 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 { get definition(): Definition|undefined {
const symbol = this.tsType.getSymbol(); const symbol = this.tsType.getSymbol();
@ -625,24 +631,21 @@ class PipeSymbol implements Symbol {
let signature = selectSignature(this.tsType, this.context, types) !; let signature = selectSignature(this.tsType, this.context, types) !;
if (types.length > 0) { if (types.length > 0) {
const parameterType = types[0]; const parameterType = types[0];
if (parameterType instanceof TypeWrapper) { let resultType: Symbol|undefined = undefined;
let resultType: ts.Type|undefined = undefined;
switch (this.name) { switch (this.name) {
case 'async': case 'async':
// Get symbol of 'Observable', 'Promise', or 'EventEmitter' type. // Get type argument of 'Observable', 'Promise', or 'EventEmitter'.
const symbol = parameterType.tsType.symbol; const tArgs = parameterType.typeArguments();
if (symbol) { if (tArgs && tArgs.length === 1) {
resultType = getTypeParameterOf(parameterType.tsType, symbol.name); resultType = tArgs[0];
} }
break; break;
case 'slice': case 'slice':
resultType = parameterType.tsType; resultType = parameterType;
break; break;
} }
if (resultType) { if (resultType) {
signature = new SignatureResultOverride( signature = new SignatureResultOverride(signature, resultType);
signature, new TypeWrapper(resultType, parameterType.context));
}
} }
} }
return signature; return signature;
@ -650,6 +653,8 @@ class PipeSymbol implements Symbol {
indexed(argument: Symbol): Symbol|undefined { return undefined; } indexed(argument: Symbol): Symbol|undefined { return undefined; }
typeArguments(): Symbol[]|undefined { return this.type.typeArguments(); }
private get tsType(): ts.Type { private get tsType(): ts.Type {
let type = this._tsType; let type = this._tsType;
if (!type) { 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 { function typeKindOf(type: ts.Type | undefined): BuiltinType {
if (type) { if (type) {
if (type.flags & ts.TypeFlags.Any) { if (type.flags & ts.TypeFlags.Any) {