Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions __tests__/fixtures/kernel-parity/torture.go
Original file line number Diff line number Diff line change
Expand Up @@ -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){
Expand Down
157 changes: 157 additions & 0 deletions __tests__/go-grouped-const-docs.test.ts
Original file line number Diff line number Diff line change
@@ -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.',
});
});
}
});
6 changes: 5 additions & 1 deletion codegraph-kernel/src/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() },
);
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/extraction/tree-sitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
}
Expand Down