Skip to content

Commit 39dc632

Browse files
committed
Funnel all v1 AST node construction through parser builders
The parser builders are the defacto constructors for the v1 AST nodes. As the nodes get increasingly more eloborate for backwards compatibility and such, it's important that we share the logic for constructing these nodes as much as possible so we don't miss any cases. Also standardize the signatures to use the object argument style, you should be able to clone a node by passing it back into the same builder method which it originated from.
1 parent 05add91 commit 39dc632

8 files changed

+286
-306
lines changed

packages/@glimmer/syntax/lib/parser.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type * as ASTv1 from './v1/api';
1111
import type * as HBS from './v1/handlebars-ast';
1212

1313
export type ParserNodeBuilder<N extends { loc: src.SourceSpan }> = Omit<N, 'loc'> & {
14-
loc: src.SourceOffset;
14+
start: src.SourceOffset;
1515
};
1616

1717
export interface Tag<T extends 'StartTag' | 'EndTag'> {
@@ -42,7 +42,7 @@ export abstract class Parser {
4242
public currentNode: Nullable<
4343
Readonly<
4444
| ParserNodeBuilder<ASTv1.CommentStatement>
45-
| ASTv1.TextNode
45+
| ParserNodeBuilder<ASTv1.TextNode>
4646
| ParserNodeBuilder<Tag<'StartTag'>>
4747
| ParserNodeBuilder<Tag<'EndTag'>>
4848
>
@@ -70,7 +70,7 @@ export abstract class Parser {
7070

7171
finish<T extends { loc: src.SourceSpan }>(node: ParserNodeBuilder<T>): T {
7272
return assign({}, node, {
73-
loc: node.loc.until(this.offset()),
73+
loc: node.start.until(this.offset()),
7474
} as const) as unknown as T;
7575

7676
// node.loc = node.loc.withEnd(end);
@@ -143,7 +143,7 @@ export abstract class Parser {
143143
return node;
144144
}
145145

146-
get currentData(): ASTv1.TextNode {
146+
get currentData(): ParserNodeBuilder<ASTv1.TextNode> {
147147
let node = this.currentNode;
148148
assert(node && node.type === 'TextNode', 'expected a text node');
149149
return node;

packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts

+47-47
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { Parser } from '../parser';
1010
import { NON_EXISTENT_LOCATION } from '../source/location';
1111
import { generateSyntaxError } from '../syntax-error';
1212
import { appendChild, isHBSLiteral, printLiteral } from '../utils';
13-
import { buildLegacyPath } from '../v1/legacy-interop';
1413
import b from '../v1/parser-builders';
1514

1615
const BEFORE_ATTRIBUTE_NAME = 'beforeAttributeName' as TokenizerState;
@@ -129,7 +128,7 @@ export abstract class HandlebarsNodeVisitors extends Parser {
129128
mustache = b.mustache({
130129
path: this.acceptNode<ASTv1.Literal>(rawMustache.path),
131130
params: [],
132-
hash: b.hash([], this.source.spanFor(rawMustache.path.loc).collapse('end')),
131+
hash: b.hash({ pairs: [], loc: this.source.spanFor(rawMustache.path.loc).collapse('end') }),
133132
trusting: !escaped,
134133
loc: this.source.spanFor(loc),
135134
strip,
@@ -229,7 +228,7 @@ export abstract class HandlebarsNodeVisitors extends Parser {
229228
}
230229

231230
const { value, loc } = rawComment;
232-
const comment = b.mustacheComment(value, this.source.spanFor(loc));
231+
const comment = b.mustacheComment({ value, loc: this.source.spanFor(loc) });
233232

234233
switch (tokenizer.state) {
235234
case 'beforeAttributeName':
@@ -336,13 +335,12 @@ export abstract class HandlebarsNodeVisitors extends Parser {
336335

337336
let pathHead: ASTv1.PathHead;
338337
if (thisHead) {
339-
pathHead = {
340-
type: 'ThisHead',
338+
pathHead = b.this({
341339
loc: this.source.spanFor({
342340
start: path.loc.start,
343341
end: { line: path.loc.start.line, column: path.loc.start.column + 4 },
344342
}),
345-
};
343+
});
346344
} else if (path.data) {
347345
const head = parts.shift();
348346

@@ -353,14 +351,13 @@ export abstract class HandlebarsNodeVisitors extends Parser {
353351
);
354352
}
355353

356-
pathHead = {
357-
type: 'AtHead',
354+
pathHead = b.atName({
358355
name: `@${head}`,
359356
loc: this.source.spanFor({
360357
start: path.loc.start,
361358
end: { line: path.loc.start.line, column: path.loc.start.column + head.length + 1 },
362359
}),
363-
};
360+
});
364361
} else {
365362
const head = parts.shift();
366363

@@ -371,17 +368,16 @@ export abstract class HandlebarsNodeVisitors extends Parser {
371368
);
372369
}
373370

374-
pathHead = {
375-
type: 'VarHead',
371+
pathHead = b.var({
376372
name: head,
377373
loc: this.source.spanFor({
378374
start: path.loc.start,
379375
end: { line: path.loc.start.line, column: path.loc.start.column + head.length },
380376
}),
381-
};
377+
});
382378
}
383379

384-
return buildLegacyPath({
380+
return b.path({
385381
head: pathHead,
386382
tail: parts,
387383
loc: this.source.spanFor(path.loc),
@@ -397,7 +393,7 @@ export abstract class HandlebarsNodeVisitors extends Parser {
397393
})
398394
);
399395

400-
return b.hash(pairs, this.source.spanFor(hash.loc));
396+
return b.hash({ pairs, loc: this.source.spanFor(hash.loc) });
401397
}
402398

403399
StringLiteral(string: HBS.StringLiteral): ASTv1.StringLiteral {
@@ -502,38 +498,43 @@ function acceptCallNodes(
502498
params: ASTv1.Expression[];
503499
hash: ASTv1.Hash;
504500
} {
505-
if (node.path.type.endsWith('Literal')) {
506-
const path = node.path as unknown as
507-
| HBS.StringLiteral
508-
| HBS.UndefinedLiteral
509-
| HBS.NullLiteral
510-
| HBS.NumberLiteral
511-
| HBS.BooleanLiteral;
512-
513-
let value = '';
514-
if (path.type === 'BooleanLiteral') {
515-
value = path.original.toString();
516-
} else if (path.type === 'StringLiteral') {
517-
value = `"${path.original}"`;
518-
} else if (path.type === 'NullLiteral') {
519-
value = 'null';
520-
} else if (path.type === 'NumberLiteral') {
521-
value = path.value.toString();
522-
} else {
523-
value = 'undefined';
501+
let path: ASTv1.PathExpression | ASTv1.SubExpression;
502+
503+
switch (node.path.type) {
504+
case 'PathExpression':
505+
path = compiler.PathExpression(node.path);
506+
break;
507+
508+
case 'SubExpression':
509+
path = compiler.SubExpression(node.path);
510+
break;
511+
512+
case 'StringLiteral':
513+
case 'UndefinedLiteral':
514+
case 'NullLiteral':
515+
case 'NumberLiteral':
516+
case 'BooleanLiteral': {
517+
let value: string;
518+
if (node.path.type === 'BooleanLiteral') {
519+
value = node.path.original.toString();
520+
} else if (node.path.type === 'StringLiteral') {
521+
value = `"${node.path.original}"`;
522+
} else if (node.path.type === 'NullLiteral') {
523+
value = 'null';
524+
} else if (node.path.type === 'NumberLiteral') {
525+
value = node.path.value.toString();
526+
} else {
527+
value = 'undefined';
528+
}
529+
throw generateSyntaxError(
530+
`${node.path.type} "${
531+
node.path.type === 'StringLiteral' ? node.path.original : value
532+
}" cannot be called as a sub-expression, replace (${value}) with ${value}`,
533+
compiler.source.spanFor(node.path.loc)
534+
);
524535
}
525-
throw generateSyntaxError(
526-
`${path.type} "${
527-
path.type === 'StringLiteral' ? path.original : value
528-
}" cannot be called as a sub-expression, replace (${value}) with ${value}`,
529-
compiler.source.spanFor(path.loc)
530-
);
531536
}
532537

533-
const path =
534-
node.path.type === 'PathExpression'
535-
? compiler.PathExpression(node.path)
536-
: compiler.SubExpression(node.path as unknown as HBS.SubExpression);
537538
const params = node.params
538539
? node.params.map((e) => compiler.acceptNode<ASTv1.Expression>(e))
539540
: [];
@@ -544,11 +545,10 @@ function acceptCallNodes(
544545

545546
const hash = node.hash
546547
? compiler.Hash(node.hash)
547-
: ({
548-
type: 'Hash',
549-
pairs: [] as ASTv1.HashPair[],
548+
: b.hash({
549+
pairs: [],
550550
loc: compiler.source.spanFor(end).collapse('end'),
551-
} as const);
551+
});
552552

553553
return { path, params, hash };
554554
}

packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts

+24-23
Original file line numberDiff line numberDiff line change
@@ -30,34 +30,37 @@ export class TokenizerEventHandlers extends HandlebarsNodeVisitors {
3030
// Comment
3131

3232
beginComment(): void {
33-
this.currentNode = b.comment('', this.source.offsetFor(this.tagOpenLine, this.tagOpenColumn));
33+
this.currentNode = {
34+
type: 'CommentStatement',
35+
value: '',
36+
start: this.source.offsetFor(this.tagOpenLine, this.tagOpenColumn),
37+
};
3438
}
3539

3640
appendToCommentData(char: string): void {
3741
this.currentComment.value += char;
3842
}
3943

4044
finishComment(): void {
41-
appendChild(this.currentElement(), this.finish(this.currentComment));
45+
appendChild(this.currentElement(), b.comment(this.finish(this.currentComment)));
4246
}
4347

4448
// Data
4549

4650
beginData(): void {
47-
this.currentNode = b.text({
51+
this.currentNode = {
52+
type: 'TextNode',
4853
chars: '',
49-
loc: this.offset().collapsed(),
50-
});
54+
start: this.offset(),
55+
};
5156
}
5257

5358
appendToData(char: string): void {
5459
this.currentData.chars += char;
5560
}
5661

5762
finishData(): void {
58-
this.currentData.loc = this.currentData.loc.withEnd(this.offset());
59-
60-
appendChild(this.currentElement(), this.currentData);
63+
appendChild(this.currentElement(), b.text(this.finish(this.currentData)));
6164
}
6265

6366
// Tags - basic
@@ -75,7 +78,7 @@ export class TokenizerEventHandlers extends HandlebarsNodeVisitors {
7578
modifiers: [],
7679
comments: [],
7780
selfClosing: false,
78-
loc: this.source.offsetFor(this.tagOpenLine, this.tagOpenColumn),
81+
start: this.source.offsetFor(this.tagOpenLine, this.tagOpenColumn),
7982
};
8083
}
8184

@@ -87,7 +90,7 @@ export class TokenizerEventHandlers extends HandlebarsNodeVisitors {
8790
modifiers: [],
8891
comments: [],
8992
selfClosing: false,
90-
loc: this.source.offsetFor(this.tagOpenLine, this.tagOpenColumn),
93+
start: this.source.offsetFor(this.tagOpenLine, this.tagOpenColumn),
9194
};
9295
}
9396

@@ -101,7 +104,7 @@ export class TokenizerEventHandlers extends HandlebarsNodeVisitors {
101104
throw generateSyntaxError(
102105
'Invalid named block named detected, you may have created a named block without a name, or you may have began your name with a number. Named blocks must have names that are at least one character long, and begin with a lower case letter',
103106
this.source.spanFor({
104-
start: this.currentTag.loc.toJSON(),
107+
start: this.currentTag.start.toJSON(),
105108
end: this.offset().toJSON(),
106109
})
107110
);
@@ -116,19 +119,14 @@ export class TokenizerEventHandlers extends HandlebarsNodeVisitors {
116119
}
117120

118121
finishStartTag(): void {
119-
let {
120-
name,
121-
attributes: attrs,
122-
modifiers,
123-
comments,
124-
selfClosing,
125-
loc,
126-
} = this.finish(this.currentStartTag);
122+
let { name, attributes, modifiers, comments, selfClosing, loc } = this.finish(
123+
this.currentStartTag
124+
);
127125

128126
let element = b.element({
129127
tag: name,
130128
selfClosing,
131-
attrs,
129+
attributes,
132130
modifiers,
133131
comments,
134132
children: [],
@@ -148,7 +146,7 @@ export class TokenizerEventHandlers extends HandlebarsNodeVisitors {
148146

149147
element.loc = element.loc.withEnd(this.offset());
150148
parseElementBlockParams(element);
151-
appendChild(parent, element);
149+
appendChild(parent, b.element(element));
152150
}
153151

154152
markTagAsSelfClosing(): void {
@@ -222,7 +220,7 @@ export class TokenizerEventHandlers extends HandlebarsNodeVisitors {
222220
if (tag.type === 'EndTag') {
223221
throw generateSyntaxError(
224222
`Invalid end tag: closing tag must not have attributes`,
225-
this.source.spanFor({ start: tag.loc.toJSON(), end: tokenizerPos.toJSON() })
223+
this.source.spanFor({ start: tag.start.toJSON(), end: tokenizerPos.toJSON() })
226224
);
227225
}
228226

@@ -256,7 +254,10 @@ export class TokenizerEventHandlers extends HandlebarsNodeVisitors {
256254
let first = getFirst(parts);
257255
let last = getLast(parts);
258256

259-
return b.concat(parts, this.source.spanFor(first.loc).extend(this.source.spanFor(last.loc)));
257+
return b.concat({
258+
parts,
259+
loc: this.source.spanFor(first.loc).extend(this.source.spanFor(last.loc)),
260+
});
260261
}
261262

262263
validateEndTag(

packages/@glimmer/syntax/lib/v1/handlebars-ast.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ export interface CommonBlock extends CommonNode {
8686
hash: Hash;
8787
program: Program;
8888
inverse: Program;
89-
openStrip: StripFlags;
90-
inverseStrip: StripFlags;
91-
closeStrip: StripFlags;
89+
openStrip?: StripFlags;
90+
inverseStrip?: StripFlags;
91+
closeStrip?: StripFlags;
9292
}
9393

9494
export interface BlockStatement extends CommonBlock {

0 commit comments

Comments
 (0)