forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
/
completion.ts
134 lines (117 loc) · 4.96 KB
/
completion.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {TmplAstReference, TmplAstTemplate} from '@angular/compiler';
import {MethodCall, PropertyRead, PropertyWrite, SafeMethodCall, SafePropertyRead} from '@angular/compiler/src/compiler';
import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../file_system';
import {CompletionKind, GlobalCompletion, ReferenceCompletion, ShimLocation, VariableCompletion} from '../api';
import {ExpressionIdentifier, findFirstMatchingNode} from './comments';
import {TemplateData} from './context';
/**
* Powers autocompletion for a specific component.
*
* Internally caches autocompletion results, and must be discarded if the component template or
* surrounding TS program have changed.
*/
export class CompletionEngine {
/**
* Cache of `GlobalCompletion`s for various levels of the template, including the root template
* (`null`).
*/
private globalCompletionCache = new Map<TmplAstTemplate|null, GlobalCompletion>();
private expressionCompletionCache =
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)!;
}
// Find the component completion expression within the TCB. This looks like: `ctx. /* ... */;`
const globalRead = findFirstMatchingNode(this.tcb, {
filter: ts.isPropertyAccessExpression,
withExpressionIdentifier: ExpressionIdentifier.COMPONENT_COMPLETION
});
if (globalRead === null) {
return null;
}
const completion: GlobalCompletion = {
componentContext: {
shimPath: this.shimPath,
// `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
// for the component context.
positionInShimFile: globalRead.name.getStart(),
},
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) {
completion.templateContext.set(node.name, {
kind: CompletionKind.Reference,
node,
});
} else {
completion.templateContext.set(node.name, {
kind: CompletionKind.Variable,
node,
});
}
}
this.globalCompletionCache.set(context, completion);
return completion;
}
getExpressionCompletionLocation(expr: PropertyRead|PropertyWrite|MethodCall|
SafeMethodCall): ShimLocation|null {
if (this.expressionCompletionCache.has(expr)) {
return this.expressionCompletionCache.get(expr)!;
}
// Completion works inside property reads and method calls.
let tsExpr: ts.PropertyAccessExpression|null = null;
if (expr instanceof PropertyRead || expr instanceof MethodCall ||
expr instanceof PropertyWrite) {
// Non-safe navigation operations are trivial: `foo.bar` or `foo.bar()`
tsExpr = findFirstMatchingNode(this.tcb, {
filter: ts.isPropertyAccessExpression,
withSpan: expr.nameSpan,
});
} else if (expr instanceof SafePropertyRead || expr instanceof SafeMethodCall) {
// Safe navigation operations are a little more complex, and involve a ternary. Completion
// happens in the "true" case of the ternary.
const ternaryExpr = findFirstMatchingNode(this.tcb, {
filter: ts.isParenthesizedExpression,
withSpan: expr.sourceSpan,
});
if (ternaryExpr === null || !ts.isConditionalExpression(ternaryExpr.expression)) {
return null;
}
const whenTrue = ternaryExpr.expression.whenTrue;
if (expr instanceof SafePropertyRead && ts.isPropertyAccessExpression(whenTrue)) {
tsExpr = whenTrue;
} else if (
expr instanceof SafeMethodCall && ts.isCallExpression(whenTrue) &&
ts.isPropertyAccessExpression(whenTrue.expression)) {
tsExpr = whenTrue.expression;
}
}
if (tsExpr === null) {
return null;
}
const res: ShimLocation = {
shimPath: this.shimPath,
positionInShimFile: tsExpr.name.getEnd(),
};
this.expressionCompletionCache.set(expr, res);
return res;
}
}