clast-ai 0.1.2 → 0.2.0
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 +447 -401
- 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 +257 -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/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 +50 -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 +256 -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/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 +7 -4
- 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
package/dist/parser/extractor.js
CHANGED
|
@@ -1,254 +1,726 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
'export_statement',
|
|
32
|
-
'export_declaration',
|
|
33
|
-
]);
|
|
34
|
-
const ENUM_TYPES = new Set([
|
|
35
|
-
'enum_declaration',
|
|
36
|
-
'enum_item', // Rust
|
|
37
|
-
'enum_specifier', // C/C++
|
|
38
|
-
]);
|
|
39
|
-
const CALL_TYPES = new Set([
|
|
40
|
-
'call_expression',
|
|
41
|
-
'new_expression',
|
|
42
|
-
'method_invocation', // Java
|
|
43
|
-
'invocation_expression', // C#
|
|
44
|
-
]);
|
|
45
|
-
function classifyNodeType(treeSitterType) {
|
|
46
|
-
if (FUNCTION_TYPES.has(treeSitterType))
|
|
47
|
-
return 'function';
|
|
48
|
-
if (CLASS_TYPES.has(treeSitterType))
|
|
49
|
-
return 'class';
|
|
50
|
-
if (INTERFACE_TYPES.has(treeSitterType))
|
|
51
|
-
return 'interface';
|
|
52
|
-
if (IMPORT_TYPES.has(treeSitterType))
|
|
53
|
-
return 'import';
|
|
54
|
-
if (EXPORT_TYPES.has(treeSitterType))
|
|
55
|
-
return 'export';
|
|
56
|
-
if (ENUM_TYPES.has(treeSitterType))
|
|
57
|
-
return 'enum';
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
function extractName(node) {
|
|
61
|
-
const nameNode = node.childForFieldName('name') ??
|
|
62
|
-
node.childForFieldName('declarator')?.childForFieldName('name') ??
|
|
63
|
-
node.childForFieldName('declarator');
|
|
64
|
-
if (nameNode) {
|
|
65
|
-
return nameNode.text;
|
|
66
|
-
}
|
|
67
|
-
// Arrow functions assigned to variables
|
|
68
|
-
if (node.type === 'arrow_function' && node.parent) {
|
|
69
|
-
const parentName = node.parent.childForFieldName('name');
|
|
70
|
-
if (parentName)
|
|
71
|
-
return parentName.text;
|
|
72
|
-
}
|
|
73
|
-
// Exports
|
|
74
|
-
if (node.type === 'export_statement') {
|
|
75
|
-
const decl = node.childForFieldName('declaration');
|
|
76
|
-
if (decl)
|
|
77
|
-
return extractName(decl);
|
|
78
|
-
return '<export>';
|
|
79
|
-
}
|
|
80
|
-
// Imports
|
|
81
|
-
if (IMPORT_TYPES.has(node.type)) {
|
|
82
|
-
const source = node.childForFieldName('source') ?? node.childForFieldName('path');
|
|
83
|
-
if (source)
|
|
84
|
-
return source.text.replace(/['"]/g, '');
|
|
85
|
-
return node.text.slice(0, 80);
|
|
86
|
-
}
|
|
87
|
-
return '<anonymous>';
|
|
88
|
-
}
|
|
89
|
-
function extractSignature(node, maxLength = 200) {
|
|
90
|
-
const text = node.text;
|
|
91
|
-
const braceIdx = text.indexOf('{');
|
|
92
|
-
const parenCloseIdx = text.indexOf(')');
|
|
93
|
-
let end = text.length;
|
|
94
|
-
if (braceIdx > 0) {
|
|
95
|
-
end = braceIdx;
|
|
96
|
-
}
|
|
97
|
-
else if (parenCloseIdx > 0) {
|
|
98
|
-
end = parenCloseIdx + 1;
|
|
99
|
-
}
|
|
100
|
-
const sig = text.slice(0, end).trim();
|
|
101
|
-
return sig.length > maxLength ? sig.slice(0, maxLength) + '...' : sig;
|
|
102
|
-
}
|
|
103
|
-
function extractPrecedingComment(node, source) {
|
|
104
|
-
const lines = source.split('\n');
|
|
105
|
-
const nodeLine = node.startPosition.row;
|
|
106
|
-
const commentLines = [];
|
|
107
|
-
let checkLine = nodeLine - 1;
|
|
108
|
-
while (checkLine >= 0) {
|
|
109
|
-
const line = lines[checkLine]?.trim() ?? '';
|
|
110
|
-
if (line.startsWith('//') ||
|
|
111
|
-
line.startsWith('#') ||
|
|
112
|
-
line.startsWith('*') ||
|
|
113
|
-
line.startsWith('/*') ||
|
|
114
|
-
line.startsWith('///') ||
|
|
115
|
-
line.startsWith('/**')) {
|
|
116
|
-
commentLines.unshift(line);
|
|
117
|
-
checkLine--;
|
|
1
|
+
import { CONTAINER_KINDS } from '../types.js';
|
|
2
|
+
import { genericArity, typeName } from './spec.js';
|
|
3
|
+
import { returnTypeOf } from './returns.js';
|
|
4
|
+
const MAX_SIGNATURE = 240;
|
|
5
|
+
const MAX_DOC = 600;
|
|
6
|
+
const MAX_OCC_LINES = 40;
|
|
7
|
+
const MAX_NAME = 120;
|
|
8
|
+
export function compileSpec(spec, language) {
|
|
9
|
+
const count = language.nodeTypeCount;
|
|
10
|
+
const visitors = new Array(count);
|
|
11
|
+
const isIdent = new Uint8Array(count);
|
|
12
|
+
const isComment = new Uint8Array(count);
|
|
13
|
+
const idsByName = new Map();
|
|
14
|
+
for (let id = 0; id < count; id++) {
|
|
15
|
+
if (!language.nodeTypeIsNamed(id))
|
|
16
|
+
continue;
|
|
17
|
+
const name = language.nodeTypeForId(id);
|
|
18
|
+
if (!name)
|
|
19
|
+
continue;
|
|
20
|
+
let list = idsByName.get(name);
|
|
21
|
+
if (!list)
|
|
22
|
+
idsByName.set(name, (list = []));
|
|
23
|
+
list.push(id);
|
|
24
|
+
}
|
|
25
|
+
const unknownTypes = [];
|
|
26
|
+
for (const [type, visitor] of Object.entries(spec.visit)) {
|
|
27
|
+
const ids = idsByName.get(type);
|
|
28
|
+
if (!ids) {
|
|
29
|
+
unknownTypes.push(type);
|
|
30
|
+
continue;
|
|
118
31
|
}
|
|
119
|
-
|
|
120
|
-
|
|
32
|
+
for (const id of ids)
|
|
33
|
+
visitors[id] = visitor;
|
|
34
|
+
}
|
|
35
|
+
for (const type of spec.identifiers) {
|
|
36
|
+
const ids = idsByName.get(type);
|
|
37
|
+
if (!ids) {
|
|
38
|
+
unknownTypes.push(type);
|
|
39
|
+
continue;
|
|
121
40
|
}
|
|
122
|
-
|
|
123
|
-
|
|
41
|
+
for (const id of ids)
|
|
42
|
+
isIdent[id] = 1;
|
|
43
|
+
}
|
|
44
|
+
const commentTypes = spec.comments ?? [...idsByName.keys()].filter(n => n.includes('comment'));
|
|
45
|
+
for (const type of commentTypes) {
|
|
46
|
+
for (const id of idsByName.get(type) ?? [])
|
|
47
|
+
isComment[id] = 1;
|
|
48
|
+
}
|
|
49
|
+
const isDocSkip = new Uint8Array(count);
|
|
50
|
+
for (const type of spec.docSkip ?? []) {
|
|
51
|
+
const ids = idsByName.get(type);
|
|
52
|
+
if (!ids) {
|
|
53
|
+
unknownTypes.push(type);
|
|
54
|
+
continue;
|
|
124
55
|
}
|
|
56
|
+
for (const id of ids)
|
|
57
|
+
isDocSkip[id] = 1;
|
|
125
58
|
}
|
|
126
|
-
|
|
127
|
-
return null;
|
|
128
|
-
const cleaned = commentLines
|
|
129
|
-
.map(l => l.replace(/^\/\/\/?\s?|^\/\*\*?\s?|^\*\/?\s?|^#\s?/g, '').trim())
|
|
130
|
-
.filter(l => l.length > 0)
|
|
131
|
-
.join('\n');
|
|
132
|
-
return cleaned || null;
|
|
59
|
+
return { spec, language, visitors, isIdent, isComment, isDocSkip, unknownTypes };
|
|
133
60
|
}
|
|
134
|
-
function
|
|
135
|
-
|
|
136
|
-
|
|
61
|
+
export function cleanComment(raw) {
|
|
62
|
+
let s = raw.trim();
|
|
63
|
+
// Block comments: /** ... */, /* ... */, """ ... """, =begin ... =end
|
|
64
|
+
s = s.replace(/^\/\*+!?/, '').replace(/\*+\/$/, '');
|
|
65
|
+
const lines = s.split(/\r?\n/).map(l => l
|
|
66
|
+
.replace(/^\s*(\/\/[\/!]?|#+!?|--+|\*+(?!\/)|;+)\s?/, '')
|
|
67
|
+
.replace(/\s+$/, ''));
|
|
68
|
+
while (lines.length && !lines[0].trim())
|
|
69
|
+
lines.shift();
|
|
70
|
+
while (lines.length && !lines[lines.length - 1].trim())
|
|
71
|
+
lines.pop();
|
|
72
|
+
return lines.join('\n').trim();
|
|
73
|
+
}
|
|
74
|
+
const VALUE_WRAPPERS = new Set([
|
|
75
|
+
'await_expression', 'try_expression', 'parenthesized_expression', 'non_null_expression', 'as_expression',
|
|
76
|
+
'expression_list', 'equals_value_clause', 'unary_expression', 'reference_expression', 'type_cast_expression',
|
|
77
|
+
]);
|
|
78
|
+
const BINDING_NODES = new Set([
|
|
79
|
+
'variable_declarator', 'assignment_expression', 'assignment', 'short_var_declaration', 'let_declaration',
|
|
80
|
+
'init_declarator', 'var_spec', 'local_variable_declaration', 'const_spec', 'augmented_assignment',
|
|
81
|
+
'property_declaration', 'initialized_variable_definition', // Swift, Dart
|
|
82
|
+
]);
|
|
83
|
+
/**
|
|
84
|
+
* If `call` is the whole right-hand side of a simple variable binding
|
|
85
|
+
* (`x = f()`, `let x = f()?`, `x, err := f()`, `auto x = f()`), the name of
|
|
86
|
+
* that variable; otherwise null. Works across grammars by looking at the
|
|
87
|
+
* usual field names.
|
|
88
|
+
*/
|
|
89
|
+
function boundVariable(call) {
|
|
90
|
+
let node = call;
|
|
91
|
+
let p = call.parent;
|
|
92
|
+
for (let i = 0; p && i < 3 && VALUE_WRAPPERS.has(p.type); i++) {
|
|
93
|
+
node = p;
|
|
94
|
+
p = p.parent;
|
|
95
|
+
}
|
|
96
|
+
if (!p || !BINDING_NODES.has(p.type))
|
|
137
97
|
return null;
|
|
138
|
-
const
|
|
139
|
-
|
|
98
|
+
const target = p.childForFieldName('name') ?? p.childForFieldName('left') ?? p.childForFieldName('pattern') ??
|
|
99
|
+
p.childForFieldName('declarator');
|
|
100
|
+
if (!target || target.endIndex > node.startIndex)
|
|
140
101
|
return null;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return
|
|
145
|
-
|
|
146
|
-
return null;
|
|
102
|
+
// Go: `x, err := f()` - the first name receives the first result.
|
|
103
|
+
const first = target.type === 'expression_list' ? target.namedChild(0) : target;
|
|
104
|
+
if (!first || first.endIndex - first.startIndex > 60)
|
|
105
|
+
return null;
|
|
106
|
+
const text = first.text.replace(/^[$@*&]+/, '').replace(/^mut\s+/, '');
|
|
107
|
+
return /^[A-Za-z_]\w*$/.test(text) ? text : null;
|
|
147
108
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
109
|
+
const ARGUMENT_LISTS = new Set(['argument_list', 'arguments', 'value_arguments']);
|
|
110
|
+
/** Languages where a bare name inside a method can be a field of the enclosing class. */
|
|
111
|
+
const IMPLICIT_THIS_SPECS = new Set(['java', 'csharp', 'cpp', 'c', 'swift', 'dart', 'kotlin']);
|
|
112
|
+
/** Number of arguments of a call node, or -1 when it cannot be counted (spread, no argument list). */
|
|
113
|
+
function argCount(node) {
|
|
114
|
+
// Flat grammars (Dart) hand in synthetic call spans that are not real nodes.
|
|
115
|
+
if (typeof node.childForFieldName !== 'function' || !Array.isArray(node.namedChildren))
|
|
116
|
+
return -1;
|
|
117
|
+
let args = node.childForFieldName('arguments');
|
|
118
|
+
if (!args) {
|
|
119
|
+
for (const c of node.namedChildren) {
|
|
120
|
+
if (c && ARGUMENT_LISTS.has(c.type)) {
|
|
121
|
+
args = c;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
158
124
|
}
|
|
159
|
-
return null;
|
|
160
125
|
}
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
|
|
126
|
+
if (!args || !ARGUMENT_LISTS.has(args.type))
|
|
127
|
+
return -1;
|
|
128
|
+
let n = 0;
|
|
129
|
+
for (const c of args.namedChildren) {
|
|
130
|
+
if (!c || c.type.includes('comment'))
|
|
131
|
+
continue;
|
|
132
|
+
// f(*args), f(...xs), f(xs...): the count is not known statically.
|
|
133
|
+
if (/splat|spread|variadic/.test(c.type))
|
|
134
|
+
return -1;
|
|
135
|
+
n++;
|
|
164
136
|
}
|
|
165
|
-
return
|
|
137
|
+
return Math.min(n, 99);
|
|
138
|
+
}
|
|
139
|
+
function squash(s, max) {
|
|
140
|
+
const t = s.replace(/\s+/g, ' ').trim();
|
|
141
|
+
return t.length > max ? t.slice(0, max - 3) + '...' : t;
|
|
166
142
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Walk a parsed tree and extract symbols, calls, imports, heritage and
|
|
145
|
+
* identifier occurrences according to the language spec.
|
|
146
|
+
*/
|
|
147
|
+
export function extract(tree, source, compiled) {
|
|
148
|
+
const { visitors, isIdent, isComment, isDocSkip } = compiled;
|
|
149
|
+
const symbols = [];
|
|
150
|
+
const imports = [];
|
|
151
|
+
const heritage = [];
|
|
152
|
+
const callMap = new Map();
|
|
153
|
+
const occ = new Map();
|
|
154
|
+
const scopes = [];
|
|
155
|
+
const containerDefs = new Map();
|
|
156
|
+
/** Outer calls of a chain waiting for the call that produces their receiver, by start offset. */
|
|
157
|
+
const chainOuters = new Map();
|
|
158
|
+
const viaRef = new Map();
|
|
159
|
+
/** Variables initialised from a call: owner symbol index -> variable name -> that call. */
|
|
160
|
+
const callHints = new Map();
|
|
161
|
+
let defaultExport = null;
|
|
162
|
+
/** Declared types: owner symbol index (-1 = file) -> variable/field name -> type name. */
|
|
163
|
+
const hints = new Map();
|
|
164
|
+
function addOcc(name, line) {
|
|
165
|
+
const lines = occ.get(name);
|
|
166
|
+
if (!lines)
|
|
167
|
+
occ.set(name, [line]);
|
|
168
|
+
else if (lines.length < MAX_OCC_LINES && lines[lines.length - 1] !== line)
|
|
169
|
+
lines.push(line);
|
|
170
|
+
}
|
|
171
|
+
function currentCaller() {
|
|
172
|
+
for (let i = scopes.length - 1; i >= 0; i--) {
|
|
173
|
+
const s = scopes[i];
|
|
174
|
+
if (s.sym >= 0 && s.callScope)
|
|
175
|
+
return s.sym;
|
|
180
176
|
}
|
|
181
|
-
return
|
|
177
|
+
return -1;
|
|
182
178
|
}
|
|
183
|
-
function
|
|
184
|
-
const
|
|
185
|
-
let
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
endCol: node.endPosition.column,
|
|
201
|
-
parentId: null,
|
|
202
|
-
language,
|
|
203
|
-
bodyText,
|
|
204
|
-
};
|
|
205
|
-
currentIndex = nodes.length;
|
|
206
|
-
nodes.push(astNode);
|
|
207
|
-
nodeIdToIndex.set(node.id, currentIndex);
|
|
208
|
-
const comment = language === 'python'
|
|
209
|
-
? (extractPythonDocstring(node) ?? extractPrecedingComment(node, source))
|
|
210
|
-
: extractPrecedingComment(node, source);
|
|
211
|
-
if (comment) {
|
|
212
|
-
comments.push({ nodeIndex: currentIndex, text: comment, source: 'original' });
|
|
179
|
+
function leadingDoc(range) {
|
|
180
|
+
const parts = [];
|
|
181
|
+
let line = 0;
|
|
182
|
+
let nextRow = range.startPosition.row;
|
|
183
|
+
let prev = range.previousSibling;
|
|
184
|
+
if (!prev) {
|
|
185
|
+
// First node of a wrapper (e.g. Ruby body_statement): the comment sits before the wrapper.
|
|
186
|
+
const p = range.parent;
|
|
187
|
+
if (p && p.startIndex === range.startIndex)
|
|
188
|
+
prev = p.previousSibling;
|
|
189
|
+
}
|
|
190
|
+
while (prev) {
|
|
191
|
+
if (isDocSkip[prev.typeId]) {
|
|
192
|
+
// Attributes / annotations between the doc comment and the item.
|
|
193
|
+
nextRow = prev.startPosition.row;
|
|
194
|
+
prev = prev.previousSibling;
|
|
195
|
+
continue;
|
|
213
196
|
}
|
|
197
|
+
if (!isComment[prev.typeId])
|
|
198
|
+
break;
|
|
199
|
+
if (prev.endPosition.row < nextRow - 1)
|
|
200
|
+
break;
|
|
201
|
+
// A comment trailing a statement on the same line documents that statement.
|
|
202
|
+
// (Preprocessor nodes end at column 0 of the next line: not trailing.)
|
|
203
|
+
const before = prev.previousSibling;
|
|
204
|
+
if (before && !isComment[before.typeId] &&
|
|
205
|
+
before.endPosition.row === prev.startPosition.row && before.endPosition.column > 0)
|
|
206
|
+
break;
|
|
207
|
+
parts.unshift(prev.text);
|
|
208
|
+
nextRow = prev.startPosition.row;
|
|
209
|
+
line = nextRow + 1;
|
|
210
|
+
if (parts.length >= 200)
|
|
211
|
+
break;
|
|
212
|
+
prev = prev.previousSibling;
|
|
214
213
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
214
|
+
if (parts.length === 0)
|
|
215
|
+
return { doc: '', line: 0, returns: null };
|
|
216
|
+
let doc = parts.map(cleanComment).join('\n').trim();
|
|
217
|
+
// Documented return type (PHPDoc / JSDoc): taken before the text is truncated.
|
|
218
|
+
const returns = /@returns?\s+\{?([^\s}|]+)/.exec(doc)?.[1] ?? null;
|
|
219
|
+
if (doc.length > MAX_DOC)
|
|
220
|
+
doc = doc.slice(0, MAX_DOC - 3) + '...';
|
|
221
|
+
return { doc, line, returns };
|
|
222
|
+
}
|
|
223
|
+
const ctx = {
|
|
224
|
+
src: source,
|
|
225
|
+
def(node, o) {
|
|
226
|
+
let name = o.name;
|
|
227
|
+
if (!name)
|
|
228
|
+
name = '<anonymous>';
|
|
229
|
+
if (name.length > MAX_NAME)
|
|
230
|
+
name = name.slice(0, MAX_NAME);
|
|
231
|
+
const range = o.range ?? node;
|
|
232
|
+
const prefix = [];
|
|
233
|
+
let parent = -1;
|
|
234
|
+
let depth = scopes.length;
|
|
235
|
+
if (o.inContainer) {
|
|
236
|
+
// Attach to the nearest enclosing class-like scope instead of the method we are in.
|
|
237
|
+
depth = 0;
|
|
238
|
+
for (let i = scopes.length - 1; i >= 0; i--) {
|
|
239
|
+
const k = scopes[i].kind;
|
|
240
|
+
if (k !== null && CONTAINER_KINDS.has(k)) {
|
|
241
|
+
depth = i + 1;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
for (let i = 0; i < depth; i++) {
|
|
247
|
+
prefix.push(scopes[i].name);
|
|
248
|
+
}
|
|
249
|
+
for (let i = depth - 1; i >= 0; i--) {
|
|
250
|
+
if (scopes[i].sym >= 0) {
|
|
251
|
+
parent = scopes[i].sym;
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (o.qualifier)
|
|
256
|
+
prefix.push(...o.qualifier.split(/::|\./).filter(Boolean));
|
|
257
|
+
const qname = prefix.length ? prefix.join('.') + '.' + name : name;
|
|
258
|
+
let signature;
|
|
259
|
+
if (o.sig !== undefined) {
|
|
260
|
+
signature = squash(o.sig, MAX_SIGNATURE).replace(/\s*\{$/, '');
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
const body = o.body === undefined ? node.childForFieldName('body') : o.body;
|
|
264
|
+
const start = range.startIndex;
|
|
265
|
+
let end;
|
|
266
|
+
if (body && body.startIndex > start) {
|
|
267
|
+
end = body.startIndex;
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
end = range.endIndex;
|
|
271
|
+
// Without a body: short declarations in full, long ones (object
|
|
272
|
+
// literals, tables) only up to the end of their first line.
|
|
273
|
+
if (end - start > 200) {
|
|
274
|
+
const nl = source.indexOf('\n', start);
|
|
275
|
+
end = nl !== -1 && nl < end ? nl : Math.min(end, start + 200);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
signature = squash(source.slice(start, end), MAX_SIGNATURE).replace(/\s*[{=]$/, '');
|
|
279
|
+
}
|
|
280
|
+
let doc = '';
|
|
281
|
+
let docLine = 0;
|
|
282
|
+
if (o.doc !== undefined && o.doc !== null) {
|
|
283
|
+
doc = o.doc.length > MAX_DOC ? o.doc.slice(0, MAX_DOC - 3) + '...' : o.doc;
|
|
284
|
+
}
|
|
285
|
+
const lead = leadingDoc(range);
|
|
286
|
+
if (lead.line) {
|
|
287
|
+
docLine = lead.line;
|
|
288
|
+
if (!doc)
|
|
289
|
+
doc = lead.doc;
|
|
290
|
+
}
|
|
291
|
+
if (o.inContainer) {
|
|
292
|
+
// Instance attributes are assigned many times; keep the first.
|
|
293
|
+
const key = o.kind + ' ' + qname;
|
|
294
|
+
const seen = containerDefs.get(key);
|
|
295
|
+
if (seen !== undefined)
|
|
296
|
+
return seen;
|
|
297
|
+
containerDefs.set(key, symbols.length);
|
|
298
|
+
}
|
|
299
|
+
let ret = null;
|
|
300
|
+
if (o.kind === 'function' || o.kind === 'method' || o.kind === 'declaration') {
|
|
301
|
+
// Enclosing class for Self / static / this return types: the last qualifier segment.
|
|
302
|
+
const selfType = prefix.length ? prefix[prefix.length - 1].split('.').pop() : null;
|
|
303
|
+
ret = returnTypeOf(signature, name, compiled.spec.id, selfType, compiled.spec.lowercaseTypes === true);
|
|
304
|
+
if (!ret) {
|
|
305
|
+
// No declared type: fall back to the documented one (`@return $this`, `@returns {Foo}`).
|
|
306
|
+
const documented = lead.returns ?? /@returns?\s+\{?([^\s}|]+)/.exec(doc)?.[1] ?? null;
|
|
307
|
+
if (documented) {
|
|
308
|
+
ret = /^\\?(\$this|static|self|this)$/.test(documented)
|
|
309
|
+
? selfType
|
|
310
|
+
: typeName(documented, compiled.spec.lowercaseTypes === true);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const index = symbols.length;
|
|
315
|
+
symbols.push({
|
|
219
316
|
name,
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
317
|
+
qname,
|
|
318
|
+
kind: o.kind,
|
|
319
|
+
ret,
|
|
320
|
+
signature,
|
|
321
|
+
doc,
|
|
322
|
+
startLine: range.startPosition.row + 1,
|
|
323
|
+
// Nodes that swallow the trailing newline (preprocessor) end at column 0 of the next row.
|
|
324
|
+
endLine: range.endPosition.column === 0 && range.endPosition.row > range.startPosition.row
|
|
325
|
+
? range.endPosition.row
|
|
326
|
+
: range.endPosition.row + 1,
|
|
327
|
+
startCol: range.startPosition.column,
|
|
328
|
+
docLine,
|
|
329
|
+
parent,
|
|
330
|
+
exported: o.exported ?? false,
|
|
229
331
|
});
|
|
332
|
+
if (o.scope !== false) {
|
|
333
|
+
// The qualifier is part of the scope so nested definitions inherit it.
|
|
334
|
+
const scopeName = o.qualifier ? o.qualifier.split(/::|\./).filter(Boolean).concat(name).join('.') : name;
|
|
335
|
+
scopes.push({ sym: index, name: scopeName, kind: o.kind, callScope: true });
|
|
336
|
+
}
|
|
337
|
+
return index;
|
|
338
|
+
},
|
|
339
|
+
scope(name) {
|
|
340
|
+
if (!name)
|
|
341
|
+
return;
|
|
342
|
+
scopes.push({ sym: -1, name, kind: null, callScope: false });
|
|
343
|
+
},
|
|
344
|
+
exportDefault(name) {
|
|
345
|
+
// '*' = "whatever the required module exports" (`module.exports = require('./lib/x')`).
|
|
346
|
+
if (name === '*')
|
|
347
|
+
defaultExport ??= '*';
|
|
348
|
+
else if (name && name.length <= MAX_NAME && /^[\p{L}_$][\p{L}\p{N}_$]*$/u.test(name))
|
|
349
|
+
defaultExport = name;
|
|
350
|
+
},
|
|
351
|
+
ref(name, node) {
|
|
352
|
+
if (!name || name.length < 2 || name.length > MAX_NAME)
|
|
353
|
+
return;
|
|
354
|
+
addOcc(name, node.startPosition.row + 1);
|
|
355
|
+
},
|
|
356
|
+
typeHint(name, type, onContainer = false) {
|
|
357
|
+
if (!name || name.length > MAX_NAME)
|
|
358
|
+
return;
|
|
359
|
+
const t = typeName(type, compiled.spec.lowercaseTypes === true, compiled.spec.packageTypes === true);
|
|
360
|
+
if (!t)
|
|
361
|
+
return;
|
|
362
|
+
// Owner: nearest enclosing function/method, else nearest container, else the file.
|
|
363
|
+
let owner = -1;
|
|
364
|
+
for (let i = scopes.length - 1; i >= 0; i--) {
|
|
365
|
+
const s = scopes[i];
|
|
366
|
+
if (s.sym < 0 || s.kind === null)
|
|
367
|
+
continue;
|
|
368
|
+
const isContainer = CONTAINER_KINDS.has(s.kind);
|
|
369
|
+
if (isContainer || (!onContainer && (s.kind === 'function' || s.kind === 'method'))) {
|
|
370
|
+
owner = s.sym;
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
let map = hints.get(owner);
|
|
375
|
+
if (!map)
|
|
376
|
+
hints.set(owner, (map = new Map()));
|
|
377
|
+
if (!map.has(name))
|
|
378
|
+
map.set(name, t);
|
|
379
|
+
},
|
|
380
|
+
typeOf(name) {
|
|
381
|
+
if (!name)
|
|
382
|
+
return null;
|
|
383
|
+
for (let i = scopes.length - 1; i >= 0; i--) {
|
|
384
|
+
const s = scopes[i];
|
|
385
|
+
// Class-level hints are fields (reached through this / self), not variables in scope.
|
|
386
|
+
if (s.sym < 0 || (s.kind !== 'function' && s.kind !== 'method'))
|
|
387
|
+
continue;
|
|
388
|
+
const hit = hints.get(s.sym)?.get(name);
|
|
389
|
+
if (hit)
|
|
390
|
+
return hit;
|
|
391
|
+
}
|
|
392
|
+
return hints.get(-1)?.get(name) ?? null;
|
|
393
|
+
},
|
|
394
|
+
call(name, node, receiver = null, receiverType = null) {
|
|
395
|
+
if (!name || name.length > MAX_NAME)
|
|
396
|
+
return;
|
|
397
|
+
if (!/^[\p{L}_$~][\p{L}\p{N}_$!?]*$/u.test(name))
|
|
398
|
+
return;
|
|
399
|
+
const caller = currentCaller();
|
|
400
|
+
const start = node.startIndex;
|
|
401
|
+
const end = node.endIndex;
|
|
402
|
+
const argc = argCount(node);
|
|
403
|
+
// Calls on expression receivers are kept apart per site until the chain is known (merged below).
|
|
404
|
+
const key = caller + '\0' + name + '\0' + (receiver === '?' ? '?' + start + ':' + end : receiver ?? '') + '\0' + argc;
|
|
405
|
+
let rec = callMap.get(key);
|
|
406
|
+
if (rec) {
|
|
407
|
+
rec.count++;
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
const rtype = receiverType
|
|
411
|
+
? typeName(receiverType, compiled.spec.lowercaseTypes === true, compiled.spec.packageTypes === true)
|
|
412
|
+
: null;
|
|
413
|
+
// A chained call spanning several lines starts at the head of the chain; report the
|
|
414
|
+
// line where this call's own argument list opens (`->then(`), which is where a reader looks.
|
|
415
|
+
let row = node.startPosition.row;
|
|
416
|
+
if (node.endPosition.row !== row) {
|
|
417
|
+
const args = node.childForFieldName('arguments');
|
|
418
|
+
if (args)
|
|
419
|
+
row = args.startPosition.row;
|
|
420
|
+
}
|
|
421
|
+
rec = { caller, name, line: row + 1, receiver, rtype, argc, count: 1, via: -1 };
|
|
422
|
+
callMap.set(key, rec);
|
|
423
|
+
}
|
|
424
|
+
// Call chains: in `a.b().c()` the expression for c starts where the expression for b starts,
|
|
425
|
+
// and the walker reaches c first. Link each outer call to the call that produces its receiver.
|
|
426
|
+
const outers = chainOuters.get(start);
|
|
427
|
+
if (outers) {
|
|
428
|
+
for (const o of outers) {
|
|
429
|
+
if (o.end > end && !viaRef.has(o.call) && o.call !== rec)
|
|
430
|
+
viaRef.set(o.call, rec);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (receiver === '?' && !rec.rtype) {
|
|
434
|
+
if (outers)
|
|
435
|
+
outers.push({ call: rec, end });
|
|
436
|
+
else
|
|
437
|
+
chainOuters.set(start, [{ call: rec, end }]);
|
|
438
|
+
}
|
|
439
|
+
// `x = make()`, `let x = Foo::new()?`, `x, err := load()`: x gets the call's return type.
|
|
440
|
+
const bound = boundVariable(node);
|
|
441
|
+
if (bound) {
|
|
442
|
+
let map = callHints.get(caller);
|
|
443
|
+
if (!map)
|
|
444
|
+
callHints.set(caller, (map = new Map()));
|
|
445
|
+
if (!map.has(bound))
|
|
446
|
+
map.set(bound, rec);
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
imp(src, node, names = []) {
|
|
450
|
+
if (!src)
|
|
451
|
+
return;
|
|
452
|
+
imports.push({ source: src.slice(0, 300), names: names.slice(0, 50), line: node.startPosition.row + 1 });
|
|
453
|
+
},
|
|
454
|
+
inherit(sym, name, kind = 'extends', arity) {
|
|
455
|
+
if (!name || sym < 0)
|
|
456
|
+
return;
|
|
457
|
+
const clean = name.trim().replace(/<.*$/s, '');
|
|
458
|
+
if (arity === undefined)
|
|
459
|
+
arity = genericArity(name);
|
|
460
|
+
if (!clean || clean.length > MAX_NAME)
|
|
461
|
+
return;
|
|
462
|
+
// `pkg.mod.Base` / `ns::Base`: index by the bare name, keep the qualifier for resolution.
|
|
463
|
+
const parts = clean.split(/\.|::|\\/).filter(Boolean);
|
|
464
|
+
const bare = parts.pop();
|
|
465
|
+
if (!bare)
|
|
466
|
+
return;
|
|
467
|
+
const qualifier = parts.length ? parts[parts.length - 1] : null;
|
|
468
|
+
// (`include Thor::Actions` and `include Rails::Generators::Actions` are two different supertypes.)
|
|
469
|
+
if (heritage.some(h => h.symbol === sym && h.name === bare && (h.qualifier ?? null) === (qualifier ?? null)))
|
|
470
|
+
return;
|
|
471
|
+
heritage.push({ symbol: sym, name: bare, qualifier, kind, arity });
|
|
472
|
+
},
|
|
473
|
+
parentKind() {
|
|
474
|
+
for (let i = scopes.length - 1; i >= 0; i--) {
|
|
475
|
+
if (scopes[i].sym >= 0)
|
|
476
|
+
return scopes[i].kind;
|
|
477
|
+
}
|
|
478
|
+
return null;
|
|
479
|
+
},
|
|
480
|
+
inCallable() {
|
|
481
|
+
for (const s of scopes) {
|
|
482
|
+
if (s.kind === 'function' || s.kind === 'method')
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
return false;
|
|
486
|
+
},
|
|
487
|
+
depth() {
|
|
488
|
+
let d = 0;
|
|
489
|
+
for (const s of scopes)
|
|
490
|
+
if (s.sym >= 0)
|
|
491
|
+
d++;
|
|
492
|
+
return d;
|
|
493
|
+
},
|
|
494
|
+
};
|
|
495
|
+
const cursor = tree.walk();
|
|
496
|
+
let hadError = tree.rootNode.hasError;
|
|
497
|
+
// Iterative pre-order walk (no recursion: generated files nest tens of thousands deep).
|
|
498
|
+
// `open` holds, for every node whose children are being visited, the scope depth to restore on leaving it.
|
|
499
|
+
const open = [];
|
|
500
|
+
try {
|
|
501
|
+
let entering = true;
|
|
502
|
+
for (;;) {
|
|
503
|
+
if (entering) {
|
|
504
|
+
const typeId = cursor.nodeTypeId;
|
|
505
|
+
if (isIdent[typeId]) {
|
|
506
|
+
const name = source.slice(cursor.startIndex, cursor.endIndex);
|
|
507
|
+
if (name.length >= 2 && name.length <= MAX_NAME) {
|
|
508
|
+
addOcc(name, cursor.startPosition.row + 1);
|
|
509
|
+
}
|
|
510
|
+
// Identifiers are leaves in every supported grammar, but a few grammars
|
|
511
|
+
// wrap them (e.g. PHP `name`); fall through to children only if any.
|
|
512
|
+
}
|
|
513
|
+
const before = scopes.length;
|
|
514
|
+
const visitor = visitors[typeId];
|
|
515
|
+
let skip = false;
|
|
516
|
+
if (visitor) {
|
|
517
|
+
try {
|
|
518
|
+
if (visitor(cursor.currentNode, ctx) === 'skip')
|
|
519
|
+
skip = true;
|
|
520
|
+
}
|
|
521
|
+
catch {
|
|
522
|
+
// A visitor bug must never take down indexing of the whole file.
|
|
523
|
+
hadError = true;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
if (!skip && cursor.gotoFirstChild()) {
|
|
527
|
+
open.push(before);
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
scopes.length = before; // leaf (or skipped subtree): its scope closes right away
|
|
531
|
+
}
|
|
532
|
+
if (cursor.gotoNextSibling()) {
|
|
533
|
+
entering = true;
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
if (open.length === 0)
|
|
537
|
+
break;
|
|
538
|
+
cursor.gotoParent();
|
|
539
|
+
scopes.length = open.pop();
|
|
540
|
+
entering = false;
|
|
230
541
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
542
|
+
}
|
|
543
|
+
finally {
|
|
544
|
+
cursor.delete();
|
|
545
|
+
}
|
|
546
|
+
// Resolve receivers to declared types: `x.foo()` with `x: Foo` in scope,
|
|
547
|
+
// `this.repo.find()` with a typed `repo` field on the enclosing class,
|
|
548
|
+
// `s.pool.Get()` where `s` is a typed receiver/parameter whose class is in this file.
|
|
549
|
+
if (hints.size) {
|
|
550
|
+
const SELF = new Set(['this', 'self', '$this', 'base', 'Self', 'super']);
|
|
551
|
+
let containerByName = null;
|
|
552
|
+
const containerNamed = (name) => {
|
|
553
|
+
if (!containerByName) {
|
|
554
|
+
containerByName = new Map();
|
|
555
|
+
for (let i = 0; i < symbols.length; i++) {
|
|
556
|
+
const s = symbols[i];
|
|
557
|
+
if (CONTAINER_KINDS.has(s.kind) && !containerByName.has(s.name))
|
|
558
|
+
containerByName.set(s.name, i);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return containerByName.get(name);
|
|
562
|
+
};
|
|
563
|
+
/** Declared type of `name` visible from symbol `owner` (walks enclosing symbols, then the file). */
|
|
564
|
+
// Only where members can be named without `this.` is a class field in scope for a bare name:
|
|
565
|
+
// in TypeScript or Python a local `targets` is not the field `this.targets`.
|
|
566
|
+
const implicitThis = IMPLICIT_THIS_SPECS.has(compiled.spec.id);
|
|
567
|
+
const lookup = (owner, name) => {
|
|
568
|
+
for (let cur = owner, guard = 0; guard < 12; guard++) {
|
|
569
|
+
const skip = !implicitThis && cur >= 0 && CONTAINER_KINDS.has(symbols[cur].kind);
|
|
570
|
+
const hit = skip ? undefined : hints.get(cur)?.get(name);
|
|
571
|
+
if (hit)
|
|
572
|
+
return hit;
|
|
573
|
+
if (cur < 0)
|
|
574
|
+
return undefined;
|
|
575
|
+
cur = symbols[cur].parent;
|
|
576
|
+
}
|
|
577
|
+
return undefined;
|
|
578
|
+
};
|
|
579
|
+
/** Class-like symbol a method belongs to: lexical parent, or by qualified name (Go receivers, Rust impl, C++ out-of-line). */
|
|
580
|
+
const containerOf = (sym) => {
|
|
581
|
+
for (let cur = sym; cur >= 0; cur = symbols[cur].parent) {
|
|
582
|
+
if (CONTAINER_KINDS.has(symbols[cur].kind))
|
|
583
|
+
return cur;
|
|
241
584
|
}
|
|
585
|
+
if (sym < 0)
|
|
586
|
+
return undefined;
|
|
587
|
+
const parts = symbols[sym].qname.split('.');
|
|
588
|
+
for (let i = parts.length - 2; i >= 0; i--) {
|
|
589
|
+
const hit = containerNamed(parts[i]);
|
|
590
|
+
if (hit !== undefined)
|
|
591
|
+
return hit;
|
|
592
|
+
}
|
|
593
|
+
return undefined;
|
|
594
|
+
};
|
|
595
|
+
const fieldType = (container, name) => container === undefined ? undefined : hints.get(container)?.get(name);
|
|
596
|
+
for (const call of callMap.values()) {
|
|
597
|
+
const r = call.receiver;
|
|
598
|
+
if (!r || r === '?' || call.rtype)
|
|
599
|
+
continue;
|
|
600
|
+
const parts = r.replace(/->|::/g, '.').split('.');
|
|
601
|
+
if (parts.length > 2 || parts.some(p => !/^[$@]?[A-Za-z_]\w*$/.test(p)))
|
|
602
|
+
continue;
|
|
603
|
+
let t;
|
|
604
|
+
if (parts.length === 1) {
|
|
605
|
+
const p = parts[0];
|
|
606
|
+
if (SELF.has(p))
|
|
607
|
+
continue;
|
|
608
|
+
// Ruby @ivar is a field of the enclosing class; anything else is a local / parameter.
|
|
609
|
+
t = p.startsWith('@')
|
|
610
|
+
? fieldType(containerOf(call.caller), p.slice(1))
|
|
611
|
+
: lookup(call.caller, p.replace(/^\$/, ''));
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
const [a, b] = parts;
|
|
615
|
+
const field = b.replace(/^[$@]/, '');
|
|
616
|
+
if (SELF.has(a)) {
|
|
617
|
+
t = fieldType(containerOf(call.caller), field);
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
const ta = lookup(call.caller, a.replace(/^[$@]/, ''));
|
|
621
|
+
if (ta) {
|
|
622
|
+
t = fieldType(containerNamed(ta), field);
|
|
623
|
+
// `opts.logger.info()` with `opts: Options` declared in another file: the type of the
|
|
624
|
+
// field is not known here. Leave "Options#logger" for the resolver, which sees every file.
|
|
625
|
+
if (!t && containerNamed(ta) === undefined)
|
|
626
|
+
t = ta + '#' + field;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (t)
|
|
631
|
+
call.rtype = t;
|
|
242
632
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
633
|
+
}
|
|
634
|
+
// Receivers that are variables initialised from a call take that call's return type
|
|
635
|
+
// (resolved at query time through the same link as call chains).
|
|
636
|
+
if (callHints.size) {
|
|
637
|
+
for (const call of callMap.values()) {
|
|
638
|
+
const r = call.receiver;
|
|
639
|
+
if (!r || r === '?' || call.rtype || viaRef.has(call))
|
|
640
|
+
continue;
|
|
641
|
+
if (!/^[$@]?[A-Za-z_]\w*$/.test(r))
|
|
642
|
+
continue;
|
|
643
|
+
const name = r.replace(/^[$@]/, '');
|
|
644
|
+
for (let cur = call.caller, guard = 0; guard < 6; guard++) {
|
|
645
|
+
const hit = callHints.get(cur)?.get(name);
|
|
646
|
+
if (hit && hit !== call) {
|
|
647
|
+
viaRef.set(call, hit);
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
if (cur < 0)
|
|
651
|
+
break;
|
|
652
|
+
const s = symbols[cur];
|
|
653
|
+
// Stop at the function boundary: locals of an enclosing class / file are not visible by bare name.
|
|
654
|
+
if (s.kind !== 'function' && s.kind !== 'method')
|
|
655
|
+
break;
|
|
656
|
+
cur = s.parent;
|
|
657
|
+
if (cur >= 0 && symbols[cur].kind !== 'function' && symbols[cur].kind !== 'method')
|
|
658
|
+
break;
|
|
248
659
|
}
|
|
249
660
|
}
|
|
250
661
|
}
|
|
251
|
-
|
|
252
|
-
|
|
662
|
+
// Merge chained calls that are the same edge (`x.a().b()` twice in one function), then
|
|
663
|
+
// express receiver links as indexes into the final list.
|
|
664
|
+
const calls = [];
|
|
665
|
+
{
|
|
666
|
+
const kept = new Map();
|
|
667
|
+
const redirect = new Map();
|
|
668
|
+
for (const call of callMap.values()) {
|
|
669
|
+
if (call.receiver !== '?') {
|
|
670
|
+
calls.push(call);
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
const inner = viaRef.get(call);
|
|
674
|
+
const key = call.caller + '\0' + call.name + '\0' + (inner ? inner.name + '\0' + (inner.receiver ?? '') : '') + '\0' +
|
|
675
|
+
(call.rtype ?? '') + '\0' + call.argc;
|
|
676
|
+
const first = kept.get(key);
|
|
677
|
+
if (first) {
|
|
678
|
+
first.count += call.count;
|
|
679
|
+
redirect.set(call, first);
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
kept.set(key, call);
|
|
683
|
+
calls.push(call);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
const indexOf = new Map();
|
|
687
|
+
calls.forEach((c, i) => indexOf.set(c, i));
|
|
688
|
+
for (const [outer, innerRaw] of viaRef) {
|
|
689
|
+
if (redirect.has(outer))
|
|
690
|
+
continue;
|
|
691
|
+
const inner = redirect.get(innerRaw) ?? innerRaw;
|
|
692
|
+
const i = indexOf.get(inner);
|
|
693
|
+
if (i !== undefined)
|
|
694
|
+
outer.via = i;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
// Field types of class-like symbols are exported so the resolver can type
|
|
698
|
+
// `this.x.foo()` / `x_->foo()` when the class lives in another file
|
|
699
|
+
// (C++ headers, Go structs in the same package, partial classes).
|
|
700
|
+
const fieldTypes = [];
|
|
701
|
+
for (const [owner, map] of hints) {
|
|
702
|
+
if (owner < 0 || !CONTAINER_KINDS.has(symbols[owner].kind))
|
|
703
|
+
continue;
|
|
704
|
+
const container = symbols[owner].name;
|
|
705
|
+
for (const [field, type] of map) {
|
|
706
|
+
if (fieldTypes.length < 4000)
|
|
707
|
+
fieldTypes.push([container, field, type]);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
let lineCount = 1;
|
|
711
|
+
for (let i = source.indexOf('\n'); i !== -1; i = source.indexOf('\n', i + 1))
|
|
712
|
+
lineCount++;
|
|
713
|
+
return {
|
|
714
|
+
lang: compiled.spec.id,
|
|
715
|
+
lineCount,
|
|
716
|
+
symbols,
|
|
717
|
+
calls,
|
|
718
|
+
imports,
|
|
719
|
+
heritage,
|
|
720
|
+
fieldTypes,
|
|
721
|
+
defaultExport,
|
|
722
|
+
occ: [...occ.entries()].map(([name, lines]) => [name, lines.join(',')]),
|
|
723
|
+
hadError,
|
|
724
|
+
};
|
|
253
725
|
}
|
|
254
726
|
//# sourceMappingURL=extractor.js.map
|