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..92b0e8e7e5 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): 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) 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..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: never }; + 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 db63f42616..dcc918d190 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']]), + b.error('Handlebars partials are not supported'), + element('img', ['attrs', ['id', 'two']]) + ]), undefined, { continueOnError: true }); +}); + test('elements can have empty attributes', () => { let t = ''; astEqual(t, b.template([element('img', ['attrs', ['id', '']])]));