feat(language-service): support autocomplete string literal union types in templates (#42729)

The native TS language service has the ability to provide autocompletions for
string literal union types. This pr is for Angular to do the same in templates.

Fixes https://github.com/angular/vscode-ng-language-service/issues/1096

PR Close #42729
This commit is contained in:
ivanwonder
2021-06-30 22:25:01 +08:00
committed by Dylan Hunn
parent 047994b048
commit 7c35ca0e00
5 changed files with 189 additions and 6 deletions
@@ -6,8 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AST, MethodCall, ParseError, ParseSourceSpan, PropertyRead, SafeMethodCall, SafePropertyRead, TmplAstElement, TmplAstNode, TmplAstTemplate} from '@angular/compiler';
import {AST, LiteralPrimitive, MethodCall, ParseError, ParseSourceSpan, PropertyRead, SafeMethodCall, SafePropertyRead, TmplAstElement, TmplAstNode, TmplAstTemplate} from '@angular/compiler';
import {AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/file_system';
import {TextAttribute} from '@angular/compiler/src/render3/r3_ast';
import * as ts from 'typescript';
import {ErrorCode} from '../../diagnostics';
@@ -119,6 +120,14 @@ export interface TemplateTypeChecker {
expr: PropertyRead|SafePropertyRead|MethodCall|SafeMethodCall,
component: ts.ClassDeclaration): ShimLocation|null;
/**
* For the given node represents a `LiteralPrimitive`(the `TextAttribute` represents a string
* literal), retrieve a `ShimLocation` that can be used to perform autocompletion at that point in
* the node, if such a location exists.
*/
getLiteralCompletionLocation(
strNode: LiteralPrimitive|TextAttribute, component: ts.ClassDeclaration): ShimLocation|null;
/**
* Get basic metadata on the directives which are in scope for the given component.
*/
@@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AST, CssSelector, DomElementSchemaRegistry, MethodCall, ParseError, ParseSourceSpan, parseTemplate, PropertyRead, SafeMethodCall, SafePropertyRead, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate, TmplAstVariable} from '@angular/compiler';
import {AST, CssSelector, DomElementSchemaRegistry, LiteralPrimitive, MethodCall, ParseError, ParseSourceSpan, parseTemplate, PropertyRead, SafeMethodCall, SafePropertyRead, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate, TmplAstVariable} from '@angular/compiler';
import {TextAttribute} from '@angular/compiler/src/render3/r3_ast';
import * as ts from 'typescript';
import {ErrorCode} from '../../diagnostics';
@@ -280,6 +281,16 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
PerfPhase.TtcAutocompletion, () => engine.getExpressionCompletionLocation(ast));
}
getLiteralCompletionLocation(
node: LiteralPrimitive|TextAttribute, component: ts.ClassDeclaration): ShimLocation|null {
const engine = this.getOrCreateCompletionEngine(component);
if (engine === null) {
return null;
}
return this.perf.inPhase(
PerfPhase.TtcAutocompletion, () => engine.getLiteralCompletionLocation(node));
}
invalidateClass(clazz: ts.ClassDeclaration): void {
this.completionCache.delete(clazz);
this.symbolBuilderCache.delete(clazz);
@@ -7,7 +7,8 @@
*/
import {TmplAstReference, TmplAstTemplate} from '@angular/compiler';
import {AST, EmptyExpr, MethodCall, PropertyRead, PropertyWrite, SafeMethodCall, SafePropertyRead, TmplAstNode} from '@angular/compiler/src/compiler';
import {AST, EmptyExpr, LiteralPrimitive, MethodCall, PropertyRead, PropertyWrite, SafeMethodCall, SafePropertyRead, TmplAstNode} from '@angular/compiler/src/compiler';
import {TextAttribute} from '@angular/compiler/src/render3/r3_ast';
import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../file_system';
@@ -32,8 +33,9 @@ export class CompletionEngine {
private templateContextCache =
new Map<TmplAstTemplate|null, Map<string, ReferenceCompletion|VariableCompletion>>();
private expressionCompletionCache =
new Map<PropertyRead|SafePropertyRead|MethodCall|SafeMethodCall, ShimLocation>();
private expressionCompletionCache = new Map<
PropertyRead|SafePropertyRead|MethodCall|SafeMethodCall|LiteralPrimitive|TextAttribute,
ShimLocation>();
constructor(private tcb: ts.Node, private data: TemplateData, private shimPath: AbsoluteFsPath) {
@@ -144,6 +146,46 @@ export class CompletionEngine {
return res;
}
getLiteralCompletionLocation(expr: LiteralPrimitive|TextAttribute): ShimLocation|null {
if (this.expressionCompletionCache.has(expr)) {
return this.expressionCompletionCache.get(expr)!;
}
let tsExpr: ts.StringLiteral|ts.NumericLiteral|null = null;
if (expr instanceof TextAttribute) {
const strNode = findFirstMatchingNode(this.tcb, {
filter: ts.isParenthesizedExpression,
withSpan: expr.sourceSpan,
});
if (strNode !== null && ts.isStringLiteral(strNode.expression)) {
tsExpr = strNode.expression;
}
} else {
tsExpr = findFirstMatchingNode(this.tcb, {
filter: (n: ts.Node): n is ts.NumericLiteral | ts.StringLiteral =>
ts.isStringLiteral(n) || ts.isNumericLiteral(n),
withSpan: expr.sourceSpan,
});
}
if (tsExpr === null) {
return null;
}
let positionInShimFile = tsExpr.getEnd();
if (ts.isStringLiteral(tsExpr)) {
// In the shimFile, if `tsExpr` is a string, the position should be in the quotes.
positionInShimFile -= 1;
}
const res: ShimLocation = {
shimPath: this.shimPath,
positionInShimFile,
};
this.expressionCompletionCache.set(expr, res);
return res;
}
/**
* Get global completions within the given template context - either a `TmplAstTemplate` embedded
* view, or `null` for the root context.