Skip to content
This repository was archived by the owner on Jun 20, 2024. It is now read-only.

Commit 10661d6

Browse files
committed
Allow for nested HTML raw tags
Fixes #156 (svg inside svg, script inside script, etc.)
1 parent 40c403e commit 10661d6

File tree

7 files changed

+56
-10
lines changed

7 files changed

+56
-10
lines changed

grammar/liquid-html.ohm

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -376,10 +376,11 @@ LiquidHTML <: Liquid {
376376
| HtmlRawTagImpl<"svg">
377377

378378
HtmlRawTagImpl<name> =
379-
#("<" name) AttrList ">" #(anyExceptStar<endTag<name>> endTag<name>)
380-
381-
endTag<name> =
382-
"</" name space* ">"
379+
TagStart<name>
380+
(HtmlRawTagImpl<name> | AnyExceptPlus<(TagStart<name> | TagEnd<name>)>)*
381+
TagEnd<name>
382+
TagStart<name> = "<" name AttrList ">"
383+
TagEnd<name> = "</" name ">"
383384

384385
HtmlVoidElement =
385386
#("<" voidElementName &(space | "/" | ">")) AttrList "/"? ">"

src/parser/grammar.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ describe('Unit: liquidHtmlGrammar', () => {
4848
[[/ isRefined ]]
4949
/>
5050
`).to.be.true;
51+
expectMatchSucceeded(`
52+
<svg>
53+
<svg a=1><svg b=2>
54+
<path d="M12"></path>
55+
</svg></svg>
56+
</svg>
57+
`).to.be.true;
5158
expectMatchSucceeded(`<div data-popup-{{ section.id }}="size-{{ section.id }}">`).to.be.true;
5259
expectMatchSucceeded('<img {% if aboveFold %} loading="lazy"{% endif %} />').to.be.true;
5360
expectMatchSucceeded('<svg><use></svg>').to.be.true;

src/parser/stage-1-cst.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,24 @@ describe('Unit: toLiquidHtmlCST(text)', () => {
101101
expectPath(cst, '1.body').to.eql('\n#id {}\n');
102102
});
103103

104+
it('should parse nested svg tags as a dump', () => {
105+
const parts = ['<svg disabled a=1>', '<svg><path d=1></svg>', '</svg>'];
106+
cst = toLiquidHtmlCST(parts.join(''));
107+
expectPath(cst, '0.type').to.eql('HtmlRawTag');
108+
expectPath(cst, '0.name').to.eql('svg');
109+
expectPath(cst, '0.body').to.eql('<svg><path d=1></svg>');
110+
expectPath(cst, '0.attrList.0.name.0.type').to.eql('TextNode');
111+
expectPath(cst, '0.attrList.0.name.0.value').to.eql('disabled');
112+
expectPath(cst, '0.locStart').to.eql(0);
113+
expectPath(cst, '0.locEnd').to.eql(parts[0].length + parts[1].length + parts[2].length);
114+
expectPath(cst, '0.blockStartLocStart').to.eql(0);
115+
expectPath(cst, '0.blockStartLocEnd').to.eql(parts[0].length);
116+
expectPath(cst, '0.blockEndLocStart').to.eql(parts[0].length + parts[1].length);
117+
expectPath(cst, '0.blockEndLocEnd').to.eql(
118+
parts[0].length + parts[1].length + parts[2].length,
119+
);
120+
});
121+
104122
it('should properly return block{Start,End}Loc{Start,End} locations of raw tags', () => {
105123
const source = '<script>const a = {{ product | json }}</script>';
106124
cst = toLiquidHtmlCST(source);

src/parser/stage-1-cst.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,16 +1056,20 @@ export function toLiquidHtmlCST(source: string): LiquidHtmlCST {
10561056

10571057
HtmlRawTagImpl: {
10581058
type: ConcreteNodeTypes.HtmlRawTag,
1059-
name: 1,
1060-
attrList: 2,
1061-
body: 4,
1059+
name: (tokens: Node[]) => tokens[0].children[1].sourceString,
1060+
attrList(tokens: Node[]) {
1061+
const mappings = (this as any).args.mapping;
1062+
return tokens[0].children[2].toAST(mappings);
1063+
},
1064+
body: (tokens: Node[]) =>
1065+
source.slice(tokens[0].source.endIdx, tokens[2].source.startIdx),
10621066
locStart,
10631067
locEnd,
10641068
source,
10651069
blockStartLocStart: (tokens: any) => tokens[0].source.startIdx,
1066-
blockStartLocEnd: (tokens: any) => tokens[3].source.endIdx,
1067-
blockEndLocStart: (tokens: any) => tokens[5].source.startIdx,
1068-
blockEndLocEnd: (tokens: any) => tokens[5].source.endIdx,
1070+
blockStartLocEnd: (tokens: any) => tokens[0].source.endIdx,
1071+
blockEndLocStart: (tokens: any) => tokens[2].source.startIdx,
1072+
blockEndLocEnd: (tokens: any) => tokens[2].source.endIdx,
10691073
},
10701074

10711075
HtmlVoidElement: {

test/issue-156/fixed.liquid

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<svg
2+
class="icon-svg icon-svg--size-24 notice__icon"
3+
aria-hidden="true"
4+
focusable="false"
5+
>
6+
<svg id="error"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 24C5.373 24 0 18.627 0 12S5.373 0 12 0s12 5.373 12 12-5.373 12-12 12zm0-2c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zm0-16c.552 0 1 .448 1 1v5c0 .552-.448 1-1 1s-1-.448-1-1V7c0-.552.448-1 1-1zm-1.5 10.5c0-.828.666-1.5 1.5-1.5.828 0 1.5.666 1.5 1.5 0 .828-.666 1.5-1.5 1.5-.828 0-1.5-.666-1.5-1.5z"></path></svg></svg>
7+
</svg>

test/issue-156/index.liquid

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<svg class="icon-svg icon-svg--size-24 notice__icon" aria-hidden="true" focusable="false">
2+
<svg id="error"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 24C5.373 24 0 18.627 0 12S5.373 0 12 0s12 5.373 12 12-5.373 12-12 12zm0-2c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zm0-16c.552 0 1 .448 1 1v5c0 .552-.448 1-1 1s-1-.448-1-1V7c0-.552.448-1 1-1zm-1.5 10.5c0-.828.666-1.5 1.5-1.5.828 0 1.5.666 1.5 1.5 0 .828-.666 1.5-1.5 1.5-.828 0-1.5-.666-1.5-1.5z"></path></svg></svg>
3+
</svg>

test/issue-156/index.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { assertFormattedEqualsFixed } from '../test-helpers';
2+
import * as path from 'path';
3+
4+
describe(`Unit: ${path.basename(__dirname)}`, () => {
5+
assertFormattedEqualsFixed(__dirname);
6+
});

0 commit comments

Comments
 (0)