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,397 @@
|
|
|
1
|
+
import { collectAmbiguousTypeNames, normalizeSignature, resolveCalls, } from '../extractor.js';
|
|
2
|
+
import { IMPORT_NAMESPACE, RECEIVER_OPAQUE } from '../../types.js';
|
|
3
|
+
import { analyze, cppDoc, cppMemberCallInfo, handleMember, makeCppSymbol, memberFqn, topFqn, CFAMILY_COMPLEXITY_OPTS, CPP_FUNCTION_BODY_SKIP_TYPES, CPP_SKIP_TYPES, PREPROC_GROUPS, } from './cpp.js';
|
|
4
|
+
import { computeComplexity } from '../complexity.js';
|
|
5
|
+
// ── skip sets / call-resolution knobs ───────────────────────────────────────
|
|
6
|
+
// resolveCalls walk skip set: the C-subset bodies/conditionals (CPP_SKIP_TYPES,
|
|
7
|
+
// incl. the `preproc_*` spurious-`#if FOO(3)`-edge guard) PLUS the Objective-C OO
|
|
8
|
+
// container bodies — each method owns its own PendingBody, so the moduleRoot walk
|
|
9
|
+
// must not re-descend into them. `block_literal` is deliberately ABSENT → DESCENDED
|
|
10
|
+
// (a `^{ … }` closure's calls roll into the enclosing method, the Go func_literal rule).
|
|
11
|
+
const OBJC_SKIP_TYPES = new Set([
|
|
12
|
+
...CPP_SKIP_TYPES,
|
|
13
|
+
'class_interface',
|
|
14
|
+
'class_implementation',
|
|
15
|
+
'protocol_declaration',
|
|
16
|
+
'method_definition',
|
|
17
|
+
]);
|
|
18
|
+
// A bare `foo()` in Objective-C is ALWAYS a C free function — ObjC has NO
|
|
19
|
+
// implicit-this (a sibling method is called via `[self foo]`, a message_expression).
|
|
20
|
+
// So the callee stays the engine-default `identifier` and binds over {function} only,
|
|
21
|
+
// with `bareCallsBindToEnclosingClass:false` (THE divergence from cpp).
|
|
22
|
+
const OBJC_BARE_CALLEE_TYPES = new Set(['identifier']);
|
|
23
|
+
const OBJC_BARE_CALLABLE_KINDS = new Set(['function']);
|
|
24
|
+
// libc free functions + the Foundation logging/assert macros that parse as bare
|
|
25
|
+
// `identifier` calls and never resolve to a local symbol. Suppressed ONLY when
|
|
26
|
+
// unresolved (a file-local shadow keeps its refs). START small + tune by dogfood.
|
|
27
|
+
const OBJC_IGNORED_BARE_CALLEES = new Set([
|
|
28
|
+
'printf', 'fprintf', 'sprintf', 'snprintf', 'scanf', 'sscanf', 'puts', 'fputs',
|
|
29
|
+
'malloc', 'calloc', 'realloc', 'free', 'memcpy', 'memset', 'memmove', 'memcmp',
|
|
30
|
+
'strlen', 'strcmp', 'strncmp', 'strcpy', 'strncpy', 'strcat', 'abort', 'exit',
|
|
31
|
+
'NSLog', 'NSLogv', 'NSAssert', 'NSCAssert', 'NSParameterAssert', 'NSCParameterAssert',
|
|
32
|
+
'NSStringFromClass', 'NSStringFromSelector', 'NSStringFromProtocol', 'NSClassFromString',
|
|
33
|
+
]);
|
|
34
|
+
// Construction + memory-management + NSObject-protocol selectors that flood the
|
|
35
|
+
// member-ref store when unresolved (`[[Foo alloc] init]`, retain/release in MRC code,
|
|
36
|
+
// reflection). Suppressed ONLY when UNRESOLVED — a same-file `[self copy]` that bound
|
|
37
|
+
// to a real sibling keeps its ref. Note the FULL keyword selector `initWithName:age:`
|
|
38
|
+
// is NOT in this set, so designated initializers still resolve. START small.
|
|
39
|
+
const OBJC_IGNORED_MEMBER_CALLEES = new Set([
|
|
40
|
+
'alloc', 'init', 'new', 'retain', 'release', 'autorelease', 'dealloc', 'copy',
|
|
41
|
+
'mutableCopy', 'class', 'superclass', 'description', 'debugDescription', 'hash',
|
|
42
|
+
'isEqual', 'respondsToSelector', 'conformsToProtocol', 'isKindOfClass',
|
|
43
|
+
'isMemberOfClass', 'performSelector',
|
|
44
|
+
]);
|
|
45
|
+
const OBJC_SELECTORS = [
|
|
46
|
+
// C calls (bare `foo()`) and C-subset member calls (`p->fn()` — discriminated by
|
|
47
|
+
// the callee node, delegated to cppMemberCallInfo).
|
|
48
|
+
{ nodeType: 'call_expression', getCallee: (n) => n.childForFieldName('function') },
|
|
49
|
+
// Objective-C message sends `[recv sel:arg]`. The selector is spread across the
|
|
50
|
+
// node, so the callee IS the node (objcMemberCallInfo reads it).
|
|
51
|
+
{ nodeType: 'message_expression', getCallee: (n) => n },
|
|
52
|
+
];
|
|
53
|
+
// File scope: a C function/struct/global in a `.m`/`.h` is never inside an ObjC
|
|
54
|
+
// `@interface` body, so there is no positional access state — `inClass:false` routes
|
|
55
|
+
// `declExported` to `!hasStaticStorage` (C internal linkage). Shared, never mutated.
|
|
56
|
+
const FILE_SCOPE = {
|
|
57
|
+
className: null,
|
|
58
|
+
qualifier: '',
|
|
59
|
+
exported: true,
|
|
60
|
+
inClass: false,
|
|
61
|
+
defaultVisibility: 'public',
|
|
62
|
+
};
|
|
63
|
+
// ── selector reconstruction (THE byte-identity invariant) ────────────────────
|
|
64
|
+
// The selector concat rule MUST be byte-identical on the declaration side
|
|
65
|
+
// (method_declaration / method_definition) and the call side (message_expression),
|
|
66
|
+
// or `[self initWithName:age:]` would never resolve to the `initWithName:age:`
|
|
67
|
+
// method. `joinSelector` is the SOLE place a `:` is appended.
|
|
68
|
+
function joinSelector(labels, hasArgs) {
|
|
69
|
+
return hasArgs ? labels.map((l) => `${l}:`).join('') : labels.join('');
|
|
70
|
+
}
|
|
71
|
+
// Declaration side: the labels are the method node's DIRECT `identifier` children
|
|
72
|
+
// (the leading selector segment + one bare identifier before each keyword
|
|
73
|
+
// `method_parameter`); the parameter NAMES live INSIDE each `method_parameter`, not
|
|
74
|
+
// as direct children. `hasArgs` ⟺ the method takes ≥1 `method_parameter`.
|
|
75
|
+
function selectorFromMethodDecl(node) {
|
|
76
|
+
const labels = [];
|
|
77
|
+
let hasArgs = false;
|
|
78
|
+
for (const c of node.namedChildren) {
|
|
79
|
+
if (c.type === 'identifier')
|
|
80
|
+
labels.push(c.text);
|
|
81
|
+
else if (c.type === 'method_parameter')
|
|
82
|
+
hasArgs = true;
|
|
83
|
+
}
|
|
84
|
+
return joinSelector(labels, hasArgs);
|
|
85
|
+
}
|
|
86
|
+
// Call side: the labels are the `method:` fields; `hasArgs` ⟺ the message carries a
|
|
87
|
+
// `:` token (a keyword message has one per labelled argument; a unary message — `[x
|
|
88
|
+
// draw]` — has none, so its single `method` field stays colon-free).
|
|
89
|
+
function selectorFromMessage(node) {
|
|
90
|
+
const labels = node.childrenForFieldName('method').map((m) => m.text);
|
|
91
|
+
const hasArgs = node.children.some((c) => c.type === ':');
|
|
92
|
+
return joinSelector(labels, hasArgs);
|
|
93
|
+
}
|
|
94
|
+
// ── call resolution ──────────────────────────────────────────────────────────
|
|
95
|
+
// Reduces an Objective-C message send OR a C-subset member callee to {receiver,
|
|
96
|
+
// property, isSelf}. Message sends:
|
|
97
|
+
// `[self sel]` → self-call (resolve against the enclosing @implementation)
|
|
98
|
+
// `[super sel]` → null (parent dispatch — the cpp super:: drop rule)
|
|
99
|
+
// `[Greeter make]` → receiver = `Greeter` (a class send RESOLVES via
|
|
100
|
+
// methodsByClass['Greeter'])
|
|
101
|
+
// `[obj greet]` → receiver = `obj` (an instance send — stays
|
|
102
|
+
// unresolved-but-findable: dynamic typing, no receiver type)
|
|
103
|
+
// `[[Foo alloc] init]` → RECEIVER_OPAQUE (nested-message receiver — findable, never
|
|
104
|
+
// resolved; the construction recall gap)
|
|
105
|
+
// C-subset member callees (`p->fn()`, `Foo::bar()`) → the shared cppMemberCallInfo.
|
|
106
|
+
function objcMemberCallInfo(callee) {
|
|
107
|
+
if (callee.type === 'message_expression') {
|
|
108
|
+
const property = selectorFromMessage(callee);
|
|
109
|
+
if (!property)
|
|
110
|
+
return null;
|
|
111
|
+
const receiver = callee.childForFieldName('receiver');
|
|
112
|
+
if (!receiver)
|
|
113
|
+
return null;
|
|
114
|
+
if (receiver.type === 'identifier') {
|
|
115
|
+
const text = receiver.text;
|
|
116
|
+
if (text === 'self')
|
|
117
|
+
return { receiver: 'self', property, isSelf: true };
|
|
118
|
+
if (text === 'super')
|
|
119
|
+
return null;
|
|
120
|
+
return { receiver: text, property, isSelf: false };
|
|
121
|
+
}
|
|
122
|
+
// nested message / field / computed receiver → opaque (findable, never resolved)
|
|
123
|
+
return { receiver: RECEIVER_OPAQUE, property, isSelf: false };
|
|
124
|
+
}
|
|
125
|
+
return cppMemberCallInfo(callee);
|
|
126
|
+
}
|
|
127
|
+
// ── entry point ────────────────────────────────────────────────────────────
|
|
128
|
+
export function extractObjc(tree, content, fileInfo) {
|
|
129
|
+
const ctx = {
|
|
130
|
+
content,
|
|
131
|
+
fileInfo,
|
|
132
|
+
occurrences: new Map(),
|
|
133
|
+
symbols: [],
|
|
134
|
+
imports: [],
|
|
135
|
+
bodies: [],
|
|
136
|
+
};
|
|
137
|
+
// C-subset nodes route through cpp's handleMember, which mutates a visibility state
|
|
138
|
+
// for in-class access labels — irrelevant at ObjC file scope, but the signature
|
|
139
|
+
// needs it. One shared object suffices (never flipped at top level).
|
|
140
|
+
const state = { visibility: 'public' };
|
|
141
|
+
// A class is declared by an `@interface` AND defined by an `@implementation`; in ONE
|
|
142
|
+
// file (a private helper class, a single-file program, an umbrella header) BOTH would
|
|
143
|
+
// emit a `class` symbol named C, making C ambiguous → excluded from methodsByClass →
|
|
144
|
+
// every `[self …]`/`[C …]` send fails to resolve. Emit the class symbol only ONCE per
|
|
145
|
+
// name per file (first-wins); both blocks still contribute members. Cross-file
|
|
146
|
+
// interface/impl stay separate symbols (separate extractions) — the decl/def split.
|
|
147
|
+
const emittedClasses = new Set();
|
|
148
|
+
for (const child of tree.rootNode.namedChildren)
|
|
149
|
+
handleTopLevel(ctx, child, state, emittedClasses);
|
|
150
|
+
// Same-name classes share the simple-name FQN; resolving through them first-wins
|
|
151
|
+
// would bind to the WRONG class (the cpp/Go/C# pattern). Categories/class-extensions
|
|
152
|
+
// do NOT emit a duplicate class symbol — they merge into the existing class — so the
|
|
153
|
+
// only genuine duplicates are distinct same-name @interface/@implementation blocks.
|
|
154
|
+
const ambiguousClassNames = collectAmbiguousTypeNames(ctx.symbols, new Set(['class']));
|
|
155
|
+
const references = resolveCalls(ctx.bodies, tree.rootNode, ctx.symbols, fileInfo, OBJC_SELECTORS, OBJC_SKIP_TYPES, CPP_FUNCTION_BODY_SKIP_TYPES, objcMemberCallInfo, {
|
|
156
|
+
bareCalleeTypes: OBJC_BARE_CALLEE_TYPES,
|
|
157
|
+
plainCalleeType: 'identifier',
|
|
158
|
+
bareCallableKinds: OBJC_BARE_CALLABLE_KINDS,
|
|
159
|
+
bareCallsBindToEnclosingClass: false, // ObjC has NO implicit-this
|
|
160
|
+
ambiguousClassNames,
|
|
161
|
+
ignoredBareCallees: OBJC_IGNORED_BARE_CALLEES,
|
|
162
|
+
ignoredMemberCallees: OBJC_IGNORED_MEMBER_CALLEES,
|
|
163
|
+
// NO constructorKinds/constructorSelectorTypes — `[[Foo alloc] init]` is a nested
|
|
164
|
+
// message send (opaque), not a distinct construction node.
|
|
165
|
+
});
|
|
166
|
+
// Cyclomatic + cognitive complexity — the SAME shared `CFAMILY_COMPLEXITY_OPTS` as cpp/c
|
|
167
|
+
// (one source of truth in cpp.ts). The AST dump confirmed tree-sitter-objc reuses the C
|
|
168
|
+
// control-flow node names (incl. `catch_clause` for `@catch`), so the shared options apply
|
|
169
|
+
// unchanged; objc method bodies are `method_definition` PendingBodies (NOT in the skip set,
|
|
170
|
+
// so they're descended) and `block_literal` (`^{}`) nests +0 like a C++ lambda.
|
|
171
|
+
computeComplexity(ctx.bodies, ctx.symbols, CFAMILY_COMPLEXITY_OPTS);
|
|
172
|
+
return { symbols: ctx.symbols, references, imports: ctx.imports };
|
|
173
|
+
}
|
|
174
|
+
function handleTopLevel(ctx, node, state, emittedClasses) {
|
|
175
|
+
const t = node.type;
|
|
176
|
+
// `#ifndef GUARD … #endif` include guards / `#if` blocks wrap the real declarations
|
|
177
|
+
// (the dominant ObjC header shape). Recurse transparently so an @interface inside a
|
|
178
|
+
// guard is still reached — handleMember can't (it has no OO cases). Both branches are
|
|
179
|
+
// extracted (over-extraction; the OccurrenceCounter keeps ids unique).
|
|
180
|
+
if (PREPROC_GROUPS.has(t)) {
|
|
181
|
+
for (const c of node.namedChildren)
|
|
182
|
+
handleTopLevel(ctx, c, state, emittedClasses);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
switch (t) {
|
|
186
|
+
case 'class_interface':
|
|
187
|
+
extractInterface(ctx, node, emittedClasses);
|
|
188
|
+
return;
|
|
189
|
+
case 'class_implementation':
|
|
190
|
+
extractImplementation(ctx, node, emittedClasses);
|
|
191
|
+
return;
|
|
192
|
+
case 'protocol_declaration':
|
|
193
|
+
extractProtocol(ctx, node);
|
|
194
|
+
return;
|
|
195
|
+
case 'module_import':
|
|
196
|
+
extractModuleImport(ctx, node);
|
|
197
|
+
return;
|
|
198
|
+
case 'class_declaration': // `@class A, B;` forward declaration
|
|
199
|
+
case 'protocol_forward_declaration': // `@protocol P;` forward declaration
|
|
200
|
+
return;
|
|
201
|
+
case 'type_definition': {
|
|
202
|
+
// `typedef NS_ENUM(NSInteger, Color) { … }` — the macro is opaque to the grammar
|
|
203
|
+
// (mis-parses; the enum NAME + members are buried in an ERROR). Skip the whole
|
|
204
|
+
// typedef rather than emit its mis-parsed enumerators as spurious `type` symbols
|
|
205
|
+
// (a documented macro-opacity recall gap, never a wrong edge).
|
|
206
|
+
if (node.childForFieldName('type')?.type === 'macro_type_specifier')
|
|
207
|
+
return;
|
|
208
|
+
handleMember(ctx, node, FILE_SCOPE, state, null);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
default:
|
|
212
|
+
// C subset (function_definition / declaration / struct / union / enum / typedef)
|
|
213
|
+
// + `#import` → the shared cpp dispatcher at file scope. Inert on the OO /
|
|
214
|
+
// namespace / template nodes it doesn't recognize.
|
|
215
|
+
handleMember(ctx, node, FILE_SCOPE, state, null);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// ── Objective-C OO surface ───────────────────────────────────────────────────
|
|
220
|
+
// `@interface C : Super <P…> {ivars} … @end`, a category `@interface C (Cat)`, OR a
|
|
221
|
+
// class-extension `@interface C ()` — all are `class_interface`. The class NAME is the
|
|
222
|
+
// first `identifier` child (no `name:` field); `superclass:`/`category:`/protocol
|
|
223
|
+
// arguments are display-only. A category/extension (it carries `(`/`)` tokens) emits
|
|
224
|
+
// NO class symbol — it only merges members into the existing class via the shared
|
|
225
|
+
// simple-name FQN `file:C.<member>` (the Go/Swift extension-apart pattern).
|
|
226
|
+
function extractInterface(ctx, node, emittedClasses) {
|
|
227
|
+
const className = firstIdentifier(node);
|
|
228
|
+
if (!className)
|
|
229
|
+
return;
|
|
230
|
+
const isCategoryOrExtension = node.children.some((c) => c.type === '(');
|
|
231
|
+
if (!isCategoryOrExtension && !emittedClasses.has(className)) {
|
|
232
|
+
emittedClasses.add(className);
|
|
233
|
+
ctx.symbols.push(makeCppSymbol(ctx, node, headSignature(ctx, node), 'class', className, topFqn(ctx, className), true, cppDoc(node), className));
|
|
234
|
+
}
|
|
235
|
+
extractMembers(ctx, node, className);
|
|
236
|
+
}
|
|
237
|
+
// `@implementation C … @end` (or a category implementation `@implementation C (Cat)`).
|
|
238
|
+
// A NON-category impl emits the class symbol (a `.m`-only private class has no
|
|
239
|
+
// `@interface`; a header decl is in a DIFFERENT file so its id differs by path; a
|
|
240
|
+
// same-file @interface/@implementation pair emits the class symbol only ONCE via the
|
|
241
|
+
// shared `emittedClasses` set, first-wins). A CATEGORY impl (it carries `(`/`)` tokens)
|
|
242
|
+
// emits NO class symbol either — the class is defined elsewhere; a second same-name
|
|
243
|
+
// `class` symbol in one file would make `C` ambiguous and EXCLUDE its methods from
|
|
244
|
+
// methodsByClass, breaking every `[self …]`/`[C …]` resolution. Methods are wrapped one
|
|
245
|
+
// level deep in `implementation_definition` (handled by extractMembers).
|
|
246
|
+
function extractImplementation(ctx, node, emittedClasses) {
|
|
247
|
+
const className = firstIdentifier(node);
|
|
248
|
+
if (!className)
|
|
249
|
+
return;
|
|
250
|
+
const isCategory = node.children.some((c) => c.type === '(');
|
|
251
|
+
if (!isCategory && !emittedClasses.has(className)) {
|
|
252
|
+
emittedClasses.add(className);
|
|
253
|
+
ctx.symbols.push(makeCppSymbol(ctx, node, headSignature(ctx, node), 'class', className, topFqn(ctx, className), true, cppDoc(node), className));
|
|
254
|
+
}
|
|
255
|
+
extractMembers(ctx, node, className);
|
|
256
|
+
}
|
|
257
|
+
// `@protocol P <Base> … @end` → an `interface` symbol with declaration-only members.
|
|
258
|
+
// Members sit under `qualified_protocol_interface_declaration` wrappers (one per
|
|
259
|
+
// `@required`/`@optional`), recursed transparently by extractMembers.
|
|
260
|
+
function extractProtocol(ctx, node) {
|
|
261
|
+
const name = firstIdentifier(node);
|
|
262
|
+
if (!name)
|
|
263
|
+
return;
|
|
264
|
+
ctx.symbols.push(makeCppSymbol(ctx, node, headSignature(ctx, node), 'interface', name, topFqn(ctx, name), true, cppDoc(node), name));
|
|
265
|
+
extractMembers(ctx, node, name);
|
|
266
|
+
}
|
|
267
|
+
// Walks an @interface / @implementation / @protocol body for method + property
|
|
268
|
+
// members. Methods are DIRECT children (interfaces, categories) or wrapped one level
|
|
269
|
+
// deep in `implementation_definition` (impl) / `qualified_protocol_interface_declaration`
|
|
270
|
+
// (a protocol's @required/@optional block) — descend those transparently. ivars
|
|
271
|
+
// (`instance_variables`) are NOT extracted in v1 (private implementation detail; the
|
|
272
|
+
// public API surface is `@property`).
|
|
273
|
+
function extractMembers(ctx, container, className) {
|
|
274
|
+
for (const child of container.namedChildren) {
|
|
275
|
+
switch (child.type) {
|
|
276
|
+
case 'implementation_definition':
|
|
277
|
+
case 'qualified_protocol_interface_declaration':
|
|
278
|
+
extractMembers(ctx, child, className);
|
|
279
|
+
break;
|
|
280
|
+
case 'method_declaration':
|
|
281
|
+
case 'method_definition':
|
|
282
|
+
extractMethod(ctx, child, className);
|
|
283
|
+
break;
|
|
284
|
+
case 'property_declaration':
|
|
285
|
+
extractProperty(ctx, child, className);
|
|
286
|
+
break;
|
|
287
|
+
default:
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// A `method_declaration` (header, bodiless) or `method_definition` (impl, with a
|
|
293
|
+
// `compound_statement` body). BOTH `+` (class) and `-` (instance) → **method** kind
|
|
294
|
+
// keyed on the class (the Ruby `def self.x` precedent — a class send `[C sel]` resolves
|
|
295
|
+
// via methodsByClass['C']). Name = the full selector. The decl→def cross-file pairing
|
|
296
|
+
// produces two symbols for one method (the documented C++ decl/def split); same-file
|
|
297
|
+
// duplicates dedup via the OccurrenceCounter.
|
|
298
|
+
function extractMethod(ctx, node, className) {
|
|
299
|
+
const selector = selectorFromMethodDecl(node);
|
|
300
|
+
if (!selector)
|
|
301
|
+
return;
|
|
302
|
+
const sym = makeCppSymbol(ctx, node, methodSignature(ctx, node), 'method', selector, memberFqn(ctx, className, selector), true, cppDoc(node), className);
|
|
303
|
+
ctx.symbols.push(sym);
|
|
304
|
+
// The impl `method_definition` owns a PendingBody (className set so `[self sel]`
|
|
305
|
+
// resolves same-file); the header `method_declaration` is bodiless (the cross-file
|
|
306
|
+
// decl→def self-call gap, the C++ precedent).
|
|
307
|
+
if (node.type === 'method_definition' && node.namedChildren.some((c) => c.type === 'compound_statement')) {
|
|
308
|
+
ctx.bodies.push({ symbolId: sym.id, body: node, className });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// `@property (attrs) Type *name;` → a **variable** member `file:C.name`. The name lives
|
|
312
|
+
// in a C-style `struct_declaration > struct_declarator > (identifier | pointer/…
|
|
313
|
+
// declarator)` — `analyze` resolves it through any pointer/function-pointer wrapper.
|
|
314
|
+
// v1 emits the variable only; synthesizing getter/setter `method` symbols (`name`/
|
|
315
|
+
// `setName:`) so a dotted/message access resolves is a noted v2.
|
|
316
|
+
function extractProperty(ctx, node, className) {
|
|
317
|
+
const sd = node.namedChildren.find((c) => c.type === 'struct_declaration');
|
|
318
|
+
if (!sd)
|
|
319
|
+
return;
|
|
320
|
+
const sig = propertySignature(ctx, node);
|
|
321
|
+
const doc = cppDoc(node);
|
|
322
|
+
// One variable per declarator: `@property (assign) int a, b;` declares both a and b.
|
|
323
|
+
// `analyze` resolves the name through any pointer / block-pointer / function-pointer
|
|
324
|
+
// wrapper (`void (^handler)(int)` → `handler`).
|
|
325
|
+
for (const declarator of sd.namedChildren) {
|
|
326
|
+
if (declarator.type !== 'struct_declarator')
|
|
327
|
+
continue;
|
|
328
|
+
const inner = declarator.namedChildren[0];
|
|
329
|
+
if (!inner)
|
|
330
|
+
continue;
|
|
331
|
+
const info = analyze(inner);
|
|
332
|
+
if (!info || !info.name)
|
|
333
|
+
continue;
|
|
334
|
+
ctx.symbols.push(makeCppSymbol(ctx, node, sig, 'variable', info.name, memberFqn(ctx, className, info.name), true, doc, className));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// `@import Module;` → a namespace-style import (a module has no named binding). The
|
|
338
|
+
// `path:` field is the module name (`UIKit`, `Foo.Bar`). `#import`/`#include` are
|
|
339
|
+
// handled by the shared extractInclude via handleMember.
|
|
340
|
+
function extractModuleImport(ctx, node) {
|
|
341
|
+
// The `path:` field has MULTIPLE children for a dotted submodule import
|
|
342
|
+
// (`@import Foo.Bar;` → identifier `Foo`, `.`, identifier `Bar`); the `.` tokens are
|
|
343
|
+
// themselves path children, so a plain join reproduces the full `Foo.Bar`.
|
|
344
|
+
const segments = node.childrenForFieldName('path');
|
|
345
|
+
if (segments.length === 0)
|
|
346
|
+
return;
|
|
347
|
+
const sourceModule = segments.map((c) => c.text).join('');
|
|
348
|
+
if (!sourceModule)
|
|
349
|
+
return;
|
|
350
|
+
ctx.imports.push({
|
|
351
|
+
file: ctx.fileInfo.path,
|
|
352
|
+
sourceModule,
|
|
353
|
+
importedNames: [{ name: IMPORT_NAMESPACE }],
|
|
354
|
+
line: node.startPosition.row + 1,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
// ── helpers ───────────────────────────────────────────────────────────────
|
|
358
|
+
function firstIdentifier(node) {
|
|
359
|
+
for (const c of node.namedChildren) {
|
|
360
|
+
if (c.type === 'identifier')
|
|
361
|
+
return c.text;
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
// Member nodes / `@end` / the ivar block that mark the END of an @interface /
|
|
366
|
+
// @protocol / @implementation HEAD line. cppSignature is unusable on these OO nodes
|
|
367
|
+
// (no `body:` field — it would slurp the whole interface), so cut at the first such.
|
|
368
|
+
const HEAD_STOP = new Set([
|
|
369
|
+
'instance_variables',
|
|
370
|
+
'property_declaration',
|
|
371
|
+
'method_declaration',
|
|
372
|
+
'method_definition',
|
|
373
|
+
'implementation_definition',
|
|
374
|
+
'qualified_protocol_interface_declaration',
|
|
375
|
+
'@end',
|
|
376
|
+
]);
|
|
377
|
+
// Signature for an @interface/@protocol/@implementation head: source from the node
|
|
378
|
+
// start to the first member / ivar block / `@end`, trimmed of a trailing `{`.
|
|
379
|
+
function headSignature(ctx, node) {
|
|
380
|
+
let cut = node.endIndex;
|
|
381
|
+
for (const c of node.children) {
|
|
382
|
+
if (HEAD_STOP.has(c.type))
|
|
383
|
+
cut = Math.min(cut, c.startIndex);
|
|
384
|
+
}
|
|
385
|
+
return normalizeSignature(ctx.content.slice(node.startIndex, cut)).replace(/\{\s*$/, '').trimEnd();
|
|
386
|
+
}
|
|
387
|
+
// `- (Type)selector …` up to the body (a definition) or end (a declaration), trimmed
|
|
388
|
+
// of a trailing `;`.
|
|
389
|
+
function methodSignature(ctx, node) {
|
|
390
|
+
const body = node.namedChildren.find((c) => c.type === 'compound_statement');
|
|
391
|
+
const cut = body ? body.startIndex : node.endIndex;
|
|
392
|
+
return normalizeSignature(ctx.content.slice(node.startIndex, cut)).replace(/;\s*$/, '').trimEnd();
|
|
393
|
+
}
|
|
394
|
+
// The whole `@property (…) Type *name` line, trimmed of a trailing `;`.
|
|
395
|
+
function propertySignature(ctx, node) {
|
|
396
|
+
return normalizeSignature(ctx.content.slice(node.startIndex, node.endIndex)).replace(/;\s*$/, '').trimEnd();
|
|
397
|
+
}
|