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..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));
@@ -355,32 +364,28 @@ export abstract class HandlebarsNodeVisitors extends Parser {
return comment;
}
- PartialStatement(partial: HBS.PartialStatement): never {
- throw generateSyntaxError(
- `Handlebars partials are not supported`,
- this.source.spanFor(partial.loc)
- );
+ PartialStatement(partial: HBS.PartialStatement): ASTv1.ErrorNode {
+ 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/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..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 };
- PartialStatement: { input: PartialStatement; output: never };
- PartialBlockStatement: { input: PartialBlockStatement; output: never };
+ DecoratorBlock: { input: DecoratorBlock; output: ASTv1.ErrorNode };
+ PartialStatement: { input: PartialStatement; output: ASTv1.ErrorNode };
+ 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/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..b07233bde6 100644
--- a/packages/@glimmer/syntax/test/parser-node-test.ts
+++ b/packages/@glimmer/syntax/test/parser-node-test.ts
@@ -900,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(
() => {
@@ -915,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(
() => {
@@ -924,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(
() => {
@@ -939,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(
() => {