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
8 changes: 1 addition & 7 deletions grammars/Handlebars.json
Original file line number Diff line number Diff line change
Expand Up @@ -693,19 +693,13 @@
]
},
"inline_script": {
"begin": "(?:^\\s+)?(<)((?i:script))\\b(?:.*(type)=([\"'](?:text/x-handlebars-template|text/x-handlebars|text/template|x-tmpl-handlebars)[\"']))(?![^>]*/>)",
"begin": "(?:^\\s+)?(<)((?i:script))\\b(?=[^>]*\\stype=[\"'](?:text/x-handlebars-template|text/x-handlebars|text/template|x-tmpl-handlebars)[\"'])(?![^>]*/>)",
"beginCaptures": {
"1": {
"name": "punctuation.definition.tag.html"
},
"2": {
"name": "entity.name.tag.script.html"
},
"3": {
"name": "entity.other.attribute-name.html"
},
"4": {
"name": "string.quoted.double.html"
}
},
"end": "(?<=</(script|SCRIPT))(>)(?:\\s*\\n)?",
Expand Down
4 changes: 1 addition & 3 deletions grammars/Handlebars.sublime-syntax
Original file line number Diff line number Diff line change
Expand Up @@ -336,12 +336,10 @@ contexts:
- match: <
scope: invalid.illegal.bad-angle-bracket.html
inline_script:
- match: '(?:^\s+)?(<)((?i:script))\b(?:.*(type)=(["''](?:text/x-handlebars-template|text/x-handlebars|text/template|x-tmpl-handlebars)["'']))(?![^>]*/>)'
- match: '(?:^\s+)?(<)((?i:script))\b(?=[^>]*\stype=(["''](?:text/x-handlebars-template|text/x-handlebars|text/template|x-tmpl-handlebars)["'']))(?![^>]*/>)'
captures:
1: punctuation.definition.tag.html
2: entity.name.tag.script.html
3: entity.other.attribute-name.html
4: string.quoted.double.html
push:
- meta_scope: source.handlebars.embedded.html
- match: (?<=</(script|SCRIPT))(>)(?:\s*\n)?
Expand Down
12 changes: 1 addition & 11 deletions grammars/Handlebars.tmLanguage
Original file line number Diff line number Diff line change
Expand Up @@ -928,7 +928,7 @@
<key>inline_script</key>
<dict>
<key>begin</key>
<string>(?:^\s+)?(&lt;)((?i:script))\b(?:.*(type)=(["'](?:text/x-handlebars-template|text/x-handlebars|text/template|x-tmpl-handlebars)["']))(?![^&gt;]*/&gt;)</string>
<string>(?:^\s+)?(&lt;)((?i:script))\b(?=[^&gt;]*\stype=(["'](?:text/x-handlebars-template|text/x-handlebars|text/template|x-tmpl-handlebars)["']))(?![^&gt;]*/&gt;)</string>
<key>beginCaptures</key>
<dict>
<key>1</key>
Expand All @@ -941,16 +941,6 @@
<key>name</key>
<string>entity.name.tag.script.html</string>
</dict>
<key>3</key>
<dict>
<key>name</key>
<string>entity.other.attribute-name.html</string>
</dict>
<key>4</key>
<dict>
<key>name</key>
<string>string.quoted.double.html</string>
</dict>
</dict>
<key>end</key>
<string>(?&lt;=&lt;/(script|SCRIPT))(&gt;)(?:\s*\n)?</string>
Expand Down
30 changes: 30 additions & 0 deletions test/embedding.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,36 @@ test('inline <script> template embeds Handlebars', async () => {
await assertScope(src, 'title', 'variable.parameter.handlebars');
});

// Regression for #58: an `id` (or any attribute) appearing BEFORE `type` in the
// opening tag must still be highlighted. The old grammar consumed everything up
// to `type=` with a greedy `.*`, swallowing the leading attributes into one
// unscoped token. The fix asserts the handlebars `type` via a lookahead and lets
// the #tag-stuff rules highlight every attribute regardless of order.
test('#58: id attribute before type is still highlighted', async () => {
const src = '<script id="t" type="text/x-handlebars-template"></script>';
await assertScope(src, 'id', 'entity.other.attribute-name.id.html');
await assertScope(src, 't', 'string.quoted.double.handlebars');
// The script body is still recognised as an embedded handlebars template.
await assertScope(src, 'script', 'entity.name.tag.script.html');
});

test('#58: id attribute after type is still highlighted (no regression)', async () => {
const src = '<script type="text/x-handlebars-template" id="t"></script>';
await assertScope(src, 'id', 'entity.other.attribute-name.id.html');
await assertScope(src, 't', 'string.quoted.double.handlebars');
});

test('#58: a plain text/javascript script is NOT a handlebars template', async () => {
// The lookahead must not fire for a non-handlebars type; the body should fall
// through to source.js, not the handlebars embedding.
const src = '<script id="x" type="text/javascript">';
const idTok = (await tokenizeLines(src)).flat().find((t) => t.text === 'id');
assert.ok(
!idTok.scopes.includes('source.handlebars.embedded.html'),
`plain JS script should not be a handlebars embedding: ${JSON.stringify(idTok.scopes)}`
);
});

test('plain HTML outside any script tag is NOT treated as embedded', async () => {
const src = '<p>{{body}}</p>';
const tok = (await tokenizeLines(src)).flat().find((t) => t.text === 'p');
Expand Down
Loading