From e0db95a0bd3912523b7bfe3b2d6d88de350f1c82 Mon Sep 17 00:00:00 2001 From: Fibi Date: Sun, 26 Apr 2026 05:37:52 +0000 Subject: [PATCH] fix: don't attach `@module` JSDoc to the first symbol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `js_doc_for_range_include_ignore` walks leading comments back from a symbol's range to find its JSDoc block. When a file's `@module` documentation comment was the only leading block before an undocumented exported symbol, that comment was returned as the symbol's JSDoc — so the same paragraph showed up twice in `deno doc` output and on unrelated symbols. Skip JSDoc blocks tagged `@module` when resolving per-symbol docs. Such blocks are file-level by intent and have already been collected by `module_js_doc_for_source`. Fixes denoland/deno#30783. --- src/util/swc.rs | 14 ++- .../module_docs_undocumented_first_symbol.txt | 97 +++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 tests/specs/module_docs_undocumented_first_symbol.txt diff --git a/src/util/swc.rs b/src/util/swc.rs index cb49aecc2..d27123797 100644 --- a/src/util/swc.rs +++ b/src/util/swc.rs @@ -47,7 +47,19 @@ pub(crate) fn js_doc_for_range_include_ignore( if let Some(js_doc_comment) = comments.iter().rev().find(|comment| { comment.kind == CommentKind::Block && comment.text.starts_with('*') }) { - parse_js_doc(js_doc_comment, module_info) + let js_doc = parse_js_doc(js_doc_comment, module_info); + // A `@module` JSDoc block belongs to the file as a whole, not to the + // first symbol that happens to follow it. Without this guard the + // module-level documentation gets duplicated onto the first + // undocumented exported symbol (denoland/deno#30783). + if js_doc + .tags + .iter() + .any(|tag| matches!(tag, JsDocTag::Module { .. })) + { + return JsDoc::default(); + } + js_doc } else { JsDoc::default() } diff --git a/tests/specs/module_docs_undocumented_first_symbol.txt b/tests/specs/module_docs_undocumented_first_symbol.txt new file mode 100644 index 000000000..3feeaa66f --- /dev/null +++ b/tests/specs/module_docs_undocumented_first_symbol.txt @@ -0,0 +1,97 @@ +# mod.ts +/** + * The module docs. + * + * @module + */ + +export function foo() {} + +/** This is the bar function. */ +export function bar() {} + +# diagnostics +error[missing-jsdoc]: exported symbol is missing JSDoc documentation + --> /mod.ts:7:1 + | +7 | export function foo() {} + | ^ + +# output.txt +The module docs. +@module + +Defined in file:///mod.ts:9:1 + +function bar(): void + This is the bar function. + +Defined in file:///mod.ts:6:1 + +function foo(): void + + +# output.json +{ + "module_doc": { + "doc": "The module docs.\n", + "tags": [ + { + "kind": "module" + } + ] + }, + "symbols": [ + { + "name": "foo", + "declarations": [ + { + "location": { + "filename": "file:///mod.ts", + "line": 6, + "col": 0, + "byteIndex": 43 + }, + "declarationKind": "export", + "kind": "function", + "def": { + "params": [], + "returnType": { + "repr": "void", + "kind": "keyword", + "value": "void" + }, + "hasBody": true + } + } + ] + }, + { + "name": "bar", + "declarations": [ + { + "location": { + "filename": "file:///mod.ts", + "line": 9, + "col": 0, + "byteIndex": 102 + }, + "declarationKind": "export", + "jsDoc": { + "doc": "This is the bar function." + }, + "kind": "function", + "def": { + "params": [], + "returnType": { + "repr": "void", + "kind": "keyword", + "value": "void" + }, + "hasBody": true + } + } + ] + } + ] +}