You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I ran into this myself, it is very annoying. And judging by the inactivity on this repo, seems likely to never be fixed.
I worked with claude to figure it out and some workarounds as well as the supposed root cause.
Workarounds
For just the type checking
First of all, if you just need the type checking, in VSCode that actually seems to be separate from the syntax highlighting, so you can get that by putting this at the very top of your HTML, like just below the <html> tag.
<script>// @ts-check</script>
It needs to be basically before any calls to the javascript from the HTML to work which is why it might not work if you simply put //@ts-check at the top of your main <script> block.
However, that still won't do any syntax highlighting of the top level JSDoc.
For syntax highlighting (tiny custom extension)
For top level syntax highlighting (this issue thread) I had the AI make a super simple VSCode extension.
It's literally 2 json files, not even any actual executable code.
Make a folder for the extension in %APPDATA%\.vscode\extensions\ . I called it ThioJoe.html-jsdoc-highlighter-1.0.0 for example.
Make two files in this folder structure: package.json (top level within the folder) html-jsdoc.json within a syntaxes folder. So syntaxes\html-jsdoc.json
Use the command palette (Ctrl + Shift + P), search for "Developer: Install Extension from Location" and select the folder.
Expand to see file contents
package.json
{
"name": "html-jsdoc-highlighter",
"displayName": "HTML JSDoc Syntax Highlighting",
"description": "Fixes JSDoc syntax highlighting for inline JS in <script> tags of HTML files. Pair with an added <script>// @ts-check</script> at the top of the page (such as just within <html> tag) for native type-checking.",
"version": "1.0.0",
"publisher": "ThioJoe",
"engines": {
"vscode": "^1.80.0"
},
"categories": [
"Programming Languages"
],
"contributes": {
"grammars": [
{
"scopeName": "jsdoc.html.injection",
"path": "./syntaxes/html-jsdoc.json",
"injectTo": [
"text.html",
"text.html.basic",
"text.html.derivative"
]
}
]
}
}
Claude / Fable 5 wrote this, so I can't be 100% sure it's correct, but it had helped me do the workarounds above, so I assume it's at least on the right track if not fully correct.
Expand to see notes/report
Root cause + proposed fix: no JSDoc highlighting at the top level of <script> blocks
Suggested use: post as a comment on #109 (the open upstream issue),
or as the description of a PR against that repo. Do not open a new microsoft/vscode issue — microsoft/vscode#98575, #135417, and #144726 all report this and were closed as upstream
duplicates pointing at html.tmbundle#109.
Summary
JSDoc comments (/** ... */) inside an HTML <script> block lose all internal highlighting
(@param, {type}, parameter names) when the comment sits at the top nesting level of the
script. The same comment inside any braced construct (function body, class, plain block)
highlights correctly. Reproduced with all extensions disabled.
<script>/** * @param {string} x <-- flat gray, no JSDoc scopes */functiontopLevel(x){}functionouter(){/** * @param {string} y <-- fully highlighted */functioninner(y){}}</script>
Root cause
The script-content rule in the HTML grammar does not delegate directly to source.js.
It defines its own comment rules first, so that a </script> appearing inside a JS
comment still terminates the script block:
/\* also matches /**, and this local rule contains no doc-comment patterns, so any
block comment at the script's top level is consumed as one flat comment.block.js token.
The rule list is only active at the top nesting level: once the tokenizer enters any
begin/end construct from source.js (e.g. a function body), the JS grammar's own comment.block.documentation rule (with its #docblock patterns) handles comments —
which is why nested JSDoc works and top-level JSDoc does not.
Proposed fix
Insert a documentation-comment variant before the flat /\* rule, keeping the same </script> guard in the end pattern so the original safety behavior is unchanged:
(In the bundle itself this lives in Syntaxes/HTML.plist; the JSON above is the
equivalent for the generated html.tmLanguage.json used by editors such as VS Code.)
Notes:
(?!/) keeps /**/ (an empty block comment) on the existing flat rule, matching how source.js distinguishes the two.
source.js#docblock resolves in grammars derived from Microsoft's
TypeScript-TmLanguage (used by VS Code), which expose a docblock repository rule.
In environments whose JS grammar has no docblock rule, the include resolves to
nothing and the comment still degrades gracefully to the comment.block.documentation.js scope — strictly better than today.
Verification
Tokenized with vscode-textmate + vscode-oniguruma against the current html.tmLanguage.json, html-derivative.tmLanguage.json, and JavaScript.tmLanguage.json from microsoft/vscode main (identical to the 1.96 stable
grammars), root scope text.html.derivative, no injection grammars.
Before the patch, the whole line * @param {string} x at top level is a single token
scoped comment.block.js. After the patch:
Safety property preserved: with a literal </script> placed inside a top-level JSDoc
comment, the script block still terminates at that point and subsequent content
tokenizes as HTML — identical to the current behavior of the flat rule, since the
patched rule uses the same end expression.
Activity
ThioJoe commentedon Jul 19, 2026
I ran into this myself, it is very annoying. And judging by the inactivity on this repo, seems likely to never be fixed.
I worked with claude to figure it out and some workarounds as well as the supposed root cause.
Workarounds
For just the type checking
First of all, if you just need the type checking, in VSCode that actually seems to be separate from the syntax highlighting, so you can get that by putting this at the very top of your HTML, like just below the
<html>tag.It needs to be basically before any calls to the javascript from the HTML to work which is why it might not work if you simply put
//@ts-checkat the top of your main<script>block.However, that still won't do any syntax highlighting of the top level JSDoc.
For syntax highlighting (tiny custom extension)
For top level syntax highlighting (this issue thread) I had the AI make a super simple VSCode extension.
It's literally 2 json files, not even any actual executable code.
%APPDATA%\.vscode\extensions\. I called itThioJoe.html-jsdoc-highlighter-1.0.0for example.package.json(top level within the folder)html-jsdoc.jsonwithin asyntaxesfolder. Sosyntaxes\html-jsdoc.jsonExpand to see file contents
package.json
{ "name": "html-jsdoc-highlighter", "displayName": "HTML JSDoc Syntax Highlighting", "description": "Fixes JSDoc syntax highlighting for inline JS in <script> tags of HTML files. Pair with an added <script>// @ts-check</script> at the top of the page (such as just within <html> tag) for native type-checking.", "version": "1.0.0", "publisher": "ThioJoe", "engines": { "vscode": "^1.80.0" }, "categories": [ "Programming Languages" ], "contributes": { "grammars": [ { "scopeName": "jsdoc.html.injection", "path": "./syntaxes/html-jsdoc.json", "injectTo": [ "text.html", "text.html.basic", "text.html.derivative" ] } ] } }html-jsdoc.json
{ "scopeName": "jsdoc.html.injection", "injectionSelector": "L:text.html source.js", "patterns": [ { "begin": "/\\*\\*(?!/)", "end": "\\*/", "name": "comment.block.documentation.js", "patterns": [ { "match": "(\\@(?:param|property|prop))\\s+(?:\\{([^}]+)\\}\\s+)?(\\[?[a-zA-Z_$][\\w$]*\\]?)", "captures": { "1": { "name": "storage.type.class.jsdoc" }, "2": { "name": "entity.name.type.jsdoc" }, "3": { "name": "variable.other.jsdoc" } } }, { "match": "(\\@\\w+)\\b", "captures": { "1": { "name": "storage.type.class.jsdoc" } } }, { "match": "\\{([^}]+)\\}", "captures": { "1": { "name": "entity.name.type.jsdoc" } } } ] } ] }Supposed root cause report
Claude / Fable 5 wrote this, so I can't be 100% sure it's correct, but it had helped me do the workarounds above, so I assume it's at least on the right track if not fully correct.
Expand to see notes/report
Root cause + proposed fix: no JSDoc highlighting at the top level of
<script>blocksSummary
JSDoc comments (
/** ... */) inside an HTML<script>block lose all internal highlighting(
@param,{type}, parameter names) when the comment sits at the top nesting level of thescript. The same comment inside any braced construct (function body, class, plain block)
highlights correctly. Reproduced with all extensions disabled.
Root cause
The script-content rule in the HTML grammar does not delegate directly to
source.js.It defines its own comment rules first, so that a
</script>appearing inside a JScomment still terminates the script block:
/\*also matches/**, and this local rule contains no doc-comment patterns, so anyblock comment at the script's top level is consumed as one flat
comment.block.jstoken.The rule list is only active at the top nesting level: once the tokenizer enters any
begin/end construct from
source.js(e.g. a function body), the JS grammar's owncomment.block.documentationrule (with its#docblockpatterns) handles comments —which is why nested JSDoc works and top-level JSDoc does not.
Proposed fix
Insert a documentation-comment variant before the flat
/\*rule, keeping the same</script>guard in theendpattern so the original safety behavior is unchanged:{ "begin": "/\\*\\*(?!/)", "captures": { "0": { "name": "punctuation.definition.comment.js" } }, "end": "\\*/|(?=</script)", "name": "comment.block.documentation.js", "patterns": [ { "include": "source.js#docblock" } ] }(In the bundle itself this lives in
Syntaxes/HTML.plist; the JSON above is theequivalent for the generated
html.tmLanguage.jsonused by editors such as VS Code.)Notes:
(?!/)keeps/**/(an empty block comment) on the existing flat rule, matching howsource.jsdistinguishes the two.source.js#docblockresolves in grammars derived from Microsoft'sTypeScript-TmLanguage (used by VS Code), which expose a
docblockrepository rule.In environments whose JS grammar has no
docblockrule, the include resolves tonothing and the comment still degrades gracefully to the
comment.block.documentation.jsscope — strictly better than today.Verification
Tokenized with
vscode-textmate+vscode-onigurumaagainst the currenthtml.tmLanguage.json,html-derivative.tmLanguage.json, andJavaScript.tmLanguage.jsonfrom microsoft/vscodemain(identical to the 1.96 stablegrammars), root scope
text.html.derivative, no injection grammars.Before the patch, the whole line
* @param {string} xat top level is a single tokenscoped
comment.block.js. After the patch:Safety property preserved: with a literal
</script>placed inside a top-level JSDoccomment, the script block still terminates at that point and subsequent content
tokenizes as HTML — identical to the current behavior of the flat rule, since the
patched rule uses the same
endexpression.