Use case
Writing a GritQL plugin that flags exported declarations missing a preceding /** … */ block — equivalent to ESLint's eslint-plugin-tsdoc syntax check plus a presence check that doesn't yet exist there either.
The presence check is the load-bearing piece: "does this exported function/class/interface/type/etc. have a /** */ block attached to it?" This is straightforwardly expressible with the TypeScript compiler API (ts.getJSDocCommentsAndTags(node)) and the internal biome_jsdoc_comment crate has the same primitive (JsdocComment::get_jsdocs(node)), but neither is reachable from a GritQL plugin.
What I tried empirically (biome v2.4)
| Pattern |
Behavior |
$fn <: not after '/** $_ */' |
Fires on documented AND undocumented functions. after/before match sibling AST nodes, not leading-trivia comments. |
$fn <: not preceded_by '/** $_ */' |
Silently no-op. preceded_by isn't a registered predicate; biome fails open rather than erroring. |
'/** $doc */ export function ...' (positive doc-capture) |
Silently no-op. The comment isn't part of the declaration's text in the CST that GritQL matches against. |
not $fn <: contains '/** $_ */' |
Fires on both. contains checks the function body, not its surroundings. |
So biome v2.4's GritQL surface cannot inspect leading comments — either via a positive /** */ capture pattern or via a negative-predicate. Trivia is unreachable from the plugin layer.
What works internally — but not in plugins
crates/biome_jsdoc_comment/src/jsdoc_comment.rs has:
pub fn get_jsdocs(node: &JsSyntaxNode) -> impl Iterator<Item = String> {
node.first_token()
.into_iter()
.flat_map(|token| token.leading_trivia().pieces())
.filter_map(|trivia| {
let text = trivia.text();
matches!(
trivia.kind(),
TriviaPieceKind::SingleLineComment | TriviaPieceKind::MultiLineComment
)
.then(|| text)
.filter(|text: &&str| Self::text_is_jsdoc_comment(text))
.map(|text| text.to_owned())
})
}
Used by biome's own Rust-built rules and the formatter — but not exposed to GritQL plugins.
Proposal
One of:
Option A — new GritQL predicate with_leading_jsdoc
`export function $name($_): $_ { $_ }` as $fn where {
$fn <: not with_leading_jsdoc()
}
Boolean predicate. Returns true if the node has at least one /** */ leading trivia piece via get_jsdocs(node).next().is_some().
Option B — expose leading_comments as a matchable list
`export function $name($_): $_ { $_ }` as $fn where {
not ($fn.leading_comments contains `/** $_ */`)
}
More general; lets plugins also match //-style comments above declarations (useful for documenting that single-line comments should be promoted to TSDoc — a sibling concern).
Option C — positive doc-capture pattern actually works
`/** $doc */ export function $name($_): $_ { $_ }` as $matched
Today this silently no-ops. If biome's GritQL pattern matcher concatenated leading trivia into the matchable text span, this would work and feels more in the spirit of GritQL's "pattern is the source" model.
Why I'm filing this
Working around the gap means projects implement a sibling TS-compiler-API scanner per repo, duplicating logic biome already has internally. Concrete example: https://github.com/brewpirate/helios/blob/main/scripts/detect-tsdoc.ts — ~400 LOC TypeScript using ts.getJSDocCommentsAndTags to do what a 5-line GritQL plugin should be able to express.
Happy to contribute any of these options if there's appetite for direction.
Related
- biome
useTsdoc lint rule (not yet shipped per Biome 2.4)
- microsoft/tsdoc eslint-plugin (validates content of existing JSDoc; doesn't solve presence)
Use case
Writing a GritQL plugin that flags exported declarations missing a preceding
/** … */block — equivalent to ESLint'seslint-plugin-tsdocsyntax check plus a presence check that doesn't yet exist there either.The presence check is the load-bearing piece: "does this exported function/class/interface/type/etc. have a
/** */block attached to it?" This is straightforwardly expressible with the TypeScript compiler API (ts.getJSDocCommentsAndTags(node)) and the internalbiome_jsdoc_commentcrate has the same primitive (JsdocComment::get_jsdocs(node)), but neither is reachable from a GritQL plugin.What I tried empirically (biome v2.4)
$fn <: not after '/** $_ */'after/beforematch sibling AST nodes, not leading-trivia comments.$fn <: not preceded_by '/** $_ */'preceded_byisn't a registered predicate; biome fails open rather than erroring.'/** $doc */ export function ...'(positive doc-capture)not $fn <: contains '/** $_ */'containschecks the function body, not its surroundings.So biome v2.4's GritQL surface cannot inspect leading comments — either via a positive
/** */capture pattern or via a negative-predicate. Trivia is unreachable from the plugin layer.What works internally — but not in plugins
crates/biome_jsdoc_comment/src/jsdoc_comment.rshas:Used by biome's own Rust-built rules and the formatter — but not exposed to GritQL plugins.
Proposal
One of:
Option A — new GritQL predicate
with_leading_jsdocBoolean predicate. Returns true if the node has at least one
/** */leading trivia piece viaget_jsdocs(node).next().is_some().Option B — expose
leading_commentsas a matchable listMore general; lets plugins also match
//-style comments above declarations (useful for documenting that single-line comments should be promoted to TSDoc — a sibling concern).Option C — positive doc-capture pattern actually works
Today this silently no-ops. If biome's GritQL pattern matcher concatenated leading trivia into the matchable text span, this would work and feels more in the spirit of GritQL's "pattern is the source" model.
Why I'm filing this
Working around the gap means projects implement a sibling TS-compiler-API scanner per repo, duplicating logic biome already has internally. Concrete example: https://github.com/brewpirate/helios/blob/main/scripts/detect-tsdoc.ts — ~400 LOC TypeScript using
ts.getJSDocCommentsAndTagsto do what a 5-line GritQL plugin should be able to express.Happy to contribute any of these options if there's appetite for direction.
Related
useTsdoclint rule (not yet shipped per Biome 2.4)