Skip to content

Commit bf5bda4

Browse files
atscottalxhub
authored andcommitted
refactor(language-service): Add quick info for built in control flow/blocks (angular#52386)
Adds hover info for: * Defer blocks * Triggers and trigger behavior keywords * For loop empty block * Track keyword in for loop block resolves angular/vscode-ng-language-service#1946 PR Close angular#52386
1 parent 73c5d1c commit bf5bda4

File tree

13 files changed

+577
-167
lines changed

13 files changed

+577
-167
lines changed

packages/compiler/src/render3/r3_ast.ts

Lines changed: 85 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -118,16 +118,22 @@ export class Element implements Node {
118118
}
119119

120120
export abstract class DeferredTrigger implements Node {
121-
constructor(public sourceSpan: ParseSourceSpan) {}
121+
constructor(
122+
public nameSpan: ParseSourceSpan|null, public sourceSpan: ParseSourceSpan,
123+
public prefetchSpan: ParseSourceSpan|null, public whenOrOnSourceSpan: ParseSourceSpan|null) {}
122124

123125
visit<Result>(visitor: Visitor<Result>): Result {
124126
return visitor.visitDeferredTrigger(this);
125127
}
126128
}
127129

128130
export class BoundDeferredTrigger extends DeferredTrigger {
129-
constructor(public value: AST, sourceSpan: ParseSourceSpan) {
130-
super(sourceSpan);
131+
constructor(
132+
public value: AST, sourceSpan: ParseSourceSpan, prefetchSpan: ParseSourceSpan|null,
133+
whenSourceSpan: ParseSourceSpan) {
134+
// BoundDeferredTrigger is for 'when' triggers. These aren't really "triggers" and don't have a
135+
// nameSpan. Trigger names are the built in event triggers like hover, interaction, etc.
136+
super(/** nameSpan */ null, sourceSpan, prefetchSpan, whenSourceSpan);
131137
}
132138
}
133139

@@ -136,54 +142,75 @@ export class IdleDeferredTrigger extends DeferredTrigger {}
136142
export class ImmediateDeferredTrigger extends DeferredTrigger {}
137143

138144
export class HoverDeferredTrigger extends DeferredTrigger {
139-
constructor(public reference: string|null, sourceSpan: ParseSourceSpan) {
140-
super(sourceSpan);
145+
constructor(
146+
public reference: string|null, nameSpan: ParseSourceSpan, sourceSpan: ParseSourceSpan,
147+
prefetchSpan: ParseSourceSpan|null, onSourceSpan: ParseSourceSpan|null) {
148+
super(nameSpan, sourceSpan, prefetchSpan, onSourceSpan);
141149
}
142150
}
143151

144152
export class TimerDeferredTrigger extends DeferredTrigger {
145-
constructor(public delay: number, sourceSpan: ParseSourceSpan) {
146-
super(sourceSpan);
153+
constructor(
154+
public delay: number, nameSpan: ParseSourceSpan, sourceSpan: ParseSourceSpan,
155+
prefetchSpan: ParseSourceSpan|null, onSourceSpan: ParseSourceSpan|null) {
156+
super(nameSpan, sourceSpan, prefetchSpan, onSourceSpan);
147157
}
148158
}
149159

150160
export class InteractionDeferredTrigger extends DeferredTrigger {
151-
constructor(public reference: string|null, sourceSpan: ParseSourceSpan) {
152-
super(sourceSpan);
161+
constructor(
162+
public reference: string|null, nameSpan: ParseSourceSpan, sourceSpan: ParseSourceSpan,
163+
prefetchSpan: ParseSourceSpan|null, onSourceSpan: ParseSourceSpan|null) {
164+
super(nameSpan, sourceSpan, prefetchSpan, onSourceSpan);
153165
}
154166
}
155167

156168
export class ViewportDeferredTrigger extends DeferredTrigger {
157-
constructor(public reference: string|null, sourceSpan: ParseSourceSpan) {
158-
super(sourceSpan);
169+
constructor(
170+
public reference: string|null, nameSpan: ParseSourceSpan, sourceSpan: ParseSourceSpan,
171+
prefetchSpan: ParseSourceSpan|null, onSourceSpan: ParseSourceSpan|null) {
172+
super(nameSpan, sourceSpan, prefetchSpan, onSourceSpan);
159173
}
160174
}
161175

162-
export class DeferredBlockPlaceholder implements Node {
176+
export class BlockNode {
163177
constructor(
164-
public children: Node[], public minimumTime: number|null, public sourceSpan: ParseSourceSpan,
178+
public nameSpan: ParseSourceSpan, public sourceSpan: ParseSourceSpan,
165179
public startSourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan|null) {}
180+
}
181+
182+
export class DeferredBlockPlaceholder extends BlockNode implements Node {
183+
constructor(
184+
public children: Node[], public minimumTime: number|null, nameSpan: ParseSourceSpan,
185+
sourceSpan: ParseSourceSpan, startSourceSpan: ParseSourceSpan,
186+
endSourceSpan: ParseSourceSpan|null) {
187+
super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
188+
}
166189

167190
visit<Result>(visitor: Visitor<Result>): Result {
168191
return visitor.visitDeferredBlockPlaceholder(this);
169192
}
170193
}
171194

172-
export class DeferredBlockLoading implements Node {
195+
export class DeferredBlockLoading extends BlockNode implements Node {
173196
constructor(
174197
public children: Node[], public afterTime: number|null, public minimumTime: number|null,
175-
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan,
176-
public endSourceSpan: ParseSourceSpan|null) {}
198+
nameSpan: ParseSourceSpan, sourceSpan: ParseSourceSpan, startSourceSpan: ParseSourceSpan,
199+
endSourceSpan: ParseSourceSpan|null) {
200+
super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
201+
}
177202

178203
visit<Result>(visitor: Visitor<Result>): Result {
179204
return visitor.visitDeferredBlockLoading(this);
180205
}
181206
}
182207

183-
export class DeferredBlockError implements Node {
208+
export class DeferredBlockError extends BlockNode implements Node {
184209
constructor(
185-
public children: Node[], public sourceSpan: ParseSourceSpan,
186-
public startSourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan|null) {}
210+
public children: Node[], nameSpan: ParseSourceSpan, sourceSpan: ParseSourceSpan,
211+
startSourceSpan: ParseSourceSpan, endSourceSpan: ParseSourceSpan|null) {
212+
super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
213+
}
187214

188215
visit<Result>(visitor: Visitor<Result>): Result {
189216
return visitor.visitDeferredBlockError(this);
@@ -200,7 +227,7 @@ export interface DeferredBlockTriggers {
200227
viewport?: ViewportDeferredTrigger;
201228
}
202229

203-
export class DeferredBlock implements Node {
230+
export class DeferredBlock extends BlockNode implements Node {
204231
readonly triggers: Readonly<DeferredBlockTriggers>;
205232
readonly prefetchTriggers: Readonly<DeferredBlockTriggers>;
206233
private readonly definedTriggers: (keyof DeferredBlockTriggers)[];
@@ -210,8 +237,9 @@ export class DeferredBlock implements Node {
210237
public children: Node[], triggers: DeferredBlockTriggers,
211238
prefetchTriggers: DeferredBlockTriggers, public placeholder: DeferredBlockPlaceholder|null,
212239
public loading: DeferredBlockLoading|null, public error: DeferredBlockError|null,
213-
public sourceSpan: ParseSourceSpan, public mainBlockSpan: ParseSourceSpan,
214-
public startSourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan|null) {
240+
nameSpan: ParseSourceSpan, sourceSpan: ParseSourceSpan, public mainBlockSpan: ParseSourceSpan,
241+
startSourceSpan: ParseSourceSpan, endSourceSpan: ParseSourceSpan|null) {
242+
super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
215243
this.triggers = triggers;
216244
this.prefetchTriggers = prefetchTriggers;
217245
// We cache the keys since we know that they won't change and we
@@ -239,25 +267,31 @@ export class DeferredBlock implements Node {
239267
}
240268
}
241269

242-
export class SwitchBlock implements Node {
270+
export class SwitchBlock extends BlockNode implements Node {
243271
constructor(
244272
public expression: AST, public cases: SwitchBlockCase[],
245273
/**
246274
* These blocks are only captured to allow for autocompletion in the language service. They
247275
* aren't meant to be processed in any other way.
248276
*/
249-
public unknownBlocks: UnknownBlock[], public sourceSpan: ParseSourceSpan,
250-
public startSourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan|null) {}
277+
public unknownBlocks: UnknownBlock[], sourceSpan: ParseSourceSpan,
278+
startSourceSpan: ParseSourceSpan, endSourceSpan: ParseSourceSpan|null,
279+
nameSpan: ParseSourceSpan) {
280+
super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
281+
}
251282

252283
visit<Result>(visitor: Visitor<Result>): Result {
253284
return visitor.visitSwitchBlock(this);
254285
}
255286
}
256287

257-
export class SwitchBlockCase implements Node {
288+
export class SwitchBlockCase extends BlockNode implements Node {
258289
constructor(
259-
public expression: AST|null, public children: Node[], public sourceSpan: ParseSourceSpan,
260-
public startSourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan|null) {}
290+
public expression: AST|null, public children: Node[], sourceSpan: ParseSourceSpan,
291+
startSourceSpan: ParseSourceSpan, endSourceSpan: ParseSourceSpan|null,
292+
nameSpan: ParseSourceSpan) {
293+
super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
294+
}
261295

262296
visit<Result>(visitor: Visitor<Result>): Result {
263297
return visitor.visitSwitchBlockCase(this);
@@ -270,44 +304,53 @@ export class SwitchBlockCase implements Node {
270304
export type ForLoopBlockContext =
271305
Record<'$index'|'$first'|'$last'|'$even'|'$odd'|'$count', Variable>;
272306

273-
export class ForLoopBlock implements Node {
307+
export class ForLoopBlock extends BlockNode implements Node {
274308
constructor(
275309
public item: Variable, public expression: ASTWithSource, public trackBy: ASTWithSource,
276-
public contextVariables: ForLoopBlockContext, public children: Node[],
277-
public empty: ForLoopBlockEmpty|null, public sourceSpan: ParseSourceSpan,
278-
public mainBlockSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan,
279-
public endSourceSpan: ParseSourceSpan|null) {}
310+
public trackKeywordSpan: ParseSourceSpan, public contextVariables: ForLoopBlockContext,
311+
public children: Node[], public empty: ForLoopBlockEmpty|null, sourceSpan: ParseSourceSpan,
312+
public mainBlockSpan: ParseSourceSpan, startSourceSpan: ParseSourceSpan,
313+
endSourceSpan: ParseSourceSpan|null, nameSpan: ParseSourceSpan) {
314+
super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
315+
}
280316

281317
visit<Result>(visitor: Visitor<Result>): Result {
282318
return visitor.visitForLoopBlock(this);
283319
}
284320
}
285321

286-
export class ForLoopBlockEmpty implements Node {
322+
export class ForLoopBlockEmpty extends BlockNode implements Node {
287323
constructor(
288-
public children: Node[], public sourceSpan: ParseSourceSpan,
289-
public startSourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan|null) {}
324+
public children: Node[], sourceSpan: ParseSourceSpan, startSourceSpan: ParseSourceSpan,
325+
endSourceSpan: ParseSourceSpan|null, nameSpan: ParseSourceSpan) {
326+
super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
327+
}
290328

291329
visit<Result>(visitor: Visitor<Result>): Result {
292330
return visitor.visitForLoopBlockEmpty(this);
293331
}
294332
}
295333

296-
export class IfBlock implements Node {
334+
export class IfBlock extends BlockNode implements Node {
297335
constructor(
298-
public branches: IfBlockBranch[], public sourceSpan: ParseSourceSpan,
299-
public startSourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan|null) {}
336+
public branches: IfBlockBranch[], sourceSpan: ParseSourceSpan,
337+
startSourceSpan: ParseSourceSpan, endSourceSpan: ParseSourceSpan|null,
338+
nameSpan: ParseSourceSpan) {
339+
super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
340+
}
300341

301342
visit<Result>(visitor: Visitor<Result>): Result {
302343
return visitor.visitIfBlock(this);
303344
}
304345
}
305346

306-
export class IfBlockBranch implements Node {
347+
export class IfBlockBranch extends BlockNode implements Node {
307348
constructor(
308349
public expression: AST|null, public children: Node[], public expressionAlias: Variable|null,
309-
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan,
310-
public endSourceSpan: ParseSourceSpan|null) {}
350+
sourceSpan: ParseSourceSpan, startSourceSpan: ParseSourceSpan,
351+
endSourceSpan: ParseSourceSpan|null, nameSpan: ParseSourceSpan) {
352+
super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
353+
}
311354

312355
visit<Result>(visitor: Visitor<Result>): Result {
313356
return visitor.visitIfBlockBranch(this);

packages/compiler/src/render3/r3_control_flow.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ export function createIfBlock(
5959
if (mainBlockParams !== null) {
6060
branches.push(new t.IfBlockBranch(
6161
mainBlockParams.expression, html.visitAll(visitor, ast.children, ast.children),
62-
mainBlockParams.expressionAlias, ast.sourceSpan, ast.startSourceSpan, ast.endSourceSpan));
62+
mainBlockParams.expressionAlias, ast.sourceSpan, ast.startSourceSpan, ast.endSourceSpan,
63+
ast.nameSpan));
6364
}
6465

6566
for (const block of connectedBlocks) {
@@ -70,12 +71,13 @@ export function createIfBlock(
7071
const children = html.visitAll(visitor, block.children, block.children);
7172
branches.push(new t.IfBlockBranch(
7273
params.expression, children, params.expressionAlias, block.sourceSpan,
73-
block.startSourceSpan, block.endSourceSpan));
74+
block.startSourceSpan, block.endSourceSpan, block.nameSpan));
7475
}
7576
} else if (block.name === 'else') {
7677
const children = html.visitAll(visitor, block.children, block.children);
7778
branches.push(new t.IfBlockBranch(
78-
null, children, null, block.sourceSpan, block.startSourceSpan, block.endSourceSpan));
79+
null, children, null, block.sourceSpan, block.startSourceSpan, block.endSourceSpan,
80+
block.nameSpan));
7981
}
8082
}
8183

@@ -92,7 +94,8 @@ export function createIfBlock(
9294
}
9395

9496
return {
95-
node: new t.IfBlock(branches, wholeSourceSpan, ast.startSourceSpan, ifBlockEndSourceSpan),
97+
node: new t.IfBlock(
98+
branches, wholeSourceSpan, ast.startSourceSpan, ifBlockEndSourceSpan, ast.nameSpan),
9699
errors,
97100
};
98101
}
@@ -115,7 +118,7 @@ export function createForLoop(
115118
} else {
116119
empty = new t.ForLoopBlockEmpty(
117120
html.visitAll(visitor, block.children, block.children), block.sourceSpan,
118-
block.startSourceSpan, block.endSourceSpan);
121+
block.startSourceSpan, block.endSourceSpan, block.nameSpan);
119122
}
120123
} else {
121124
errors.push(new ParseError(block.sourceSpan, `Unrecognized @for loop block "${block.name}"`));
@@ -135,9 +138,9 @@ export function createForLoop(
135138
const sourceSpan =
136139
new ParseSourceSpan(ast.sourceSpan.start, endSpan?.end ?? ast.sourceSpan.end);
137140
node = new t.ForLoopBlock(
138-
params.itemName, params.expression, params.trackBy, params.context,
139-
html.visitAll(visitor, ast.children, ast.children), empty, sourceSpan, ast.sourceSpan,
140-
ast.startSourceSpan, endSpan);
141+
params.itemName, params.expression, params.trackBy.expression, params.trackBy.keywordSpan,
142+
params.context, html.visitAll(visitor, ast.children, ast.children), empty, sourceSpan,
143+
ast.sourceSpan, ast.startSourceSpan, endSpan, ast.nameSpan);
141144
}
142145
}
143146

@@ -172,7 +175,7 @@ export function createSwitchBlock(
172175
null;
173176
const ast = new t.SwitchBlockCase(
174177
expression, html.visitAll(visitor, node.children, node.children), node.sourceSpan,
175-
node.startSourceSpan, node.endSourceSpan);
178+
node.startSourceSpan, node.endSourceSpan, node.nameSpan);
176179

177180
if (expression === null) {
178181
defaultCase = ast;
@@ -189,7 +192,7 @@ export function createSwitchBlock(
189192
return {
190193
node: new t.SwitchBlock(
191194
primaryExpression, cases, unknownBlocks, ast.sourceSpan, ast.startSourceSpan,
192-
ast.endSourceSpan),
195+
ast.endSourceSpan, ast.nameSpan),
193196
errors
194197
};
195198
}
@@ -217,7 +220,7 @@ function parseForLoopParameters(
217220
const result = {
218221
itemName: new t.Variable(
219222
itemName, '$implicit', expressionParam.sourceSpan, expressionParam.sourceSpan),
220-
trackBy: null as ASTWithSource | null,
223+
trackBy: null as {expression: ASTWithSource, keywordSpan: ParseSourceSpan} | null,
221224
expression: parseBlockParameterToBinding(expressionParam, bindingParser, rawExpression),
222225
context: {} as t.ForLoopBlockContext,
223226
};
@@ -237,7 +240,10 @@ function parseForLoopParameters(
237240
errors.push(
238241
new ParseError(param.sourceSpan, '@for loop can only have one "track" expression'));
239242
} else {
240-
result.trackBy = parseBlockParameterToBinding(param, bindingParser, trackMatch[1]);
243+
const expression = parseBlockParameterToBinding(param, bindingParser, trackMatch[1]);
244+
const keywordSpan = new ParseSourceSpan(
245+
param.sourceSpan.start, param.sourceSpan.start.moveBy('track'.length));
246+
result.trackBy = {expression, keywordSpan};
241247
}
242248
continue;
243249
}

packages/compiler/src/render3/r3_deferred_blocks.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,7 @@ export function createDeferredBlock(
4848
const {triggers, prefetchTriggers} =
4949
parsePrimaryTriggers(ast.parameters, bindingParser, errors, placeholder);
5050

51-
// The `defer` block has a main span encompassing all of the connected branches as well. For the
52-
// span of only the first "main" branch, use `mainSourceSpan`.
51+
// The `defer` block has a main span encompassing all of the connected branches as well.
5352
let lastEndSourceSpan = ast.endSourceSpan;
5453
let endOfLastSourceSpan = ast.sourceSpan.end;
5554
if (connectedBlocks.length > 0) {
@@ -58,12 +57,13 @@ export function createDeferredBlock(
5857
endOfLastSourceSpan = lastConnectedBlock.sourceSpan.end;
5958
}
6059

61-
const mainDeferredSourceSpan = new ParseSourceSpan(ast.sourceSpan.start, endOfLastSourceSpan);
60+
const sourceSpanWithConnectedBlocks =
61+
new ParseSourceSpan(ast.sourceSpan.start, endOfLastSourceSpan);
6262

6363
const node = new t.DeferredBlock(
6464
html.visitAll(visitor, ast.children, ast.children), triggers, prefetchTriggers, placeholder,
65-
loading, error, mainDeferredSourceSpan, ast.sourceSpan, ast.startSourceSpan,
66-
lastEndSourceSpan);
65+
loading, error, ast.nameSpan, sourceSpanWithConnectedBlocks, ast.sourceSpan,
66+
ast.startSourceSpan, lastEndSourceSpan);
6767

6868
return {node, errors};
6969
}
@@ -140,7 +140,7 @@ function parsePlaceholderBlock(ast: html.Block, visitor: html.Visitor): t.Deferr
140140
}
141141

142142
return new t.DeferredBlockPlaceholder(
143-
html.visitAll(visitor, ast.children, ast.children), minimumTime, ast.sourceSpan,
143+
html.visitAll(visitor, ast.children, ast.children), minimumTime, ast.nameSpan, ast.sourceSpan,
144144
ast.startSourceSpan, ast.endSourceSpan);
145145
}
146146

@@ -181,8 +181,8 @@ function parseLoadingBlock(ast: html.Block, visitor: html.Visitor): t.DeferredBl
181181
}
182182

183183
return new t.DeferredBlockLoading(
184-
html.visitAll(visitor, ast.children, ast.children), afterTime, minimumTime, ast.sourceSpan,
185-
ast.startSourceSpan, ast.endSourceSpan);
184+
html.visitAll(visitor, ast.children, ast.children), afterTime, minimumTime, ast.nameSpan,
185+
ast.sourceSpan, ast.startSourceSpan, ast.endSourceSpan);
186186
}
187187

188188

@@ -192,8 +192,8 @@ function parseErrorBlock(ast: html.Block, visitor: html.Visitor): t.DeferredBloc
192192
}
193193

194194
return new t.DeferredBlockError(
195-
html.visitAll(visitor, ast.children, ast.children), ast.sourceSpan, ast.startSourceSpan,
196-
ast.endSourceSpan);
195+
html.visitAll(visitor, ast.children, ast.children), ast.nameSpan, ast.sourceSpan,
196+
ast.startSourceSpan, ast.endSourceSpan);
197197
}
198198

199199
function parsePrimaryTriggers(

0 commit comments

Comments
 (0)