codedeep-mcp 0.1.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/LICENSE +21 -0
- package/README.md +177 -0
- package/dist/config.js +223 -0
- package/dist/git/analyzer.js +177 -0
- package/dist/git/git-service.js +568 -0
- package/dist/git/head-watcher.js +113 -0
- package/dist/git/runner.js +204 -0
- package/dist/index.js +138 -0
- package/dist/indexer/code-index.js +1801 -0
- package/dist/indexer/complexity.js +633 -0
- package/dist/indexer/extractor.js +354 -0
- package/dist/indexer/languages/cpp.js +934 -0
- package/dist/indexer/languages/csharp.js +854 -0
- package/dist/indexer/languages/dart.js +777 -0
- package/dist/indexer/languages/go.js +665 -0
- package/dist/indexer/languages/java.js +507 -0
- package/dist/indexer/languages/kotlin.js +709 -0
- package/dist/indexer/languages/objc.js +397 -0
- package/dist/indexer/languages/php.js +771 -0
- package/dist/indexer/languages/python.js +455 -0
- package/dist/indexer/languages/ruby.js +697 -0
- package/dist/indexer/languages/rust.js +754 -0
- package/dist/indexer/languages/swift.js +691 -0
- package/dist/indexer/languages/typescript.js +485 -0
- package/dist/indexer/parser.js +175 -0
- package/dist/indexer/pipeline.js +342 -0
- package/dist/indexer/scanner.js +279 -0
- package/dist/indexer/watcher.js +353 -0
- package/dist/logger.js +16 -0
- package/dist/server.js +170 -0
- package/dist/tools/common.js +207 -0
- package/dist/tools/find-references.js +224 -0
- package/dist/tools/find-symbol.js +94 -0
- package/dist/tools/get-context.js +370 -0
- package/dist/tools/impact.js +218 -0
- package/dist/tools/overview.js +482 -0
- package/dist/tools/search-structure.js +303 -0
- package/dist/types.js +61 -0
- package/grammars/tree-sitter-c.wasm +0 -0
- package/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/grammars/tree-sitter-cpp.wasm +0 -0
- package/grammars/tree-sitter-dart.wasm +0 -0
- package/grammars/tree-sitter-go.wasm +0 -0
- package/grammars/tree-sitter-java.wasm +0 -0
- package/grammars/tree-sitter-javascript.wasm +0 -0
- package/grammars/tree-sitter-kotlin.wasm +0 -0
- package/grammars/tree-sitter-objc.wasm +0 -0
- package/grammars/tree-sitter-php.wasm +0 -0
- package/grammars/tree-sitter-python.wasm +0 -0
- package/grammars/tree-sitter-ruby.wasm +0 -0
- package/grammars/tree-sitter-rust.wasm +0 -0
- package/grammars/tree-sitter-swift.wasm +0 -0
- package/grammars/tree-sitter-tsx.wasm +0 -0
- package/grammars/tree-sitter-typescript.wasm +0 -0
- package/package.json +67 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { log } from '../logger.js';
|
|
3
|
+
import { NON_CALLABLE_KINDS, classNameFromFqn } from '../types.js';
|
|
4
|
+
import { extractCpp } from './languages/cpp.js';
|
|
5
|
+
import { extractCSharp } from './languages/csharp.js';
|
|
6
|
+
import { extractDart } from './languages/dart.js';
|
|
7
|
+
import { extractGo } from './languages/go.js';
|
|
8
|
+
import { extractJava } from './languages/java.js';
|
|
9
|
+
import { extractKotlin } from './languages/kotlin.js';
|
|
10
|
+
import { extractObjc } from './languages/objc.js';
|
|
11
|
+
import { extractPHP } from './languages/php.js';
|
|
12
|
+
import { extractRuby } from './languages/ruby.js';
|
|
13
|
+
import { extractRust } from './languages/rust.js';
|
|
14
|
+
import { extractSwift } from './languages/swift.js';
|
|
15
|
+
import { extractPython } from './languages/python.js';
|
|
16
|
+
import { extractTypeScript } from './languages/typescript.js';
|
|
17
|
+
const DEFAULT_BARE_CALLEE_TYPES = new Set(['identifier']);
|
|
18
|
+
// Selector for bare decorator forms (`@foo`, `@dataclass`). For `@foo()` and
|
|
19
|
+
// `@ns.dec()` the child is `call_expression`/`call` and the call selector
|
|
20
|
+
// emits the ref via walkCalls (the latter as a member ref); bare `@foo.bar`
|
|
21
|
+
// has a `member_expression`/`attribute` child and stays skipped — it is an
|
|
22
|
+
// access, not a call. Shared by TS and Python — both use `identifier` as
|
|
23
|
+
// the node type for plain names.
|
|
24
|
+
export function bareDecoratorIdentifier(node) {
|
|
25
|
+
const child = node.firstNamedChild;
|
|
26
|
+
return child?.type === 'identifier' ? child : null;
|
|
27
|
+
}
|
|
28
|
+
export function extractSymbols(tree, content, fileInfo) {
|
|
29
|
+
switch (fileInfo.language) {
|
|
30
|
+
case 'typescript':
|
|
31
|
+
case 'tsx':
|
|
32
|
+
case 'javascript':
|
|
33
|
+
return extractTypeScript(tree, content, fileInfo);
|
|
34
|
+
case 'python':
|
|
35
|
+
return extractPython(tree, content, fileInfo);
|
|
36
|
+
case 'java':
|
|
37
|
+
return extractJava(tree, content, fileInfo);
|
|
38
|
+
case 'go':
|
|
39
|
+
return extractGo(tree, content, fileInfo);
|
|
40
|
+
case 'rust':
|
|
41
|
+
return extractRust(tree, content, fileInfo);
|
|
42
|
+
case 'swift':
|
|
43
|
+
return extractSwift(tree, content, fileInfo);
|
|
44
|
+
case 'kotlin':
|
|
45
|
+
return extractKotlin(tree, content, fileInfo);
|
|
46
|
+
case 'dart':
|
|
47
|
+
return extractDart(tree, content, fileInfo);
|
|
48
|
+
case 'csharp':
|
|
49
|
+
return extractCSharp(tree, content, fileInfo);
|
|
50
|
+
case 'php':
|
|
51
|
+
return extractPHP(tree, content, fileInfo);
|
|
52
|
+
case 'ruby':
|
|
53
|
+
return extractRuby(tree, content, fileInfo);
|
|
54
|
+
case 'cpp':
|
|
55
|
+
// C reuses the C++ extractor wholesale: tree-sitter-c and tree-sitter-cpp
|
|
56
|
+
// produce byte-identical ASTs for the C subset, and every C++-specific
|
|
57
|
+
// branch (namespaces, templates, `::`, `new`, operators, `extern "C"`,
|
|
58
|
+
// access specifiers) is simply inert on C. `.c` uses the dedicated C
|
|
59
|
+
// grammar (parser.ts) so K&R + C++-keyword-identifier code parses cleanly;
|
|
60
|
+
// symbols still carry language 'c' (makeCppSymbol reads fileInfo.language).
|
|
61
|
+
case 'c':
|
|
62
|
+
return extractCpp(tree, content, fileInfo);
|
|
63
|
+
case 'objc':
|
|
64
|
+
// Objective-C is a C SUPERSET: extractObjc reuses cpp.ts's C-subset machinery
|
|
65
|
+
// (delegating every non-OO node to handleMember at file scope) and implements
|
|
66
|
+
// the OO surface (@interface/@implementation/@protocol/@property/methods/message
|
|
67
|
+
// sends) itself. A fresh extractor — NOT folded into extractCpp like C (a subset).
|
|
68
|
+
return extractObjc(tree, content, fileInfo);
|
|
69
|
+
default:
|
|
70
|
+
log.warn(`extractSymbols: unsupported language "${fileInfo.language}"`);
|
|
71
|
+
return { symbols: [], references: [], imports: [] };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function symbolId(file, name, kind, signature, qualifier = '') {
|
|
75
|
+
const tail = qualifier ? `\0${qualifier}` : '';
|
|
76
|
+
return createHash('sha1')
|
|
77
|
+
.update(`${file}\0${name}\0${kind}\0${signature}${tail}`)
|
|
78
|
+
.digest('hex')
|
|
79
|
+
.slice(0, 16);
|
|
80
|
+
}
|
|
81
|
+
const SIGNATURE_WS = /\s+/g;
|
|
82
|
+
// Stored/displayed signatures are capped at this length; the symbol id
|
|
83
|
+
// hashes the FULL normalized signature. Capping the hash input too would
|
|
84
|
+
// collide overloads that differ only past the cap (rxjava's 10 `just`
|
|
85
|
+
// overloads produced 5 ids), silently merging their reference graphs.
|
|
86
|
+
export const SIGNATURE_DISPLAY_CAP = 120;
|
|
87
|
+
// Shared by every language module. The output feeds symbolId hashing, so
|
|
88
|
+
// all languages must normalize identically — never fork a local copy.
|
|
89
|
+
// Returns the FULL normalized signature; the language symbol constructors
|
|
90
|
+
// apply SIGNATURE_DISPLAY_CAP to the stored copy only.
|
|
91
|
+
export function normalizeSignature(raw) {
|
|
92
|
+
return raw.trim().replace(SIGNATURE_WS, ' ');
|
|
93
|
+
}
|
|
94
|
+
// First non-empty line of a `/** */`, `/* */`, or `//` comment, cleaned.
|
|
95
|
+
// Shared by the TS and Java doc extractors (Python docs are docstrings).
|
|
96
|
+
export function commentDocLine(text) {
|
|
97
|
+
if (text.startsWith('/**')) {
|
|
98
|
+
const inner = text.slice(3, text.endsWith('*/') ? -2 : undefined);
|
|
99
|
+
for (const line of inner.split('\n')) {
|
|
100
|
+
const cleaned = line.replace(/^\s*\*?\s?/, '').trimEnd();
|
|
101
|
+
if (cleaned)
|
|
102
|
+
return cleaned;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
if (text.startsWith('/*')) {
|
|
107
|
+
const inner = text.slice(2, text.endsWith('*/') ? -2 : undefined);
|
|
108
|
+
for (const line of inner.split('\n')) {
|
|
109
|
+
const cleaned = line.trim();
|
|
110
|
+
if (cleaned)
|
|
111
|
+
return cleaned;
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
if (text.startsWith('//')) {
|
|
116
|
+
const cleaned = text.replace(/^\/\/+\s?/, '').trim();
|
|
117
|
+
return cleaned || null;
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
// Declaration signature = source from the declaration start to its body
|
|
122
|
+
// (or the declaration end when bodiless). Shared by the TS and Go
|
|
123
|
+
// extractors; feeds symbolId hashing, so — like normalizeSignature — it
|
|
124
|
+
// must stay one copy. Java forks its own (annotation exclusion + trailing
|
|
125
|
+
// `;` strip) and can't share this.
|
|
126
|
+
export function declSignature(decl, content) {
|
|
127
|
+
const body = decl.childForFieldName('body');
|
|
128
|
+
const sigEnd = body ? body.startIndex : decl.endIndex;
|
|
129
|
+
return normalizeSignature(content.slice(decl.startIndex, sigEnd));
|
|
130
|
+
}
|
|
131
|
+
// A comment sharing its line with the END of an earlier sibling is a
|
|
132
|
+
// TRAILING comment on that statement (`var z int // about z`), not doc for
|
|
133
|
+
// the next declaration. Shared by the Go and Java doc extractors.
|
|
134
|
+
export function isTrailingComment(comment) {
|
|
135
|
+
const before = comment.previousSibling;
|
|
136
|
+
return before !== null && before.endPosition.row === comment.startPosition.row;
|
|
137
|
+
}
|
|
138
|
+
// Type names that appear more than once among `symbols` (restricted to
|
|
139
|
+
// `kinds`). Same-name types share a simple-name FQN, so resolving calls
|
|
140
|
+
// through them first-wins would bind to the WRONG type — callers pass the
|
|
141
|
+
// result as `ResolveCallsOptions.ambiguousClassNames` to exclude them from
|
|
142
|
+
// extract-time resolution. Shared by the Go and Java extractors (the kinds
|
|
143
|
+
// set differs: Go class/interface/type, Java class/interface/enum).
|
|
144
|
+
export function collectAmbiguousTypeNames(symbols, kinds) {
|
|
145
|
+
const seen = new Set();
|
|
146
|
+
const ambiguous = new Set();
|
|
147
|
+
for (const s of symbols) {
|
|
148
|
+
if (!kinds.has(s.kind))
|
|
149
|
+
continue;
|
|
150
|
+
if (seen.has(s.name))
|
|
151
|
+
ambiguous.add(s.name);
|
|
152
|
+
else
|
|
153
|
+
seen.add(s.name);
|
|
154
|
+
}
|
|
155
|
+
return ambiguous;
|
|
156
|
+
}
|
|
157
|
+
export function resolveCalls(bodies, moduleRoot, symbols, fileInfo, selectors, skipTypes, functionBodySkipTypes, memberCallInfo, opts) {
|
|
158
|
+
const bareCalleeTypes = opts?.bareCalleeTypes ?? DEFAULT_BARE_CALLEE_TYPES;
|
|
159
|
+
const bindToEnclosingClass = opts?.bareCallsBindToEnclosingClass ?? false;
|
|
160
|
+
const bareCallableKinds = opts?.bareCallableKinds;
|
|
161
|
+
const constructorKinds = opts?.constructorKinds;
|
|
162
|
+
const ambiguousClassNames = opts?.ambiguousClassNames;
|
|
163
|
+
const ignoredBareCallees = opts?.ignoredBareCallees;
|
|
164
|
+
const plainCalleeType = opts?.plainCalleeType ?? 'identifier';
|
|
165
|
+
const constructorSelectorTypes = opts?.constructorSelectorTypes;
|
|
166
|
+
const ambiguousBareCallees = opts?.ambiguousBareCallees;
|
|
167
|
+
const ignoredMemberCallees = opts?.ignoredMemberCallees;
|
|
168
|
+
const nameToId = new Map();
|
|
169
|
+
for (const sym of symbols) {
|
|
170
|
+
if (bareCallableKinds ? !bareCallableKinds.has(sym.kind) : NON_CALLABLE_KINDS.has(sym.kind)) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
// An ambiguous bare-callable name (same name on >1 symbol — e.g. cross-namespace
|
|
174
|
+
// same-name functions in one file) stays out: first-wins would bind a bare call
|
|
175
|
+
// to the wrong one, so leave it unresolved instead.
|
|
176
|
+
if (ambiguousBareCallees?.has(sym.name))
|
|
177
|
+
continue;
|
|
178
|
+
if (!nameToId.has(sym.name))
|
|
179
|
+
nameToId.set(sym.name, sym.id);
|
|
180
|
+
}
|
|
181
|
+
// Constructor-form name map (`new X()` callees), built only when the
|
|
182
|
+
// language configures it. Ambiguous names stay out: binding first-wins
|
|
183
|
+
// between two same-named types would be confidently wrong.
|
|
184
|
+
const typeNameToId = new Map();
|
|
185
|
+
if (constructorKinds) {
|
|
186
|
+
for (const sym of symbols) {
|
|
187
|
+
if (!constructorKinds.has(sym.kind))
|
|
188
|
+
continue;
|
|
189
|
+
if (ambiguousClassNames?.has(sym.name))
|
|
190
|
+
continue;
|
|
191
|
+
if (!typeNameToId.has(sym.name))
|
|
192
|
+
typeNameToId.set(sym.name, sym.id);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Methods stay out of `nameToId` (a bare `m()` never binds to one) but
|
|
196
|
+
// member calls resolve through their class: `this.m()` / `Class.m()`.
|
|
197
|
+
// The class name only lives in the fqn (`file:Class.method`).
|
|
198
|
+
const methodsByClass = new Map();
|
|
199
|
+
for (const sym of symbols) {
|
|
200
|
+
if (sym.kind !== 'method')
|
|
201
|
+
continue;
|
|
202
|
+
const cls = classNameFromFqn(sym.fqn);
|
|
203
|
+
if (!cls || ambiguousClassNames?.has(cls))
|
|
204
|
+
continue;
|
|
205
|
+
let methods = methodsByClass.get(cls);
|
|
206
|
+
if (!methods)
|
|
207
|
+
methodsByClass.set(cls, (methods = new Map()));
|
|
208
|
+
if (!methods.has(sym.name))
|
|
209
|
+
methods.set(sym.name, sym.id);
|
|
210
|
+
}
|
|
211
|
+
const calleeByType = new Map(selectors.map((s) => [s.nodeType, s.getCallee]));
|
|
212
|
+
const references = [];
|
|
213
|
+
const seenCallNodeIds = new Set();
|
|
214
|
+
const emit = (node, sourceId, className, selfReceiverName) => {
|
|
215
|
+
if (seenCallNodeIds.has(node.id))
|
|
216
|
+
return;
|
|
217
|
+
const getCallee = calleeByType.get(node.type);
|
|
218
|
+
if (!getCallee)
|
|
219
|
+
return;
|
|
220
|
+
const callee = getCallee(node);
|
|
221
|
+
if (!callee)
|
|
222
|
+
return;
|
|
223
|
+
if (bareCalleeTypes.has(callee.type)) {
|
|
224
|
+
seenCallNodeIds.add(node.id);
|
|
225
|
+
const targetName = callee.text;
|
|
226
|
+
// Constructor-form callees resolve against type symbols only. This is
|
|
227
|
+
// either a callee node type that isn't the plain bare type (Java's
|
|
228
|
+
// type_identifier from `new X()`) OR a call NODE in
|
|
229
|
+
// constructorSelectorTypes (C#'s object_creation_expression, whose callee
|
|
230
|
+
// IS a plain identifier — so it must be recognized by node, not callee,
|
|
231
|
+
// type; otherwise `new Foo()` would mis-bind to an enclosing METHOD Foo).
|
|
232
|
+
// Identifier callees that are NOT constructor-form resolve via the
|
|
233
|
+
// enclosing class when configured, then the callable-name map. Either way
|
|
234
|
+
// the ref stays a plain bare ref — the call site has no receiver token.
|
|
235
|
+
const isConstructorForm = (constructorSelectorTypes?.has(node.type) ?? false) || callee.type !== plainCalleeType;
|
|
236
|
+
const targetId = isConstructorForm
|
|
237
|
+
? typeNameToId.get(targetName) ?? null
|
|
238
|
+
: ((bindToEnclosingClass && className !== undefined
|
|
239
|
+
? methodsByClass.get(className)?.get(targetName)
|
|
240
|
+
: undefined) ??
|
|
241
|
+
nameToId.get(targetName) ??
|
|
242
|
+
null);
|
|
243
|
+
// Ignored names (Go builtins) are dropped only when unresolved — the
|
|
244
|
+
// node is already in seenCallNodeIds, so the moduleRoot re-walk stays
|
|
245
|
+
// cheap and never re-emits it.
|
|
246
|
+
if (targetId === null && !isConstructorForm && ignoredBareCallees?.has(targetName)) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
references.push({
|
|
250
|
+
sourceId,
|
|
251
|
+
targetId,
|
|
252
|
+
targetName,
|
|
253
|
+
kind: 'calls',
|
|
254
|
+
file: fileInfo.path,
|
|
255
|
+
line: node.startPosition.row + 1,
|
|
256
|
+
});
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
// Member-expression callee (`obj.method()`, `this.x()`, `new ns.X()`).
|
|
260
|
+
// Single-level receivers carry their token; chained/computed receivers
|
|
261
|
+
// carry RECEIVER_OPAQUE and flow through here as unresolved name-keyed
|
|
262
|
+
// member refs (recall — never resolved). The reader returns null only for
|
|
263
|
+
// `super`/`base`/`parent::` and computed-property calls with no clean name.
|
|
264
|
+
// (JSX member components and bare member decorators never reach here —
|
|
265
|
+
// their selectors return null for non-identifier names.)
|
|
266
|
+
const member = memberCallInfo(callee);
|
|
267
|
+
if (!member)
|
|
268
|
+
return;
|
|
269
|
+
seenCallNodeIds.add(node.id);
|
|
270
|
+
// Selfness comes from the reader (TS `this` node, Python self/cls) or,
|
|
271
|
+
// for Go, from the receiver token matching the enclosing method's
|
|
272
|
+
// declared receiver variable (PendingBody.selfReceiverName).
|
|
273
|
+
const isSelf = member.isSelf ||
|
|
274
|
+
(selfReceiverName !== undefined && member.receiver === selfReceiverName);
|
|
275
|
+
const lookupClass = isSelf ? className : member.receiver;
|
|
276
|
+
const targetId = (lookupClass ? methodsByClass.get(lookupClass)?.get(member.property) : undefined) ??
|
|
277
|
+
null;
|
|
278
|
+
// Dominant fluent/stdlib method names (`.filter()`, `.then()`, ...) flood
|
|
279
|
+
// the name-keyed store once chained/member calls are captured. Drop them
|
|
280
|
+
// only when UNRESOLVED — a same-file `this.filter()` that bound to a real
|
|
281
|
+
// sibling method keeps its ref. Node is already in seenCallNodeIds, so the
|
|
282
|
+
// moduleRoot re-walk stays cheap and never re-emits it. Qualified PATH calls
|
|
283
|
+
// (`crate::cfg::parse()`) are EXEMPT: they're a small intra-crate population,
|
|
284
|
+
// not the dot-method flood, so suppressing them would defeat path-call recall.
|
|
285
|
+
if (targetId === null && !member.pathQualified && ignoredMemberCallees?.has(member.property)) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const ref = {
|
|
289
|
+
sourceId,
|
|
290
|
+
targetId,
|
|
291
|
+
targetName: member.property,
|
|
292
|
+
kind: 'calls',
|
|
293
|
+
file: fileInfo.path,
|
|
294
|
+
line: node.startPosition.row + 1,
|
|
295
|
+
receiver: member.receiver,
|
|
296
|
+
};
|
|
297
|
+
// Recorded so isCallerOf can reject unresolved self-calls without
|
|
298
|
+
// guessing from the receiver token (`self` is a legal TS identifier).
|
|
299
|
+
// Gated on an enclosing class actually existing: `self.x()` in a
|
|
300
|
+
// plain Python function (or `this.x()` in a plain JS function) has
|
|
301
|
+
// no class instance to refer to and stays an ordinary member ref.
|
|
302
|
+
if (isSelf && className !== undefined)
|
|
303
|
+
ref.selfReceiver = true;
|
|
304
|
+
references.push(ref);
|
|
305
|
+
};
|
|
306
|
+
// Languages without a decorator selector (Java) skip the decorator walk
|
|
307
|
+
// entirely — it could never match and would re-traverse every subtree.
|
|
308
|
+
const hasDecoratorSelector = calleeByType.has('decorator');
|
|
309
|
+
// Body walks first so a function-nested decorator gets attributed to the
|
|
310
|
+
// enclosing body when reachable; the seen-set then drops the (null-sourced)
|
|
311
|
+
// module-root duplicate.
|
|
312
|
+
for (const { symbolId: sourceId, body, className, selfReceiverName } of bodies) {
|
|
313
|
+
walkCalls(body, calleeByType, skipTypes, (call) => emit(call, sourceId, className, selfReceiverName));
|
|
314
|
+
// TS decorators sit under skip-typed parents (class_declaration etc.)
|
|
315
|
+
// that walkCalls can't enter — walkDecorators descends through them
|
|
316
|
+
// so nested decorated classes attribute to the enclosing body.
|
|
317
|
+
if (hasDecoratorSelector) {
|
|
318
|
+
walkDecorators(body, calleeByType, skipTypes, functionBodySkipTypes, (call) => emit(call, sourceId, className, selfReceiverName));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (moduleRoot) {
|
|
322
|
+
walkCalls(moduleRoot, calleeByType, skipTypes, (call) => emit(call, null));
|
|
323
|
+
if (hasDecoratorSelector) {
|
|
324
|
+
walkDecorators(moduleRoot, calleeByType, skipTypes, functionBodySkipTypes, (call) => emit(call, null));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return references;
|
|
328
|
+
}
|
|
329
|
+
function walkCalls(node, calleeByType, skipTypes, onCall) {
|
|
330
|
+
if (calleeByType.has(node.type))
|
|
331
|
+
onCall(node);
|
|
332
|
+
for (const child of node.namedChildren) {
|
|
333
|
+
if (skipTypes.has(child.type))
|
|
334
|
+
continue;
|
|
335
|
+
walkCalls(child, calleeByType, skipTypes, onCall);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function walkDecorators(node, calleeByType, skipTypes, functionBodySkipTypes, onCall) {
|
|
339
|
+
if (node.type === 'decorator') {
|
|
340
|
+
walkCalls(node, calleeByType, skipTypes, onCall);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
for (const child of node.namedChildren) {
|
|
344
|
+
// Skip nested function bodies — decorators inside a nested function
|
|
345
|
+
// only fire when that function is invoked, so they shouldn't
|
|
346
|
+
// attribute to the enclosing body. Class types stay descended so
|
|
347
|
+
// top-level decorators on inner classes still attribute correctly
|
|
348
|
+
// (the original walkDecorators rationale, mirrored by walkCalls
|
|
349
|
+
// skipping classes via skipTypes).
|
|
350
|
+
if (functionBodySkipTypes.has(child.type))
|
|
351
|
+
continue;
|
|
352
|
+
walkDecorators(child, calleeByType, skipTypes, functionBodySkipTypes, onCall);
|
|
353
|
+
}
|
|
354
|
+
}
|