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
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
import type { Node } from 'web-tree-sitter';
|
|
2
|
+
import type { Ctx, LangSpec, Visitor } from '../spec.js';
|
|
3
|
+
import { COMPLEX_RECEIVER, field, genericArity, lastSegment, simpleReceiver } from '../spec.js';
|
|
4
|
+
import type { SymbolKind } from '../../types.js';
|
|
5
|
+
|
|
6
|
+
const MAX_SIG_SLICE = 400;
|
|
7
|
+
const TYPE_DECLS = ['class_declaration', 'struct_declaration', 'record_declaration', 'record_struct_declaration'];
|
|
8
|
+
|
|
9
|
+
function hasModifier(node: Node, keyword: string): boolean {
|
|
10
|
+
for (const c of node.children) {
|
|
11
|
+
if (!c) continue;
|
|
12
|
+
if (c.type === 'modifier') {
|
|
13
|
+
// `modifier` is a tiny leaf-like node: its text is the keyword.
|
|
14
|
+
if (c.text === keyword) return true;
|
|
15
|
+
} else if (c.type !== 'attribute_list' && c.type !== 'comment') {
|
|
16
|
+
break; // modifiers only appear at the start of a declaration
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Offset where the signature starts: after leading `[Attribute]` lists. */
|
|
23
|
+
function sigStart(node: Node): number {
|
|
24
|
+
for (const c of node.children) {
|
|
25
|
+
if (!c) continue;
|
|
26
|
+
if (c.type === 'attribute_list' || c.type === 'comment' || c.type.startsWith('preproc_')) continue;
|
|
27
|
+
return c.startIndex;
|
|
28
|
+
}
|
|
29
|
+
return node.startIndex;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Signature from after the attributes up to `end` (body start or node end). */
|
|
33
|
+
function signature(node: Node, ctx: Ctx, bodyNode: Node | null = field(node, 'body')): string {
|
|
34
|
+
const start = sigStart(node);
|
|
35
|
+
let end = bodyNode && bodyNode.startIndex > start ? bodyNode.startIndex : node.endIndex;
|
|
36
|
+
if (end - start > MAX_SIG_SLICE) end = start + MAX_SIG_SLICE;
|
|
37
|
+
return ctx.src.slice(start, end).replace(/\s*[{;]\s*$/, '');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* `///` XML documentation directly above `range`, with the XML markup removed.
|
|
42
|
+
* Returns undefined when there is none so that the default comment lookup applies.
|
|
43
|
+
*/
|
|
44
|
+
function xmlDoc(range: Node): string | undefined {
|
|
45
|
+
const lines: string[] = [];
|
|
46
|
+
let nextRow = range.startPosition.row;
|
|
47
|
+
let prev = range.previousSibling;
|
|
48
|
+
while (prev && prev.type === 'comment' && lines.length < 60) {
|
|
49
|
+
if (prev.endPosition.row < nextRow - 1) break;
|
|
50
|
+
const t = prev.text;
|
|
51
|
+
if (!t.startsWith('///')) break;
|
|
52
|
+
lines.unshift(t.replace(/^\/\/\/ ?/, ''));
|
|
53
|
+
nextRow = prev.startPosition.row;
|
|
54
|
+
prev = prev.previousSibling;
|
|
55
|
+
}
|
|
56
|
+
if (lines.length === 0) return undefined;
|
|
57
|
+
const doc = lines
|
|
58
|
+
.join('\n')
|
|
59
|
+
.replace(/<(?:see|seealso|paramref|typeparamref)\s+(?:cref|name|langword|href)\s*=\s*"(?:[A-Z]:)?([^"]*)"\s*\/>/g, '$1')
|
|
60
|
+
.replace(/<param(?:ref)?\s+name\s*=\s*"([^"]*)"\s*>/g, '$1: ')
|
|
61
|
+
.replace(/<typeparam\s+name\s*=\s*"([^"]*)"\s*>/g, '$1: ')
|
|
62
|
+
.replace(/<returns>/g, 'Returns: ')
|
|
63
|
+
.replace(/<\/?[A-Za-z][^<>]*>/g, '')
|
|
64
|
+
.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&')
|
|
65
|
+
.split('\n')
|
|
66
|
+
.map(l => l.trim())
|
|
67
|
+
.filter(Boolean)
|
|
68
|
+
.join('\n');
|
|
69
|
+
return doc || undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Simple name of a type / name node: strips generics, namespaces, `?` and array ranks. */
|
|
73
|
+
function simpleName(node: Node | null | undefined): string | null {
|
|
74
|
+
let n = node ?? null;
|
|
75
|
+
for (let i = 0; n && i < 5; i++) {
|
|
76
|
+
switch (n.type) {
|
|
77
|
+
case 'identifier':
|
|
78
|
+
return n.text;
|
|
79
|
+
case 'generic_name':
|
|
80
|
+
n = n.namedChild(0);
|
|
81
|
+
break;
|
|
82
|
+
case 'qualified_name':
|
|
83
|
+
case 'alias_qualified_name':
|
|
84
|
+
n = field(n, 'name');
|
|
85
|
+
break;
|
|
86
|
+
case 'nullable_type':
|
|
87
|
+
case 'array_type':
|
|
88
|
+
n = field(n, 'type') ?? n.namedChild(0);
|
|
89
|
+
break;
|
|
90
|
+
case 'predefined_type':
|
|
91
|
+
return null;
|
|
92
|
+
default:
|
|
93
|
+
if (n.endIndex - n.startIndex > 200) return null;
|
|
94
|
+
return lastSegment(n.text.replace(/<.*$/s, '').replace(/[\[\]?\s]+$/, ''));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Receiver text of a member call: `_next`, `this._next`, `base`, `app.Services`.
|
|
102
|
+
* Null-conditional and null-forgiving operators are dropped (`x?.Inner` ->
|
|
103
|
+
* `x.Inner`, `x!` -> `x`); call chains and other expressions yield '?'.
|
|
104
|
+
* Never null: a member call always has a receiver.
|
|
105
|
+
*/
|
|
106
|
+
function receiverOf(node: Node | null): string {
|
|
107
|
+
if (!node) return COMPLEX_RECEIVER;
|
|
108
|
+
if (node.type === 'generic_name') return node.namedChild(0)?.text ?? COMPLEX_RECEIVER; // `Foo<int>.Create()`
|
|
109
|
+
const r = simpleReceiver(node);
|
|
110
|
+
if (!r || r === COMPLEX_RECEIVER) return COMPLEX_RECEIVER;
|
|
111
|
+
return r.includes('?') || r.includes('!') ? r.replace(/[?!]/g, '') || COMPLEX_RECEIVER : r;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const MAX_TYPE_TEXT = 120;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Raw text of a declared type for ctx.typeHint (which normalises it), or null
|
|
118
|
+
* for types that never name the class of the variable: `var`, arrays, tuples,
|
|
119
|
+
* predefined types.
|
|
120
|
+
*/
|
|
121
|
+
function hintType(type: Node | null | undefined): string | null {
|
|
122
|
+
let n = type ?? null;
|
|
123
|
+
if (n && n.type === 'nullable_type') n = field(n, 'type') ?? n.namedChild(0);
|
|
124
|
+
if (!n) return null;
|
|
125
|
+
switch (n.type) {
|
|
126
|
+
case 'identifier':
|
|
127
|
+
case 'qualified_name':
|
|
128
|
+
case 'alias_qualified_name':
|
|
129
|
+
return n.endIndex - n.startIndex > MAX_TYPE_TEXT ? null : n.text;
|
|
130
|
+
case 'generic_name':
|
|
131
|
+
// Long generic types: the outer name is enough (wrappers are not unwrapped then).
|
|
132
|
+
return n.endIndex - n.startIndex > MAX_TYPE_TEXT ? simpleName(n) : n.text;
|
|
133
|
+
default:
|
|
134
|
+
return null; // implicit_type, predefined_type, array_type, tuple_type, pointer_type, ...
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Declared type of a declarator: the written type, or for `var` the class
|
|
140
|
+
* named by the initializer: `new Foo(...)`, `(Foo)x`, `x as Foo`.
|
|
141
|
+
*/
|
|
142
|
+
function declaredType(type: Node | null, declarator: Node): string | null {
|
|
143
|
+
if (!type) return null;
|
|
144
|
+
if (type.type !== 'implicit_type') return hintType(type);
|
|
145
|
+
if (declarator.namedChildCount < 2) return null;
|
|
146
|
+
const value = declarator.namedChild(declarator.namedChildCount - 1);
|
|
147
|
+
if (!value) return null;
|
|
148
|
+
switch (value.type) {
|
|
149
|
+
case 'object_creation_expression':
|
|
150
|
+
case 'cast_expression':
|
|
151
|
+
return hintType(field(value, 'type'));
|
|
152
|
+
case 'as_expression':
|
|
153
|
+
return hintType(field(value, 'right'));
|
|
154
|
+
default:
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Node types that declare one typed name through `type` and `name` fields. */
|
|
160
|
+
const typedName: Visitor = (node, ctx) => {
|
|
161
|
+
const name = field(node, 'name');
|
|
162
|
+
if (name && name.type === 'identifier') ctx.typeHint(name.text, hintType(field(node, 'type')));
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Method, lambda and local function parameters (incl. `this Foo x`, `out`,
|
|
167
|
+
* `ref`, `in`). Primary-constructor parameters of classes, structs and records
|
|
168
|
+
* act like fields and are attached to the type.
|
|
169
|
+
*/
|
|
170
|
+
const parameter: Visitor = (node, ctx) => {
|
|
171
|
+
const type = field(node, 'type');
|
|
172
|
+
const name = field(node, 'name');
|
|
173
|
+
if (!type || !name) return;
|
|
174
|
+
const owner = node.parent?.parent;
|
|
175
|
+
if (!owner || owner.type === 'delegate_declaration') return;
|
|
176
|
+
ctx.typeHint(name.text, hintType(type), TYPE_DECLS.includes(owner.type));
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/** `foreach (Item it in items)`. */
|
|
180
|
+
const foreachStatement: Visitor = (node, ctx) => {
|
|
181
|
+
const left = field(node, 'left');
|
|
182
|
+
if (left && left.type === 'identifier') ctx.typeHint(left.text, hintType(field(node, 'type')));
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/** `[Authorize]` refers to the class `AuthorizeAttribute`. */
|
|
186
|
+
const attribute: Visitor = (node, ctx) => {
|
|
187
|
+
const name = simpleName(field(node, 'name'));
|
|
188
|
+
if (name && !name.endsWith('Attribute')) ctx.ref(name + 'Attribute', node);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
function memberExported(node: Node, ctx: Ctx): boolean {
|
|
192
|
+
if (hasModifier(node, 'public')) return true;
|
|
193
|
+
if (ctx.parentKind() !== 'interface') return false;
|
|
194
|
+
return !hasModifier(node, 'private') && !hasModifier(node, 'protected') && !hasModifier(node, 'internal');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function isRecordStruct(node: Node): boolean {
|
|
198
|
+
if (node.type === 'record_struct_declaration') return true;
|
|
199
|
+
if (node.type !== 'record_declaration') return false;
|
|
200
|
+
const name = field(node, 'name');
|
|
201
|
+
for (const c of node.children) {
|
|
202
|
+
if (!c || c === name || (name && c.startIndex >= name.startIndex)) break;
|
|
203
|
+
if (c.type === 'struct') return true;
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function baseList(node: Node, sym: number, kind: SymbolKind, ctx: Ctx): void {
|
|
209
|
+
let list: Node | null = null;
|
|
210
|
+
for (const c of node.namedChildren) {
|
|
211
|
+
if (c && c.type === 'base_list') { list = c; break; }
|
|
212
|
+
}
|
|
213
|
+
if (!list) return;
|
|
214
|
+
for (const b of list.namedChildren) {
|
|
215
|
+
if (!b || b.type === 'argument_list' || b.type === 'comment') continue;
|
|
216
|
+
// `record R(int X) : Base(X)` wraps the base in primary_constructor_base_type.
|
|
217
|
+
const target = b.type === 'primary_constructor_base_type' ? (field(b, 'type') ?? b.namedChild(0)) : b;
|
|
218
|
+
const name = simpleName(target);
|
|
219
|
+
if (!name) continue;
|
|
220
|
+
let rel: 'extends' | 'implements';
|
|
221
|
+
if (kind === 'interface') rel = 'extends';
|
|
222
|
+
else if (kind === 'struct') rel = 'implements';
|
|
223
|
+
else rel = /^I[A-Z]/.test(name) ? 'implements' : 'extends';
|
|
224
|
+
const text = target && target.endIndex - target.startIndex <= 400 ? target.text : '';
|
|
225
|
+
ctx.inherit(sym, name, rel, genericArity(text));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const typeDecl = (declKind: 'class' | 'struct' | 'interface'): Visitor => (node, ctx) => {
|
|
230
|
+
const name = field(node, 'name');
|
|
231
|
+
if (!name) return;
|
|
232
|
+
const kind: SymbolKind = declKind === 'class' && isRecordStruct(node) ? 'struct' : declKind;
|
|
233
|
+
const exported = hasModifier(node, 'public');
|
|
234
|
+
const sym = ctx.def(node, {
|
|
235
|
+
name: name.text,
|
|
236
|
+
kind,
|
|
237
|
+
sig: signature(node, ctx),
|
|
238
|
+
doc: xmlDoc(node),
|
|
239
|
+
exported,
|
|
240
|
+
});
|
|
241
|
+
baseList(node, sym, kind, ctx);
|
|
242
|
+
|
|
243
|
+
if (node.type === 'record_declaration' || node.type === 'record_struct_declaration') {
|
|
244
|
+
// Positional record parameters become public properties.
|
|
245
|
+
for (const c of node.namedChildren) {
|
|
246
|
+
if (!c || c.type !== 'parameter_list') continue;
|
|
247
|
+
for (const p of c.namedChildren) {
|
|
248
|
+
if (!p || p.type !== 'parameter') continue;
|
|
249
|
+
const pName = field(p, 'name');
|
|
250
|
+
if (pName) ctx.def(p, { name: pName.text, kind: 'property', body: null, scope: false, exported });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const enumDecl: Visitor = (node, ctx) => {
|
|
257
|
+
const name = field(node, 'name');
|
|
258
|
+
if (!name) return;
|
|
259
|
+
ctx.def(node, {
|
|
260
|
+
name: name.text,
|
|
261
|
+
kind: 'enum',
|
|
262
|
+
sig: signature(node, ctx),
|
|
263
|
+
doc: xmlDoc(node),
|
|
264
|
+
exported: hasModifier(node, 'public'),
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const enumMember: Visitor = (node, ctx) => {
|
|
269
|
+
const name = field(node, 'name');
|
|
270
|
+
if (!name) return;
|
|
271
|
+
const decl = node.parent?.parent;
|
|
272
|
+
ctx.def(node, {
|
|
273
|
+
name: name.text,
|
|
274
|
+
kind: 'constant',
|
|
275
|
+
sig: signature(node, ctx, null),
|
|
276
|
+
doc: xmlDoc(node),
|
|
277
|
+
exported: !!decl && hasModifier(decl, 'public'),
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const delegateDecl: Visitor = (node, ctx) => {
|
|
282
|
+
const name = field(node, 'name');
|
|
283
|
+
if (!name) return;
|
|
284
|
+
ctx.def(node, {
|
|
285
|
+
name: name.text,
|
|
286
|
+
kind: 'type',
|
|
287
|
+
sig: signature(node, ctx, null),
|
|
288
|
+
doc: xmlDoc(node),
|
|
289
|
+
exported: hasModifier(node, 'public'),
|
|
290
|
+
});
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const callable = (nameOf: (node: Node, ctx: Ctx) => string | null | undefined, kind: SymbolKind = 'method'): Visitor =>
|
|
294
|
+
(node, ctx) => {
|
|
295
|
+
const name = nameOf(node, ctx);
|
|
296
|
+
if (!name) return;
|
|
297
|
+
ctx.def(node, {
|
|
298
|
+
name,
|
|
299
|
+
kind,
|
|
300
|
+
sig: signature(node, ctx),
|
|
301
|
+
doc: xmlDoc(node),
|
|
302
|
+
exported: memberExported(node, ctx),
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const fieldName = (node: Node): string | undefined => field(node, 'name')?.text;
|
|
307
|
+
|
|
308
|
+
const operatorName = (node: Node, ctx: Ctx): string | null => {
|
|
309
|
+
const op = field(node, 'operator');
|
|
310
|
+
if (op) return 'operator ' + op.text;
|
|
311
|
+
// Fallback: text between the `operator` keyword and the parameter list.
|
|
312
|
+
const params = field(node, 'parameters');
|
|
313
|
+
if (!params) return null;
|
|
314
|
+
const head = ctx.src.slice(Math.max(node.startIndex, params.startIndex - 40), params.startIndex);
|
|
315
|
+
const m = /operator\s*(\S+)\s*$/.exec(head);
|
|
316
|
+
return m ? 'operator ' + m[1] : null;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const conversionOperatorName = (node: Node): string | null => {
|
|
320
|
+
const type = field(node, 'type');
|
|
321
|
+
if (!type || type.endIndex - type.startIndex > 80) return null;
|
|
322
|
+
return 'operator ' + type.text.replace(/\s+/g, ' ');
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const localFunction: Visitor = (node, ctx) => {
|
|
326
|
+
const name = field(node, 'name');
|
|
327
|
+
if (!name) return;
|
|
328
|
+
ctx.def(node, { name: name.text, kind: 'function', sig: signature(node, ctx), doc: xmlDoc(node) });
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/** True if every accessor is of the bodiless `get; set; init;` form. */
|
|
332
|
+
function autoAccessors(accessors: Node): boolean {
|
|
333
|
+
if (accessors.endIndex - accessors.startIndex > 60) return false;
|
|
334
|
+
for (const a of accessors.namedChildren) {
|
|
335
|
+
if (!a) continue;
|
|
336
|
+
if (a.type !== 'accessor_declaration' || field(a, 'body')) return false;
|
|
337
|
+
}
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const propertyLike = (nameOf: (node: Node) => string | undefined, typed = false): Visitor => (node, ctx) => {
|
|
342
|
+
const name = nameOf(node);
|
|
343
|
+
if (!name) return;
|
|
344
|
+
const accessors = field(node, 'accessors');
|
|
345
|
+
const value = field(node, 'value');
|
|
346
|
+
let sig: string;
|
|
347
|
+
if (accessors) {
|
|
348
|
+
sig = signature(node, ctx, accessors);
|
|
349
|
+
if (autoAccessors(accessors)) sig += ' ' + accessors.text.replace(/\s+/g, ' ');
|
|
350
|
+
} else {
|
|
351
|
+
sig = signature(node, ctx, value);
|
|
352
|
+
}
|
|
353
|
+
ctx.def(node, {
|
|
354
|
+
name,
|
|
355
|
+
kind: 'property',
|
|
356
|
+
sig,
|
|
357
|
+
doc: xmlDoc(node),
|
|
358
|
+
exported: memberExported(node, ctx),
|
|
359
|
+
});
|
|
360
|
+
// The property symbol is not a container, so the hint lands on the enclosing type.
|
|
361
|
+
if (typed) ctx.typeHint(name, hintType(field(node, 'type')));
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Fields and field-like events: one symbol per declarator. Locals (also in
|
|
366
|
+
* `using` / `for` / `fixed` statements) are not symbols; they only contribute
|
|
367
|
+
* a receiver type hint.
|
|
368
|
+
*/
|
|
369
|
+
const variableDeclarator: Visitor = (node, ctx) => {
|
|
370
|
+
const declaration = node.parent;
|
|
371
|
+
const decl = declaration?.parent;
|
|
372
|
+
if (!declaration || !decl || declaration.type !== 'variable_declaration') return;
|
|
373
|
+
const nameNode = field(node, 'name') ?? node.namedChild(0);
|
|
374
|
+
if (!nameNode || nameNode.type !== 'identifier') return;
|
|
375
|
+
if (decl.type !== 'field_declaration' && decl.type !== 'event_field_declaration') {
|
|
376
|
+
ctx.typeHint(nameNode.text, declaredType(field(declaration, 'type'), node));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Signature: modifiers + type + this declarator (with a short initializer).
|
|
381
|
+
const start = sigStart(decl);
|
|
382
|
+
const first = node.parent!.namedChildren.find(c => c && c.type === 'variable_declarator');
|
|
383
|
+
const headEnd = Math.min(first ? first.startIndex : node.startIndex, start + MAX_SIG_SLICE);
|
|
384
|
+
const declEnd = node.endIndex - node.startIndex > 100 ? nameNode.endIndex : node.endIndex;
|
|
385
|
+
const sig = ctx.src.slice(start, Math.max(start, headEnd)) + ctx.src.slice(node.startIndex, declEnd);
|
|
386
|
+
|
|
387
|
+
ctx.def(node, {
|
|
388
|
+
name: nameNode.text,
|
|
389
|
+
kind: decl.type === 'field_declaration' && hasModifier(decl, 'const') ? 'constant' : 'field',
|
|
390
|
+
range: decl,
|
|
391
|
+
sig,
|
|
392
|
+
doc: xmlDoc(decl),
|
|
393
|
+
exported: memberExported(decl, ctx),
|
|
394
|
+
});
|
|
395
|
+
// The field symbol is not a container, so the hint lands on the enclosing type.
|
|
396
|
+
if (decl.type === 'field_declaration') ctx.typeHint(nameNode.text, declaredType(field(declaration, 'type'), node));
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
/** Name and receiver of the callee expression of an invocation. */
|
|
400
|
+
function callee(fn: Node | null, ctx: Ctx, node: Node): void {
|
|
401
|
+
for (let i = 0; fn && i < 4; i++) {
|
|
402
|
+
switch (fn.type) {
|
|
403
|
+
case 'identifier':
|
|
404
|
+
ctx.call(fn.text, node);
|
|
405
|
+
return;
|
|
406
|
+
case 'generic_name':
|
|
407
|
+
ctx.call(fn.namedChild(0)?.text, node);
|
|
408
|
+
return;
|
|
409
|
+
case 'member_access_expression': {
|
|
410
|
+
const recv = field(fn, 'expression');
|
|
411
|
+
// `x?.Foo.Bar()` may parse with a member_binding_expression receiver (`.Foo`).
|
|
412
|
+
ctx.call(
|
|
413
|
+
simpleName(field(fn, 'name')),
|
|
414
|
+
node,
|
|
415
|
+
recv && recv.type === 'member_binding_expression' ? COMPLEX_RECEIVER : receiverOf(recv)
|
|
416
|
+
);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
case 'member_binding_expression':
|
|
420
|
+
// `x?.Foo()`: the receiver is the condition of the conditional access.
|
|
421
|
+
ctx.call(simpleName(field(fn, 'name')), node, receiverOf(field(fn.parent, 'condition')));
|
|
422
|
+
return;
|
|
423
|
+
case 'conditional_access_expression':
|
|
424
|
+
fn = fn.namedChild(fn.namedChildCount - 1);
|
|
425
|
+
break;
|
|
426
|
+
case 'parenthesized_expression':
|
|
427
|
+
fn = fn.namedChild(0);
|
|
428
|
+
break;
|
|
429
|
+
default:
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const invocation: Visitor = (node, ctx) => {
|
|
436
|
+
callee(field(node, 'function'), ctx, node);
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
const objectCreation: Visitor = (node, ctx) => {
|
|
440
|
+
ctx.call(simpleName(field(node, 'type')), node);
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
/** `: base(...)` / `: this(...)` after a constructor's parameter list. */
|
|
444
|
+
const constructorInitializer: Visitor = (node, ctx) => {
|
|
445
|
+
let cls: Node | null = node.parent;
|
|
446
|
+
for (let i = 0; cls && i < 4 && !TYPE_DECLS.includes(cls.type); i++) cls = cls.parent;
|
|
447
|
+
if (!cls || !TYPE_DECLS.includes(cls.type)) return;
|
|
448
|
+
let isThis = false;
|
|
449
|
+
for (const c of node.children) {
|
|
450
|
+
if (!c) continue;
|
|
451
|
+
if (c.type === 'this') { isThis = true; break; }
|
|
452
|
+
if (c.type === 'base' || c.type === 'argument_list') break;
|
|
453
|
+
}
|
|
454
|
+
if (isThis) {
|
|
455
|
+
ctx.call(field(cls, 'name')?.text, node);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
for (const c of cls.namedChildren) {
|
|
459
|
+
if (!c || c.type !== 'base_list') continue;
|
|
460
|
+
const name = simpleName(c.namedChild(0));
|
|
461
|
+
if (name && !/^I[A-Z]/.test(name)) ctx.call(name, node);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
const usingDirective: Visitor = (node, ctx) => {
|
|
467
|
+
const alias = field(node, 'name');
|
|
468
|
+
let target: Node | null = null;
|
|
469
|
+
let isStatic = false;
|
|
470
|
+
for (const c of node.children) {
|
|
471
|
+
if (!c) continue;
|
|
472
|
+
if (c.type === 'static') isStatic = true;
|
|
473
|
+
else if (c.isNamed && c !== alias && c.id !== alias?.id && c.type !== 'comment') target = c;
|
|
474
|
+
}
|
|
475
|
+
if (!target || target.endIndex - target.startIndex > 300) return;
|
|
476
|
+
const source = target.text.replace(/\s+/g, '').replace(/^global::/, '');
|
|
477
|
+
const names = alias ? [alias.text] : isStatic ? [lastSegment(source)] : [];
|
|
478
|
+
ctx.imp(source, node, names);
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
export const csharp: LangSpec = {
|
|
482
|
+
id: 'csharp',
|
|
483
|
+
grammar: 'c_sharp',
|
|
484
|
+
extensions: ['.cs'],
|
|
485
|
+
identifiers: ['identifier'],
|
|
486
|
+
visit: {
|
|
487
|
+
class_declaration: typeDecl('class'),
|
|
488
|
+
record_declaration: typeDecl('class'),
|
|
489
|
+
struct_declaration: typeDecl('struct'),
|
|
490
|
+
interface_declaration: typeDecl('interface'),
|
|
491
|
+
enum_declaration: enumDecl,
|
|
492
|
+
enum_member_declaration: enumMember,
|
|
493
|
+
delegate_declaration: delegateDecl,
|
|
494
|
+
method_declaration: callable(fieldName),
|
|
495
|
+
constructor_declaration: callable(fieldName),
|
|
496
|
+
destructor_declaration: callable(node => {
|
|
497
|
+
const n = fieldName(node);
|
|
498
|
+
return n ? '~' + n : null;
|
|
499
|
+
}),
|
|
500
|
+
operator_declaration: callable(operatorName),
|
|
501
|
+
conversion_operator_declaration: callable(conversionOperatorName),
|
|
502
|
+
local_function_statement: localFunction,
|
|
503
|
+
property_declaration: propertyLike(fieldName, true),
|
|
504
|
+
event_declaration: propertyLike(fieldName),
|
|
505
|
+
indexer_declaration: propertyLike(() => 'this[]'),
|
|
506
|
+
variable_declarator: variableDeclarator,
|
|
507
|
+
parameter,
|
|
508
|
+
foreach_statement: foreachStatement,
|
|
509
|
+
catch_declaration: typedName,
|
|
510
|
+
declaration_pattern: typedName, // `o is Foo f`, `case Foo f:`
|
|
511
|
+
recursive_pattern: typedName, // `o is Foo { X: 1 } f`
|
|
512
|
+
declaration_expression: typedName, // `TryGet(out Foo f)`
|
|
513
|
+
attribute,
|
|
514
|
+
invocation_expression: invocation,
|
|
515
|
+
object_creation_expression: objectCreation,
|
|
516
|
+
constructor_initializer: constructorInitializer,
|
|
517
|
+
using_directive: usingDirective,
|
|
518
|
+
},
|
|
519
|
+
};
|