fix(go): a comment directive like //go:embed is not part of a declaration's doc (#2679)

go doc leaves comment directives out of a doc comment's text: a // comment
with no space after the slashes that is //line, //extern or //export
followed by text, or that opens with a lowercase word, a colon and a
lowercase letter or digit, like //go:embed static or //nolint:errcheck
(go/ast's isDirective). Both extractors kept them, so prometheus's
testsFs had the docstring "go:embed testdata", etcd's store.restore
"nolint:unparam", and a doc comment with a directive under it ended in
that directive.

Go now drops directives from the docstring's text through a Go-only
extractor option (docstringSkipsDirectives in TS; the kernel's Go
docstring helper becomes preceding_go_doc_comment, which applies all
three Go rules). A directive still counts as a comment when deciding
where the doc comment starts, as in go/parser, so a declaration whose
only comment is a directive has no docstring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-19 20:45:40 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 23f728fac2
commit f43432c86a
9 changed files with 299 additions and 26 deletions
+3
View File
@@ -261,4 +261,7 @@ export const goExtractor: LanguageExtractor = {
// line ends it, as go doc reads it: a banner or a note above that blank line
// documents nothing.
docstringSkipsDetachedComments: true,
// A directive Go tools read, like `//go:embed static` or
// `//nolint:errcheck`, is not part of the doc comment go doc shows.
docstringSkipsDirectives: true,
};
+25 -4
View File
@@ -145,13 +145,19 @@ function cleanCommentMarkers(comment: string): string {
* a blank line ends the docstring, and a comment that ends on the
* declaration's own line documents nothing. That is Go's doc comment, the one
* go doc shows. Default: off.
*
* `skipDirectives` leaves the comment directives Go tools read, like
* `//go:embed static` or `//nolint:errcheck`, out of the docstring's text, as
* go doc does. They still count as comments when the other options decide
* where the docstring starts. Default: off.
*/
export function getPrecedingDocstring(
node: SyntaxNode,
source: string,
stepOver: readonly string[] = [],
skipTrailing = false,
skipDetached = false
skipDetached = false,
skipDirectives = false
): string | undefined {
// Climb out of any wrapper(s) so a comment preceding the WHOLE construct
// (export-, decorator-, or const-arrow-wrapped) is reachable as a sibling.
@@ -206,16 +212,31 @@ export function getPrecedingDocstring(
}
first = start;
}
if (first === comments.length) return undefined;
const docComments = comments
.slice(first)
.filter((c) => !skipDirectives || !isDirective(getNodeText(c, source)));
if (docComments.length === 0) return undefined;
// Strip each comment's syntax markers (language-aware), then join.
return comments
.slice(first)
return docComments
.map((c) => cleanCommentMarkers(getNodeText(c, source)))
.join('\n')
.trim();
}
/**
* Whether a comment is a directive Go tools read, which go doc leaves out of a
* doc comment's text (go/ast's isDirective): a `//` comment with no space
* after the slashes that is `//line `, `//extern ` or `//export ` followed by
* text, or that opens with a lowercase word, a colon and a lowercase letter or
* digit, like `//go:embed static` or `//nolint:errcheck`.
*/
function isDirective(comment: string): boolean {
if (!comment.startsWith('//')) return false;
const text = comment.slice(2);
return /^(?:line|extern|export) /.test(text) || /^[a-z0-9]+:[a-z0-9]/.test(text);
}
/** Whether code comes before `node` on the line it starts on. */
function followsCodeOnItsLine(node: SyntaxNode, source: string): boolean {
let i = node.startIndex;
+6
View File
@@ -198,6 +198,12 @@ export interface LanguageExtractor {
* declaration documents nothing.
*/
docstringSkipsDetachedComments?: boolean;
/**
* Leave out of a docstring's text the comment directives Go tools read, like
* `//go:embed static`, `//nolint:errcheck` or `//export F`, as go doc does:
* a declaration whose only comment is a directive has no doc.
*/
docstringSkipsDirectives?: boolean;
/**
* Node types that may stand between a declaration and the decorators written
* before it without ending the scan for them. Dart writes comments there
+2 -1
View File
@@ -542,7 +542,8 @@ export class TreeSitterExtractor {
this.source,
this.extractor?.docstringStepOverTypes,
this.extractor?.docstringSkipsTrailingComments,
this.extractor?.docstringSkipsDetachedComments
this.extractor?.docstringSkipsDetachedComments,
this.extractor?.docstringSkipsDirectives
);
const body = this.extractor?.getBodyDocstring?.(node, this.source);
if (preceding && body) return `${preceding}\n\n${body}`;