Skip to content

JsDoc in HTML files syntax highlight #109

Open

Description

@alexr00

Originally from @Laser42 in microsoft/vscode#98575

  • VSCode Version: 1.45.1
  • OS Version: Windows 10 Pro x64

JsDoc comments highlights incorrectly when editing html files.

Steps to Reproduce:

  1. Create a HTML file
  2. Add <script></script> section as follows:
<script>

    /**
     * A function
     * @param {string} arg - string argument
     * @returns {null} - desc
     */
    function MyFunc(arg) {
        return null;
    }
    /**
     * A class
     */
    class MyClass {
        /**
         * Ctor
         * @param {string} field - desc
         */
        constructor(field) {
            this.field = field;
        }
    }

</script>
  1. Note that JsDoc syntax inside class has a highlight, while there is no syntax highlight for function's JsDoc:
    image

Does this issue occur when all extensions are disabled?: Yes

Activity

  1. ThioJoe commented on Jul 19, 2026

    @ThioJoe

    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.

    1. Make a folder for the extension in %APPDATA%\.vscode\extensions\ . I called it ThioJoe.html-jsdoc-highlighter-1.0.0 for example.
    2. 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
    3. 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"
            ]
          }
        ]
      }
    }

    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> 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
       */
      function topLevel(x) {}
    
      function outer() {
        /**
         * @param {string} y <-- fully highlighted
         */
        function inner(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:

    "patterns": [
      { "begin": "(^[ \\t]+)?(?=//)", ... },
      { "begin": "/\\*",
        "captures": { "0": { "name": "punctuation.definition.comment.js" } },
        "end": "\\*/|(?=</script)",
        "name": "comment.block.js" },
      { "include": "source.js" }
    ]

    /\* 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:

    {
      "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 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:

    " * "      -> comment.block.documentation.js
    "@"        -> punctuation.definition.block.tag.jsdoc
    "param"    -> storage.type.class.jsdoc
    "{"        -> punctuation.definition.bracket.curly.begin.jsdoc
    "string"   -> entity.name.type.instance.jsdoc
    "}"        -> punctuation.definition.bracket.curly.end.jsdoc
    "x"        -> variable.other.jsdoc
    

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions