From 5c9a3f63fa4b94a8460d670dc3396747c29e695e Mon Sep 17 00:00:00 2001 From: Marine Dunstetter Date: Wed, 20 Nov 2024 12:26:25 +0100 Subject: [PATCH 1/3] Initiate a possible approach to continue parsing when a GlimmerSyntaxError is found --- packages/@glimmer/syntax/lib/parser.ts | 5 ++++- .../syntax/lib/parser/handlebars-node-visitors.ts | 10 +++++++++- .../syntax/lib/parser/tokenizer-event-handlers.ts | 3 ++- packages/@glimmer/syntax/lib/v1/handlebars-ast.ts | 2 +- packages/@glimmer/syntax/test/parser-node-test.ts | 9 +++++++++ 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/@glimmer/syntax/lib/parser.ts b/packages/@glimmer/syntax/lib/parser.ts index efbc7c7ee2..8f4a2e4821 100644 --- a/packages/@glimmer/syntax/lib/parser.ts +++ b/packages/@glimmer/syntax/lib/parser.ts @@ -58,15 +58,18 @@ export abstract class Parser { > > = null; public tokenizer: EventedTokenizer; + protected continueOnError: boolean; constructor( source: src.Source, entityParser = new EntityParser(namedCharRefs), - mode: 'precompile' | 'codemod' = 'precompile' + mode: 'precompile' | 'codemod' = 'precompile', + continueOnError = false, ) { this.source = source; this.lines = source.source.split(/\r\n?|\n/u); this.tokenizer = new EventedTokenizer(this, entityParser, mode); + this.continueOnError = continueOnError; } offset(): src.SourceOffset { diff --git a/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts b/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts index 8b3831761d..183326f39d 100644 --- a/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts +++ b/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts @@ -355,7 +355,15 @@ export abstract class HandlebarsNodeVisitors extends Parser { return comment; } - PartialStatement(partial: HBS.PartialStatement): never { + PartialStatement(partial: HBS.PartialStatement): unknown { // Error node type + if(this.continueOnError) { + // return b.error({ + // loc: partial.loc, + // error: `Handlebars partials are not supported` + // }) + return null; + } + throw generateSyntaxError( `Handlebars partials are not supported`, this.source.spanFor(partial.loc) diff --git a/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts b/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts index a0432acb8d..ee516256d2 100644 --- a/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts +++ b/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts @@ -716,6 +716,7 @@ export interface PreprocessOptions { escaping/unescaping of HTML entity codes. */ mode?: 'codemod' | 'precompile' | undefined; + continueOnError?: boolean | undefined; } export interface Syntax { @@ -786,7 +787,7 @@ export function preprocess( end: offsets.endPosition, }; - let template = new TokenizerEventHandlers(source, entityParser, mode).parse( + let template = new TokenizerEventHandlers(source, entityParser, mode, options.continueOnError).parse( ast, options.locals ?? [] ); diff --git a/packages/@glimmer/syntax/lib/v1/handlebars-ast.ts b/packages/@glimmer/syntax/lib/v1/handlebars-ast.ts index 371503f7b9..352bc27f77 100644 --- a/packages/@glimmer/syntax/lib/v1/handlebars-ast.ts +++ b/packages/@glimmer/syntax/lib/v1/handlebars-ast.ts @@ -18,7 +18,7 @@ export interface NodeMap { Decorator: { input: Decorator; output: never }; BlockStatement: { input: BlockStatement; output: ASTv1.BlockStatement | void }; DecoratorBlock: { input: DecoratorBlock; output: never }; - PartialStatement: { input: PartialStatement; output: never }; + PartialStatement: { input: PartialStatement; output: unknown }; PartialBlockStatement: { input: PartialBlockStatement; output: never }; ContentStatement: { input: ContentStatement; output: void }; CommentStatement: { input: CommentStatement; output: ASTv1.MustacheCommentStatement | null }; diff --git a/packages/@glimmer/syntax/test/parser-node-test.ts b/packages/@glimmer/syntax/test/parser-node-test.ts index db63f42616..69a48e966a 100644 --- a/packages/@glimmer/syntax/test/parser-node-test.ts +++ b/packages/@glimmer/syntax/test/parser-node-test.ts @@ -35,6 +35,15 @@ test('various html element paths', () => { } }); +test('continue parsing after an error', () => { + let t = '{{> face}}'; + astEqual(t, b.template([ + element('img', ['attrs', ['id', 'one']]), + // errorNode('error message'), + element('img', ['attrs', ['id', 'two']]) + ]), undefined, { continueOnError: true }); +}); + test('elements can have empty attributes', () => { let t = ''; astEqual(t, b.template([element('img', ['attrs', ['id', '']])])); From 8058e32c742775ed1a7261cfc888099da0b102f2 Mon Sep 17 00:00:00 2001 From: Marine Dunstetter Date: Thu, 21 Nov 2024 10:16:51 +0100 Subject: [PATCH 2/3] Additionally to continue parsing, add the ErrorNode in the result template --- .../lib/parser/handlebars-node-visitors.ts | 14 +++++++------- .../@glimmer/syntax/lib/v1/handlebars-ast.ts | 2 +- packages/@glimmer/syntax/lib/v1/nodes-v1.ts | 8 ++++++++ .../@glimmer/syntax/lib/v1/parser-builders.ts | 8 ++++++++ .../@glimmer/syntax/lib/v1/public-builders.ts | 8 ++++++++ .../@glimmer/syntax/test/parser-node-test.ts | 18 +++++++++--------- 6 files changed, 41 insertions(+), 17 deletions(-) diff --git a/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts b/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts index 183326f39d..92b0e8e7e5 100644 --- a/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts +++ b/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts @@ -355,15 +355,15 @@ export abstract class HandlebarsNodeVisitors extends Parser { return comment; } - PartialStatement(partial: HBS.PartialStatement): unknown { // Error node type + PartialStatement(partial: HBS.PartialStatement): ASTv1.ErrorNode { if(this.continueOnError) { - // return b.error({ - // loc: partial.loc, - // error: `Handlebars partials are not supported` - // }) - return null; + let error = b.error({ + loc: this.source.spanFor(partial.loc), + message: `Handlebars partials are not supported` + }); + appendChild(this.currentElement(), error); + return error; } - throw generateSyntaxError( `Handlebars partials are not supported`, this.source.spanFor(partial.loc) diff --git a/packages/@glimmer/syntax/lib/v1/handlebars-ast.ts b/packages/@glimmer/syntax/lib/v1/handlebars-ast.ts index 352bc27f77..ef5f923c41 100644 --- a/packages/@glimmer/syntax/lib/v1/handlebars-ast.ts +++ b/packages/@glimmer/syntax/lib/v1/handlebars-ast.ts @@ -18,7 +18,7 @@ export interface NodeMap { Decorator: { input: Decorator; output: never }; BlockStatement: { input: BlockStatement; output: ASTv1.BlockStatement | void }; DecoratorBlock: { input: DecoratorBlock; output: never }; - PartialStatement: { input: PartialStatement; output: unknown }; + PartialStatement: { input: PartialStatement; output: ASTv1.ErrorNode }; PartialBlockStatement: { input: PartialBlockStatement; output: never }; ContentStatement: { input: ContentStatement; output: void }; CommentStatement: { input: CommentStatement; output: ASTv1.MustacheCommentStatement | null }; diff --git a/packages/@glimmer/syntax/lib/v1/nodes-v1.ts b/packages/@glimmer/syntax/lib/v1/nodes-v1.ts index baf49864b9..e0ad70329f 100644 --- a/packages/@glimmer/syntax/lib/v1/nodes-v1.ts +++ b/packages/@glimmer/syntax/lib/v1/nodes-v1.ts @@ -135,6 +135,7 @@ export type StatementName = | 'BlockStatement' | 'MustacheCommentStatement' | 'TextNode' + | 'ErrorNode' | 'ElementNode'; export interface AttrNode extends BaseNode { @@ -288,6 +289,11 @@ export interface HashPair extends BaseNode { value: Expression; } +export interface ErrorNode extends BaseNode { + type: 'ErrorNode'; + message: string; +} + export interface StripFlags { open: boolean; close: boolean; @@ -318,6 +324,8 @@ export type Nodes = { Hash: Hash; HashPair: HashPair; + + ErrorNode: ErrorNode; }; export type NodeType = keyof Nodes; diff --git a/packages/@glimmer/syntax/lib/v1/parser-builders.ts b/packages/@glimmer/syntax/lib/v1/parser-builders.ts index be683407c3..4ea2ecbe45 100644 --- a/packages/@glimmer/syntax/lib/v1/parser-builders.ts +++ b/packages/@glimmer/syntax/lib/v1/parser-builders.ts @@ -434,6 +434,14 @@ class Builders { }): T { return buildLegacyLiteral({ type, value, loc }); } + + error({ message, loc }: { message: string; loc: SourceSpan }): ASTv1.ErrorNode { + return { + type: 'ErrorNode', + message, + loc, + }; + } } const b = new Builders(); diff --git a/packages/@glimmer/syntax/lib/v1/public-builders.ts b/packages/@glimmer/syntax/lib/v1/public-builders.ts index fee841de63..35507426c3 100644 --- a/packages/@glimmer/syntax/lib/v1/public-builders.ts +++ b/packages/@glimmer/syntax/lib/v1/public-builders.ts @@ -483,6 +483,13 @@ function buildLoc( } } +function buildError(message = '', loc?: SourceLocation): ASTv1.ErrorNode { + return b.error({ + message, + loc: buildLoc(loc || null), + }); +} + export default { mustache: buildMustache, block: buildBlock, @@ -503,6 +510,7 @@ export default { template: buildTemplate, loc: buildLoc, pos: buildPosition, + error: buildError, path: buildPath, diff --git a/packages/@glimmer/syntax/test/parser-node-test.ts b/packages/@glimmer/syntax/test/parser-node-test.ts index 69a48e966a..68d8424451 100644 --- a/packages/@glimmer/syntax/test/parser-node-test.ts +++ b/packages/@glimmer/syntax/test/parser-node-test.ts @@ -35,15 +35,6 @@ test('various html element paths', () => { } }); -test('continue parsing after an error', () => { - let t = '{{> face}}'; - astEqual(t, b.template([ - element('img', ['attrs', ['id', 'one']]), - // errorNode('error message'), - element('img', ['attrs', ['id', 'two']]) - ]), undefined, { continueOnError: true }); -}); - test('elements can have empty attributes', () => { let t = ''; astEqual(t, b.template([element('img', ['attrs', ['id', '']])])); @@ -909,6 +900,15 @@ test('Handlebars partial should error', (assert) => { ); }); +test('Continue on error - Handlebars partial should error', () => { + let t = '{{> foo}}'; + astEqual(t, b.template([ + element('img', ['attrs', ['id', 'one']]), + b.error('Handlebars partials are not supported'), + element('img', ['attrs', ['id', 'two']]) + ]), undefined, { continueOnError: true }); +}); + test('Handlebars partial block should error', (assert) => { assert.throws( () => { From 7dd110440893dfe52e9f067aea62a44ee2a88403 Mon Sep 17 00:00:00 2001 From: Marine Dunstetter Date: Fri, 29 Nov 2024 18:14:42 +0100 Subject: [PATCH 3/3] Generate error node for the other cases --- .../lib/parser/handlebars-node-visitors.ts | 51 +++++++++---------- .../@glimmer/syntax/lib/v1/handlebars-ast.ts | 6 +-- .../@glimmer/syntax/test/parser-node-test.ts | 21 ++++++++ 3 files changed, 48 insertions(+), 30 deletions(-) diff --git a/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts b/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts index 92b0e8e7e5..f72b763971 100644 --- a/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts +++ b/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts @@ -105,6 +105,15 @@ export abstract class HandlebarsNodeVisitors extends Parser { return node; } + triggerError(message: string, loc: SourceSpan): ASTv1.ErrorNode { + if(this.continueOnError) { + let error = b.error({ loc, message }); + appendChild(this.currentElement(), error); + return error; + } + throw generateSyntaxError(message, loc); + } + BlockStatement(block: HBS.BlockStatement): ASTv1.BlockStatement | void { if (this.tokenizer.state === 'comment') { this.appendToCommentData(this.sourceForNode(block)); @@ -356,39 +365,27 @@ export abstract class HandlebarsNodeVisitors extends Parser { } PartialStatement(partial: HBS.PartialStatement): ASTv1.ErrorNode { - if(this.continueOnError) { - let error = b.error({ - loc: this.source.spanFor(partial.loc), - message: `Handlebars partials are not supported` - }); - appendChild(this.currentElement(), error); - return error; - } - throw generateSyntaxError( - `Handlebars partials are not supported`, - this.source.spanFor(partial.loc) - ); + const loc = this.source.spanFor(partial.loc); + const message = `Handlebars partials are not supported`; + return this.triggerError(message, loc); } - PartialBlockStatement(partialBlock: HBS.PartialBlockStatement): never { - throw generateSyntaxError( - `Handlebars partial blocks are not supported`, - this.source.spanFor(partialBlock.loc) - ); + PartialBlockStatement(partialBlock: HBS.PartialBlockStatement): ASTv1.ErrorNode { + const loc = this.source.spanFor(partialBlock.loc); + const message = `Handlebars partial blocks are not supported`; + return this.triggerError(message, loc); } - Decorator(decorator: HBS.Decorator): never { - throw generateSyntaxError( - `Handlebars decorators are not supported`, - this.source.spanFor(decorator.loc) - ); + Decorator(decorator: HBS.Decorator): ASTv1.ErrorNode { + const loc = this.source.spanFor(decorator.loc); + const message = `Handlebars decorators are not supported`; + return this.triggerError(message, loc); } - DecoratorBlock(decoratorBlock: HBS.DecoratorBlock): never { - throw generateSyntaxError( - `Handlebars decorator blocks are not supported`, - this.source.spanFor(decoratorBlock.loc) - ); + DecoratorBlock(decoratorBlock: HBS.DecoratorBlock): ASTv1.ErrorNode { + const loc = this.source.spanFor(decoratorBlock.loc); + const message = `Handlebars decorator blocks are not supported`; + return this.triggerError(message, loc); } SubExpression(sexpr: HBS.SubExpression): ASTv1.SubExpression { diff --git a/packages/@glimmer/syntax/lib/v1/handlebars-ast.ts b/packages/@glimmer/syntax/lib/v1/handlebars-ast.ts index ef5f923c41..7e523d711f 100644 --- a/packages/@glimmer/syntax/lib/v1/handlebars-ast.ts +++ b/packages/@glimmer/syntax/lib/v1/handlebars-ast.ts @@ -15,11 +15,11 @@ export interface CommonNode { export interface NodeMap { Program: { input: Program; output: ASTv1.Block }; MustacheStatement: { input: MustacheStatement; output: ASTv1.MustacheStatement | void }; - Decorator: { input: Decorator; output: never }; + Decorator: { input: Decorator; output: ASTv1.ErrorNode }; BlockStatement: { input: BlockStatement; output: ASTv1.BlockStatement | void }; - DecoratorBlock: { input: DecoratorBlock; output: never }; + DecoratorBlock: { input: DecoratorBlock; output: ASTv1.ErrorNode }; PartialStatement: { input: PartialStatement; output: ASTv1.ErrorNode }; - PartialBlockStatement: { input: PartialBlockStatement; output: never }; + PartialBlockStatement: { input: PartialBlockStatement; output: ASTv1.ErrorNode }; ContentStatement: { input: ContentStatement; output: void }; CommentStatement: { input: CommentStatement; output: ASTv1.MustacheCommentStatement | null }; SubExpression: { input: SubExpression; output: ASTv1.SubExpression }; diff --git a/packages/@glimmer/syntax/test/parser-node-test.ts b/packages/@glimmer/syntax/test/parser-node-test.ts index 68d8424451..b07233bde6 100644 --- a/packages/@glimmer/syntax/test/parser-node-test.ts +++ b/packages/@glimmer/syntax/test/parser-node-test.ts @@ -924,6 +924,13 @@ test('Handlebars partial block should error', (assert) => { ); }); +test('Continue on error - Handlebars partial block should error', () => { + let t = '{{#> foo}}{{/foo}}'; + astEqual(t, b.template([ + b.error('Handlebars partial blocks are not supported'), + ]), undefined, { continueOnError: true }); +}); + test('Handlebars decorator should error', (assert) => { assert.throws( () => { @@ -933,6 +940,13 @@ test('Handlebars decorator should error', (assert) => { ); }); +test('Continue on error - Handlebars decorator should error', () => { + let t = '{{* foo}}'; + astEqual(t, b.template([ + b.error('Handlebars decorators are not supported'), + ]), undefined, { continueOnError: true }); +}); + test('Handlebars decorator block should error', (assert) => { assert.throws( () => { @@ -948,6 +962,13 @@ test('Handlebars decorator block should error', (assert) => { ); }); +test('Continue on error - Handlebars decorator block should error', () => { + let t = '{{#* foo}}{{/foo}}'; + astEqual(t, b.template([ + b.error('Handlebars decorator blocks are not supported'), + ]), undefined, { continueOnError: true }); +}); + test('disallowed mustaches in the tagName space', (assert) => { assert.throws( () => {