gitnexus 1.6.3-rc.40 → 1.6.3-rc.41
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/README.md +16 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts +12 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/parsed-file.js +12 -0
- package/dist/_shared/scope-resolution/parsed-file.js.map +1 -1
- package/dist/_shared/scope-resolution/reference-site.d.ts +8 -0
- package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
- package/dist/core/ingestion/language-provider.d.ts +3 -2
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
- package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
- package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
- package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
- package/dist/core/ingestion/languages/csharp/arity.js +37 -0
- package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
- package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
- package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
- package/dist/core/ingestion/languages/csharp/captures.js +249 -0
- package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
- package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
- package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
- package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
- package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
- package/dist/core/ingestion/languages/csharp/index.js +82 -0
- package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
- package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
- package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
- package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
- package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
- package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
- package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
- package/dist/core/ingestion/languages/csharp/query.js +515 -0
- package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
- package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
- package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
- package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
- package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
- package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
- package/dist/core/ingestion/languages/csharp.js +14 -0
- package/dist/core/ingestion/languages/python/import-target.d.ts +4 -0
- package/dist/core/ingestion/languages/python/import-target.js +4 -0
- package/dist/core/ingestion/languages/python/merge-bindings.d.ts +2 -2
- package/dist/core/ingestion/languages/python/merge-bindings.js +1 -1
- package/dist/core/ingestion/languages/python/query.d.ts +1 -6
- package/dist/core/ingestion/languages/python/query.js +1 -6
- package/dist/core/ingestion/languages/python/receiver-binding.d.ts +3 -3
- package/dist/core/ingestion/languages/python/receiver-binding.js +3 -3
- package/dist/core/ingestion/languages/python/scope-resolver.js +15 -15
- package/dist/core/ingestion/languages/python.js +2 -2
- package/dist/core/ingestion/model/method-registry.d.ts +9 -0
- package/dist/core/ingestion/model/method-registry.js +4 -0
- package/dist/core/ingestion/model/semantic-model.d.ts +39 -0
- package/dist/core/ingestion/model/semantic-model.js +39 -0
- package/dist/core/ingestion/registry-primary-flag.js +1 -0
- package/dist/core/ingestion/scope-extractor.js +20 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +216 -12
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +149 -12
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +1 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +9 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +1 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +11 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +12 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +10 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +61 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +3 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +73 -3
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +8 -4
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +164 -37
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +8 -1
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +5 -1
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +11 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +33 -7
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +36 -9
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +86 -24
- package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +39 -33
- package/dist/core/ingestion/scope-resolution/workspace-index.js +39 -87
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -155,6 +155,7 @@ gitnexus analyze --force # Force full re-index
|
|
|
155
155
|
gitnexus analyze --embeddings # Enable embedding generation (slower, better search)
|
|
156
156
|
gitnexus analyze --skip-agents-md # Preserve custom AGENTS.md/CLAUDE.md gitnexus section edits
|
|
157
157
|
gitnexus analyze --verbose # Log skipped files when parsers are unavailable
|
|
158
|
+
gitnexus analyze --max-file-size 1024 # Skip files larger than N KB (default: 512, cap: 32768)
|
|
158
159
|
gitnexus mcp # Start MCP server (stdio) — serves all indexed repos
|
|
159
160
|
gitnexus serve # Start local HTTP server (multi-repo) for web UI
|
|
160
161
|
gitnexus index # Register an existing .gitnexus/ folder into the global registry
|
|
@@ -307,6 +308,21 @@ echo "vendor/" >> .gitnexusignore
|
|
|
307
308
|
echo "dist/" >> .gitnexusignore
|
|
308
309
|
```
|
|
309
310
|
|
|
311
|
+
### Large files are being skipped
|
|
312
|
+
|
|
313
|
+
By default the walker skips files larger than **512 KB** (see log line `Skipped N large files (>512KB)`). Raise the threshold via either the CLI flag or the environment variable — both accept a value in **KB**:
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# CLI flag (takes precedence over the env var)
|
|
317
|
+
npx gitnexus analyze --max-file-size 2048 # skip only files > 2 MB
|
|
318
|
+
|
|
319
|
+
# Environment variable (persists across commands)
|
|
320
|
+
export GITNEXUS_MAX_FILE_SIZE=2048
|
|
321
|
+
npx gitnexus analyze
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Values above **32768 KB (32 MB)** are clamped to the tree-sitter parser ceiling; invalid values fall back to the 512 KB default with a one-time warning. When an override is active, `analyze` prints the effective threshold in its startup banner (e.g. `GITNEXUS_MAX_FILE_SIZE: effective threshold 2048KB (default 512KB)`).
|
|
325
|
+
|
|
310
326
|
## Privacy
|
|
311
327
|
|
|
312
328
|
- All processing happens locally on your machine
|
|
@@ -37,6 +37,18 @@
|
|
|
37
37
|
* `localDefs`. A `ParsedFile` is trivially convertible to a `FinalizeFile`
|
|
38
38
|
* by picking those four fields, so the finalize orchestrator threads
|
|
39
39
|
* ParsedFile through to the shared algorithm without shape-shifting.
|
|
40
|
+
*
|
|
41
|
+
* ## Source-of-truth invariant
|
|
42
|
+
*
|
|
43
|
+
* `ParsedFile` is the single semantic model consumed by both the legacy
|
|
44
|
+
* DAG (`gitnexus/src/core/ingestion/` outside `scope-resolution/`) and
|
|
45
|
+
* the scope-resolution pipeline (`gitnexus/src/core/ingestion/scope-resolution/`).
|
|
46
|
+
* Downstream passes MUST NOT build a parallel parse representation; if
|
|
47
|
+
* a pass needs AST-level facts that `ParsedFile` doesn't expose, it
|
|
48
|
+
* should reuse the orchestrator's `treeCache` rather than re-invoke
|
|
49
|
+
* `parser.parse(...)` on its own. See the
|
|
50
|
+
* `ScopeResolver` contract (`gitnexus/src/core/ingestion/scope-resolution/contract/scope-resolver.ts`)
|
|
51
|
+
* for the full list of invariants downstream consumers rely on.
|
|
40
52
|
*/
|
|
41
53
|
import type { Scope, ScopeId } from './types.js';
|
|
42
54
|
import type { ParsedImport } from './types.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parsed-file.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/parsed-file.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"parsed-file.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/parsed-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,SAAS,YAAY,EAAE,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAChD,QAAQ,CAAC,cAAc,EAAE,SAAS,aAAa,EAAE,CAAC;CACnD"}
|
|
@@ -37,6 +37,18 @@
|
|
|
37
37
|
* `localDefs`. A `ParsedFile` is trivially convertible to a `FinalizeFile`
|
|
38
38
|
* by picking those four fields, so the finalize orchestrator threads
|
|
39
39
|
* ParsedFile through to the shared algorithm without shape-shifting.
|
|
40
|
+
*
|
|
41
|
+
* ## Source-of-truth invariant
|
|
42
|
+
*
|
|
43
|
+
* `ParsedFile` is the single semantic model consumed by both the legacy
|
|
44
|
+
* DAG (`gitnexus/src/core/ingestion/` outside `scope-resolution/`) and
|
|
45
|
+
* the scope-resolution pipeline (`gitnexus/src/core/ingestion/scope-resolution/`).
|
|
46
|
+
* Downstream passes MUST NOT build a parallel parse representation; if
|
|
47
|
+
* a pass needs AST-level facts that `ParsedFile` doesn't expose, it
|
|
48
|
+
* should reuse the orchestrator's `treeCache` rather than re-invoke
|
|
49
|
+
* `parser.parse(...)` on its own. See the
|
|
50
|
+
* `ScopeResolver` contract (`gitnexus/src/core/ingestion/scope-resolution/contract/scope-resolver.ts`)
|
|
51
|
+
* for the full list of invariants downstream consumers rely on.
|
|
40
52
|
*/
|
|
41
53
|
export {};
|
|
42
54
|
//# sourceMappingURL=parsed-file.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parsed-file.js","sourceRoot":"","sources":["../../src/scope-resolution/parsed-file.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"parsed-file.js","sourceRoot":"","sources":["../../src/scope-resolution/parsed-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG"}
|
|
@@ -63,5 +63,13 @@ export interface ReferenceSite {
|
|
|
63
63
|
};
|
|
64
64
|
/** Argument count at the call site; used by `provider.arityCompatibility`. */
|
|
65
65
|
readonly arity?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Inferred argument types at the call site, one per argument. An
|
|
68
|
+
* empty-string entry means "unknown" — consumers narrowing overload
|
|
69
|
+
* candidates treat unknown as any-match. Populated by languages
|
|
70
|
+
* that can derive types from literals / constructor expressions
|
|
71
|
+
* (C#: `42` → `'int'`, `"alice"` → `'string'`).
|
|
72
|
+
*/
|
|
73
|
+
readonly argumentTypes?: readonly string[];
|
|
66
74
|
}
|
|
67
75
|
//# sourceMappingURL=reference-site.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reference-site.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/reference-site.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,MAAM,GACN,OAAO,GACP,gBAAgB,GAChB,UAAU,GACV,YAAY,CAAC;AAEjB;;;;;;;;;GASG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,aAAa,GAAG,OAAO,CAAC;AAEnE,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,kCAAkC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"reference-site.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/reference-site.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,MAAM,GACN,OAAO,GACP,gBAAgB,GAChB,UAAU,GACV,YAAY,CAAC;AAEjB;;;;;;;;;GASG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,aAAa,GAAG,OAAO,CAAC;AAEnE,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,kCAAkC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5C"}
|
|
@@ -224,8 +224,9 @@ interface LanguageProviderConfig {
|
|
|
224
224
|
readonly builtInNames?: ReadonlySet<string>;
|
|
225
225
|
/**
|
|
226
226
|
* Emit scope captures from raw source, **pre-grouped per tree-sitter
|
|
227
|
-
* query match**. Tree-sitter-based providers run a
|
|
228
|
-
*
|
|
227
|
+
* query match**. Tree-sitter-based providers run a scope query
|
|
228
|
+
* (embedded as a string constant in each language's `query.ts`) and
|
|
229
|
+
* emit one `CaptureMatch` per query match; standalone providers
|
|
229
230
|
* (COBOL) emit matches from a regex tagger. The return shape is
|
|
230
231
|
* parser-agnostic: the central `ScopeExtractor` consumes
|
|
231
232
|
* `CaptureMatch[]` without knowing which parser produced them.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C# collection-accessor unwrapping.
|
|
3
|
+
*
|
|
4
|
+
* When the compound-receiver resolver encounters a trailing
|
|
5
|
+
* `.Values` / `.Keys` on a dotted member-access chain, it calls the
|
|
6
|
+
* provider's `unwrapCollectionAccessor` hook to find the element
|
|
7
|
+
* type. This module supplies the C# implementation — recognizing
|
|
8
|
+
* Dictionary-family generics and returning the value or key type.
|
|
9
|
+
*
|
|
10
|
+
* Other languages (Python, Java, TypeScript) use method-call syntax
|
|
11
|
+
* for the same access (`.values()` / `.keys()`), which the compound-
|
|
12
|
+
* receiver's call-expression branch already handles; they leave this
|
|
13
|
+
* hook undefined.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Resolve `data.Values` / `data.Keys` on a Dictionary-like receiver
|
|
17
|
+
* to its element-type simple name. Returns `undefined` for any
|
|
18
|
+
* receiver / accessor combination we don't recognize, letting the
|
|
19
|
+
* compound-receiver pass fall through to the regular field walk.
|
|
20
|
+
*/
|
|
21
|
+
export declare function unwrapCsharpCollectionAccessor(receiverType: string, accessor: string): string | undefined;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C# collection-accessor unwrapping.
|
|
3
|
+
*
|
|
4
|
+
* When the compound-receiver resolver encounters a trailing
|
|
5
|
+
* `.Values` / `.Keys` on a dotted member-access chain, it calls the
|
|
6
|
+
* provider's `unwrapCollectionAccessor` hook to find the element
|
|
7
|
+
* type. This module supplies the C# implementation — recognizing
|
|
8
|
+
* Dictionary-family generics and returning the value or key type.
|
|
9
|
+
*
|
|
10
|
+
* Other languages (Python, Java, TypeScript) use method-call syntax
|
|
11
|
+
* for the same access (`.values()` / `.keys()`), which the compound-
|
|
12
|
+
* receiver's call-expression branch already handles; they leave this
|
|
13
|
+
* hook undefined.
|
|
14
|
+
*/
|
|
15
|
+
/** Extract (K, V) from `Dictionary<K, V>` / `IDictionary<K, V>` /
|
|
16
|
+
* `IReadOnlyDictionary<K, V>` / `SortedDictionary<K, V>` /
|
|
17
|
+
* `ConcurrentDictionary<K, V>` / `ImmutableDictionary<K, V>`.
|
|
18
|
+
* Returns undefined if the type name doesn't match or the argument
|
|
19
|
+
* list isn't exactly two top-level args. */
|
|
20
|
+
function extractDictionaryArgs(rawName) {
|
|
21
|
+
const match = rawName.match(/^(?:[A-Za-z_][A-Za-z0-9_.]*\.)?(?:Dictionary|IDictionary|IReadOnlyDictionary|SortedDictionary|ConcurrentDictionary|ImmutableDictionary)<(.+)>$/);
|
|
22
|
+
if (match === null)
|
|
23
|
+
return undefined;
|
|
24
|
+
const inner = match[1];
|
|
25
|
+
// Split on the top-level comma (tolerate nested `<...>`).
|
|
26
|
+
let depth = 0;
|
|
27
|
+
let commaIdx = -1;
|
|
28
|
+
for (let i = 0; i < inner.length; i++) {
|
|
29
|
+
const ch = inner[i];
|
|
30
|
+
if (ch === '<')
|
|
31
|
+
depth++;
|
|
32
|
+
else if (ch === '>')
|
|
33
|
+
depth--;
|
|
34
|
+
else if (ch === ',' && depth === 0) {
|
|
35
|
+
commaIdx = i;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (commaIdx === -1)
|
|
40
|
+
return undefined;
|
|
41
|
+
return { key: inner.slice(0, commaIdx).trim(), value: inner.slice(commaIdx + 1).trim() };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolve `data.Values` / `data.Keys` on a Dictionary-like receiver
|
|
45
|
+
* to its element-type simple name. Returns `undefined` for any
|
|
46
|
+
* receiver / accessor combination we don't recognize, letting the
|
|
47
|
+
* compound-receiver pass fall through to the regular field walk.
|
|
48
|
+
*/
|
|
49
|
+
export function unwrapCsharpCollectionAccessor(receiverType, accessor) {
|
|
50
|
+
if (accessor !== 'Values' && accessor !== 'Keys')
|
|
51
|
+
return undefined;
|
|
52
|
+
const args = extractDictionaryArgs(receiverType);
|
|
53
|
+
if (args === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
return accessor === 'Values' ? args.value : args.key;
|
|
56
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract C# arity metadata from a method-like tree-sitter node —
|
|
3
|
+
* `method_declaration`, `constructor_declaration`, `destructor_declaration`,
|
|
4
|
+
* `operator_declaration`, `conversion_operator_declaration`, or
|
|
5
|
+
* `local_function_statement`.
|
|
6
|
+
*
|
|
7
|
+
* Reuses `csharpMethodConfig.extractParameters` so scope-extracted defs
|
|
8
|
+
* carry the same arity semantics as the legacy parse-worker path:
|
|
9
|
+
* - `params` variadic collapses `parameterCount` to `undefined`,
|
|
10
|
+
* which `csharpArityCompatibility` then treats as "max unknown" —
|
|
11
|
+
* the candidate stays eligible at `argCount >= required`.
|
|
12
|
+
* - Defaulted parameters (`= expr`) contribute to `optionalCount`;
|
|
13
|
+
* `requiredParameterCount = total − optionalCount`.
|
|
14
|
+
* - `parameterTypes` collects declared type names (with `ref`/`out`/
|
|
15
|
+
* `in` prefix) for overload narrowing; a literal `'params'` marker
|
|
16
|
+
* is appended for variadic methods so `csharpArityCompatibility`
|
|
17
|
+
* can detect them without re-reading the AST.
|
|
18
|
+
*/
|
|
19
|
+
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
|
20
|
+
interface CsharpArityMetadata {
|
|
21
|
+
readonly parameterCount: number | undefined;
|
|
22
|
+
readonly requiredParameterCount: number | undefined;
|
|
23
|
+
readonly parameterTypes: readonly string[] | undefined;
|
|
24
|
+
}
|
|
25
|
+
export declare function computeCsharpArityMetadata(fnNode: SyntaxNode): CsharpArityMetadata;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract C# arity metadata from a method-like tree-sitter node —
|
|
3
|
+
* `method_declaration`, `constructor_declaration`, `destructor_declaration`,
|
|
4
|
+
* `operator_declaration`, `conversion_operator_declaration`, or
|
|
5
|
+
* `local_function_statement`.
|
|
6
|
+
*
|
|
7
|
+
* Reuses `csharpMethodConfig.extractParameters` so scope-extracted defs
|
|
8
|
+
* carry the same arity semantics as the legacy parse-worker path:
|
|
9
|
+
* - `params` variadic collapses `parameterCount` to `undefined`,
|
|
10
|
+
* which `csharpArityCompatibility` then treats as "max unknown" —
|
|
11
|
+
* the candidate stays eligible at `argCount >= required`.
|
|
12
|
+
* - Defaulted parameters (`= expr`) contribute to `optionalCount`;
|
|
13
|
+
* `requiredParameterCount = total − optionalCount`.
|
|
14
|
+
* - `parameterTypes` collects declared type names (with `ref`/`out`/
|
|
15
|
+
* `in` prefix) for overload narrowing; a literal `'params'` marker
|
|
16
|
+
* is appended for variadic methods so `csharpArityCompatibility`
|
|
17
|
+
* can detect them without re-reading the AST.
|
|
18
|
+
*/
|
|
19
|
+
import { csharpMethodConfig } from '../../method-extractors/configs/csharp.js';
|
|
20
|
+
export function computeCsharpArityMetadata(fnNode) {
|
|
21
|
+
const params = csharpMethodConfig.extractParameters?.(fnNode) ?? [];
|
|
22
|
+
let hasVariadic = false;
|
|
23
|
+
let optionalCount = 0;
|
|
24
|
+
const types = [];
|
|
25
|
+
for (const p of params) {
|
|
26
|
+
if (p.isVariadic)
|
|
27
|
+
hasVariadic = true;
|
|
28
|
+
else if (p.isOptional)
|
|
29
|
+
optionalCount++;
|
|
30
|
+
if (p.type !== null)
|
|
31
|
+
types.push(p.type);
|
|
32
|
+
}
|
|
33
|
+
if (hasVariadic)
|
|
34
|
+
types.push('params');
|
|
35
|
+
const total = params.length;
|
|
36
|
+
// `params int[] args` declares one formal param but accepts any arg
|
|
37
|
+
// count ≥ required — mirror Python's treatment of `*args` and leave
|
|
38
|
+
// `parameterCount` undefined so the registry treats max as unknown.
|
|
39
|
+
const parameterCount = hasVariadic ? undefined : total;
|
|
40
|
+
const requiredParameterCount = hasVariadic ? undefined : total - optionalCount;
|
|
41
|
+
return {
|
|
42
|
+
parameterCount,
|
|
43
|
+
requiredParameterCount,
|
|
44
|
+
parameterTypes: types.length > 0 ? types : undefined,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C# arity check, accommodating `params` variadic and default parameters.
|
|
3
|
+
*
|
|
4
|
+
* The `def` metadata we care about (synthesized by `arity-metadata.ts`):
|
|
5
|
+
* - `parameterCount` — total formal parameters; `undefined`
|
|
6
|
+
* when the method has `params T[]` variadic.
|
|
7
|
+
* - `requiredParameterCount` — min required (excludes defaulted params
|
|
8
|
+
* and `params` variadic).
|
|
9
|
+
* - `parameterTypes` — declared type strings; contains the
|
|
10
|
+
* literal `'params'` when the method is
|
|
11
|
+
* variadic.
|
|
12
|
+
*
|
|
13
|
+
* Verdicts:
|
|
14
|
+
* - `'compatible'` — `requiredParameterCount <= argCount <= parameterCount`,
|
|
15
|
+
* OR the def takes `params` (then any `argCount >= required`).
|
|
16
|
+
* - `'incompatible'` — argCount is below required, OR above max with no variadic.
|
|
17
|
+
* - `'unknown'` — metadata is absent / incomplete.
|
|
18
|
+
*
|
|
19
|
+
* `'incompatible'` is a soft signal in `Registry.lookup` (penalized but
|
|
20
|
+
* still considered when no compatible candidate exists), per RFC §4.
|
|
21
|
+
*/
|
|
22
|
+
import type { Callsite, SymbolDefinition } from '../../../../_shared/index.js';
|
|
23
|
+
export declare function csharpArityCompatibility(def: SymbolDefinition, callsite: Callsite): 'compatible' | 'unknown' | 'incompatible';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C# arity check, accommodating `params` variadic and default parameters.
|
|
3
|
+
*
|
|
4
|
+
* The `def` metadata we care about (synthesized by `arity-metadata.ts`):
|
|
5
|
+
* - `parameterCount` — total formal parameters; `undefined`
|
|
6
|
+
* when the method has `params T[]` variadic.
|
|
7
|
+
* - `requiredParameterCount` — min required (excludes defaulted params
|
|
8
|
+
* and `params` variadic).
|
|
9
|
+
* - `parameterTypes` — declared type strings; contains the
|
|
10
|
+
* literal `'params'` when the method is
|
|
11
|
+
* variadic.
|
|
12
|
+
*
|
|
13
|
+
* Verdicts:
|
|
14
|
+
* - `'compatible'` — `requiredParameterCount <= argCount <= parameterCount`,
|
|
15
|
+
* OR the def takes `params` (then any `argCount >= required`).
|
|
16
|
+
* - `'incompatible'` — argCount is below required, OR above max with no variadic.
|
|
17
|
+
* - `'unknown'` — metadata is absent / incomplete.
|
|
18
|
+
*
|
|
19
|
+
* `'incompatible'` is a soft signal in `Registry.lookup` (penalized but
|
|
20
|
+
* still considered when no compatible candidate exists), per RFC §4.
|
|
21
|
+
*/
|
|
22
|
+
export function csharpArityCompatibility(def, callsite) {
|
|
23
|
+
const max = def.parameterCount;
|
|
24
|
+
const min = def.requiredParameterCount;
|
|
25
|
+
if (max === undefined && min === undefined)
|
|
26
|
+
return 'unknown';
|
|
27
|
+
const argCount = callsite.arity;
|
|
28
|
+
if (!Number.isFinite(argCount) || argCount < 0)
|
|
29
|
+
return 'unknown';
|
|
30
|
+
const hasVarArgs = def.parameterTypes !== undefined &&
|
|
31
|
+
def.parameterTypes.some((t) => t === 'params' || t.startsWith('params '));
|
|
32
|
+
if (min !== undefined && argCount < min)
|
|
33
|
+
return 'incompatible';
|
|
34
|
+
if (max !== undefined && argCount > max && !hasVarArgs)
|
|
35
|
+
return 'incompatible';
|
|
36
|
+
return 'compatible';
|
|
37
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-mode counters for the cross-phase scope-captures parse cache
|
|
3
|
+
* (C# mirror of `languages/python/cache-stats.ts`).
|
|
4
|
+
*
|
|
5
|
+
* Gated by `PROF_SCOPE_RESOLUTION=1`. Production builds fold every
|
|
6
|
+
* increment into dead code via the module-level `PROF` constant, so
|
|
7
|
+
* the hot path in `captures.ts` stays branch-free.
|
|
8
|
+
*/
|
|
9
|
+
export declare function recordCacheHit(): void;
|
|
10
|
+
export declare function recordCacheMiss(): void;
|
|
11
|
+
export declare function getCsharpCaptureCacheStats(): {
|
|
12
|
+
hits: number;
|
|
13
|
+
misses: number;
|
|
14
|
+
};
|
|
15
|
+
export declare function resetCsharpCaptureCacheStats(): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-mode counters for the cross-phase scope-captures parse cache
|
|
3
|
+
* (C# mirror of `languages/python/cache-stats.ts`).
|
|
4
|
+
*
|
|
5
|
+
* Gated by `PROF_SCOPE_RESOLUTION=1`. Production builds fold every
|
|
6
|
+
* increment into dead code via the module-level `PROF` constant, so
|
|
7
|
+
* the hot path in `captures.ts` stays branch-free.
|
|
8
|
+
*/
|
|
9
|
+
const PROF = process.env.PROF_SCOPE_RESOLUTION === '1';
|
|
10
|
+
let CACHE_HITS = 0;
|
|
11
|
+
let CACHE_MISSES = 0;
|
|
12
|
+
export function recordCacheHit() {
|
|
13
|
+
if (PROF)
|
|
14
|
+
CACHE_HITS++;
|
|
15
|
+
}
|
|
16
|
+
export function recordCacheMiss() {
|
|
17
|
+
if (PROF)
|
|
18
|
+
CACHE_MISSES++;
|
|
19
|
+
}
|
|
20
|
+
export function getCsharpCaptureCacheStats() {
|
|
21
|
+
return { hits: CACHE_HITS, misses: CACHE_MISSES };
|
|
22
|
+
}
|
|
23
|
+
export function resetCsharpCaptureCacheStats() {
|
|
24
|
+
CACHE_HITS = 0;
|
|
25
|
+
CACHE_MISSES = 0;
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `emitScopeCaptures` for C#.
|
|
3
|
+
*
|
|
4
|
+
* Drives the C# scope query against tree-sitter-c-sharp and groups raw
|
|
5
|
+
* matches into `CaptureMatch[]` for the central extractor. Layers one
|
|
6
|
+
* synthesized stream on top today:
|
|
7
|
+
*
|
|
8
|
+
* 1. **Decomposed using directives** — each `using_directive` is
|
|
9
|
+
* re-emitted with `@import.kind/source/name/alias` markers so
|
|
10
|
+
* `interpretCsharpImport` can recover the ParsedImport shape
|
|
11
|
+
* without re-parsing raw text (see `import-decomposer.ts`).
|
|
12
|
+
*
|
|
13
|
+
* Receiver-binding synthesis (`this` / `base` type anchors) and arity
|
|
14
|
+
* metadata synthesis (Unit 5) layer on top later.
|
|
15
|
+
*
|
|
16
|
+
* Pure given the input source text. No I/O, no globals consulted.
|
|
17
|
+
*/
|
|
18
|
+
import type { CaptureMatch } from '../../../../_shared/index.js';
|
|
19
|
+
export declare function emitCsharpScopeCaptures(sourceText: string, _filePath: string, cachedTree?: unknown): readonly CaptureMatch[];
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `emitScopeCaptures` for C#.
|
|
3
|
+
*
|
|
4
|
+
* Drives the C# scope query against tree-sitter-c-sharp and groups raw
|
|
5
|
+
* matches into `CaptureMatch[]` for the central extractor. Layers one
|
|
6
|
+
* synthesized stream on top today:
|
|
7
|
+
*
|
|
8
|
+
* 1. **Decomposed using directives** — each `using_directive` is
|
|
9
|
+
* re-emitted with `@import.kind/source/name/alias` markers so
|
|
10
|
+
* `interpretCsharpImport` can recover the ParsedImport shape
|
|
11
|
+
* without re-parsing raw text (see `import-decomposer.ts`).
|
|
12
|
+
*
|
|
13
|
+
* Receiver-binding synthesis (`this` / `base` type anchors) and arity
|
|
14
|
+
* metadata synthesis (Unit 5) layer on top later.
|
|
15
|
+
*
|
|
16
|
+
* Pure given the input source text. No I/O, no globals consulted.
|
|
17
|
+
*/
|
|
18
|
+
import { findNodeAtRange, nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
|
|
19
|
+
import { splitUsingDirective } from './import-decomposer.js';
|
|
20
|
+
import { computeCsharpArityMetadata } from './arity-metadata.js';
|
|
21
|
+
import { synthesizeCsharpReceiverBinding } from './receiver-binding.js';
|
|
22
|
+
import { getCsharpParser, getCsharpScopeQuery } from './query.js';
|
|
23
|
+
import { recordCacheHit, recordCacheMiss } from './cache-stats.js';
|
|
24
|
+
/** Declaration anchors that carry function-like arity metadata. */
|
|
25
|
+
const FUNCTION_DECL_TAGS = [
|
|
26
|
+
'@declaration.method',
|
|
27
|
+
'@declaration.constructor',
|
|
28
|
+
'@declaration.function',
|
|
29
|
+
];
|
|
30
|
+
/** tree-sitter-c-sharp node types that the method extractor accepts. */
|
|
31
|
+
const FUNCTION_NODE_TYPES = [
|
|
32
|
+
'method_declaration',
|
|
33
|
+
'constructor_declaration',
|
|
34
|
+
'destructor_declaration',
|
|
35
|
+
'operator_declaration',
|
|
36
|
+
'conversion_operator_declaration',
|
|
37
|
+
'local_function_statement',
|
|
38
|
+
];
|
|
39
|
+
export function emitCsharpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
40
|
+
// Skip the parse when the caller (parse phase's scopeTreeCache)
|
|
41
|
+
// already produced a Tree for this source. Cache miss = re-parse,
|
|
42
|
+
// same as before. The cachedTree parameter is typed as `unknown` at
|
|
43
|
+
// the LanguageProvider contract layer; cast here at the use site.
|
|
44
|
+
let tree = cachedTree;
|
|
45
|
+
if (tree === undefined) {
|
|
46
|
+
tree = getCsharpParser().parse(sourceText);
|
|
47
|
+
recordCacheMiss();
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
recordCacheHit();
|
|
51
|
+
}
|
|
52
|
+
const rawMatches = getCsharpScopeQuery().matches(tree.rootNode);
|
|
53
|
+
const out = [];
|
|
54
|
+
for (const m of rawMatches) {
|
|
55
|
+
// Group captures by their tag name. Tree-sitter strips the leading
|
|
56
|
+
// `@`; we put it back so the central extractor's prefix lookups
|
|
57
|
+
// (`@scope.`, `@declaration.`, …) work.
|
|
58
|
+
const grouped = {};
|
|
59
|
+
for (const c of m.captures) {
|
|
60
|
+
const tag = '@' + c.name;
|
|
61
|
+
grouped[tag] = nodeToCapture(tag, c.node);
|
|
62
|
+
}
|
|
63
|
+
if (Object.keys(grouped).length === 0)
|
|
64
|
+
continue;
|
|
65
|
+
// Decompose each `using_directive` so `interpretCsharpImport` sees
|
|
66
|
+
// the kind/source/name/alias markers it consumes. Raw query match
|
|
67
|
+
// only carries the @import.statement anchor.
|
|
68
|
+
if (grouped['@import.statement'] !== undefined) {
|
|
69
|
+
const stmtCapture = grouped['@import.statement'];
|
|
70
|
+
const stmtNode = findNodeAtRange(tree.rootNode, stmtCapture.range, 'using_directive');
|
|
71
|
+
if (stmtNode !== null) {
|
|
72
|
+
const decomposed = splitUsingDirective(stmtNode);
|
|
73
|
+
if (decomposed !== null) {
|
|
74
|
+
out.push(decomposed);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Defensive fallback: emit the raw match so the extractor at
|
|
79
|
+
// least sees an anchor, even without markers.
|
|
80
|
+
out.push(grouped);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// Synthesize `this` / `base` receiver type-bindings on every
|
|
84
|
+
// instance method-like. Tree-sitter can't cleanly express "the
|
|
85
|
+
// implicit receiver of a non-static member of a class/struct/
|
|
86
|
+
// record/interface" via a static `.scm` pattern, so we walk up
|
|
87
|
+
// the AST in code. Mirrors Python's `self`/`cls` synthesis on
|
|
88
|
+
// `@scope.function` matches.
|
|
89
|
+
if (grouped['@scope.function'] !== undefined) {
|
|
90
|
+
out.push(grouped);
|
|
91
|
+
const anchor = grouped['@scope.function'];
|
|
92
|
+
const fnNode = findFunctionNode(tree.rootNode, anchor.range);
|
|
93
|
+
if (fnNode !== null) {
|
|
94
|
+
for (const synth of synthesizeCsharpReceiverBinding(fnNode)) {
|
|
95
|
+
out.push(synth);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
// Synthesize arity metadata on function-like declarations so the
|
|
101
|
+
// registry can narrow overloads (C# relies heavily on this). Mirrors
|
|
102
|
+
// Python's captures.ts pattern — one anchor per match, so we find
|
|
103
|
+
// the first tag that matches.
|
|
104
|
+
const declTag = FUNCTION_DECL_TAGS.find((t) => grouped[t] !== undefined);
|
|
105
|
+
if (declTag !== undefined) {
|
|
106
|
+
const anchor = grouped[declTag];
|
|
107
|
+
const fnNode = findFunctionNode(tree.rootNode, anchor.range);
|
|
108
|
+
if (fnNode !== null) {
|
|
109
|
+
const arity = computeCsharpArityMetadata(fnNode);
|
|
110
|
+
if (arity.parameterCount !== undefined) {
|
|
111
|
+
grouped['@declaration.parameter-count'] = syntheticCapture('@declaration.parameter-count', fnNode, String(arity.parameterCount));
|
|
112
|
+
}
|
|
113
|
+
if (arity.requiredParameterCount !== undefined) {
|
|
114
|
+
grouped['@declaration.required-parameter-count'] = syntheticCapture('@declaration.required-parameter-count', fnNode, String(arity.requiredParameterCount));
|
|
115
|
+
}
|
|
116
|
+
if (arity.parameterTypes !== undefined) {
|
|
117
|
+
grouped['@declaration.parameter-types'] = syntheticCapture('@declaration.parameter-types', fnNode, JSON.stringify(arity.parameterTypes));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Synthesize `@reference.arity` on every callsite so the
|
|
122
|
+
// registry's arity filter can narrow overloads. Count the
|
|
123
|
+
// `argument` named children of the backing `argument_list`.
|
|
124
|
+
// Python doesn't synthesize this today; C# needs it because the
|
|
125
|
+
// language has method overloading and the suite asserts overload
|
|
126
|
+
// resolution.
|
|
127
|
+
const callTag = ['@reference.call.free', '@reference.call.member', '@reference.call.constructor'].find((t) => grouped[t] !== undefined);
|
|
128
|
+
if (callTag !== undefined && grouped['@reference.arity'] === undefined) {
|
|
129
|
+
const anchor = grouped[callTag];
|
|
130
|
+
const callNode = findNodeAtRange(tree.rootNode, anchor.range, 'invocation_expression') ??
|
|
131
|
+
findNodeAtRange(tree.rootNode, anchor.range, 'object_creation_expression');
|
|
132
|
+
if (callNode !== null) {
|
|
133
|
+
const argList = callNode.childForFieldName('arguments');
|
|
134
|
+
const args = argList === null
|
|
135
|
+
? []
|
|
136
|
+
: argList.namedChildren.filter((c) => c !== null && c.type === 'argument');
|
|
137
|
+
grouped['@reference.arity'] = syntheticCapture('@reference.arity', callNode, String(args.length));
|
|
138
|
+
// Infer argument types from literal nodes so overload
|
|
139
|
+
// disambiguation can narrow same-arity candidates by param
|
|
140
|
+
// type. Non-literal arguments emit empty string to indicate
|
|
141
|
+
// "unknown" — consumers treat unknown as any-match.
|
|
142
|
+
const argTypes = args.map((arg) => inferArgType(arg));
|
|
143
|
+
grouped['@reference.parameter-types'] = syntheticCapture('@reference.parameter-types', callNode, JSON.stringify(argTypes));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
out.push(grouped);
|
|
147
|
+
// Synthesize primary-constructor declarations on class/record
|
|
148
|
+
// declarations that carry a `parameter_list` child (C# 12 syntax
|
|
149
|
+
// `public class User(string name, int age) { ... }` or
|
|
150
|
+
// `public record Person(string FirstName, string LastName)`).
|
|
151
|
+
// Legacy `csharpMethodConfig.extractPrimaryConstructor` runs via
|
|
152
|
+
// the parse phase; the scope-resolution path needs its own emit so
|
|
153
|
+
// `new User(...)` resolves to a Constructor def in memberByOwner.
|
|
154
|
+
if (grouped['@declaration.class'] !== undefined ||
|
|
155
|
+
grouped['@declaration.record'] !== undefined) {
|
|
156
|
+
const anchor = grouped['@declaration.class'] ?? grouped['@declaration.record'];
|
|
157
|
+
const typeNode = findNodeAtRange(tree.rootNode, anchor.range, 'class_declaration') ??
|
|
158
|
+
findNodeAtRange(tree.rootNode, anchor.range, 'record_declaration');
|
|
159
|
+
if (typeNode !== null) {
|
|
160
|
+
const synth = synthesizePrimaryConstructor(typeNode);
|
|
161
|
+
if (synth !== null)
|
|
162
|
+
out.push(synth);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
/** C# 12 primary constructor: `class X(a, b) { }` / `record X(a, b)`.
|
|
169
|
+
* The parameters are a bare `parameter_list` named child of the type
|
|
170
|
+
* declaration (no `constructor_declaration` node). Emit a synthetic
|
|
171
|
+
* @declaration.constructor match so the extractor creates a
|
|
172
|
+
* Constructor def in memberByOwner — free-call-fallback's
|
|
173
|
+
* `pickConstructorOrClass` then targets it for `new X(...)` calls. */
|
|
174
|
+
function synthesizePrimaryConstructor(typeNode) {
|
|
175
|
+
// Skip types with an explicit constructor_declaration — that would
|
|
176
|
+
// create duplicate defs.
|
|
177
|
+
const body = typeNode.childForFieldName('body');
|
|
178
|
+
if (body !== null) {
|
|
179
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
180
|
+
const child = body.namedChild(i);
|
|
181
|
+
if (child !== null && child.type === 'constructor_declaration')
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
let paramList = null;
|
|
186
|
+
for (let i = 0; i < typeNode.namedChildCount; i++) {
|
|
187
|
+
const child = typeNode.namedChild(i);
|
|
188
|
+
if (child !== null && child.type === 'parameter_list') {
|
|
189
|
+
paramList = child;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (paramList === null)
|
|
194
|
+
return null;
|
|
195
|
+
const nameNode = typeNode.childForFieldName('name');
|
|
196
|
+
if (nameNode === null)
|
|
197
|
+
return null;
|
|
198
|
+
const paramCount = paramList.namedChildren.filter((c) => c !== null && c.type === 'parameter').length;
|
|
199
|
+
const m = {
|
|
200
|
+
'@declaration.constructor': nodeToCapture('@declaration.constructor', paramList),
|
|
201
|
+
'@declaration.name': syntheticCapture('@declaration.name', nameNode, nameNode.text),
|
|
202
|
+
'@declaration.parameter-count': syntheticCapture('@declaration.parameter-count', paramList, String(paramCount)),
|
|
203
|
+
'@declaration.required-parameter-count': syntheticCapture('@declaration.required-parameter-count', paramList, String(paramCount)),
|
|
204
|
+
};
|
|
205
|
+
return m;
|
|
206
|
+
}
|
|
207
|
+
/** Infer a C# argument's static type from literal / constructor
|
|
208
|
+
* patterns. Returns `''` when the arg has no statically-derivable
|
|
209
|
+
* type (e.g. identifier — would require full type inference). */
|
|
210
|
+
function inferArgType(argNode) {
|
|
211
|
+
// `argument > expression` — tree-sitter-c-sharp wraps the value.
|
|
212
|
+
const expr = argNode.namedChild(0);
|
|
213
|
+
if (expr === null)
|
|
214
|
+
return '';
|
|
215
|
+
switch (expr.type) {
|
|
216
|
+
case 'integer_literal':
|
|
217
|
+
return 'int';
|
|
218
|
+
case 'real_literal':
|
|
219
|
+
return 'double';
|
|
220
|
+
case 'string_literal':
|
|
221
|
+
case 'verbatim_string_literal':
|
|
222
|
+
case 'interpolated_string_expression':
|
|
223
|
+
case 'raw_string_literal':
|
|
224
|
+
return 'string';
|
|
225
|
+
case 'character_literal':
|
|
226
|
+
return 'char';
|
|
227
|
+
case 'boolean_literal':
|
|
228
|
+
return 'bool';
|
|
229
|
+
case 'null_literal':
|
|
230
|
+
return 'null';
|
|
231
|
+
case 'object_creation_expression': {
|
|
232
|
+
const typeNode = expr.childForFieldName('type');
|
|
233
|
+
return typeNode?.text ?? '';
|
|
234
|
+
}
|
|
235
|
+
default:
|
|
236
|
+
return '';
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/** Find the first C# function-like node at the given range. The
|
|
240
|
+
* declaration anchor range covers the whole method/constructor/etc.
|
|
241
|
+
* node, but the tag alone doesn't tell us which node type. */
|
|
242
|
+
function findFunctionNode(rootNode, range) {
|
|
243
|
+
for (const nodeType of FUNCTION_NODE_TYPES) {
|
|
244
|
+
const n = findNodeAtRange(rootNode, range, nodeType);
|
|
245
|
+
if (n !== null)
|
|
246
|
+
return n;
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|