diff --git a/packages/language-service/src/typescript_host.ts b/packages/language-service/src/typescript_host.ts index 8df4c7c292..c98de00651 100644 --- a/packages/language-service/src/typescript_host.ts +++ b/packages/language-service/src/typescript_host.ts @@ -803,6 +803,7 @@ class TypeWrapper implements Symbol { class SymbolWrapper implements Symbol { private _tsType: ts.Type; + private _members: SymbolTable; constructor(private symbol: ts.Symbol, private context: TypeContext) {} @@ -825,7 +826,18 @@ class SymbolWrapper implements Symbol { get definition(): Definition { return definitionFromTsSymbol(this.symbol); } - members(): SymbolTable { return new SymbolTableWrapper(this.symbol.members, this.context); } + members(): SymbolTable { + if (!this._members) { + if ((this.symbol.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Interface)) != 0) { + const declaredType = this.context.checker.getDeclaredTypeOfSymbol(this.symbol); + const typeWrapper = new TypeWrapper(declaredType, this.context); + this._members = typeWrapper.members(); + } else { + this._members = new SymbolTableWrapper(this.symbol.members, this.context); + } + } + return this._members; + } signatures(): Signature[] { return signaturesOf(this.tsType, this.context); } @@ -1044,7 +1056,7 @@ class PipeSymbol implements Symbol { } private findTransformMethodType(classSymbol: ts.Symbol): ts.Type { - const transform = classSymbol.members['transform']; + const transform = classSymbol.members && classSymbol.members['transform']; if (transform) { return this.context.checker.getTypeOfSymbolAtLocation(transform, this.context.node); } diff --git a/packages/language-service/test/diagnostics_spec.ts b/packages/language-service/test/diagnostics_spec.ts index 7a4dc1147f..28b11f7976 100644 --- a/packages/language-service/test/diagnostics_spec.ts +++ b/packages/language-service/test/diagnostics_spec.ts @@ -171,6 +171,36 @@ describe('diagnostics', () => { }); }); + // Issue #15460 + it('should be able to find members defined on an ancestor type', () => { + const app_component = ` + import { Component } from '@angular/core'; + import { NgForm } from '@angular/common'; + + @Component({ + selector: 'example-app', + template: \` +
+ + + +
+

First name value: {{ first.value }}

+

First name valid: {{ first.valid }}

+

Form value: {{ f.value | json }}

+

Form valid: {{ f.valid }}

+ \`, + }) + export class AppComponent { + onSubmit(form: NgForm) {} + } + `; + const fileName = '/app/app.component.ts'; + mockHost.override(fileName, app_component); + const diagnostic = ngService.getDiagnostics(fileName); + expect(diagnostic).toEqual([]); + }); + function addCode(code: string, cb: (fileName: string, content?: string) => void) { const fileName = '/app/app.component.ts'; const originalContent = mockHost.getFileContent(fileName);