From d7ea389c849679b97af774badd37edd38a068cab Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 26 Dec 2019 08:44:14 -0600 Subject: [PATCH] feat(language-service): provide completion for $event variable (#34566) This commit adds a completion for the `$event` variable in bound event expressions. This is the first in a series of PRs to support completions for properties on `$event` variables (https://github.com/angular/vscode-ng-language-service/issues/531). PR Close #34566 --- packages/language-service/src/completions.ts | 5 ++--- .../src/expression_diagnostics.ts | 16 +++++----------- packages/language-service/src/locate_symbol.ts | 4 ++-- .../language-service/test/completions_spec.ts | 9 +++++++++ 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/language-service/src/completions.ts b/packages/language-service/src/completions.ts index 752b8acd6b..64abe85f5b 100644 --- a/packages/language-service/src/completions.ts +++ b/packages/language-service/src/completions.ts @@ -308,7 +308,7 @@ function attributeValueCompletions(info: AstResult, htmlPath: HtmlAstPath): ng.C const templatePath = findTemplateAstAt(info.templateAst, htmlPath.position); const visitor = new ExpressionVisitor(info, htmlPath.position, () => { const dinfo = diagnosticInfoFromTemplateInfo(info); - return getExpressionScope(dinfo, templatePath, false); + return getExpressionScope(dinfo, templatePath); }); if (templatePath.tail instanceof AttrAst || templatePath.tail instanceof BoundElementPropertyAst || @@ -398,8 +398,7 @@ function interpolationCompletions(info: AstResult, position: number): ng.Complet return []; } const visitor = new ExpressionVisitor( - info, position, - () => getExpressionScope(diagnosticInfoFromTemplateInfo(info), templatePath, false)); + info, position, () => getExpressionScope(diagnosticInfoFromTemplateInfo(info), templatePath)); templatePath.tail.visit(visitor, null); return visitor.results; } diff --git a/packages/language-service/src/expression_diagnostics.ts b/packages/language-service/src/expression_diagnostics.ts index 2382fd85f0..b5c7e43402 100644 --- a/packages/language-service/src/expression_diagnostics.ts +++ b/packages/language-service/src/expression_diagnostics.ts @@ -25,8 +25,7 @@ export interface DiagnosticTemplateInfo { export function getTemplateExpressionDiagnostics(info: DiagnosticTemplateInfo): Diagnostic[] { const visitor = new ExpressionDiagnosticsVisitor( - info, (path: TemplateAstPath, includeEvent: boolean) => - getExpressionScope(info, path, includeEvent)); + info, (path: TemplateAstPath) => getExpressionScope(info, path)); templateVisitAll(visitor, info.templateAst); return visitor.diagnostics; } @@ -194,9 +193,9 @@ function refinedVariableType( return query.getBuiltinType(BuiltinType.Any); } -function getEventDeclaration(info: DiagnosticTemplateInfo, includeEvent?: boolean) { +function getEventDeclaration(info: DiagnosticTemplateInfo, path: TemplateAstPath) { let result: SymbolDeclaration[] = []; - if (includeEvent) { + if (path.tail instanceof BoundEventAst) { // TODO: Determine the type of the event parameter based on the Observable or EventEmitter // of the event. result = [{name: '$event', kind: 'variable', type: info.query.getBuiltinType(BuiltinType.Any)}]; @@ -205,11 +204,11 @@ function getEventDeclaration(info: DiagnosticTemplateInfo, includeEvent?: boolea } export function getExpressionScope( - info: DiagnosticTemplateInfo, path: TemplateAstPath, includeEvent: boolean): SymbolTable { + info: DiagnosticTemplateInfo, path: TemplateAstPath): SymbolTable { let result = info.members; const references = getReferences(info); const variables = getVarDeclarations(info, path); - const events = getEventDeclaration(info, includeEvent); + const events = getEventDeclaration(info, path); if (references.length || variables.length || events.length) { const referenceTable = info.query.createSymbolTable(references); const variableTable = info.query.createSymbolTable(variables); @@ -334,11 +333,6 @@ class ExpressionDiagnosticsVisitor extends RecursiveTemplateAstVisitor { {span: offsetSpan(span, this.info.offset), kind: ts.DiagnosticCategory.Error, message}); } } - - private reportWarning(message: string, span: Span) { - this.diagnostics.push( - {span: offsetSpan(span, this.info.offset), kind: ts.DiagnosticCategory.Warning, message}); - } } function hasTemplateReference(type: CompileTypeMetadata): boolean { diff --git a/packages/language-service/src/locate_symbol.ts b/packages/language-service/src/locate_symbol.ts index 36d09f80d7..392c043c52 100644 --- a/packages/language-service/src/locate_symbol.ts +++ b/packages/language-service/src/locate_symbol.ts @@ -37,7 +37,7 @@ export function locateSymbol(info: AstResult, position: number): SymbolInfo|unde if (attribute) { if (inSpan(templatePosition, spanOf(attribute.valueSpan))) { const dinfo = diagnosticInfoFromTemplateInfo(info); - const scope = getExpressionScope(dinfo, path, inEvent); + const scope = getExpressionScope(dinfo, path); if (attribute.valueSpan) { const result = getExpressionSymbol(scope, ast, templatePosition, info.template.query); if (result) { @@ -113,7 +113,7 @@ export function locateSymbol(info: AstResult, position: number): SymbolInfo|unde const expressionPosition = templatePosition - ast.sourceSpan.start.offset; if (inSpan(expressionPosition, ast.value.span)) { const dinfo = diagnosticInfoFromTemplateInfo(info); - const scope = getExpressionScope(dinfo, path, /* includeEvent */ false); + const scope = getExpressionScope(dinfo, path); const result = getExpressionSymbol(scope, ast.value, templatePosition, info.template.query); if (result) { diff --git a/packages/language-service/test/completions_spec.ts b/packages/language-service/test/completions_spec.ts index e2795dab59..a0cd9f73d5 100644 --- a/packages/language-service/test/completions_spec.ts +++ b/packages/language-service/test/completions_spec.ts @@ -744,6 +744,15 @@ describe('completions', () => { const completions = ngLS.getCompletionsAt(TEST_TEMPLATE, marker.start); expectContain(completions, CompletionKind.PROPERTY, ['title']); }); + + describe('$event completions', () => { + it('should suggest $event in event bindings', () => { + mockHost.override(TEST_TEMPLATE, `
`); + const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'cursor'); + const completions = ngLS.getCompletionsAt(TEST_TEMPLATE, marker.start); + expectContain(completions, CompletionKind.VARIABLE, ['$event']); + }); + }); }); function expectContain(