diff --git a/.changeset/htmljs-parser-5-18.md b/.changeset/htmljs-parser-5-18.md
new file mode 100644
index 00000000..34a7e6df
--- /dev/null
+++ b/.changeset/htmljs-parser-5-18.md
@@ -0,0 +1,6 @@
+---
+"@marko/language-tools": patch
+"@marko/language-server": patch
+---
+
+Require htmljs-parser 5.18.0, which ends a type at a trailing `void`, reads a TypeScript `!` after an operand as a non-null assertion, reads `delete` as an operator, and reads types in scriptlets, so templates using them are no longer misparsed.
diff --git a/.changeset/postfix-bang-type-void-highlighting.md b/.changeset/postfix-bang-type-void-highlighting.md
new file mode 100644
index 00000000..9b39e582
--- /dev/null
+++ b/.changeset/postfix-bang-type-void-highlighting.md
@@ -0,0 +1,5 @@
+---
+"marko-vscode": patch
+---
+
+Highlight attribute values the way the parser now reads them: a `!` directly after an operand (`x!`, `f()!`, `x!!`, `"s"!`) is a non-null assertion that ends the value, `void` in a tag variable's type no longer runs on into the next attribute, and `delete x` stays one value.
diff --git a/packages/language-server/package.json b/packages/language-server/package.json
index 0baee187..62cd6c59 100644
--- a/packages/language-server/package.json
+++ b/packages/language-server/package.json
@@ -47,7 +47,7 @@
"@marko/compiler": "^5.42.8",
"@marko/language-tools": "^2.7.0",
"axe-core": "^4.12.1",
- "htmljs-parser": "^5.17.1",
+ "htmljs-parser": "^5.18.0",
"jsdom": "^29.1.1",
"marko": "^5.39.43",
"prettier": "^3.8.4",
diff --git a/packages/language-tools/package.json b/packages/language-tools/package.json
index a6132ac5..2e8f2040 100644
--- a/packages/language-tools/package.json
+++ b/packages/language-tools/package.json
@@ -39,7 +39,7 @@
"dependencies": {
"@luxass/strip-json-comments": "^1.4.0",
"@marko/compiler": "^5.42.8",
- "htmljs-parser": "^5.17.1",
+ "htmljs-parser": "^5.18.0",
"relative-import-path": "^1.0.1"
},
"devDependencies": {
diff --git a/packages/vscode/syntaxes/marko.tmLanguage.json b/packages/vscode/syntaxes/marko.tmLanguage.json
index c2c5df0c..549f697d 100644
--- a/packages/vscode/syntaxes/marko.tmLanguage.json
+++ b/packages/vscode/syntaxes/marko.tmLanguage.json
@@ -110,7 +110,7 @@
"name": "meta.marko-spread-attribute",
"contentName": "source.ts",
"begin": "(\\.\\.\\.)",
- "end": "(?=[,;\\]]|/>|(?<=[^=>])(?:(?|>(?!=))|(?+=-]=|[=>]>|[^.]\\.|[^-]-|[^+]\\+|[a-zA-Z0-9%).<\\]}]\\s/|[^.$\\w]await|[^.$\\w]async|[^.$\\w]class|[^.$\\w]function|[^.$\\w]keyof|[^.$\\w]new|[^.$\\w]readonly|[^.$\\w]infer|[^.$\\w]typeof|[^.$\\w]void)\\s+(?![\\n{(+!~*%&^|?:]|[<>/=-]=|[=>]>|\\.[^.]|-[^-]|/[^>]|(?:in|instanceof|satisfies|as|extends)\\s+[^:=/,;>]))",
+ "end": "(?=[,;\\]]|/>|(?<=[^=>])(?:(?|>(?!=))|(?+=-]=|[=>]>|[^.]\\.|[^-]-|[^+]\\+|[a-zA-Z0-9%).<\\]}]\\s/|[^.$\\w]await|[^.$\\w]async|[^.$\\w]class|[^.$\\w]function|[^.$\\w]keyof|[^.$\\w]new|[^.$\\w]readonly|[^.$\\w]infer|[^.$\\w]typeof|[^.$\\w]void|[^.$\\w]delete)\\s+(?![\\n{(+!~*%&^|?:]|[<>/=-]=|[=>]>|\\.[^.]|-[^-]|/[^>]|(?:in|instanceof|satisfies|as|extends)\\s+[^:=/,;>]))",
"patterns": [{ "include": "#javascript-expression" }],
"beginCaptures": { "1": { "name": "keyword.operator.spread.marko" } }
},
@@ -128,7 +128,7 @@
"name": "meta.embedded.ts",
"contentName": "source.ts",
"begin": "\\s*(:?=)\\s*",
- "end": "(?=[,;\\]]|/>|(?<=[^=>])(?:(?|>(?!=))|(?+=-]=|[=>]>|[^.]\\.|[^-]-|[^+]\\+|[a-zA-Z0-9%).<\\]}]\\s/|[^.$\\w]await|[^.$\\w]async|[^.$\\w]class|[^.$\\w]function|[^.$\\w]keyof|[^.$\\w]new|[^.$\\w]readonly|[^.$\\w]infer|[^.$\\w]typeof|[^.$\\w]void)\\s+(?![\\n{(+!~*%&^|?:]|[<>/=-]=|[=>]>|\\.[^.]|-[^-]|/[^>]|(?:in|instanceof|satisfies|as|extends)\\s+[^:=/,;>]))",
+ "end": "(?=[,;\\]]|/>|(?<=[^=>])(?:(?|>(?!=))|(?+=-]=|[=>]>|[^.]\\.|[^-]-|[^+]\\+|[a-zA-Z0-9%).<\\]}]\\s/|[^.$\\w]await|[^.$\\w]async|[^.$\\w]class|[^.$\\w]function|[^.$\\w]keyof|[^.$\\w]new|[^.$\\w]readonly|[^.$\\w]infer|[^.$\\w]typeof|[^.$\\w]void|[^.$\\w]delete)\\s+(?![\\n{(+!~*%&^|?:]|[<>/=-]=|[=>]>|\\.[^.]|-[^-]|/[^>]|(?:in|instanceof|satisfies|as|extends)\\s+[^:=/,;>]))",
"patterns": [{ "include": "#javascript-expression" }],
"beginCaptures": { "1": { "patterns": [{ "include": "source.ts" }] } }
},
@@ -643,7 +643,7 @@
{
"comment": "We must match the some expressions ourselves since otherwise the js grammar can overstep.",
"contentName": "source.ts",
- "match": "(?:\\s*\\b(?:as|await|extends|in|instanceof|satisfies|keyof|new|typeof|void))+\\s+(?![:=/,;>])[a-zA-Z0-9_$@#]*",
+ "match": "(?:\\s*\\b(?:as|await|delete|extends|in|instanceof|satisfies|keyof|new|typeof|void))+\\s+(?![:=/,;>])[a-zA-Z0-9_$@#]*",
"captures": { "0": { "patterns": [{ "include": "source.ts" }] } }
},
{
@@ -763,9 +763,16 @@
{
"comment": "Match type",
"begin": "\\s*(:)(?!=)",
- "end": "(?=[,;\\](]|/>|(?<=[^=>])(?:(?|>(?!=))|(?+=-]=|[=>]>|[^.]\\.|[^-]-|[^+]\\+|[a-zA-Z0-9%).<\\]}]\\s/|[^.$\\w]await|[^.$\\w]async|[^.$\\w]class|[^.$\\w]function|[^.$\\w]keyof|[^.$\\w]new|[^.$\\w]readonly|[^.$\\w]infer|[^.$\\w]typeof|[^.$\\w]void)\\s+(?![\\n{+!~*%&^|?:]|[<>/=-]=|[=>]>|\\.[^.]|-[^-]|/[^>]|(?:in|instanceof|satisfies|as|extends)\\s+[^:=/,;>]))",
+ "end": "(?=[,;\\](]|/>|(?<=[^=>])(?:(?|>(?!=))|(?+=-]=|[=>]>|[^.]\\.|[^-]-|[^+]\\+|[a-zA-Z0-9%).<\\]}]\\s/|[^.$\\w]await|[^.$\\w]async|[^.$\\w]class|[^.$\\w]function|[^.$\\w]keyof|[^.$\\w]new|[^.$\\w]readonly|[^.$\\w]infer|[^.$\\w]typeof)\\s+(?![\\n{+!~*%&^|?:]|[<>/=-]=|[=>]>|\\.[^.]|-[^-]|/[^>]|(?:in|instanceof|satisfies|as|extends)\\s+[^:=/,;>]))",
"patterns": [
{ "include": "source.ts#type" },
+ {
+ "comment": "In a type `void` is the void type, so unlike the operator it takes no operand",
+ "match": "\\s*\\b(void)\\b",
+ "captures": {
+ "1": { "name": "support.type.primitive.ts" }
+ }
+ },
{ "include": "#javascript-expression" }
],
"beginCaptures": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 26a97327..33447c2a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -91,8 +91,8 @@ importers:
specifier: ^4.12.1
version: 4.12.1
htmljs-parser:
- specifier: ^5.17.1
- version: 5.17.1
+ specifier: ^5.18.0
+ version: 5.18.0
jsdom:
specifier: ^29.1.1
version: 29.1.1(patch_hash=63260bcabe348afac2b14cafcd725106f132adbad0fab0328f16e5a9c6a1a1e4)
@@ -140,8 +140,8 @@ importers:
specifier: ^5.42.8
version: 5.42.8
htmljs-parser:
- specifier: ^5.17.1
- version: 5.17.1
+ specifier: ^5.18.0
+ version: 5.18.0
relative-import-path:
specifier: ^1.0.1
version: 1.0.1
@@ -1715,8 +1715,8 @@ packages:
resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
- htmljs-parser@5.17.1:
- resolution: {integrity: sha512-ww3wkVhxedYGoqZIFbA5tQfgyDvdqv2rIOkMsdu4fez2XOAcOgR+3ZLKbpFwhfaOWYAmSpj8JFrjOqRcjAg+Sg==}
+ htmljs-parser@5.18.0:
+ resolution: {integrity: sha512-ANKBAi2UyiQ7K/In2a3jZLt/WLAIKl8GFE3k5KIYEaroUdOBtt/gnclkJcSMI+kPChWdGOw3YHnbequVNy39Tg==}
htmlparser2@10.1.0:
resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==}
@@ -3441,7 +3441,7 @@ snapshots:
dependencies:
'@luxass/strip-json-comments': 2.0.2
complain: 1.6.1
- htmljs-parser: 5.17.1
+ htmljs-parser: 5.18.0
jsesc: 3.1.0
kleur: 4.1.5
lasso-package-root: 1.0.1
@@ -4603,7 +4603,7 @@ snapshots:
transitivePeerDependencies:
- '@noble/hashes'
- htmljs-parser@5.17.1: {}
+ htmljs-parser@5.18.0: {}
htmlparser2@10.1.0:
dependencies:
@@ -5237,7 +5237,7 @@ snapshots:
prettier-plugin-marko@4.1.3:
dependencies:
- htmljs-parser: 5.17.1
+ htmljs-parser: 5.18.0
prettier-plugin-packagejson@3.0.2(prettier@3.8.4):
dependencies: