Revert "fix(compiler-cli): autocomplete literal types in templates. (#41456)" (#41623)

This reverts commit 1d12c50f63.

PR Close #41623
This commit is contained in:
Joey Perrott 2021-04-14 08:16:47 -07:00 committed by Zach Arend
parent de93a7a4bb
commit 0bc539af29
7 changed files with 53 additions and 166 deletions

View File

@ -105,9 +105,8 @@ export interface TemplateTypeChecker {
* include completions from the template's context component, as well as any local references or * include completions from the template's context component, as well as any local references or
* template variables which are in scope for that expression. * template variables which are in scope for that expression.
*/ */
getGlobalCompletions( getGlobalCompletions(context: TmplAstTemplate|null, component: ts.ClassDeclaration):
context: TmplAstTemplate|null, component: ts.ClassDeclaration, GlobalCompletion|null;
node: AST|TmplAstNode): GlobalCompletion|null;
/** /**

View File

@ -71,10 +71,4 @@ export interface GlobalCompletion {
* the same name (from the `componentContext` completions). * the same name (from the `componentContext` completions).
*/ */
templateContext: Map<string, ReferenceCompletion|VariableCompletion>; templateContext: Map<string, ReferenceCompletion|VariableCompletion>;
/**
* A location within the type-checking shim where TypeScript's completion APIs can be used to
* access completions for the AST node of the cursor position (primitive constants).
*/
nodeContext: ShimLocation|null;
} }

View File

