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;},
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;},
};
}

View File

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

View File

@ -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(); }
}

View File

@ -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;
}
/**

View File

@ -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<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;
@ -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<string, ts.Symbol>();
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<string, ts.Symbol>` and `ts.SymbolTable` will be considered as incompatible
// types by the compiler
return <ts.SymbolTable>(<any>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) {