🔎 Search terms
jsdoc, fenced code block, markdown, hover, quick info, @, tag, parser, ember, template, angular, vue, jsx
🕗 Version & regression information
Reproduced with current typescript@latest. Behavior appears to have existed for many years — likely since JSDoc was first recognized by the language service.
⏯ Context & motivation
We're building a design system in Ember.js and documenting components with JSDoc. Ember templates use @argName as the standard syntax for passing arguments to components:
<Input @placeholder="Hello" />
This is the canonical, idiomatic way to write Ember — every component invocation uses this syntax. The same pattern shows up in Angular (@Input() inside code examples), Vue template refs, JSX attribute shortcuts, TypeScript decorators, email addresses, and anywhere else an @ naturally appears inside a prose code example.
Because the JSDoc parser does not respect markdown fenced code block boundaries, any occurrence of @something inside a ``` code block hijacks the parser, turns into a phantom tag, and destroys the hover preview that users see.
Since VS Code's hover consumes what tsserver already split into { documentation, tags }, this cannot be fixed in VS Code — the description has already been cut in half before VS Code ever sees it. See microsoft/tsdoc#216 where the tsdoc maintainer correctly pointed out the fix has to live elsewhere.
💻 Code
/**
* A test function for JSDoc parser behavior.
*
* Usage:
* ```hbs
* <Input as |I|>
* <I.Label>Label</I.Label>
* <I.TextField @placeholder="Hello" />
* <I.HelpText>Optional help text goes here</I.HelpText>
* </Input>
* ```
*
* Description continues after the code block.
*
* @param name the name
* @returns a greeting
*/
export function greet(name: string): string {
return `Hello, ${name}`;
}
Probe script using the public API (symbol.getDocumentationComment + symbol.getJsDocTags, i.e. the same data that powers hover quick info):
import * as ts from "typescript";
import * as path from "path";
const filePath = path.resolve(__dirname, "index.ts");
const program = ts.createProgram([filePath], { declaration: true });
const checker = program.getTypeChecker();
const source = program.getSourceFile(filePath)!;
ts.forEachChild(source, (node) => {
if (ts.isFunctionDeclaration(node) && node.name?.text === "greet") {
const symbol = checker.getSymbolAtLocation(node.name)!;
const docs = symbol.getDocumentationComment(checker);
const tags = symbol.getJsDocTags(checker);
console.log("=== Description ===");
console.log(ts.displayPartsToString(docs));
console.log("\n=== Tags ===");
for (const t of tags) {
console.log(`@${t.name}:`, t.text ? ts.displayPartsToString(t.text) : "(no text)");
}
}
});
🙁 Actual behavior
=== Description ===
A test function for JSDoc parser behavior.
Usage:
```hbs
<Input as |I|>
<I.Label>Label</I.Label>
<I.TextField ← description CUT OFF mid-line
=== Tags ===
@placeholder: ="Hello" /> ← phantom tag invented from markup
<I.HelpText>Optional help text goes here</I.HelpText>
</Input>
Description continues after the code block.
@param: name the name
@returns: a greeting
Observations:
1. The description is truncated at `<I.TextField` — right where the parser ran into `@placeholder`.
2. A phantom `@placeholder` tag is created. Its "text" swallows the rest of the fenced block, the closing ` ``` `, and the prose paragraph that follows, until the next *real* tag (`@param`) is encountered.
3. Everything between `@placeholder` and `@param` — including what should have stayed in the description — is now associated with a made-up tag that the user never wrote.
In VS Code this manifests as a broken hover: the code example is cut in half, and whatever followed the fence is displayed as if it were documentation for a nonexistent `@placeholder` tag.
## 🙂 Expected behavior
The JSDoc parser should track markdown fenced code block state while scanning for tags. While inside a fence opened by ` ``` ` or `~~~` (optionally with a language identifier), `@` characters should **not** start new tags.
After the fence is closed, tag scanning resumes as normal.
Effectively: the parser already tokenizes per-line; adding a "currently inside fence" flag that flips when it sees a line whose trimmed `*`-prefix content starts with ` ``` ` (matching open/close) would cover the vast majority of real-world cases.
## Additional notes
- Current workaround is escaping with a backslash (`\@placeholder`), which renders acceptably in VS Code hover (the backslash is consumed by the markdown renderer) but is visibly ugly in the source comment and looks wrong to anyone reading the raw file or using tools that don't strip the escape.
- This affects not just Ember — any code example that includes `@` will trip it: Angular decorators, Python decorators in polyglot examples, email addresses in prose-adjacent examples, npm scoped package names (`@scope/name`), JSX/TSX demos using libraries that put `@` in attribute names, etc.
- Related but closed without action: [microsoft/tsdoc#216](https://github.com/microsoft/tsdoc/issues/216), [microsoft/vscode#72674](https://github.com/microsoft/vscode/issues/72674). Both were closed with the tsdoc side saying VS Code needs to fix it, and the VS Code side not owning the parser. This leaves the bug with no clear home — which is why I'm filing here, since the probe above demonstrates the truncation happens in the TypeScript language service itself.
🔎 Search terms
jsdoc, fenced code block, markdown, hover, quick info,
@, tag, parser, ember, template, angular, vue, jsx🕗 Version & regression information
Reproduced with current
typescript@latest. Behavior appears to have existed for many years — likely since JSDoc was first recognized by the language service.⏯ Context & motivation
We're building a design system in Ember.js and documenting components with JSDoc. Ember templates use
@argNameas the standard syntax for passing arguments to components:This is the canonical, idiomatic way to write Ember — every component invocation uses this syntax. The same pattern shows up in Angular (
@Input()inside code examples), Vue template refs, JSX attribute shortcuts, TypeScript decorators, email addresses, and anywhere else an@naturally appears inside a prose code example.Because the JSDoc parser does not respect markdown fenced code block boundaries, any occurrence of
@somethinginside a```code block hijacks the parser, turns into a phantom tag, and destroys the hover preview that users see.Since VS Code's hover consumes what
tsserveralready split into{ documentation, tags }, this cannot be fixed in VS Code — the description has already been cut in half before VS Code ever sees it. See microsoft/tsdoc#216 where the tsdoc maintainer correctly pointed out the fix has to live elsewhere.💻 Code
Probe script using the public API (
symbol.getDocumentationComment+symbol.getJsDocTags, i.e. the same data that powers hover quick info):🙁 Actual behavior
Description continues after the code block.
@param: name the name
@returns: a greeting