diff --git a/modules/@angular/language-service/src/diagnostics.ts b/modules/@angular/language-service/src/diagnostics.ts index c021208b6b..f0ef19c411 100644 --- a/modules/@angular/language-service/src/diagnostics.ts +++ b/modules/@angular/language-service/src/diagnostics.ts @@ -208,12 +208,13 @@ class ExpressionDiagnosticsVisitor extends TemplateAstChildVisitor { private diagnoseExpression(ast: AST, offset: number, includeEvent: boolean) { const scope = this.getExpressionScope(this.path, includeEvent); this.diagnostics.push( - ...getExpressionDiagnostics(scope, ast, this.info.template.query) - .map(d => ({ - span: offsetSpan(d.ast.span, offset + this.info.template.span.start), - kind: d.kind, - message: d.message - }))); + ...getExpressionDiagnostics(scope, ast, this.info.template.query, { + event: includeEvent + }).map(d => ({ + span: offsetSpan(d.ast.span, offset + this.info.template.span.start), + kind: d.kind, + message: d.message + }))); } private push(ast: TemplateAst) { this.path.push(ast); } diff --git a/modules/@angular/language-service/src/expressions.ts b/modules/@angular/language-service/src/expressions.ts index 7f609512fe..e71ae41580 100644 --- a/modules/@angular/language-service/src/expressions.ts +++ b/modules/@angular/language-service/src/expressions.ts @@ -16,9 +16,12 @@ import {TemplateAstChildVisitor, TemplateAstPath} from './template_path'; import {BuiltinType, CompletionKind, Definition, DiagnosticKind, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './types'; import {inSpan, spanOf} from './utils'; +export interface ExpressionDiagnosticsContext { event?: boolean; } + export function getExpressionDiagnostics( - scope: SymbolTable, ast: AST, query: SymbolQuery): TypeDiagnostic[] { - const analyzer = new AstType(scope, query); + scope: SymbolTable, ast: AST, query: SymbolQuery, + context: ExpressionDiagnosticsContext = {}): TypeDiagnostic[] { + const analyzer = new AstType(scope, query, context); analyzer.getDiagnostics(ast); return analyzer.diagnostics; } @@ -30,7 +33,7 @@ export function getExpressionCompletions( const tail = path.tail; let result: SymbolTable|undefined = scope; - function getType(ast: AST): Symbol { return new AstType(scope, query).getType(ast); } + function getType(ast: AST): Symbol { return new AstType(scope, query, {}).getType(ast); } // If the completion request is in a not in a pipe or property access then the global scope // (that is the scope of the implicit receiver) is the right scope as the user is typing the @@ -88,7 +91,7 @@ export function getExpressionSymbol( if (path.empty) return undefined; const tail = path.tail; - function getType(ast: AST): Symbol { return new AstType(scope, query).getType(ast); } + function getType(ast: AST): Symbol { return new AstType(scope, query, {}).getType(ast); } let symbol: Symbol = undefined; let span: Span = undefined; @@ -189,13 +192,18 @@ export class TypeDiagnostic { class AstType implements ExpressionVisitor { public diagnostics: TypeDiagnostic[]; - constructor(private scope: SymbolTable, private query: SymbolQuery) {} + constructor( + private scope: SymbolTable, private query: SymbolQuery, + private context: ExpressionDiagnosticsContext) {} getType(ast: AST): Symbol { return ast.visit(this); } getDiagnostics(ast: AST): TypeDiagnostic[] { this.diagnostics = []; - ast.visit(this); + const type: Symbol = ast.visit(this); + if (this.context.event && type.callable) { + this.reportWarning('Unexpected callable expression. Expected a method call', ast); + } return this.diagnostics; } @@ -746,7 +754,7 @@ function refinedVariableType( const ngForOfBinding = ngForDirective.inputs.find(i => i.directiveName == 'ngForOf'); if (ngForOfBinding) { const bindingType = - new AstType(info.template.members, info.template.query).getType(ngForOfBinding.value); + new AstType(info.template.members, info.template.query, {}).getType(ngForOfBinding.value); if (bindingType) { return info.template.query.getElementType(bindingType); } diff --git a/modules/@angular/language-service/test/diagnostics_spec.ts b/modules/@angular/language-service/test/diagnostics_spec.ts index 8327449c66..cf0d0e8cbb 100644 --- a/modules/@angular/language-service/test/diagnostics_spec.ts +++ b/modules/@angular/language-service/test/diagnostics_spec.ts @@ -130,6 +130,17 @@ describe('diagnostics', () => { }); }); + it('should report a warning if an event results in a callable expression', () => { + const code = + ` @Component({template: \`
\`}) export class MyComponent { onClick() { } }`; + addCode(code, (fileName, content) => { + const diagnostics = ngService.getDiagnostics(fileName); + includeDiagnostic( + diagnostics, 'Unexpected callable expression. Expected a method call', 'onClick', + content); + }); + }); + function addCode(code: string, cb: (fileName: string, content?: string) => void) { const fileName = '/app/app.component.ts'; const originalContent = mockHost.getFileContent(fileName);