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
This commit is contained in:
ayazhafiz 2019-12-26 08:44:14 -06:00 committed by atscott
parent cb142b6df9
commit d7ea389c84
4 changed files with 18 additions and 16 deletions

View File

@ -308,7 +308,7 @@ function attributeValueCompletions(info: AstResult, htmlPath: HtmlAstPath): ng.C
const templatePath = findTemplateAstAt(info.templateAst, htmlPath.position); const templatePath = findTemplateAstAt(info.templateAst, htmlPath.position);
const visitor = new ExpressionVisitor(info, htmlPath.position, () => { const visitor = new ExpressionVisitor(info, htmlPath.position, () => {
const dinfo = diagnosticInfoFromTemplateInfo(info); const dinfo = diagnosticInfoFromTemplateInfo(info);
return getExpressionScope(dinfo, templatePath, false); return getExpressionScope(dinfo, templatePath);
}); });
if (templatePath.tail instanceof AttrAst || if (templatePath.tail instanceof AttrAst ||
templatePath.tail instanceof BoundElementPropertyAst || templatePath.tail instanceof BoundElementPropertyAst ||
@ -398,8 +398,7 @@ function interpolationCompletions(info: AstResult, position: number): ng.Complet
return []; return [];
} }
const visitor = new ExpressionVisitor( const visitor = new ExpressionVisitor(
info, position, info, position, () => getExpressionScope(diagnosticInfoFromTemplateInfo(info), templatePath));
() => getExpressionScope(diagnosticInfoFromTemplateInfo(info), templatePath, false));
templatePath.tail.visit(visitor, null); templatePath.tail.visit(visitor, null);
return visitor.results; return visitor.results;
} }

View File

@ -25,8 +25,7 @@ export interface DiagnosticTemplateInfo {
export function getTemplateExpressionDiagnostics(info: DiagnosticTemplateInfo): Diagnostic[] { export function getTemplateExpressionDiagnostics(info: DiagnosticTemplateInfo): Diagnostic[] {
const visitor = new ExpressionDiagnosticsVisitor( const visitor = new ExpressionDiagnosticsVisitor(
info, (path: TemplateAstPath, includeEvent: boolean) => info, (path: TemplateAstPath) => getExpressionScope(info, path));
getExpressionScope(info, path, includeEvent));
templateVisitAll(visitor, info.templateAst); templateVisitAll(visitor, info.templateAst);
return visitor.diagnostics; return visitor.diagnostics;
} }
@ -194,9 +193,9 @@ function refinedVariableType(
return query.getBuiltinType(BuiltinType.Any); return query.getBuiltinType(BuiltinType.Any);
} }
function getEventDeclaration(info: DiagnosticTemplateInfo, includeEvent?: boolean) { function getEventDeclaration(info: DiagnosticTemplateInfo, path: TemplateAstPath) {
let result: SymbolDeclaration[] = []; let result: SymbolDeclaration[] = [];
if (includeEvent) { if (path.tail instanceof BoundEventAst) {
// TODO: Determine the type of the event parameter based on the Observable<T> or EventEmitter<T> // TODO: Determine the type of the event parameter based on the Observable<T> or EventEmitter<T>
// of the event. // of the event.
result = [{name: '$event', kind: 'variable', type: info.query.getBuiltinType(BuiltinType.Any)}]; result = [{name: '$event', kind: 'variable', type: info.query.getBuiltinType(BuiltinType.Any)}];
@ -205,11 +204,11 @@ function getEventDeclaration(info: DiagnosticTemplateInfo, includeEvent?: boolea
} }
export function getExpressionScope( export function getExpressionScope(
info: DiagnosticTemplateInfo, path: TemplateAstPath, includeEvent: boolean): SymbolTable { info: DiagnosticTemplateInfo, path: TemplateAstPath): SymbolTable {
let result = info.members; let result = info.members;
const references = getReferences(info); const references = getReferences(info);
const variables = getVarDeclarations(info, path); const variables = getVarDeclarations(info, path);
const events = getEventDeclaration(info, includeEvent); const events = getEventDeclaration(info, path);
if (references.length || variables.length || events.length) { if (references.length || variables.length || events.length) {
const referenceTable = info.query.createSymbolTable(references); const referenceTable = info.query.createSymbolTable(references);
const variableTable = info.query.createSymbolTable(variables); 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}); {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 { function hasTemplateReference(type: CompileTypeMetadata): boolean {

View File

@ -37,7 +37,7 @@ export function locateSymbol(info: AstResult, position: number): SymbolInfo|unde
if (attribute) { if (attribute) {
if (inSpan(templatePosition, spanOf(attribute.valueSpan))) { if (inSpan(templatePosition, spanOf(attribute.valueSpan))) {
const dinfo = diagnosticInfoFromTemplateInfo(info); const dinfo = diagnosticInfoFromTemplateInfo(info);
const scope = getExpressionScope(dinfo, path, inEvent); const scope = getExpressionScope(dinfo, path);
if (attribute.valueSpan) { if (attribute.valueSpan) {
const result = getExpressionSymbol(scope, ast, templatePosition, info.template.query); const result = getExpressionSymbol(scope, ast, templatePosition, info.template.query);
if (result) { if (result) {
@ -113,7 +113,7 @@ export function locateSymbol(info: AstResult, position: number): SymbolInfo|unde
const expressionPosition = templatePosition - ast.sourceSpan.start.offset; const expressionPosition = templatePosition - ast.sourceSpan.start.offset;
if (inSpan(expressionPosition, ast.value.span)) { if (inSpan(expressionPosition, ast.value.span)) {
const dinfo = diagnosticInfoFromTemplateInfo(info); const dinfo = diagnosticInfoFromTemplateInfo(info);
const scope = getExpressionScope(dinfo, path, /* includeEvent */ false); const scope = getExpressionScope(dinfo, path);
const result = const result =
getExpressionSymbol(scope, ast.value, templatePosition, info.template.query); getExpressionSymbol(scope, ast.value, templatePosition, info.template.query);
if (result) { if (result) {

View File

@ -744,6 +744,15 @@ describe('completions', () => {
const completions = ngLS.getCompletionsAt(TEST_TEMPLATE, marker.start); const completions = ngLS.getCompletionsAt(TEST_TEMPLATE, marker.start);
expectContain(completions, CompletionKind.PROPERTY, ['title']); expectContain(completions, CompletionKind.PROPERTY, ['title']);
}); });
describe('$event completions', () => {
it('should suggest $event in event bindings', () => {
mockHost.override(TEST_TEMPLATE, `<div (click)="myClick(~{cursor});"></div>`);
const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'cursor');
const completions = ngLS.getCompletionsAt(TEST_TEMPLATE, marker.start);
expectContain(completions, CompletionKind.VARIABLE, ['$event']);
});
});
}); });
function expectContain( function expectContain(