From 5a227d8d7cd839e677976807d9aedff3da12bed3 Mon Sep 17 00:00:00 2001 From: ivanwonder Date: Wed, 20 Nov 2019 07:23:45 +0000 Subject: [PATCH] feat(language-service): completions support for tuple array (#33928) PR Close #33928 --- packages/language-service/src/expression_type.ts | 3 ++- packages/language-service/src/symbols.ts | 7 +++++-- packages/language-service/src/typescript_symbols.ts | 11 +++++++++-- packages/language-service/test/completions_spec.ts | 7 +++++++ packages/language-service/test/diagnostics_spec.ts | 9 +++++++++ .../test/project/app/parsing-cases.ts | 1 + 6 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/language-service/src/expression_type.ts b/packages/language-service/src/expression_type.ts index 359ed1c385..1aee6ed289 100644 --- a/packages/language-service/src/expression_type.ts +++ b/packages/language-service/src/expression_type.ts @@ -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; } diff --git a/packages/language-service/src/symbols.ts b/packages/language-service/src/symbols.ts index f8ea3ced6e..8a7ea96b3f 100644 --- a/packages/language-service/src/symbols.ts +++ b/packages/language-service/src/symbols.ts @@ -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; -} \ No newline at end of file +} diff --git a/packages/language-service/src/typescript_symbols.ts b/packages/language-service/src/typescript_symbols.ts index 1bae4d0946..2e4343206f 100644 --- a/packages/language-service/src/typescript_symbols.ts +++ b/packages/language-service/src/typescript_symbols.ts @@ -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); diff --git a/packages/language-service/test/completions_spec.ts b/packages/language-service/test/completions_spec.ts index 9bee38e345..972cb3db66 100644 --- a/packages/language-service/test/completions_spec.ts +++ b/packages/language-service/test/completions_spec.ts @@ -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}}}`); diff --git a/packages/language-service/test/diagnostics_spec.ts b/packages/language-service/test/diagnostics_spec.ts index 1e76337262..0820891005 100644 --- a/packages/language-service/test/diagnostics_spec.ts +++ b/packages/language-service/test/diagnostics_spec.ts @@ -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, ` diff --git a/packages/language-service/test/project/app/parsing-cases.ts b/packages/language-service/test/project/app/parsing-cases.ts index e9bd88f136..6050caa0b1 100644 --- a/packages/language-service/test/project/app/parsing-cases.ts +++ b/packages/language-service/test/project/app/parsing-cases.ts @@ -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;