diff --git a/packages/language-service/src/typescript_symbols.ts b/packages/language-service/src/typescript_symbols.ts index 6635ee5a18..73c7dfe28e 100644 --- a/packages/language-service/src/typescript_symbols.ts +++ b/packages/language-service/src/typescript_symbols.ts @@ -280,7 +280,20 @@ class TypeWrapper implements Symbol { return selectSignature(this.tsType, this.context, types); } - indexed(argument: Symbol): Symbol|undefined { return undefined; } + indexed(argument: Symbol): Symbol|undefined { + const type = argument instanceof TypeWrapper ? argument : argument.type; + if (!(type instanceof TypeWrapper)) return; + + const typeKind = typeKindOf(type.tsType); + switch (typeKind) { + case BuiltinType.Number: + const nType = this.tsType.getNumberIndexType(); + return nType && new TypeWrapper(nType, this.context); + case BuiltinType.String: + const sType = this.tsType.getStringIndexType(); + return sType && new TypeWrapper(sType, this.context); + } + } } class SymbolWrapper implements Symbol { diff --git a/packages/language-service/test/completions_spec.ts b/packages/language-service/test/completions_spec.ts index 94ce983126..5dc83ef417 100644 --- a/packages/language-service/test/completions_spec.ts +++ b/packages/language-service/test/completions_spec.ts @@ -117,6 +117,20 @@ describe('completions', () => { expectContain(completions, CompletionKind.PROPERTY, ['id', 'name']); }); + it('should be able to get property completions for members in an array', () => { + mockHost.override(TEST_TEMPLATE, `{{ heroes[0].~{heroes-number-index}}}`); + const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'heroes-number-index'); + const completions = ngLS.getCompletionsAt(TEST_TEMPLATE, marker.start); + expectContain(completions, CompletionKind.PROPERTY, ['id', 'name']); + }); + + it('should be able to get property completions for members in an indexed type', () => { + mockHost.override(TEST_TEMPLATE, `{{ heroesByName['Jacky'].~{heroes-string-index}}}`); + const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'heroes-string-index'); + const completions = ngLS.getCompletionsAt(TEST_TEMPLATE, marker.start); + expectContain(completions, CompletionKind.PROPERTY, ['id', 'name']); + }); + it('should be able to return attribute names with an incompete attribute', () => { const marker = mockHost.getLocationMarkerFor(PARSING_CASES, 'no-value-attribute'); const completions = ngLS.getCompletionsAt(PARSING_CASES, marker.start); diff --git a/packages/language-service/test/diagnostics_spec.ts b/packages/language-service/test/diagnostics_spec.ts index edd0ec103a..ad0127d3ce 100644 --- a/packages/language-service/test/diagnostics_spec.ts +++ b/packages/language-service/test/diagnostics_spec.ts @@ -119,6 +119,15 @@ describe('diagnostics', () => { expect(diagnostics).toEqual([]); }); + it('should produce diagnostics for invalid index type property access', () => { + mockHost.override(TEST_TEMPLATE, ` + {{heroes[0].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`); + }); + describe('in expression-cases.ts', () => { it('should report access to an unknown field', () => { const diags = ngLS.getDiagnostics(EXPRESSION_CASES).map(d => d.messageText); diff --git a/packages/language-service/test/project/app/parsing-cases.ts b/packages/language-service/test/project/app/parsing-cases.ts index 1fe3aa7ab6..e9bd88f136 100644 --- a/packages/language-service/test/project/app/parsing-cases.ts +++ b/packages/language-service/test/project/app/parsing-cases.ts @@ -190,6 +190,7 @@ export class TemplateReference { hero: Hero = {id: 1, name: 'Windstorm'}; heroes: Hero[] = [this.hero]; league: Hero[][] = [this.heroes]; + heroesByName: {[name: string]: Hero} = {}; anyValue: any; myClick(event: any) {} }