mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-21 00:38:18 +08:00
fix(go): a constant in a const ( … ) group keeps its own doc comment (#2680)
Both extractors read every member's docstring from the comment above `const (`, which documents the group. A constant documented inside the group, like gin's ErrorTypeBind under "ErrorTypeBind is used when Context.Bind() fails.", stored no docstring, or the group's comment instead of its own. A member's own doc comment, read with Go's doc comment rules, is now its docstring. A member without one keeps the group's comment, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
- In Go, a type declared on its own, like `type Point struct{…}` under a `// Point is …` comment, now keeps that comment as its documentation, as a type inside a `type ( … )` group already did, so searching for what a type does can find it. A comment at the end of a line of code, like `const sides = 4 // sides of a square.`, is no longer taken as the documentation of the declaration under it. Re-index Go projects after upgrading.
|
||||
- In Go, a declaration's documentation is now just the comment written right above it, as `go doc` shows it. A section banner, a TODO or commented-out code that a blank line separates from a function, type, constant or variable is no longer added to the front of its documentation, or taken as its documentation when it has none of its own. Re-index Go projects after upgrading.
|
||||
- In Go, a comment directive written above a declaration, like `//go:embed static`, `//go:generate stringer -type=Kind` or `//nolint:errcheck`, is no longer part of its documentation, as `go doc` shows it, so a variable whose only comment is a `//go:embed` line no longer has that line as its description. Re-index Go projects after upgrading.
|
||||
- In Go, a constant declared inside a `const ( … )` group now keeps the doc comment written above it, like gin's `// ErrorTypeBind is used when Context.Bind() fails.`, so searching for what a constant means can find it. Before, every member of the group got the group's comment, or nothing when the group had none; a member without a comment of its own still gets the group's. Re-index Go projects after upgrading.
|
||||
- Indexing a project that includes large bundled JavaScript files, such as a copy of pdf.js or d3, is fast again: since 1.6.2, resolving the calls in a JavaScript or TypeScript file re-read the file's text above each call, so a single bundled library could add many seconds to an index. The graph it builds is unchanged. Thanks @bompus for the report. (#2334)
|
||||
- Indexing a project that ships a large minified JavaScript file, such as the GraphiQL bundle inside go-ethereum, no longer stalls at "Resolving refs" when CodeGraph runs on Node 22, and linking such a file is faster on any Node version. The graph it builds is unchanged.
|
||||
- The time `codegraph init` and `codegraph index` print beside the node and edge counts now covers the whole run, resolving references and linking included, and `codegraph sync` reports its whole run the same way. Before, it counted only reading and parsing the files, which can be a small part of an index, so a slow index looked fast. Thanks @bompus for the report. (#2334)
|
||||
|
||||
@@ -8,6 +8,13 @@ import (
|
||||
|
||||
const MAX_ITEMS = 128 // the line's own comment, not DefaultRegistry's doc
|
||||
|
||||
// Limits: the group's comment, for a member without its own.
|
||||
const (
|
||||
// MinItems is documented inside its group.
|
||||
MinItems = 1
|
||||
MaxSlots = 64
|
||||
)
|
||||
|
||||
var DefaultRegistry = NewRegistry()
|
||||
|
||||
var handlerTable = map[string]func(int){
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* A constant declared inside a `const ( … )` group lost the doc comment
|
||||
* written above it:
|
||||
*
|
||||
* const (
|
||||
* // ErrorTypeBind is used when Context.Bind() fails.
|
||||
* ErrorTypeBind ErrorType = 1 << 63
|
||||
*
|
||||
* gin's `ErrorTypeBind` had no docstring, because both extractors read every
|
||||
* member's docstring from the comment above `const (`, the group's. A member's
|
||||
* own doc comment, the one go doc prints beside it, is now its docstring; a
|
||||
* member without one keeps the group's comment, as before.
|
||||
*
|
||||
* Runs against the native kernel (when built) and the wasm extractor, which
|
||||
* must agree.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { extractFromSource } from '../src/extraction';
|
||||
import { initGrammars, loadGrammarsForLanguages } from '../src/extraction/grammars';
|
||||
import { tryKernelExtract, resetKernelForTests } from '../src/extraction/kernel';
|
||||
import type { ExtractionResult } from '../src/types';
|
||||
|
||||
const KERNEL_PATH = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'codegraph-kernel',
|
||||
'prebuilds',
|
||||
`${process.platform}-${process.arch}`,
|
||||
'codegraph-kernel.node'
|
||||
);
|
||||
const kernelAvailable = fs.existsSync(KERNEL_PATH) || process.env.CODEGRAPH_KERNEL_EXPECT === '1';
|
||||
|
||||
const SOURCE = `// Package shapes is documented.
|
||||
package shapes
|
||||
|
||||
// Kinds of shapes. The comment documents the group.
|
||||
const (
|
||||
// Circle is documented on its own.
|
||||
Circle = iota
|
||||
Square
|
||||
Pentagon = 5 // five sides
|
||||
Heptagon
|
||||
// A note a blank line separates from Hexagon.
|
||||
|
||||
Hexagon
|
||||
// Octagon is documented on its own.
|
||||
//nolint:revive
|
||||
Octagon
|
||||
)
|
||||
|
||||
const (
|
||||
// Red is documented, and its group is not.
|
||||
Red = "red"
|
||||
Blue = "blue"
|
||||
)
|
||||
|
||||
// Answer is documented above its own declaration.
|
||||
const Answer = 42
|
||||
|
||||
// Lone is the only member of its group.
|
||||
const (
|
||||
Lone = 1
|
||||
)
|
||||
|
||||
// Own's group comment.
|
||||
const (
|
||||
// Own is documented inside its group of one.
|
||||
Own = 2
|
||||
)
|
||||
|
||||
// limit is documented above its own declaration.
|
||||
var limit = 10
|
||||
`;
|
||||
|
||||
const ENV_KEYS = ['CODEGRAPH_KERNEL', 'CODEGRAPH_KERNEL_LANGS'] as const;
|
||||
|
||||
describe('Go constants in a group keep their own doc comment', () => {
|
||||
let savedEnv: Record<string, string | undefined> = {};
|
||||
|
||||
beforeAll(async () => {
|
||||
await initGrammars();
|
||||
await loadGrammarsForLanguages(['go']);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
savedEnv = Object.fromEntries(ENV_KEYS.map((k) => [k, process.env[k]]));
|
||||
resetKernelForTests();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
for (const k of ENV_KEYS) {
|
||||
if (savedEnv[k] === undefined) delete process.env[k];
|
||||
else process.env[k] = savedEnv[k];
|
||||
}
|
||||
resetKernelForTests();
|
||||
});
|
||||
|
||||
function extract(backend: 'kernel' | 'wasm', source: string): ExtractionResult {
|
||||
if (backend === 'wasm') {
|
||||
process.env.CODEGRAPH_KERNEL = '0';
|
||||
return extractFromSource('shapes/shapes.go', source, 'go');
|
||||
}
|
||||
delete process.env.CODEGRAPH_KERNEL;
|
||||
process.env.CODEGRAPH_KERNEL_LANGS = 'all';
|
||||
const result = tryKernelExtract('shapes/shapes.go', source, 'go');
|
||||
expect(result, 'kernel extraction').not.toBeNull();
|
||||
return result!;
|
||||
}
|
||||
|
||||
const backends = kernelAvailable ? (['kernel', 'wasm'] as const) : (['wasm'] as const);
|
||||
|
||||
for (const crlf of [false, true]) {
|
||||
it.each(backends)(`%s${crlf ? ' (CRLF)' : ''}`, (backend) => {
|
||||
const result = extract(backend, crlf ? SOURCE.replace(/\n/g, '\r\n') : SOURCE);
|
||||
const doc = (kind: string, name: string): string | null => {
|
||||
const found = result.nodes.filter((n) => n.kind === kind && n.name === name);
|
||||
expect(found, `${kind} ${name}`).toHaveLength(1);
|
||||
return found[0].docstring ?? null;
|
||||
};
|
||||
const group = 'Kinds of shapes. The comment documents the group.';
|
||||
|
||||
expect({
|
||||
// A member's own doc comment is its docstring, read the way go doc
|
||||
// reads one: without a directive, and ended by a blank line.
|
||||
Circle: doc('constant', 'Circle'),
|
||||
Octagon: doc('constant', 'Octagon'),
|
||||
Red: doc('constant', 'Red'),
|
||||
Own: doc('constant', 'Own'),
|
||||
// A member without one keeps the group's comment, if there is one; a
|
||||
// comment after the code on the line above, or one a blank line
|
||||
// separates from the member, is not its doc.
|
||||
Square: doc('constant', 'Square'),
|
||||
Heptagon: doc('constant', 'Heptagon'),
|
||||
Hexagon: doc('constant', 'Hexagon'),
|
||||
Blue: doc('constant', 'Blue'),
|
||||
Lone: doc('constant', 'Lone'),
|
||||
// A declaration on its own is read as before.
|
||||
Answer: doc('constant', 'Answer'),
|
||||
limit: doc('variable', 'limit'),
|
||||
}).toEqual({
|
||||
Circle: 'Circle is documented on its own.',
|
||||
Octagon: 'Octagon is documented on its own.',
|
||||
Red: 'Red is documented, and its group is not.',
|
||||
Own: 'Own is documented inside its group of one.',
|
||||
Square: group,
|
||||
Heptagon: group,
|
||||
Hexagon: group,
|
||||
Blue: null,
|
||||
Lone: 'Lone is the only member of its group.',
|
||||
Answer: 'Answer is documented above its own declaration.',
|
||||
limit: 'limit is documented above its own declaration.',
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -697,11 +697,15 @@ impl<'t> Walker<'t> {
|
||||
None
|
||||
};
|
||||
let signature = value_node.map(|v| util::init_signature(self.text(v)));
|
||||
// A member of a `const ( … )` group has its own doc
|
||||
// comment beside it in the parentheses; one without keeps
|
||||
// the group's.
|
||||
let doc = self.docstring_of(spec).or_else(|| docstring.clone());
|
||||
var_row = self.create_node(
|
||||
if is_const_decl { "constant" } else { "variable" },
|
||||
&name,
|
||||
spec,
|
||||
Extra { docstring: docstring.clone(), signature, ..Extra::default() },
|
||||
Extra { docstring: doc, signature, ..Extra::default() },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3572,8 +3572,10 @@ export class TreeSitterExtractor {
|
||||
const initValue = valueNode ? getNodeText(valueNode, this.source).slice(0, 100) : undefined;
|
||||
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
||||
|
||||
// A member of a `const ( … )` group has its own doc comment beside
|
||||
// it in the parentheses; one without keeps the group's.
|
||||
varNode = this.createNode(node.type === 'const_declaration' ? 'constant' : 'variable', name, spec, {
|
||||
docstring,
|
||||
docstring: this.docstringFor(spec) ?? docstring,
|
||||
signature: initSignature,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user