@ -257,15 +257,14 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
return this.getLatestComponentState(component).tcb; return this.getLatestComponentState(component).tcb;
} }
getGlobalCompletions( getGlobalCompletions(context: TmplAstTemplate|null, component: ts.ClassDeclaration):
context: TmplAstTemplate|null, component: ts.ClassDeclaration, GlobalCompletion|null {
node: AST|TmplAstNode): GlobalCompletion|null {
const engine = this.getOrCreateCompletionEngine(component); const engine = this.getOrCreateCompletionEngine(component);
if (engine === null) { if (engine === null) {
return null; return null;
} }
return this.perf.inPhase( return this.perf.inPhase(
PerfPhase.TtcAutocompletion, () => engine.getGlobalCompletions(context, node)); PerfPhase.TtcAutocompletion, () => engine.getGlobalCompletions(context));
} }
getExpressionCompletionLocation( getExpressionCompletionLocation(

View File

@ -7,7 +7,7 @@
*/ */
import {TmplAstReference, TmplAstTemplate} from '@angular/compiler'; import {TmplAstReference, TmplAstTemplate} from '@angular/compiler';
import {AST, EmptyExpr, MethodCall, PropertyRead, PropertyWrite, SafeMethodCall, SafePropertyRead, TmplAstNode} from '@angular/compiler/src/compiler'; import {MethodCall, PropertyRead, PropertyWrite, SafeMethodCall, SafePropertyRead} from '@angular/compiler/src/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../file_system'; import {AbsoluteFsPath} from '../../file_system';
@ -23,77 +23,65 @@ import {TemplateData} from './context';
* surrounding TS program have changed. * surrounding TS program have changed.
*/ */
export class CompletionEngine { export class CompletionEngine {
private componentContext: ShimLocation|null;
/** /**
* Cache of completions for various levels of the template, including the root template (`null`). * Cache of `GlobalCompletion`s for various levels of the template, including the root template
* Memoizes `getTemplateContextCompletions`. * (`null`).
*/ */
private templateContextCache = private globalCompletionCache = new Map<TmplAstTemplate|null, GlobalCompletion>();
new Map<TmplAstTemplate|null, Map<string, ReferenceCompletion|VariableCompletion>>();
private expressionCompletionCache = private expressionCompletionCache =
new Map<PropertyRead|SafePropertyRead|MethodCall|SafeMethodCall, ShimLocation>(); new Map<PropertyRead|SafePropertyRead|MethodCall|SafeMethodCall, ShimLocation>();
constructor(private tcb: ts.Node, private data: TemplateData, private shimPath: AbsoluteFsPath) {}
/**
* Get global completions within the given template context - either a `TmplAstTemplate` embedded
* view, or `null` for the root template context.
*/
getGlobalCompletions(context: TmplAstTemplate|null): GlobalCompletion|null {
if (this.globalCompletionCache.has(context)) {
return this.globalCompletionCache.get(context)!;
}
constructor(private tcb: ts.Node, private data: TemplateData, private shimPath: AbsoluteFsPath) {
// Find the component completion expression within the TCB. This looks like: `ctx. /* ... */;` // Find the component completion expression within the TCB. This looks like: `ctx. /* ... */;`
const globalRead = findFirstMatchingNode(this.tcb, { const globalRead = findFirstMatchingNode(this.tcb, {
filter: ts.isPropertyAccessExpression, filter: ts.isPropertyAccessExpression,
withExpressionIdentifier: ExpressionIdentifier.COMPONENT_COMPLETION withExpressionIdentifier: ExpressionIdentifier.COMPONENT_COMPLETION
}); });
if (globalRead !== null) { if (globalRead === null) {
this.componentContext = { return null;
}
const completion: GlobalCompletion = {
componentContext: {
shimPath: this.shimPath, shimPath: this.shimPath,
// `globalRead.name` is an empty `ts.Identifier`, so its start position immediately follows // `globalRead.name` is an empty `ts.Identifier`, so its start position immediately follows
// the `.` in `ctx.`. TS autocompletion APIs can then be used to access completion results // the `.` in `ctx.`. TS autocompletion APIs can then be used to access completion results
// for the component context. // for the component context.
positionInShimFile: globalRead.name.getStart(), positionInShimFile: globalRead.name.getStart(),
},
templateContext: new Map<string, ReferenceCompletion|VariableCompletion>(),
}; };
} else {
this.componentContext = null;
}
}
/** // The bound template already has details about the references and variables in scope in the
* Get global completions within the given template context and AST node. // `context` template - they just need to be converted to `Completion`s.
* for (const node of this.data.boundTarget.getEntitiesInTemplateScope(context)) {
* @param context the given template context - either a `TmplAstTemplate` embedded view, or `null` if (node instanceof TmplAstReference) {
* for the root completion.templateContext.set(node.name, {
* template context. kind: CompletionKind.Reference,
* @param node the given AST node node,
*/ });
getGlobalCompletions(context: TmplAstTemplate|null, node: AST|TmplAstNode): GlobalCompletion } else {
|null { completion.templateContext.set(node.name, {
if (this.componentContext === null) { kind: CompletionKind.Variable,
return null; node,
}
const templateContext = this.getTemplateContextCompletions(context);
if (templateContext === null) {
return null;
}
let nodeContext: ShimLocation|null = null;
if (node instanceof EmptyExpr) {
const nodeLocation = findFirstMatchingNode(this.tcb, {
filter: ts.isIdentifier,
withSpan: node.sourceSpan,
}); });
if (nodeLocation !== null) {
nodeContext = {
shimPath: this.shimPath,
positionInShimFile: nodeLocation.getStart(),
};
} }
} }
return { this.globalCompletionCache.set(context, completion);
componentContext: this.componentContext, return completion;
templateContext,
nodeContext,
};
} }
getExpressionCompletionLocation(expr: PropertyRead|PropertyWrite|MethodCall| getExpressionCompletionLocation(expr: PropertyRead|PropertyWrite|MethodCall|
@ -143,36 +131,4 @@ export class CompletionEngine {
this.expressionCompletionCache.set(expr, res); this.expressionCompletionCache.set(expr, res);
return res; return res;
} }
/**
* Get global completions within the given template context - either a `TmplAstTemplate` embedded
* view, or `null` for the root context.
*/
private getTemplateContextCompletions(context: TmplAstTemplate|null):
Map<string, ReferenceCompletion|VariableCompletion>|null {
if (this.templateContextCache.has(context)) {
return this.templateContextCache.get(context)!;
}
const templateContext = new Map<string, ReferenceCompletion|VariableCompletion>();
// The bound template already has details about the references and variables in scope in the
// `context` template - they just need to be converted to `Completion`s.
for (const node of this.data.boundTarget.getEntitiesInTemplateScope(context)) {
if (node instanceof TmplAstReference) {
templateContext.set(node.name, {
kind: CompletionKind.Reference,
node,
});
} else {
templateContext.set(node.name, {
kind: CompletionKind.Variable,
node,
});
}
}
this.templateContextCache.set(context, templateContext);
return templateContext;
}
} }

