Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/liquid-unclosed-block-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/liquid-html-parser': patch
---

Fix the end of an unclosed block in `{% liquid %}`. It ended past the end of the template, as if its end tag came after the last line. It now ends where the last line does, as an unclosed block outside `{% liquid %}` does.
29 changes: 14 additions & 15 deletions packages/liquid-html-parser/src/document/liquid-lines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,19 +153,15 @@ export function parseLineBlockTag(
reason = e instanceof Error ? e.message : 'unknown error';
}

const endTagLength = 3 + envelope.tagName.length; // 'end' + tagName

if (def.branches.length === 0) {
const { children, endNameOffset } = parseLineBlockBody(parser, envelope.tagName, ctx);
const endPosition: Position = { start: endNameOffset, end: endNameOffset + endTagLength };
const { children, endPosition } = parseLineBlockBody(parser, envelope.tagName, ctx);
if (markupParsed) {
return makeLiquidTagNamed(envelope, markup, children, endPosition, { start: '', end: '' });
}
return makeLiquidTagBaseCase(envelope, children, endPosition, { start: '', end: '' }, reason);
}

const { branches, endNameOffset } = parseLineBranchedBody(parser, envelope.tagName, def, ctx);
const endPosition: Position = { start: endNameOffset, end: endNameOffset + endTagLength };
const { branches, endPosition } = parseLineBranchedBody(parser, envelope.tagName, def, ctx);
if (markupParsed) {
return makeLiquidTagNamed(envelope, markup, branches, endPosition, { start: '', end: '' });
}
Expand Down Expand Up @@ -282,24 +278,26 @@ export function parseLineBlockBody(
parser: LineParserDelegate,
parentName: string,
ctx: LiquidLineContext,
): { children: LiquidStatement[]; endNameOffset: number } {
): { children: LiquidStatement[]; endPosition: Position } {
const children: LiquidStatement[] = [];

while (ctx.index < ctx.lines.length) {
const line = ctx.lines[ctx.index];

if (line.tagName === `end${parentName}`) {
ctx.index++;
return { children, endNameOffset: line.nameOffset };
const endPosition = { start: line.nameOffset, end: line.nameOffset + line.tagName.length };
return { children, endPosition };
}

ctx.index++;
children.push(parseLiquidStatement(parser, line.tagName, line.markup, line.markupOffset, ctx));
}

if (parser.lineAllowUnclosedDocumentNode) {
const endNameOffset = ctx.lines.length > 0 ? ctx.lines[ctx.lines.length - 1].lineEnd : 0;
return { children, endNameOffset };
// An unclosed block ends where its last line does, as at the top level.
const end = ctx.lines.length > 0 ? ctx.lines[ctx.lines.length - 1].lineEnd : 0;
return { children, endPosition: { start: end, end } };
}

throw new LiquidHTMLASTParsingError(
Expand All @@ -316,7 +314,7 @@ export function parseLineBranchedBody(
parentName: string,
def: TagDefinitionBlock,
ctx: LiquidLineContext,
): { branches: LiquidBranch[]; endNameOffset: number } {
): { branches: LiquidBranch[]; endPosition: Position } {
const branches: LiquidBranch[] = [];
const bodyStart = ctx.index > 0 ? ctx.lines[ctx.index - 1].lineEnd : 0;
let currentBranch: LiquidBranchUnnamed | LiquidBranchNamed = makeLiquidBranchUnnamed(
Expand All @@ -338,7 +336,8 @@ export function parseLineBranchedBody(
);
branches.push(currentBranch);
ctx.index++;
return { branches, endNameOffset: line.nameOffset };
const endPosition = { start: line.nameOffset, end: line.nameOffset + line.tagName.length };
return { branches, endPosition };
}

if (isBranchName(line.tagName) && def.branches.includes(line.tagName)) {
Expand Down Expand Up @@ -371,16 +370,16 @@ export function parseLineBranchedBody(
}

if (parser.lineAllowUnclosedDocumentNode) {
const endNameOffset = ctx.lines.length > 0 ? ctx.lines[ctx.lines.length - 1].lineEnd : 0;
const end = ctx.lines.length > 0 ? ctx.lines[ctx.lines.length - 1].lineEnd : 0;
finalizeBranch(
currentBranch,
currentChildren,
endNameOffset,
end,
parser.getSource(),
parser.lineParseHtml ? ChildFilterMode.StripEdges : ChildFilterMode.Syntactic,
);
branches.push(currentBranch);
return { branches, endNameOffset };
return { branches, endPosition: { start: end, end } };
}

throw new LiquidHTMLASTParsingError(
Expand Down
3 changes: 3 additions & 0 deletions packages/liquid-html-parser/src/document/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ describe('Unit: document-parser', () => {
// Verify inner structure is properly parsed (not base case fallback)
expectPath(ast, 'children.0.markup.0.name').to.eql('assign');
expectPath(ast, 'children.0.markup.1.name').to.eql('if');
// It ends where its last line does, since there's no endif.
expectPosition(ast, 'children.0.markup.1').toEqual('if use_variant\n assign var2 = var1');
expectPath(ast, 'children.0.markup.1.blockEndPosition').to.eql({ start: 74, end: 74 });
});

it('should parse raw Liquid tags normally (doc, comment, raw)', () => {
Expand Down
Loading