feat(language-service): completions support for tuple array (#33928)

PR Close #33928
This commit is contained in:
ivanwonder 2019-11-20 07:23:45 +00:00 committed by Miško Hevery
parent de7713d6ea
commit 5a227d8d7c
6 changed files with 33 additions and 5 deletions

View File

@ -241,7 +241,8 @@ export class AstType implements AstVisitor {
visitKeyedRead(ast: KeyedRead): Symbol {
const targetType = this.getType(ast.obj);
const keyType = this.getType(ast.key);
const result = targetType.indexed(keyType);
const result = targetType.indexed(
keyType, ast.key instanceof LiteralPrimitive ? ast.key.value : undefined);
return result || this.anyType;
}

View File

@ -116,9 +116,12 @@ export interface Symbol {
/**
* Return the type of the expression if this symbol is indexed by `argument`.
* Sometimes we need the key of arguments to get the type of the expression, for example
* in the case of tuples (`type Example = [string, number]`).
* [string, number]).
* If the symbol cannot be indexed, this method should return `undefined`.
*/
indexed(argument: Symbol): Symbol|undefined;
indexed(argument: Symbol, key?: any): Symbol|undefined;
}
/**
@ -349,4 +352,4 @@ export interface SymbolQuery {
* Return the span of the narrowest non-token node at the given location.
*/
getSpanAt(line: number, column: number): Span|undefined;
}
}

View File

@ -284,7 +284,7 @@ class TypeWrapper implements Symbol {
return selectSignature(this.tsType, this.context, types);
}
indexed(argument: Symbol): Symbol|undefined {
indexed(argument: Symbol, value: any): Symbol|undefined {
const type = argument instanceof TypeWrapper ? argument : argument.type;
if (!(type instanceof TypeWrapper)) return;
@ -292,7 +292,14 @@ class TypeWrapper implements Symbol {
switch (typeKind) {
case BuiltinType.Number:
const nType = this.tsType.getNumberIndexType();
return nType && new TypeWrapper(nType, this.context);
if (nType) {
// get the right tuple type by value, like 'var t: [number, string];'
if (nType.isUnion()) {
return new TypeWrapper(nType.types[value], this.context);
}
return new TypeWrapper(nType, this.context);
}
return undefined;
case BuiltinType.String:
const sType = this.tsType.getStringIndexType();
return sType && new TypeWrapper(sType, this.context);

View File

@ -125,6 +125,13 @@ describe('completions', () => {
expectContain(completions, CompletionKind.PROPERTY, ['id', 'name']);
});
it('should work with numeric index signatures (tuple arrays)', () => {
mockHost.override(TEST_TEMPLATE, `{{ tupleArray[1].~{tuple-array-number-index}}}`);
const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'tuple-array-number-index');
const completions = ngLS.getCompletionsAt(TEST_TEMPLATE, marker.start);
expectContain(completions, CompletionKind.PROPERTY, ['id', 'name']);
});
describe('with string index signatures', () => {
it('should work with index notation', () => {
mockHost.override(TEST_TEMPLATE, `{{ heroesByName['Jacky'].~{heroes-string-index}}}`);

View File

@ -150,6 +150,15 @@ describe('diagnostics', () => {
});
});
it('should produce diagnostics for invalid tuple type property access', () => {
mockHost.override(TEST_TEMPLATE, `
{{tupleArray[1].badProperty}}`);
const diags = ngLS.getDiagnostics(TEST_TEMPLATE);
expect(diags.length).toBe(1);
expect(diags[0].messageText)
.toBe(`Identifier 'badProperty' is not defined. 'Hero' does not contain such a member`);
});
it('should not produce errors on function.bind()', () => {
mockHost.override(TEST_TEMPLATE, `
<test-comp (test)="myClick.bind(this)">

View File

@ -189,6 +189,7 @@ export class TemplateReference {
title = 'Some title';
hero: Hero = {id: 1, name: 'Windstorm'};
heroes: Hero[] = [this.hero];
tupleArray: [string, Hero] = ['test', this.hero];
league: Hero[][] = [this.heroes];
heroesByName: {[name: string]: Hero} = {};
anyValue: any;