Skip to content

Commit

Permalink
fix(language-service): Function alias should be callable
Browse files Browse the repository at this point in the history
This commit fixes a long standing bug whereby a template variable that
gets initialized to a class method gets resolved to the Any type, thus
when it is called the language service produces error "Member X is not
callable".

PR closes angular#16643
PR closes angular/vscode-ng-language-service#234
  • Loading branch information
kyliau committed Nov 13, 2019
1 parent 98214af commit d9aca37
Show file tree
Hide file tree
Showing 4 changed files with 12 additions and 28 deletions.
28 changes: 2 additions & 26 deletions packages/compiler-cli/src/diagnostics/expression_diagnostics.ts
Expand Up @@ -90,29 +90,6 @@ function getDefinitionOf(info: DiagnosticTemplateInfo, ast: TemplateAst): Defini
}
}

/**
* Resolve the specified `variable` from the `directives` list and return the
* corresponding symbol. If resolution fails, return the `any` type.
* @param variable template variable to resolve
* @param directives template context
* @param query
*/
function findSymbolForVariableInDirectives(
variable: VariableAst, directives: DirectiveAst[], query: SymbolQuery): Symbol {
for (const d of directives) {
// Get the symbol table for the directive's StaticSymbol
const table = query.getTemplateContext(d.directive.type.reference);
if (!table) {
continue;
}
const symbol = table.get(variable.value);
if (symbol) {
return symbol;
}
}
return query.getBuiltinType(BuiltinType.Any);
}

/**
* Resolve all variable declarations in a template by traversing the specified
* `path`.
Expand All @@ -126,9 +103,8 @@ function getVarDeclarations(
if (!(current instanceof EmbeddedTemplateAst)) {
continue;
}
const {directives, variables} = current;
for (const variable of variables) {
let symbol = findSymbolForVariableInDirectives(variable, directives, info.query);
for (const variable of current.variables) {
let symbol = info.members.get(variable.value) || info.query.getBuiltinType(BuiltinType.Any);
const kind = info.query.getTypeKind(symbol);
if (kind === BuiltinType.Any || kind === BuiltinType.Unbound) {
// For special cases such as ngFor and ngIf, the any type is not very useful.
Expand Down
1 change: 0 additions & 1 deletion packages/compiler-cli/src/diagnostics/expression_type.ts
Expand Up @@ -363,7 +363,6 @@ export class AstType implements AstVisitor {
if (this.isAny(receiverType)) {
return this.anyType;
}

// The type of a method is the selected methods result type.
const method = receiverType.members().get(ast.name);
if (!method) return this.reportError(`Unknown method '${ast.name}'`, ast);
Expand Down
1 change: 0 additions & 1 deletion packages/language-service/src/typescript_host.ts
Expand Up @@ -198,7 +198,6 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
*/
private upToDate(): boolean {
const {lastProgram, program} = this;
console.error("last program === program ?", lastProgram === program);
if (lastProgram === program) {
return true;
}
Expand Down
10 changes: 10 additions & 0 deletions packages/language-service/test/diagnostics_spec.ts
Expand Up @@ -109,6 +109,16 @@ describe('diagnostics', () => {
.toBe(`Identifier 'age' is not defined. 'Hero' does not contain such a member`);
});

it('should not report error for variable initialized as class method', () => {
mockHost.override(TEST_TEMPLATE, `
<ng-template let-greet="myClick">
<span (click)="greet()"></span>
</ng-template>
`);
const diagnostics = ngLS.getDiagnostics(TEST_TEMPLATE);
expect(diagnostics).toEqual([]);
});

describe('in expression-cases.ts', () => {
it('should report access to an unknown field', () => {
const diags = ngLS.getDiagnostics(EXPRESSION_CASES).map(d => d.messageText);
Expand Down

0 comments on commit d9aca37

Please sign in to comment.