clast-ai 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +14 -0
- package/.claude-plugin/plugin.json +11 -9
- package/README.md +468 -398
- package/bench/ab/onair-concept.json +22 -0
- package/bench/ab/onair-deadcode.json +7 -0
- package/bench/ab/onair-hard.json +32 -0
- package/bench/ab/onair-impact.json +17 -0
- package/bench/ab/onair-refactors.json +18 -0
- package/bench/ab/onair-survey.json +17 -0
- package/bench/ab/onair-tasks.json +20 -0
- package/bench/ab/onair.json +52 -0
- package/bench/ab/vscode-concept.json +22 -0
- package/bench/ab/vscode-hard.json +32 -0
- package/bench/ab/vscode-impact.json +17 -0
- package/bench/ab/vscode-refactors.json +18 -0
- package/bench/ab/vscode-tasks.json +20 -0
- package/bench/ab/vscode.json +52 -0
- package/bench/ab.mjs +213 -0
- package/bench/bench.mjs +98 -0
- package/bench/resolution.mjs +75 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +266 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/defaults.d.ts +12 -2
- package/dist/config/defaults.js +38 -31
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +26 -14
- package/dist/config/index.js.map +1 -1
- package/dist/db/index.d.ts +73 -13
- package/dist/db/index.js +481 -39
- package/dist/db/index.js.map +1 -1
- package/dist/engine.d.ts +15 -0
- package/dist/engine.js +28 -0
- package/dist/engine.js.map +1 -0
- package/dist/indexer/imports.d.ts +6 -0
- package/dist/indexer/imports.js +254 -0
- package/dist/indexer/imports.js.map +1 -0
- package/dist/indexer/index.d.ts +63 -0
- package/dist/indexer/index.js +360 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/indexer/process.d.ts +22 -0
- package/dist/indexer/process.js +42 -0
- package/dist/indexer/process.js.map +1 -0
- package/dist/indexer/scan.d.ts +43 -0
- package/dist/indexer/scan.js +223 -0
- package/dist/indexer/scan.js.map +1 -0
- package/dist/indexer/worker.d.ts +1 -0
- package/dist/indexer/worker.js +23 -0
- package/dist/indexer/worker.js.map +1 -0
- package/dist/llm/comment-generator.d.ts +17 -19
- package/dist/llm/comment-generator.js +46 -80
- package/dist/llm/comment-generator.js.map +1 -1
- package/dist/llm/index.d.ts +0 -1
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/prompts.d.ts +0 -1
- package/dist/llm/prompts.js +16 -16
- package/dist/migrate.d.ts +35 -0
- package/dist/migrate.js +194 -0
- package/dist/migrate.js.map +1 -0
- package/dist/parser/extractor.d.ts +21 -16
- package/dist/parser/extractor.js +700 -228
- package/dist/parser/extractor.js.map +1 -1
- package/dist/parser/index.d.ts +17 -21
- package/dist/parser/index.js +115 -171
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/langs/c.d.ts +3 -0
- package/dist/parser/langs/c.js +879 -0
- package/dist/parser/langs/c.js.map +1 -0
- package/dist/parser/langs/csharp.d.ts +2 -0
- package/dist/parser/langs/csharp.js +548 -0
- package/dist/parser/langs/csharp.js.map +1 -0
- package/dist/parser/langs/dart.d.ts +2 -0
- package/dist/parser/langs/dart.js +893 -0
- package/dist/parser/langs/dart.js.map +1 -0
- package/dist/parser/langs/go.d.ts +2 -0
- package/dist/parser/langs/go.js +455 -0
- package/dist/parser/langs/go.js.map +1 -0
- package/dist/parser/langs/java.d.ts +2 -0
- package/dist/parser/langs/java.js +390 -0
- package/dist/parser/langs/java.js.map +1 -0
- package/dist/parser/langs/php.d.ts +2 -0
- package/dist/parser/langs/php.js +486 -0
- package/dist/parser/langs/php.js.map +1 -0
- package/dist/parser/langs/python.d.ts +2 -0
- package/dist/parser/langs/python.js +216 -0
- package/dist/parser/langs/python.js.map +1 -0
- package/dist/parser/langs/ruby.d.ts +2 -0
- package/dist/parser/langs/ruby.js +602 -0
- package/dist/parser/langs/ruby.js.map +1 -0
- package/dist/parser/langs/rust.d.ts +2 -0
- package/dist/parser/langs/rust.js +931 -0
- package/dist/parser/langs/rust.js.map +1 -0
- package/dist/parser/langs/swift.d.ts +2 -0
- package/dist/parser/langs/swift.js +616 -0
- package/dist/parser/langs/swift.js.map +1 -0
- package/dist/parser/langs/typescript.d.ts +4 -0
- package/dist/parser/langs/typescript.js +535 -0
- package/dist/parser/langs/typescript.js.map +1 -0
- package/dist/parser/registry.d.ts +16 -0
- package/dist/parser/registry.js +87 -0
- package/dist/parser/registry.js.map +1 -0
- package/dist/parser/returns.d.ts +9 -0
- package/dist/parser/returns.js +152 -0
- package/dist/parser/returns.js.map +1 -0
- package/dist/parser/spec.d.ts +165 -0
- package/dist/parser/spec.js +188 -0
- package/dist/parser/spec.js.map +1 -0
- package/dist/query/arity.d.ts +27 -0
- package/dist/query/arity.js +187 -0
- package/dist/query/arity.js.map +1 -0
- package/dist/query/callgraph.d.ts +113 -0
- package/dist/query/callgraph.js +332 -0
- package/dist/query/callgraph.js.map +1 -0
- package/dist/query/format.d.ts +36 -0
- package/dist/query/format.js +136 -0
- package/dist/query/format.js.map +1 -0
- package/dist/query/hierarchy.d.ts +44 -0
- package/dist/query/hierarchy.js +441 -0
- package/dist/query/hierarchy.js.map +1 -0
- package/dist/query/resolve.d.ts +265 -0
- package/dist/query/resolve.js +1957 -0
- package/dist/query/resolve.js.map +1 -0
- package/dist/query/search.d.ts +35 -0
- package/dist/query/search.js +379 -0
- package/dist/query/search.js.map +1 -0
- package/dist/query/words.d.ts +30 -0
- package/dist/query/words.js +201 -0
- package/dist/query/words.js.map +1 -0
- package/dist/server/index.d.ts +1 -3
- package/dist/server/index.js +313 -73
- package/dist/server/index.js.map +1 -1
- package/dist/tools/context.d.ts +35 -0
- package/dist/tools/context.js +79 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/explore.d.ts +51 -0
- package/dist/tools/explore.js +779 -0
- package/dist/tools/explore.js.map +1 -0
- package/dist/tools/impact.d.ts +21 -0
- package/dist/tools/impact.js +743 -0
- package/dist/tools/impact.js.map +1 -0
- package/dist/tools/search.d.ts +59 -0
- package/dist/tools/search.js +843 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/stats.d.ts +26 -0
- package/dist/tools/stats.js +604 -0
- package/dist/tools/stats.js.map +1 -0
- package/dist/types.d.ts +95 -40
- package/dist/types.js +9 -1
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/dist/watcher/index.d.ts +18 -16
- package/dist/watcher/index.js +94 -77
- package/dist/watcher/index.js.map +1 -1
- package/package.json +21 -24
- package/plugin/server.mjs +72 -0
- package/skills/audit/SKILL.md +37 -0
- package/skills/deadcode/SKILL.md +33 -0
- package/skills/explore/SKILL.md +54 -0
- package/skills/impact/SKILL.md +24 -0
- package/skills/status/SKILL.md +11 -0
- package/src/cli.ts +265 -0
- package/src/config/defaults.ts +50 -32
- package/src/config/index.ts +23 -14
- package/src/db/index.ts +540 -43
- package/src/engine.ts +30 -0
- package/src/indexer/imports.ts +241 -0
- package/src/indexer/index.ts +369 -0
- package/src/indexer/process.ts +63 -0
- package/src/indexer/scan.ts +226 -0
- package/src/indexer/worker.ts +24 -0
- package/src/llm/comment-generator.ts +52 -103
- package/src/llm/index.ts +80 -80
- package/src/llm/prompts.ts +95 -95
- package/src/migrate.ts +213 -0
- package/src/parser/extractor.ts +643 -256
- package/src/parser/index.ts +105 -205
- package/src/parser/langs/c.ts +860 -0
- package/src/parser/langs/csharp.ts +519 -0
- package/src/parser/langs/dart.ts +820 -0
- package/src/parser/langs/go.ts +437 -0
- package/src/parser/langs/java.ts +361 -0
- package/src/parser/langs/php.ts +450 -0
- package/src/parser/langs/python.ts +206 -0
- package/src/parser/langs/ruby.ts +554 -0
- package/src/parser/langs/rust.ts +904 -0
- package/src/parser/langs/swift.ts +590 -0
- package/src/parser/langs/typescript.ts +499 -0
- package/src/parser/registry.ts +88 -0
- package/src/parser/returns.ts +137 -0
- package/src/parser/spec.ts +309 -0
- package/src/query/arity.ts +173 -0
- package/src/query/callgraph.ts +473 -0
- package/src/query/format.ts +136 -0
- package/src/query/hierarchy.ts +447 -0
- package/src/query/resolve.ts +1972 -0
- package/src/query/search.ts +430 -0
- package/src/query/words.ts +183 -0
- package/src/server/index.ts +390 -79
- package/src/tools/context.ts +82 -0
- package/src/tools/explore.ts +860 -0
- package/src/tools/impact.ts +748 -0
- package/src/tools/search.ts +886 -0
- package/src/tools/stats.ts +633 -0
- package/src/types.ts +113 -45
- package/src/version.ts +2 -0
- package/src/watcher/index.ts +82 -88
- package/tsconfig.json +6 -3
- package/.mcp.json +0 -8
- package/commands/clast-status.md +0 -9
- package/dist/config/defaults.d.ts.map +0 -1
- package/dist/config/index.d.ts.map +0 -1
- package/dist/db/index.d.ts.map +0 -1
- package/dist/db/queries.d.ts +0 -44
- package/dist/db/queries.d.ts.map +0 -1
- package/dist/db/queries.js +0 -251
- package/dist/db/queries.js.map +0 -1
- package/dist/db/schema.d.ts +0 -3
- package/dist/db/schema.d.ts.map +0 -1
- package/dist/db/schema.js +0 -94
- package/dist/db/schema.js.map +0 -1
- package/dist/db/types.d.ts +0 -44
- package/dist/db/types.d.ts.map +0 -1
- package/dist/db/types.js +0 -2
- package/dist/db/types.js.map +0 -1
- package/dist/llm/comment-generator.d.ts.map +0 -1
- package/dist/llm/index.d.ts.map +0 -1
- package/dist/llm/prompts.d.ts.map +0 -1
- package/dist/parser/call-resolver.d.ts +0 -15
- package/dist/parser/call-resolver.d.ts.map +0 -1
- package/dist/parser/call-resolver.js +0 -16
- package/dist/parser/call-resolver.js.map +0 -1
- package/dist/parser/extractor.d.ts.map +0 -1
- package/dist/parser/index.d.ts.map +0 -1
- package/dist/parser/languages.d.ts +0 -11
- package/dist/parser/languages.d.ts.map +0 -1
- package/dist/parser/languages.js +0 -74
- package/dist/parser/languages.js.map +0 -1
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/register-tools.d.ts +0 -7
- package/dist/server/register-tools.d.ts.map +0 -1
- package/dist/server/register-tools.js +0 -17
- package/dist/server/register-tools.js.map +0 -1
- package/dist/server/tools/clast-call-graph.d.ts +0 -4
- package/dist/server/tools/clast-call-graph.d.ts.map +0 -1
- package/dist/server/tools/clast-call-graph.js +0 -70
- package/dist/server/tools/clast-call-graph.js.map +0 -1
- package/dist/server/tools/clast-comment.d.ts +0 -5
- package/dist/server/tools/clast-comment.d.ts.map +0 -1
- package/dist/server/tools/clast-comment.js +0 -64
- package/dist/server/tools/clast-comment.js.map +0 -1
- package/dist/server/tools/clast-file-summary.d.ts +0 -4
- package/dist/server/tools/clast-file-summary.d.ts.map +0 -1
- package/dist/server/tools/clast-file-summary.js +0 -42
- package/dist/server/tools/clast-file-summary.js.map +0 -1
- package/dist/server/tools/clast-get-context.d.ts +0 -4
- package/dist/server/tools/clast-get-context.d.ts.map +0 -1
- package/dist/server/tools/clast-get-context.js +0 -80
- package/dist/server/tools/clast-get-context.js.map +0 -1
- package/dist/server/tools/clast-reindex.d.ts +0 -5
- package/dist/server/tools/clast-reindex.d.ts.map +0 -1
- package/dist/server/tools/clast-reindex.js +0 -36
- package/dist/server/tools/clast-reindex.js.map +0 -1
- package/dist/server/tools/clast-search.d.ts +0 -4
- package/dist/server/tools/clast-search.d.ts.map +0 -1
- package/dist/server/tools/clast-search.js +0 -29
- package/dist/server/tools/clast-search.js.map +0 -1
- package/dist/server/tools/clast-status.d.ts +0 -5
- package/dist/server/tools/clast-status.d.ts.map +0 -1
- package/dist/server/tools/clast-status.js +0 -23
- package/dist/server/tools/clast-status.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/watcher/gitignore.d.ts +0 -9
- package/dist/watcher/gitignore.d.ts.map +0 -1
- package/dist/watcher/gitignore.js +0 -38
- package/dist/watcher/gitignore.js.map +0 -1
- package/dist/watcher/index.d.ts.map +0 -1
- package/skills/clast-explore/SKILL.md +0 -41
- package/src/db/queries.ts +0 -332
- package/src/db/schema.ts +0 -106
- package/src/db/types.ts +0 -47
- package/src/parser/call-resolver.ts +0 -17
- package/src/parser/languages.ts +0 -86
- package/src/server/register-tools.ts +0 -30
- package/src/server/tools/clast-call-graph.ts +0 -82
- package/src/server/tools/clast-comment.ts +0 -76
- package/src/server/tools/clast-file-summary.ts +0 -59
- package/src/server/tools/clast-get-context.ts +0 -97
- package/src/server/tools/clast-reindex.ts +0 -52
- package/src/server/tools/clast-search.ts +0 -38
- package/src/server/tools/clast-status.ts +0 -37
- package/src/watcher/gitignore.ts +0 -42
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import type { Node } from 'web-tree-sitter';
|
|
2
|
+
import type { Ctx, LangSpec, Visitor } from '../spec.js';
|
|
3
|
+
import { field, simpleReceiver, stripQuotes } from '../spec.js';
|
|
4
|
+
|
|
5
|
+
/** Builtin functions and predeclared types: calls/conversions that never resolve to a user symbol. */
|
|
6
|
+
const BUILTINS = new Set([
|
|
7
|
+
'append', 'cap', 'clear', 'close', 'complex', 'copy', 'delete', 'imag', 'len', 'make', 'max', 'min',
|
|
8
|
+
'new', 'panic', 'print', 'println', 'real', 'recover',
|
|
9
|
+
'bool', 'byte', 'rune', 'string', 'error', 'any', 'uintptr',
|
|
10
|
+
'int', 'int8', 'int16', 'int32', 'int64', 'uint', 'uint8', 'uint16', 'uint32', 'uint64',
|
|
11
|
+
'float32', 'float64', 'complex64', 'complex128',
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const MAX_SIG_SLICE = 300;
|
|
15
|
+
|
|
16
|
+
function isExported(name: string): boolean {
|
|
17
|
+
return /^\p{Lu}/u.test(name);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Bare name of a (possibly pointer / generic / package-qualified) named type. */
|
|
21
|
+
function typeName(node: Node | null | undefined): string | null {
|
|
22
|
+
for (let i = 0; node && i < 6; i++) {
|
|
23
|
+
switch (node.type) {
|
|
24
|
+
case 'type_identifier':
|
|
25
|
+
case 'identifier':
|
|
26
|
+
return node.text;
|
|
27
|
+
case 'qualified_type':
|
|
28
|
+
return field(node, 'name')?.text ?? null;
|
|
29
|
+
case 'generic_type':
|
|
30
|
+
node = field(node, 'type');
|
|
31
|
+
break;
|
|
32
|
+
case 'pointer_type':
|
|
33
|
+
case 'parenthesized_type':
|
|
34
|
+
case 'type_elem':
|
|
35
|
+
node = node.namedChildren[0];
|
|
36
|
+
break;
|
|
37
|
+
default:
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Like typeName, but a package-qualified type keeps its package: `*deps.Deps` -> `deps.Deps`. */
|
|
45
|
+
function hintType(node: Node | null | undefined): string | null {
|
|
46
|
+
for (let i = 0; node && i < 6; i++) {
|
|
47
|
+
switch (node.type) {
|
|
48
|
+
case 'qualified_type':
|
|
49
|
+
return node.text.replace(/\s+/g, '');
|
|
50
|
+
case 'generic_type':
|
|
51
|
+
node = field(node, 'type');
|
|
52
|
+
break;
|
|
53
|
+
case 'pointer_type':
|
|
54
|
+
case 'parenthesized_type':
|
|
55
|
+
case 'type_elem':
|
|
56
|
+
node = node.namedChildren[0];
|
|
57
|
+
break;
|
|
58
|
+
default:
|
|
59
|
+
return typeName(node);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Outermost node of a spec: the surrounding declaration when it holds only
|
|
67
|
+
* this spec (`type X struct{}`, `const A = 1`), so that the doc comment in
|
|
68
|
+
* front of the keyword is found. Specs in `( ... )` groups keep their own range.
|
|
69
|
+
*/
|
|
70
|
+
function specRange(spec: Node): Node {
|
|
71
|
+
const decl = spec.parent;
|
|
72
|
+
if (decl && decl.namedChildCount === 1 && decl.type.endsWith('_declaration')) return decl;
|
|
73
|
+
return spec;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function slice(ctx: Ctx, start: number, end: number): string {
|
|
77
|
+
return ctx.src.slice(start, Math.min(end, start + MAX_SIG_SLICE));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** True for a (pointer to a / generic) type qualified by a package: `http.Handler`, `*pkg.Base`. */
|
|
81
|
+
function isQualified(node: Node | null | undefined): boolean {
|
|
82
|
+
for (let i = 0; node && i < 6; i++) {
|
|
83
|
+
switch (node.type) {
|
|
84
|
+
case 'qualified_type':
|
|
85
|
+
return true;
|
|
86
|
+
case 'generic_type':
|
|
87
|
+
node = field(node, 'type');
|
|
88
|
+
break;
|
|
89
|
+
case 'pointer_type':
|
|
90
|
+
case 'parenthesized_type':
|
|
91
|
+
case 'type_elem':
|
|
92
|
+
node = node.namedChildren[0];
|
|
93
|
+
break;
|
|
94
|
+
default:
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Per-file state (a fresh `Ctx` is created for every extraction): type
|
|
103
|
+
* symbols and interface satisfaction assertions (`var _ I = (*T)(nil)`),
|
|
104
|
+
* which may come before or after the declaration of `T`.
|
|
105
|
+
*/
|
|
106
|
+
interface FileState {
|
|
107
|
+
types: Map<string, number>;
|
|
108
|
+
/** Type name -> interfaces asserted before the type was declared. */
|
|
109
|
+
asserted: Map<string, string[]>;
|
|
110
|
+
}
|
|
111
|
+
const states = new WeakMap<Ctx, FileState>();
|
|
112
|
+
function state(ctx: Ctx): FileState {
|
|
113
|
+
let s = states.get(ctx);
|
|
114
|
+
if (!s) states.set(ctx, (s = { types: new Map(), asserted: new Map() }));
|
|
115
|
+
return s;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Type of the value an expression obviously constructs: `Foo{..}`, `&Foo{..}`,
|
|
120
|
+
* `new(Foo)`, and the constructor convention `NewFoo(..)` / `pkg.NewFoo(..)`.
|
|
121
|
+
* Other calls are not guessed.
|
|
122
|
+
*/
|
|
123
|
+
function valueType(value: Node | null | undefined, qualified = false): string | null {
|
|
124
|
+
if (!value) return null;
|
|
125
|
+
const nameOf = qualified ? hintType : typeName;
|
|
126
|
+
if (value.type === 'unary_expression') {
|
|
127
|
+
value = field(value, 'operand');
|
|
128
|
+
if (!value || value.type !== 'composite_literal') return null;
|
|
129
|
+
}
|
|
130
|
+
if (value.type === 'composite_literal') return nameOf(field(value, 'type'));
|
|
131
|
+
if (value.type !== 'call_expression') return null;
|
|
132
|
+
let fn = field(value, 'function');
|
|
133
|
+
if (!fn) return null;
|
|
134
|
+
let pkg = '';
|
|
135
|
+
if (fn.type === 'selector_expression') {
|
|
136
|
+
const operand = field(fn, 'operand');
|
|
137
|
+
if (qualified && operand && operand.type === 'identifier') pkg = operand.text + '.';
|
|
138
|
+
fn = field(fn, 'field');
|
|
139
|
+
}
|
|
140
|
+
if (!fn || (fn.type !== 'identifier' && fn.type !== 'field_identifier')) return null;
|
|
141
|
+
const name = fn.text;
|
|
142
|
+
if (name === 'new') return nameOf(field(value, 'arguments')?.namedChildren[0]);
|
|
143
|
+
const m = /^New([A-Z]\w*)$/.exec(name);
|
|
144
|
+
return m ? pkg + m[1]! : null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Parameters, method receivers and named results: `a, b Foo`, `s *Site`. */
|
|
148
|
+
const parameterDecl: Visitor = (node, ctx) => {
|
|
149
|
+
const owner = node.parent?.parent?.type;
|
|
150
|
+
if (owner !== 'function_declaration' && owner !== 'method_declaration' && owner !== 'func_literal') return;
|
|
151
|
+
const type = hintType(field(node, 'type'));
|
|
152
|
+
if (!type) return;
|
|
153
|
+
for (const n of node.childrenForFieldName('name')) {
|
|
154
|
+
if (n) ctx.typeHint(n.text, type);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/** `x := Foo{}`, `a, b := &Foo{}, new(Bar)` */
|
|
159
|
+
const shortVarDecl: Visitor = (node, ctx) => {
|
|
160
|
+
const left = field(node, 'left');
|
|
161
|
+
const right = field(node, 'right');
|
|
162
|
+
if (!left || !right || left.namedChildCount !== right.namedChildCount) return;
|
|
163
|
+
for (let i = 0; i < left.namedChildCount; i++) {
|
|
164
|
+
const type = valueType(right.namedChildren[i], true);
|
|
165
|
+
if (!type) continue;
|
|
166
|
+
const name = left.namedChildren[i];
|
|
167
|
+
if (name && name.type === 'identifier') ctx.typeHint(name.text, type);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/** Type hints of `var x Foo` / `var x = Foo{}` (locals and package level). */
|
|
172
|
+
function varHints(node: Node, names: Node[], ctx: Ctx): void {
|
|
173
|
+
const declared = field(node, 'type');
|
|
174
|
+
if (declared) {
|
|
175
|
+
const type = hintType(declared);
|
|
176
|
+
if (type) for (const n of names) ctx.typeHint(n.text, type);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const values = field(node, 'value');
|
|
180
|
+
if (!values || values.namedChildCount !== names.length) return;
|
|
181
|
+
for (let i = 0; i < names.length; i++) {
|
|
182
|
+
const type = valueType(values.namedChildren[i], true);
|
|
183
|
+
if (type) ctx.typeHint(names[i]!.text, type);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Record `type implements iface` now, or when the type is declared later in the file. */
|
|
188
|
+
function assertImplements(type: string, iface: string, ctx: Ctx): void {
|
|
189
|
+
if (type === iface) return;
|
|
190
|
+
const st = state(ctx);
|
|
191
|
+
const sym = st.types.get(type);
|
|
192
|
+
if (sym !== undefined) {
|
|
193
|
+
ctx.inherit(sym, iface, 'implements');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const list = st.asserted.get(type);
|
|
197
|
+
if (list) list.push(iface);
|
|
198
|
+
else st.asserted.set(type, [iface]);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** `(*Engine)(nil)`, `&Engine{}`, `Engine{}`, `new(Engine)` -> Engine */
|
|
202
|
+
function assertedType(value: Node | null | undefined): string | null {
|
|
203
|
+
if (!value) return null;
|
|
204
|
+
if (value.type === 'call_expression') {
|
|
205
|
+
let fn = field(value, 'function');
|
|
206
|
+
if (fn && fn.type === 'parenthesized_expression') {
|
|
207
|
+
fn = fn.namedChildren[0] ?? null;
|
|
208
|
+
if (fn && fn.type === 'unary_expression') fn = field(fn, 'operand');
|
|
209
|
+
if (fn && fn.type === 'selector_expression') fn = field(fn, 'field');
|
|
210
|
+
return fn && (fn.type === 'identifier' || fn.type === 'field_identifier') ? fn.text : null;
|
|
211
|
+
}
|
|
212
|
+
return fn && fn.type === 'identifier' && fn.text === 'new' ? valueType(value) : null;
|
|
213
|
+
}
|
|
214
|
+
if (value.type === 'type_conversion_expression') return typeName(field(value, 'type'));
|
|
215
|
+
return value.type === 'unary_expression' || value.type === 'composite_literal' ? valueType(value) : null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const functionDecl: Visitor = (node, ctx) => {
|
|
219
|
+
const name = field(node, 'name');
|
|
220
|
+
if (!name) return;
|
|
221
|
+
ctx.def(node, { name: name.text, kind: 'function', exported: isExported(name.text) });
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const methodDecl: Visitor = (node, ctx) => {
|
|
225
|
+
const name = field(node, 'name');
|
|
226
|
+
if (!name) return;
|
|
227
|
+
let recv: Node | null | undefined = field(node, 'receiver');
|
|
228
|
+
recv = recv?.namedChildren.find(c => c && c.type === 'parameter_declaration');
|
|
229
|
+
ctx.def(node, {
|
|
230
|
+
name: name.text,
|
|
231
|
+
kind: 'method',
|
|
232
|
+
qualifier: typeName(field(recv, 'type')),
|
|
233
|
+
exported: isExported(name.text),
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
function structFields(struct: Node, sym: number, own: string, ctx: Ctx): void {
|
|
238
|
+
const list = struct.namedChildren.find(c => c && c.type === 'field_declaration_list');
|
|
239
|
+
if (!list) return;
|
|
240
|
+
for (const fd of list.namedChildren) {
|
|
241
|
+
if (!fd || fd.type !== 'field_declaration') continue;
|
|
242
|
+
const names = fd.childrenForFieldName('name');
|
|
243
|
+
if (names.length === 0) {
|
|
244
|
+
// Embedded field: `Base`, `*Base`, `pkg.Other`, `Gen[T]`
|
|
245
|
+
const type = field(fd, 'type');
|
|
246
|
+
const name = typeName(type);
|
|
247
|
+
if (!name) continue;
|
|
248
|
+
ctx.def(fd, { name, kind: 'field', body: null, scope: false, exported: isExported(name) });
|
|
249
|
+
ctx.typeHint(name, hintType(type) ?? name);
|
|
250
|
+
// `type ResponseWriter struct { http.ResponseWriter }` does not extend itself.
|
|
251
|
+
// The package qualifier is kept (`page.ContentProvider`): it tells same-named types apart.
|
|
252
|
+
if (name !== own || !isQualified(type)) ctx.inherit(sym, hintType(type) ?? name, 'extends');
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const type = hintType(field(fd, 'type'));
|
|
256
|
+
for (const n of names) {
|
|
257
|
+
if (!n) continue;
|
|
258
|
+
ctx.def(fd, { name: n.text, kind: 'field', body: null, scope: false, exported: isExported(n.text) });
|
|
259
|
+
if (type) ctx.typeHint(n.text, type);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function interfaceMembers(iface: Node, sym: number, own: string, ctx: Ctx): void {
|
|
265
|
+
// `type ResponseWriter interface { http.ResponseWriter }` does not extend itself.
|
|
266
|
+
const embed = (t: Node): void => {
|
|
267
|
+
const name = typeName(t);
|
|
268
|
+
if (name && (name !== own || !isQualified(t))) ctx.inherit(sym, hintType(t) ?? name, 'extends');
|
|
269
|
+
};
|
|
270
|
+
for (const m of iface.namedChildren) {
|
|
271
|
+
if (!m) continue;
|
|
272
|
+
if (m.type === 'method_elem' || m.type === 'method_spec') {
|
|
273
|
+
const name = field(m, 'name');
|
|
274
|
+
if (name) ctx.def(m, { name: name.text, kind: 'method', body: null, scope: false, exported: isExported(name.text) });
|
|
275
|
+
} else if (m.type === 'type_elem' || m.type === 'constraint_elem') {
|
|
276
|
+
// A single named type is an embedded interface; unions / ~T are constraints.
|
|
277
|
+
if (m.namedChildCount !== 1) continue;
|
|
278
|
+
const t = m.namedChildren[0];
|
|
279
|
+
if (t && t.type !== 'negated_type') embed(t);
|
|
280
|
+
} else if (m.type === 'type_identifier' || m.type === 'qualified_type' || m.type === 'generic_type') {
|
|
281
|
+
embed(m);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Remember a type symbol and apply interface assertions seen before its declaration. */
|
|
287
|
+
function registerType(name: string, sym: number, ctx: Ctx): void {
|
|
288
|
+
if (ctx.inCallable()) return; // local type
|
|
289
|
+
const st = state(ctx);
|
|
290
|
+
if (!st.types.has(name)) st.types.set(name, sym);
|
|
291
|
+
const pending = st.asserted.get(name);
|
|
292
|
+
if (!pending) return;
|
|
293
|
+
st.asserted.delete(name);
|
|
294
|
+
for (const iface of pending) ctx.inherit(sym, iface, 'implements');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const typeSpec: Visitor = (node, ctx) => {
|
|
298
|
+
const nameNode = field(node, 'name');
|
|
299
|
+
if (!nameNode) return;
|
|
300
|
+
const name = nameNode.text;
|
|
301
|
+
const type = field(node, 'type');
|
|
302
|
+
const range = specRange(node);
|
|
303
|
+
const prefix = range === node ? 'type ' : '';
|
|
304
|
+
const exported = isExported(name) && !ctx.inCallable();
|
|
305
|
+
|
|
306
|
+
if (type && node.type === 'type_spec' && (type.type === 'struct_type' || type.type === 'interface_type')) {
|
|
307
|
+
const isStruct = type.type === 'struct_type';
|
|
308
|
+
const sig = prefix + ctx.src.slice(range.startIndex, type.startIndex) + (isStruct ? 'struct' : 'interface');
|
|
309
|
+
const sym = ctx.def(node, { name, kind: isStruct ? 'struct' : 'interface', range, sig, exported });
|
|
310
|
+
if (isStruct) {
|
|
311
|
+
registerType(name, sym, ctx);
|
|
312
|
+
structFields(type, sym, name, ctx);
|
|
313
|
+
} else {
|
|
314
|
+
interfaceMembers(type, sym, name, ctx);
|
|
315
|
+
}
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const sym = ctx.def(node, { name, kind: 'type', range, sig: prefix + slice(ctx, range.startIndex, range.endIndex), exported });
|
|
319
|
+
if (node.type === 'type_spec') registerType(name, sym, ctx);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/** Package-level `const` / `var` specs. Locals are never symbols. */
|
|
323
|
+
const valueSpec = (kind: 'constant' | 'variable'): Visitor => (node, ctx) => {
|
|
324
|
+
let decl = node.parent;
|
|
325
|
+
if (decl && decl.type === 'var_spec_list') decl = decl.parent;
|
|
326
|
+
const names = node.childrenForFieldName('name').filter((n): n is Node => !!n);
|
|
327
|
+
if (names.length === 0) return;
|
|
328
|
+
if (kind === 'variable') varHints(node, names, ctx);
|
|
329
|
+
if (!decl || decl.parent?.type !== 'source_file') return;
|
|
330
|
+
const range = specRange(node);
|
|
331
|
+
const prefix = range === node ? (kind === 'constant' ? 'const ' : 'var ') : '';
|
|
332
|
+
|
|
333
|
+
const values = field(node, 'value');
|
|
334
|
+
const value = names.length === 1 && values && values.namedChildCount === 1 ? values.namedChildren[0] : null;
|
|
335
|
+
if (kind === 'variable' && value && names[0]!.text === '_') {
|
|
336
|
+
// Interface satisfaction assertion: `var _ IRouter = (*Engine)(nil)` / `= &Engine{}`
|
|
337
|
+
const iface = typeName(field(node, 'type'));
|
|
338
|
+
const type = iface ? assertedType(value) : null;
|
|
339
|
+
if (iface && type && !BUILTINS.has(iface)) assertImplements(type, hintType(field(node, 'type')) ?? iface, ctx);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (value && value.type === 'func_literal' && names[0]) {
|
|
343
|
+
const name = names[0].text;
|
|
344
|
+
const body = field(value, 'body');
|
|
345
|
+
const sig = prefix + slice(ctx, range.startIndex, body ? body.startIndex : range.endIndex);
|
|
346
|
+
ctx.def(node, { name, kind: 'function', range, sig, exported: isExported(name) });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
// Cut composite literals (`var table = map[string]int{ ... }`) at the literal body.
|
|
350
|
+
const lit = value && value.type === 'composite_literal' ? field(value, 'body') : null;
|
|
351
|
+
const sig = prefix + slice(ctx, range.startIndex, lit ? lit.startIndex : range.endIndex);
|
|
352
|
+
for (const n of names) {
|
|
353
|
+
if (!n || n.text === '_') continue;
|
|
354
|
+
ctx.def(node, { name: n.text, kind, range, sig, scope: false, exported: isExported(n.text) });
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
function recordCall(fn: Node | null, node: Node, ctx: Ctx): void {
|
|
359
|
+
for (let i = 0; fn && i < 4; i++) {
|
|
360
|
+
switch (fn.type) {
|
|
361
|
+
case 'identifier':
|
|
362
|
+
case 'type_identifier': {
|
|
363
|
+
const name = fn.text;
|
|
364
|
+
if (!BUILTINS.has(name)) ctx.call(name, node);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
case 'selector_expression':
|
|
368
|
+
// Always a receiver ('?' for call chains etc.): a member call is never a bare call.
|
|
369
|
+
ctx.call(field(fn, 'field')?.text, node, simpleReceiver(field(fn, 'operand')) ?? '?');
|
|
370
|
+
return;
|
|
371
|
+
case 'qualified_type':
|
|
372
|
+
ctx.call(field(fn, 'name')?.text, node, field(fn, 'package')?.text);
|
|
373
|
+
return;
|
|
374
|
+
case 'index_expression': // generic instantiation `f[T](x)`
|
|
375
|
+
fn = field(fn, 'operand');
|
|
376
|
+
break;
|
|
377
|
+
case 'type_instantiation_expression':
|
|
378
|
+
case 'generic_type':
|
|
379
|
+
fn = field(fn, 'type');
|
|
380
|
+
break;
|
|
381
|
+
default:
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const callExpression: Visitor = (node, ctx) => {
|
|
388
|
+
recordCall(field(node, 'function'), node, ctx);
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
/** `helper[int](1)` is parsed as a conversion to the generic type `helper[int]`. */
|
|
392
|
+
const typeConversion: Visitor = (node, ctx) => {
|
|
393
|
+
const type = field(node, 'type');
|
|
394
|
+
if (type && type.type === 'generic_type') recordCall(type, node, ctx);
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
/** `Server{...}`, `pkg.Thing{...}`, `List[int]{...}` use the type. */
|
|
398
|
+
const compositeLiteral: Visitor = (node, ctx) => {
|
|
399
|
+
const type = field(node, 'type');
|
|
400
|
+
if (!type) return;
|
|
401
|
+
if (type.type === 'type_identifier' || type.type === 'qualified_type' || type.type === 'generic_type') {
|
|
402
|
+
recordCall(type, node, ctx);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const importSpec: Visitor = (node, ctx) => {
|
|
407
|
+
const path = field(node, 'path');
|
|
408
|
+
if (!path) return;
|
|
409
|
+
const source = stripQuotes(path.text);
|
|
410
|
+
const alias = field(node, 'name');
|
|
411
|
+
const name = alias && alias.type === 'package_identifier' ? alias.text : source.slice(source.lastIndexOf('/') + 1);
|
|
412
|
+
ctx.imp(source, node, name ? [name] : []);
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
export const go: LangSpec = {
|
|
416
|
+
id: 'go',
|
|
417
|
+
lowercaseTypes: true,
|
|
418
|
+
packageTypes: true,
|
|
419
|
+
grammar: 'go',
|
|
420
|
+
extensions: ['.go'],
|
|
421
|
+
identifiers: ['identifier', 'type_identifier', 'field_identifier', 'package_identifier'],
|
|
422
|
+
comments: ['comment'],
|
|
423
|
+
visit: {
|
|
424
|
+
function_declaration: functionDecl,
|
|
425
|
+
method_declaration: methodDecl,
|
|
426
|
+
type_spec: typeSpec,
|
|
427
|
+
type_alias: typeSpec,
|
|
428
|
+
const_spec: valueSpec('constant'),
|
|
429
|
+
var_spec: valueSpec('variable'),
|
|
430
|
+
call_expression: callExpression,
|
|
431
|
+
type_conversion_expression: typeConversion,
|
|
432
|
+
composite_literal: compositeLiteral,
|
|
433
|
+
import_spec: importSpec,
|
|
434
|
+
parameter_declaration: parameterDecl,
|
|
435
|
+
short_var_declaration: shortVarDecl,
|
|
436
|
+
},
|
|
437
|
+
};
|