From b4e31f5de47fe253a80f539d557f98b23b02a103 Mon Sep 17 00:00:00 2001 From: patrick Date: Thu, 25 Jan 2024 14:04:36 +0100 Subject: [PATCH 1/2] add element parts, startTag & endTag & nameNode --- .../lib/parser/tokenizer-event-handlers.ts | 14 ++++++- packages/@glimmer/syntax/lib/utils.ts | 21 +++++++++++ packages/@glimmer/syntax/lib/v1/nodes-v1.ts | 37 +++++++++++++++++++ .../@glimmer/syntax/lib/v1/parser-builders.ts | 15 ++++++++ .../@glimmer/syntax/lib/v1/public-builders.ts | 15 ++++++++ .../@glimmer/syntax/lib/v1/visitor-keys.ts | 4 ++ .../@glimmer/syntax/test/loc-node-test.ts | 22 +++++++++++ .../@glimmer/syntax/test/parser-node-test.ts | 17 ++++++++- 8 files changed, 143 insertions(+), 2 deletions(-) diff --git a/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts b/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts index 486fb6bafd..0677ea31b5 100644 --- a/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts +++ b/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts @@ -14,7 +14,7 @@ import * as src from '../source/api'; import { generateSyntaxError } from '../syntax-error'; import traverse from '../traversal/traverse'; import Walker from '../traversal/walker'; -import { appendChild, parseElementBlockParams } from '../utils'; +import { appendChild, parseElementBlockParams, parseElementPartLocs } from '../utils'; import b from '../v1/parser-builders'; import publicBuilder from '../v1/public-builders'; import { HandlebarsNodeVisitors } from './handlebars-node-visitors'; @@ -135,6 +135,12 @@ export class TokenizerEventHandlers extends HandlebarsNodeVisitors { blockParams: [], loc, }); + element.startTag = { + type: 'ElementStartNode', + value: name, + loc: loc, + }; + parseElementPartLocs(this.source, element); this.elementStack.push(element); } @@ -143,6 +149,12 @@ export class TokenizerEventHandlers extends HandlebarsNodeVisitors { let element = this.elementStack.pop() as ASTv1.ElementNode; + element.endTag = { + type: 'ElementEndNode', + loc: tag.loc, + value: element.selfClosing ? '' : tag.name, + }; + this.validateEndTag(tag, element, isVoid); let parent = this.currentElement(); diff --git a/packages/@glimmer/syntax/lib/utils.ts b/packages/@glimmer/syntax/lib/utils.ts index c0c2d1cb1d..12decf566b 100644 --- a/packages/@glimmer/syntax/lib/utils.ts +++ b/packages/@glimmer/syntax/lib/utils.ts @@ -1,6 +1,7 @@ import type { Nullable } from '@glimmer/interfaces'; import { expect, unwrap } from '@glimmer/util'; +import type { src } from '..'; import type * as ASTv1 from './v1/api'; import type * as HBS from './v1/handlebars-ast'; @@ -20,6 +21,26 @@ export function parseElementBlockParams(element: ASTv1.ElementNode): void { if (params) element.blockParams = params; } +export function parseElementPartLocs(code: src.Source, element: ASTv1.ElementNode) { + const elementRange = [element.loc.getStart().offset!, element.loc.getEnd().offset!] as [ + number, + number, + ]; + let start = elementRange[0]; + let codeSlice = code.slice(...elementRange); + for (const part of element.parts) { + const idx = codeSlice.indexOf(part.value); + const range = [start + idx, 0] as [number, number]; + range[1] = range[0] + part.value.length; + codeSlice = code.slice(range[1], elementRange[1]); + start = range[1]; + part.loc = code.spanFor({ + start: code.hbsPosFor(range[0])!, + end: code.hbsPosFor(range[1])!, + }); + } +} + function parseBlockParams(element: ASTv1.ElementNode): Nullable { let l = element.attributes.length; let attrNames = []; diff --git a/packages/@glimmer/syntax/lib/v1/nodes-v1.ts b/packages/@glimmer/syntax/lib/v1/nodes-v1.ts index 229bd5fa9a..410dbe5e4b 100644 --- a/packages/@glimmer/syntax/lib/v1/nodes-v1.ts +++ b/packages/@glimmer/syntax/lib/v1/nodes-v1.ts @@ -141,9 +141,42 @@ export interface ElementName { loc: src.SourceLocation; } +export interface ElementStartNode extends BaseNode { + type: 'ElementStartNode'; + value: string; +} + +export interface ElementNameNode extends BaseNode { + type: 'ElementNameNode'; + value: string; +} + +export interface ElementEndNode extends BaseNode { + type: 'ElementEndNode'; + value: string; +} + +export interface ElementPartNode extends BaseNode { + type: 'ElementPartNode'; + value: string; +} + +/* + + ^-- ElementPartNode + ^-- ElementPartNode + ^- ElementPartNode + ^-------- ElementNameNode + ^------------------ ElementStartNode + ^----------- ElementEndNode + */ export interface ElementNode extends BaseNode { type: 'ElementNode'; tag: string; + nameNode: ElementNameNode; + startTag: ElementStartNode; + endTag: ElementEndNode; + parts: ElementPartNode[]; selfClosing: boolean; attributes: AttrNode[]; blockParams: string[]; @@ -315,6 +348,10 @@ export type SharedNodes = { }; export type Nodes = SharedNodes & { + ElementEndNode: ElementEndNode; + ElementStartNode: ElementStartNode; + ElementPartNode: ElementPartNode; + ElementNameNode: ElementNameNode; Program: Program; Template: Template; Block: Block; diff --git a/packages/@glimmer/syntax/lib/v1/parser-builders.ts b/packages/@glimmer/syntax/lib/v1/parser-builders.ts index 2d9b0a72f0..4a73643a32 100644 --- a/packages/@glimmer/syntax/lib/v1/parser-builders.ts +++ b/packages/@glimmer/syntax/lib/v1/parser-builders.ts @@ -166,6 +166,21 @@ class Builders { return { type: 'ElementNode', tag, + nameNode: { + type: 'ElementNameNode', + value: tag, + } as ASTv1.ElementNameNode, + startTag: { + type: 'ElementStartNode', + value: tag, + } as ASTv1.ElementStartNode, + endTag: { + type: 'ElementEndNode', + value: selfClosing ? '' : tag, + } as ASTv1.ElementEndNode, + parts: tag + .split('.') + .map((t) => ({ type: 'ElementPartNode', value: t }) as ASTv1.ElementPartNode), selfClosing: selfClosing, attributes: attrs || [], blockParams: blockParams || [], diff --git a/packages/@glimmer/syntax/lib/v1/public-builders.ts b/packages/@glimmer/syntax/lib/v1/public-builders.ts index adeefd0865..2c01edc06c 100644 --- a/packages/@glimmer/syntax/lib/v1/public-builders.ts +++ b/packages/@glimmer/syntax/lib/v1/public-builders.ts @@ -224,6 +224,21 @@ function buildElement(tag: TagDescriptor, options: BuildElementOptions = {}): AS return { type: 'ElementNode', tag: tagName, + nameNode: { + type: 'ElementNameNode', + value: tag, + } as ASTv1.ElementNameNode, + startTag: { + type: 'ElementStartNode', + value: tag, + } as ASTv1.ElementStartNode, + endTag: { + type: 'ElementEndNode', + value: selfClosing ? '' : tag, + } as ASTv1.ElementEndNode, + parts: tagName + .split('.') + .map((t) => ({ type: 'ElementPartNode', value: t }) as ASTv1.ElementPartNode), selfClosing: selfClosing, attributes: attrs || [], blockParams: blockParams || [], diff --git a/packages/@glimmer/syntax/lib/v1/visitor-keys.ts b/packages/@glimmer/syntax/lib/v1/visitor-keys.ts index 2454e6e333..0c873fb1d8 100644 --- a/packages/@glimmer/syntax/lib/v1/visitor-keys.ts +++ b/packages/@glimmer/syntax/lib/v1/visitor-keys.ts @@ -14,6 +14,10 @@ const visitorKeys = { CommentStatement: [], MustacheCommentStatement: [], ElementNode: ['attributes', 'modifiers', 'children', 'comments'], + ElementStartNode: [], + ElementPartNode: [], + ElementEndNode: [], + ElementNameNode: [], AttrNode: ['value'], TextNode: [], diff --git a/packages/@glimmer/syntax/test/loc-node-test.ts b/packages/@glimmer/syntax/test/loc-node-test.ts index c41a431944..dc3fbcca19 100644 --- a/packages/@glimmer/syntax/test/loc-node-test.ts +++ b/packages/@glimmer/syntax/test/loc-node-test.ts @@ -134,6 +134,28 @@ test('html elements', () => { } }); +test('html elements with paths', () => { + let ast = parse(` + + + + + `); + + let [, section] = ast.body; + locEqual(section, 2, 4, 5, 10, 'Foo element'); + if (assertNodeType(section, 'ElementNode')) { + locEqual(section.startTag, 2, 4, 2, 18, 'Foo start tag'); + locEqual(section.endTag, 5, 4, 5, 10, 'Foo end tag'); + let [, barSelfClosed] = section.children; + if (assertNodeType(barSelfClosed, 'ElementNode')) { + locEqual(barSelfClosed.parts[0], 3, 7, 3, 10, 'bar.x.y bar part'); + locEqual(barSelfClosed.parts[1], 3, 11, 3, 12, 'bar.x.y x part'); + locEqual(barSelfClosed.parts[2], 3, 13, 3, 14, 'bar.x.y y part'); + } + } +}); + test('html elements with nested blocks', (assert) => { let ast = parse(`
diff --git a/packages/@glimmer/syntax/test/parser-node-test.ts b/packages/@glimmer/syntax/test/parser-node-test.ts index a55be7f066..940111441f 100644 --- a/packages/@glimmer/syntax/test/parser-node-test.ts +++ b/packages/@glimmer/syntax/test/parser-node-test.ts @@ -891,7 +891,22 @@ export function element(tag: TagDescriptor, ...options: ElementParts[]): ASTv1.E return { type: 'ElementNode', - tag: tag || '', + tag: tag, + nameNode: { + type: 'ElementNameNode', + value: tag, + } as ASTv1.ElementNameNode, + startTag: { + type: 'ElementStartNode', + value: tag, + } as ASTv1.ElementStartNode, + endTag: { + type: 'ElementEndNode', + value: selfClosing ? '' : tag, + } as ASTv1.ElementEndNode, + parts: tag + .split('.') + .map((t) => ({ type: 'ElementPartNode', value: t }) as ASTv1.ElementPartNode), selfClosing: selfClosing, attributes: attrs || [], blockParams: blockParams || [], From c7682c5a2fce315d1a1ffe4954c28d4e5a81437a Mon Sep 17 00:00:00 2001 From: patrickpircher Date: Fri, 26 Jan 2024 10:54:14 +0100 Subject: [PATCH 2/2] add ast test --- .../lib/parser/tokenizer-event-handlers.ts | 9 +++++++++ packages/@glimmer/syntax/test/loc-node-test.ts | 17 +++++++++-------- .../@glimmer/syntax/test/parser-node-test.ts | 11 +++++++++++ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts b/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts index 0677ea31b5..6b0e39598a 100644 --- a/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts +++ b/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts @@ -140,6 +140,15 @@ export class TokenizerEventHandlers extends HandlebarsNodeVisitors { value: name, loc: loc, }; + element.nameNode = { + type: 'ElementNameNode', + value: name, + loc: loc + .withStart(this.source.offsetFor(loc.startPosition.line, loc.startPosition.column + 1)) + .withEnd( + this.source.offsetFor(loc.startPosition.line, loc.startPosition.column + 1 + name.length) + ), + }; parseElementPartLocs(this.source, element); this.elementStack.push(element); } diff --git a/packages/@glimmer/syntax/test/loc-node-test.ts b/packages/@glimmer/syntax/test/loc-node-test.ts index dc3fbcca19..62304b5aea 100644 --- a/packages/@glimmer/syntax/test/loc-node-test.ts +++ b/packages/@glimmer/syntax/test/loc-node-test.ts @@ -137,17 +137,18 @@ test('html elements', () => { test('html elements with paths', () => { let ast = parse(` - - + + `); - let [, section] = ast.body; - locEqual(section, 2, 4, 5, 10, 'Foo element'); - if (assertNodeType(section, 'ElementNode')) { - locEqual(section.startTag, 2, 4, 2, 18, 'Foo start tag'); - locEqual(section.endTag, 5, 4, 5, 10, 'Foo end tag'); - let [, barSelfClosed] = section.children; + let [, foo] = ast.body; + locEqual(foo, 2, 4, 5, 10, 'Foo element'); + if (assertNodeType(foo, 'ElementNode')) { + locEqual(foo.startTag, 2, 4, 2, 18, 'Foo start tag'); + locEqual(foo.nameNode, 2, 5, 2, 8, 'Foo name node'); + locEqual(foo.endTag, 5, 4, 5, 10, 'Foo end tag'); + let [, barSelfClosed] = foo.children; if (assertNodeType(barSelfClosed, 'ElementNode')) { locEqual(barSelfClosed.parts[0], 3, 7, 3, 10, 'bar.x.y bar part'); locEqual(barSelfClosed.parts[1], 3, 11, 3, 12, 'bar.x.y x part'); diff --git a/packages/@glimmer/syntax/test/parser-node-test.ts b/packages/@glimmer/syntax/test/parser-node-test.ts index 940111441f..ffcd37e497 100644 --- a/packages/@glimmer/syntax/test/parser-node-test.ts +++ b/packages/@glimmer/syntax/test/parser-node-test.ts @@ -266,6 +266,17 @@ test('Element modifiers', () => { ); }); +test('Element paths', (assert) => { + let t = ""; + const elem = element('bar.x.y', ['attrs', ['class', 'bar']]); + astEqual(t, b.program([elem])); + assert.strictEqual(elem.parts.length, 3); + assert.deepEqual( + elem.parts.map((p) => p.value), + ['bar', 'x', 'y'] + ); +}); + test('Tokenizer: MustacheStatement encountered in beforeAttributeName state', () => { let t = ''; astEqual(t, b.program([element('input', ['modifiers', 'bar'])]));