View File

@ -141,7 +141,7 @@ function setupCompletions(
context = tmpl; context = tmpl;
} }
const completions = templateTypeChecker.getGlobalCompletions(context, SomeCmp, null!)!; const completions = templateTypeChecker.getGlobalCompletions(context, SomeCmp)!;
expect(completions).toBeDefined(); expect(completions).toBeDefined();
return { return {
completions, completions,

View File

@ -216,21 +216,21 @@ export class CompletionBuilder<N extends TmplAstNode|AST> {
options: ts.GetCompletionsAtPositionOptions| options: ts.GetCompletionsAtPositionOptions|
undefined): ts.WithMetadata<ts.CompletionInfo>|undefined { undefined): ts.WithMetadata<ts.CompletionInfo>|undefined {
const completions = const completions =
this.templateTypeChecker.getGlobalCompletions(this.template, this.component, this.node); this.templateTypeChecker.getGlobalCompletions(this.template, this.component);
if (completions === null) { if (completions === null) {
return undefined; return undefined;
} }
const {componentContext, templateContext, nodeContext: astContext} = completions; const {componentContext, templateContext} = completions;
const replacementSpan = makeReplacementSpanFromAst(this.node); const replacementSpan = makeReplacementSpanFromAst(this.node);
// Merge TS completion results with results from the template scope. // Merge TS completion results with results from the template scope.
let entries: ts.CompletionEntry[] = []; let entries: ts.CompletionEntry[] = [];
const componentCompletions = this.tsLS.getCompletionsAtPosition( const tsLsCompletions = this.tsLS.getCompletionsAtPosition(
componentContext.shimPath, componentContext.positionInShimFile, options); componentContext.shimPath, componentContext.positionInShimFile, options);
if (componentCompletions !== undefined) { if (tsLsCompletions !== undefined) {
for (const tsCompletion of componentCompletions.entries) { for (const tsCompletion of tsLsCompletions.entries) {
// Skip completions that are shadowed by a template entity definition. // Skip completions that are shadowed by a template entity definition.
if (templateContext.has(tsCompletion.name)) { if (templateContext.has(tsCompletion.name)) {
continue; continue;
@ -244,24 +244,6 @@ export class CompletionBuilder<N extends TmplAstNode|AST> {
} }
} }
// Merge TS completion results with results from the ast context.
if (astContext !== null) {
const nodeCompletions = this.tsLS.getCompletionsAtPosition(
astContext.shimPath, astContext.positionInShimFile, options);
if (nodeCompletions !== undefined) {
for (const tsCompletion of nodeCompletions.entries) {
if (this.isValidNodeContextCompletion(tsCompletion)) {
entries.push({
...tsCompletion,
// Substitute the TS completion's `replacementSpan` (which uses offsets within the
// TCB) with the `replacementSpan` within the template source.
replacementSpan,
});
}
}
}
}
for (const [name, entity] of templateContext) { for (const [name, entity] of templateContext) {
entries.push({ entries.push({
name, name,
@ -294,7 +276,7 @@ export class CompletionBuilder<N extends TmplAstNode|AST> {
formatOptions: ts.FormatCodeOptions|ts.FormatCodeSettings|undefined, formatOptions: ts.FormatCodeOptions|ts.FormatCodeSettings|undefined,
preferences: ts.UserPreferences|undefined): ts.CompletionEntryDetails|undefined { preferences: ts.UserPreferences|undefined): ts.CompletionEntryDetails|undefined {
const completions = const completions =
this.templateTypeChecker.getGlobalCompletions(this.template, this.component, this.node); this.templateTypeChecker.getGlobalCompletions(this.template, this.component);
if (completions === null) { if (completions === null) {
return undefined; return undefined;
} }
@ -335,7 +317,7 @@ export class CompletionBuilder<N extends TmplAstNode|AST> {
private getGlobalPropertyExpressionCompletionSymbol( private getGlobalPropertyExpressionCompletionSymbol(
this: PropertyExpressionCompletionBuilder, entryName: string): ts.Symbol|undefined { this: PropertyExpressionCompletionBuilder, entryName: string): ts.Symbol|undefined {
const completions = const completions =
this.templateTypeChecker.getGlobalCompletions(this.template, this.component, this.node); this.templateTypeChecker.getGlobalCompletions(this.template, this.component);
if (completions === null) { if (completions === null) {
return undefined; return undefined;
} }
@ -656,25 +638,6 @@ export class CompletionBuilder<N extends TmplAstNode|AST> {
isNewIdentifierLocation: false, isNewIdentifierLocation: false,
}; };
} }
/**
* From the AST node of the cursor position, include completion of string literals, number
* literals, `true`, `false`, `null`, and `undefined`.
*/
private isValidNodeContextCompletion(completion: ts.CompletionEntry): boolean {
if (completion.kind === ts.ScriptElementKind.string) {
// 'string' kind includes both string literals and number literals
return true;
}
if (completion.kind === ts.ScriptElementKind.keyword) {
return completion.name === 'true' || completion.name === 'false' ||
completion.name === 'null';
}
if (completion.kind === ts.ScriptElementKind.variableElement) {
return completion.name === 'undefined';
}
return false;
}
} }
function makeReplacementSpanFromParseSourceSpan(span: ParseSourceSpan): ts.TextSpan { function makeReplacementSpanFromParseSourceSpan(span: ParseSourceSpan): ts.TextSpan {

View File

@ -24,18 +24,6 @@ const DIR_WITH_INPUT = {
` `
}; };
const DIR_WITH_UNION_TYPE_INPUT = {
'Dir': `
@Directive({
selector: '[dir]',
inputs: ['myInput']
})
export class Dir {
myInput!: 'foo'|42|null|undefined
}
`
};
const DIR_WITH_OUTPUT = { const DIR_WITH_OUTPUT = {
'Dir': ` 'Dir': `
@Directive({ @Directive({
@ -215,18 +203,6 @@ describe('completions', () => {
const completions = templateFile.getCompletionsAtPosition(); const completions = templateFile.getCompletionsAtPosition();
expectContain(completions, ts.ScriptElementKind.memberVariableElement, ['title']); expectContain(completions, ts.ScriptElementKind.memberVariableElement, ['title']);
}); });
it('should return completions of string literals, number literals, `true`, `false`, `null` and `undefined`',
() => {
const {templateFile} = setup(`<input dir [myInput]="">`, '', DIR_WITH_UNION_TYPE_INPUT);
templateFile.moveCursorToText('dir [myInput]="¦">');
const completions = templateFile.getCompletionsAtPosition();
expectContain(completions, ts.ScriptElementKind.string, [`'foo'`, '42']);
expectContain(completions, ts.ScriptElementKind.keyword, ['null']);
expectContain(completions, ts.ScriptElementKind.variableElement, ['undefined']);
expectDoesNotContain(completions, ts.ScriptElementKind.parameterElement, ['ctx']);
});
}); });
describe('in an expression scope', () => { describe('in an expression scope', () => {