feat(language-service): completions support for template reference variables (#34363)

PR Close #34363
This commit is contained in:
ivanwonder 2019-12-21 16:32:56 +08:00 committed by atscott
parent 97c3e9d59f
commit 181d766941
2 changed files with 56 additions and 3 deletions

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AST, AstPath, AttrAst, Attribute, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, Element, ElementAst, ImplicitReceiver, NAMED_ENTITIES, Node as HtmlAst, NullTemplateVisitor, ParseSpan, PropertyRead, TagContentType, TemplateBinding, Text, getHtmlTagDefinition} from '@angular/compiler';
import {AST, AstPath, AttrAst, Attribute, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, Element, ElementAst, ImplicitReceiver, NAMED_ENTITIES, Node as HtmlAst, NullTemplateVisitor, ParseSpan, PropertyRead, ReferenceAst, TagContentType, TemplateBinding, Text, getHtmlTagDefinition} from '@angular/compiler';
import {$$, $_, isAsciiLetter, isDigit} from '@angular/compiler/src/chars';
import {AstResult} from './common';
@ -170,11 +170,18 @@ export function getTemplateCompletions(
}
},
visitAttribute(ast) {
if (!ast.valueSpan || !inSpan(templatePosition, spanOf(ast.valueSpan))) {
const bindParts = ast.name.match(BIND_NAME_REGEXP);
const isReference = bindParts && bindParts[ATTR.KW_REF_IDX] !== undefined;
if (!isReference &&
(!ast.valueSpan || !inSpan(templatePosition, spanOf(ast.valueSpan)))) {
// We are in the name of an attribute. Show attribute completions.
result = attributeCompletions(templateInfo, path);
} else if (ast.valueSpan && inSpan(templatePosition, spanOf(ast.valueSpan))) {
result = attributeValueCompletions(templateInfo, templatePosition, ast);
if (isReference) {
result = referenceAttributeValueCompletions(templateInfo, templatePosition, ast);
} else {
result = attributeValueCompletions(templateInfo, templatePosition, ast);
}
}
},
visitText(ast) {
@ -313,6 +320,26 @@ function attributeValueCompletions(
return visitor.results;
}
function referenceAttributeValueCompletions(
info: AstResult, position: number, attr: Attribute): ng.CompletionEntry[] {
const path = findTemplateAstAt(info.templateAst, position);
if (!path.tail) {
return [];
}
// When the template parser does not find a directive with matching "exportAs",
// the ReferenceAst will be ignored.
if (!(path.tail instanceof ReferenceAst)) {
// The sourceSpan of an ReferenceAst is the valueSpan of the HTML Attribute.
path.push(new ReferenceAst(attr.name, null !, attr.value, attr.valueSpan !));
}
const dinfo = diagnosticInfoFromTemplateInfo(info);
const visitor =
new ExpressionVisitor(info, position, () => getExpressionScope(dinfo, path, false));
path.tail.visit(visitor, path.parentOf(path.tail));
return visitor.results;
}
function elementCompletions(info: AstResult): ng.CompletionEntry[] {
const results: ng.CompletionEntry[] = [...ANGULAR_ELEMENTS];
@ -443,6 +470,16 @@ class ExpressionVisitor extends NullTemplateVisitor {
}
}
visitReference(ast: ReferenceAst, context: ElementAst) {
context.directives.forEach(dir => {
const {exportAs} = dir.directive;
if (exportAs) {
this.completions.set(
exportAs, {name: exportAs, kind: ng.CompletionKind.REFERENCE, sortText: exportAs});
}
});
}
visitBoundText(ast: BoundTextAst) {
if (inSpan(this.position, ast.value.sourceSpan)) {
const completions = getExpressionCompletions(

View File

@ -717,6 +717,22 @@ describe('completions', () => {
expectContain(completions, CompletionKind.METHOD, ['substring']);
});
});
describe('with template reference variables', () => {
it('should be able to get the completions (ref- prefix)', () => {
mockHost.override(TEST_TEMPLATE, `<form ref-itemForm="ngF~{reference}"></form>`);
const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'reference');
const completions = ngLS.getCompletionsAt(TEST_TEMPLATE, marker.start) !;
expectContain(completions, CompletionKind.REFERENCE, ['ngForm']);
});
it('should be able to get the completions (# prefix)', () => {
mockHost.override(TEST_TEMPLATE, `<form #itemForm="ngF~{reference}"></form>`);
const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'reference');
const completions = ngLS.getCompletionsAt(TEST_TEMPLATE, marker.start) !;
expectContain(completions, CompletionKind.REFERENCE, ['ngForm']);
});
});
});
});