gitnexus 1.6.6-rc.157 → 1.6.6-rc.159
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/dist/core/ingestion/field-extractors/configs/dart.d.ts +0 -6
- package/dist/core/ingestion/field-extractors/configs/dart.js +61 -40
- package/dist/core/ingestion/field-extractors/configs/jvm.js +43 -4
- package/dist/core/ingestion/field-extractors/configs/swift.d.ts +12 -1
- package/dist/core/ingestion/field-extractors/configs/swift.js +30 -5
- package/dist/core/ingestion/languages/c/import-decomposer.d.ts +9 -0
- package/dist/core/ingestion/languages/c/import-decomposer.js +15 -0
- package/dist/core/ingestion/languages/cpp/import-decomposer.d.ts +7 -0
- package/dist/core/ingestion/languages/cpp/import-decomposer.js +12 -0
- package/dist/core/ingestion/languages/dart/query.js +40 -0
- package/dist/core/ingestion/languages/kotlin/captures.js +105 -0
- package/dist/core/ingestion/languages/kotlin/query.js +30 -0
- package/dist/core/ingestion/method-extractors/configs/jvm.js +25 -10
- package/dist/core/ingestion/method-extractors/configs/swift.js +11 -4
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +1 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +10 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +15 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +4 -4
- package/dist/core/ingestion/tree-sitter-queries.js +125 -6
- package/dist/core/ingestion/utils/ast-helpers.d.ts +36 -0
- package/dist/core/ingestion/utils/ast-helpers.js +68 -0
- package/dist/core/ingestion/variable-extractors/configs/c-cpp.js +49 -0
- package/dist/core/ingestion/variable-extractors/configs/dart.js +76 -53
- package/dist/core/ingestion/variable-extractors/configs/jvm.js +35 -8
- package/dist/core/ingestion/workers/parse-worker.js +30 -2
- package/package.json +2 -1
|
@@ -1,8 +1,2 @@
|
|
|
1
1
|
import type { FieldExtractionConfig } from '../generic.js';
|
|
2
|
-
/**
|
|
3
|
-
* Dart field extraction config.
|
|
4
|
-
*
|
|
5
|
-
* Dart class fields appear as declaration nodes inside class_body.
|
|
6
|
-
* Visibility is convention-based: underscore prefix = private.
|
|
7
|
-
*/
|
|
8
2
|
export declare const dartConfig: FieldExtractionConfig;
|
|
@@ -5,41 +5,68 @@ import { extractSimpleTypeName } from '../../type-extractors/shared.js';
|
|
|
5
5
|
/**
|
|
6
6
|
* Dart field extraction config.
|
|
7
7
|
*
|
|
8
|
-
* Dart class fields appear as declaration nodes inside class_body
|
|
8
|
+
* Dart class fields appear as `declaration` nodes inside `class_body`.
|
|
9
|
+
* Two shapes carry the field name(s):
|
|
10
|
+
* - instance / plain fields → `initialized_identifier_list`
|
|
11
|
+
* (`int z = 0;`, `int a = 1, b = 2;`)
|
|
12
|
+
* - `static const` / `static final` / `const` fields → `static_final_declaration_list`
|
|
13
|
+
* (`static const a = 1;`, `static final String b = 'x', c = 'y';`)
|
|
14
|
+
* Both shapes may declare SEVERAL fields in one declaration, so name extraction
|
|
15
|
+
* is multi-name (`extractNames`). The structure query (`DART_QUERIES`) emits one
|
|
16
|
+
* `@definition.property` per name for both shapes; this config enriches each.
|
|
17
|
+
*
|
|
9
18
|
* Visibility is convention-based: underscore prefix = private.
|
|
10
19
|
*/
|
|
20
|
+
/** All field names declared by a `declaration` node, across both Dart shapes. */
|
|
21
|
+
function extractDartFieldNames(node) {
|
|
22
|
+
const names = [];
|
|
23
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
24
|
+
const child = node.namedChild(i);
|
|
25
|
+
if (!child)
|
|
26
|
+
continue;
|
|
27
|
+
// instance / plain fields: initialized_identifier_list > initialized_identifier > identifier
|
|
28
|
+
if (child.type === 'initialized_identifier_list') {
|
|
29
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
30
|
+
const init = child.namedChild(j);
|
|
31
|
+
if (init?.type === 'initialized_identifier') {
|
|
32
|
+
const ident = init.firstNamedChild;
|
|
33
|
+
if (ident?.type === 'identifier')
|
|
34
|
+
names.push(ident.text);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// static const / final fields: static_final_declaration_list > static_final_declaration > identifier
|
|
39
|
+
if (child.type === 'static_final_declaration_list') {
|
|
40
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
41
|
+
const decl = child.namedChild(j);
|
|
42
|
+
if (decl?.type === 'static_final_declaration') {
|
|
43
|
+
const ident = decl.firstNamedChild;
|
|
44
|
+
if (ident?.type === 'identifier')
|
|
45
|
+
names.push(ident.text);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return names;
|
|
51
|
+
}
|
|
11
52
|
export const dartConfig = {
|
|
12
53
|
language: SupportedLanguages.Dart,
|
|
13
54
|
typeDeclarationNodes: ['class_definition'],
|
|
14
55
|
fieldNodeTypes: ['declaration'],
|
|
15
56
|
bodyNodeTypes: ['class_body'],
|
|
16
57
|
defaultVisibility: 'public',
|
|
58
|
+
// One AST `declaration` node may declare several fields (`int a, b;`,
|
|
59
|
+
// `static final String b = 'x', c = 'y';`), so use the multi-name path.
|
|
17
60
|
extractName(node) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
for (let j = 0; j < child.namedChildCount; j++) {
|
|
23
|
-
const init = child.namedChild(j);
|
|
24
|
-
if (init?.type === 'initialized_identifier') {
|
|
25
|
-
const ident = init.firstNamedChild;
|
|
26
|
-
if (ident?.type === 'identifier')
|
|
27
|
-
return ident.text;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
if (child?.type === 'initialized_identifier') {
|
|
32
|
-
const ident = child.firstNamedChild;
|
|
33
|
-
if (ident?.type === 'identifier')
|
|
34
|
-
return ident.text;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
// fallback: look for direct identifier
|
|
38
|
-
const name = node.childForFieldName('name');
|
|
39
|
-
return name?.text;
|
|
61
|
+
return extractDartFieldNames(node)[0];
|
|
62
|
+
},
|
|
63
|
+
extractNames(node) {
|
|
64
|
+
return extractDartFieldNames(node);
|
|
40
65
|
},
|
|
41
66
|
extractType(node) {
|
|
42
|
-
// declaration > type_identifier (
|
|
67
|
+
// declaration > type_identifier (the type annotation, present for both the
|
|
68
|
+
// instance-field shape and `static final String b = …`). `static const a = 1;`
|
|
69
|
+
// has no annotation → undefined (untyped).
|
|
43
70
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
44
71
|
const child = node.namedChild(i);
|
|
45
72
|
if (child && (child.type === 'type_identifier' || child.type === 'function_type')) {
|
|
@@ -48,28 +75,22 @@ export const dartConfig = {
|
|
|
48
75
|
}
|
|
49
76
|
return undefined;
|
|
50
77
|
},
|
|
78
|
+
// Per-name: Dart convention is underscore-prefixed = private. A single
|
|
79
|
+
// declaration can mix visibilities (`static const _p = 1, q = 2;`), so the
|
|
80
|
+
// decision is keyed on the individual field name.
|
|
81
|
+
extractVisibilityForName(_node, name) {
|
|
82
|
+
return name.startsWith('_') ? 'private' : 'public';
|
|
83
|
+
},
|
|
51
84
|
extractVisibility(node) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
55
|
-
const child = node.namedChild(i);
|
|
56
|
-
if (child?.type === 'initialized_identifier_list') {
|
|
57
|
-
for (let j = 0; j < child.namedChildCount; j++) {
|
|
58
|
-
const init = child.namedChild(j);
|
|
59
|
-
if (init?.type === 'initialized_identifier') {
|
|
60
|
-
const ident = init.firstNamedChild;
|
|
61
|
-
if (ident?.text?.startsWith('_'))
|
|
62
|
-
return 'private';
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return 'public';
|
|
85
|
+
const first = extractDartFieldNames(node)[0];
|
|
86
|
+
return first?.startsWith('_') ? 'private' : 'public';
|
|
68
87
|
},
|
|
69
88
|
isStatic(node) {
|
|
70
89
|
return hasKeyword(node, 'static');
|
|
71
90
|
},
|
|
72
91
|
isReadonly(node) {
|
|
92
|
+
// `final` / `const` (both `final_builtin`/`const_builtin` nodes whose text
|
|
93
|
+
// is `final`/`const`) are read-only.
|
|
73
94
|
return hasKeyword(node, 'final') || hasKeyword(node, 'const');
|
|
74
95
|
},
|
|
75
96
|
};
|
|
@@ -60,12 +60,49 @@ export const javaConfig = {
|
|
|
60
60
|
// Kotlin
|
|
61
61
|
// ---------------------------------------------------------------------------
|
|
62
62
|
const KOTLIN_VIS = new Set(['public', 'private', 'protected', 'internal']);
|
|
63
|
+
/** A property_declaration is a companion-object member when its nearest
|
|
64
|
+
* class-body ancestor is the body of a companion_object (F52, issue #1919).
|
|
65
|
+
* Companion members are addressed statically through the enclosing class
|
|
66
|
+
* (`C.TAG`), so they are marked static. */
|
|
67
|
+
function isInsideKotlinCompanion(node) {
|
|
68
|
+
for (let cur = node.parent; cur !== null; cur = cur.parent) {
|
|
69
|
+
if (cur.type === 'class_body')
|
|
70
|
+
return cur.parent?.type === 'companion_object';
|
|
71
|
+
if (cur.type === 'companion_object')
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
63
76
|
export const kotlinConfig = {
|
|
64
77
|
language: SupportedLanguages.Kotlin,
|
|
65
|
-
|
|
78
|
+
// F52: include companion_object so a companion property's innermost
|
|
79
|
+
// class-container owner (findEnclosingClassNode returns the companion_object)
|
|
80
|
+
// is recognized as a type declaration and its nested class_body is walked.
|
|
81
|
+
// The structure query already creates the Property node and owns it on the
|
|
82
|
+
// ENCLOSING class for anonymous companions / on the named companion Class —
|
|
83
|
+
// this entry only drives field-metadata enrichment, so it does NOT change
|
|
84
|
+
// ownership or emit a second node (no double-count).
|
|
85
|
+
typeDeclarationNodes: ['class_declaration', 'object_declaration', 'companion_object'],
|
|
66
86
|
fieldNodeTypes: ['property_declaration'],
|
|
67
87
|
bodyNodeTypes: ['class_body'],
|
|
68
88
|
defaultVisibility: 'public',
|
|
89
|
+
// F52: an anonymous `companion object { ... }` has no name child, so the
|
|
90
|
+
// generic factory's `childForFieldName('name')` owner lookup is empty and
|
|
91
|
+
// `extract()` would bail before walking the body. Supply a stable owner
|
|
92
|
+
// name (the named companion's identifier, else "Companion") so the body IS
|
|
93
|
+
// walked; the resulting FieldInfo map is keyed by field NAME only, so the
|
|
94
|
+
// owner name does not affect which Property node gets enriched.
|
|
95
|
+
extractOwnerName(node) {
|
|
96
|
+
const typeIdentifierText = node.namedChildren.find((c) => c.type === 'type_identifier')?.text;
|
|
97
|
+
if (node.type === 'companion_object') {
|
|
98
|
+
// Anonymous companions have no type_identifier — fall back to "Companion".
|
|
99
|
+
return typeIdentifierText ?? 'Companion';
|
|
100
|
+
}
|
|
101
|
+
const name = node.childForFieldName('name');
|
|
102
|
+
if (name)
|
|
103
|
+
return name.text;
|
|
104
|
+
return typeIdentifierText;
|
|
105
|
+
},
|
|
69
106
|
extractName(node) {
|
|
70
107
|
// property_declaration > variable_declaration > simple_identifier
|
|
71
108
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -107,9 +144,11 @@ export const kotlinConfig = {
|
|
|
107
144
|
extractVisibility(node) {
|
|
108
145
|
return findVisibility(node, KOTLIN_VIS, 'public', 'modifiers');
|
|
109
146
|
},
|
|
110
|
-
isStatic(
|
|
111
|
-
// Kotlin
|
|
112
|
-
|
|
147
|
+
isStatic(node) {
|
|
148
|
+
// Kotlin has no `static`, but companion-object members are accessed
|
|
149
|
+
// statically through the enclosing class (`C.TAG`) — mark them static
|
|
150
|
+
// so the field metadata reflects that (F52).
|
|
151
|
+
return isInsideKotlinCompanion(node);
|
|
113
152
|
},
|
|
114
153
|
isReadonly(node) {
|
|
115
154
|
// 'val' = readonly, 'var' = mutable
|
|
@@ -2,7 +2,18 @@ import type { FieldExtractionConfig } from '../generic.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Swift field extraction config.
|
|
4
4
|
*
|
|
5
|
-
* Handles property_declaration inside class_body / protocol_body
|
|
5
|
+
* Handles property_declaration inside class_body / protocol_body and
|
|
6
|
+
* protocol_property_declaration inside protocol_body (F75 — protocol property
|
|
7
|
+
* requirements like "var title: String { get }").
|
|
8
|
+
*
|
|
6
9
|
* tree-sitter-swift uses property_declaration for stored/computed properties.
|
|
10
|
+
* A protocol property requirement parses to its own node type,
|
|
11
|
+
* protocol_property_declaration, whose name lives in a "name:" pattern field
|
|
12
|
+
* (pattern > value_binding_pattern + simple_identifier(bound_identifier)), its
|
|
13
|
+
* type in a sibling type_annotation, and its "{ get }" / "{ get set }" in a
|
|
14
|
+
* protocol_property_requirements child. Note: Swift reuses the "name:" field
|
|
15
|
+
* across many positions (func name, every parameter label, parameter/return
|
|
16
|
+
* type), so the name is synthesized from the simple_identifier inside the
|
|
17
|
+
* pattern rather than read blindly off "name:".
|
|
7
18
|
*/
|
|
8
19
|
export declare const swiftConfig: FieldExtractionConfig;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// gitnexus/src/core/ingestion/field-extractors/configs/swift.ts
|
|
2
2
|
import { SupportedLanguages } from '../../../../_shared/index.js';
|
|
3
|
-
import { hasKeyword, findVisibility } from './helpers.js';
|
|
3
|
+
import { hasKeyword, hasModifier, findVisibility } from './helpers.js';
|
|
4
4
|
import { extractSimpleTypeName } from '../../type-extractors/shared.js';
|
|
5
5
|
const SWIFT_VIS = new Set([
|
|
6
6
|
'public',
|
|
@@ -12,17 +12,32 @@ const SWIFT_VIS = new Set([
|
|
|
12
12
|
/**
|
|
13
13
|
* Swift field extraction config.
|
|
14
14
|
*
|
|
15
|
-
* Handles property_declaration inside class_body / protocol_body
|
|
15
|
+
* Handles property_declaration inside class_body / protocol_body and
|
|
16
|
+
* protocol_property_declaration inside protocol_body (F75 — protocol property
|
|
17
|
+
* requirements like "var title: String { get }").
|
|
18
|
+
*
|
|
16
19
|
* tree-sitter-swift uses property_declaration for stored/computed properties.
|
|
20
|
+
* A protocol property requirement parses to its own node type,
|
|
21
|
+
* protocol_property_declaration, whose name lives in a "name:" pattern field
|
|
22
|
+
* (pattern > value_binding_pattern + simple_identifier(bound_identifier)), its
|
|
23
|
+
* type in a sibling type_annotation, and its "{ get }" / "{ get set }" in a
|
|
24
|
+
* protocol_property_requirements child. Note: Swift reuses the "name:" field
|
|
25
|
+
* across many positions (func name, every parameter label, parameter/return
|
|
26
|
+
* type), so the name is synthesized from the simple_identifier inside the
|
|
27
|
+
* pattern rather than read blindly off "name:".
|
|
17
28
|
*/
|
|
18
29
|
export const swiftConfig = {
|
|
19
30
|
language: SupportedLanguages.Swift,
|
|
20
31
|
typeDeclarationNodes: ['class_declaration', 'protocol_declaration'],
|
|
21
|
-
fieldNodeTypes: ['property_declaration'],
|
|
32
|
+
fieldNodeTypes: ['property_declaration', 'protocol_property_declaration'],
|
|
22
33
|
bodyNodeTypes: ['class_body', 'protocol_body'],
|
|
23
34
|
defaultVisibility: 'internal',
|
|
24
35
|
extractName(node) {
|
|
25
|
-
// property_declaration > pattern > simple_identifier
|
|
36
|
+
// property_declaration > pattern > simple_identifier, and
|
|
37
|
+
// protocol_property_declaration > name: (pattern ... simple_identifier).
|
|
38
|
+
// For protocol_property_declaration the pattern wraps a leading
|
|
39
|
+
// value_binding_pattern ("var") plus the simple_identifier — the loop
|
|
40
|
+
// below skips the binding keyword and returns the identifier.
|
|
26
41
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
27
42
|
const child = node.namedChild(i);
|
|
28
43
|
if (child?.type === 'pattern') {
|
|
@@ -56,7 +71,17 @@ export const swiftConfig = {
|
|
|
56
71
|
return findVisibility(node, SWIFT_VIS, 'internal', 'modifiers');
|
|
57
72
|
},
|
|
58
73
|
isStatic(node) {
|
|
59
|
-
|
|
74
|
+
// `static`/`class` (type-level) modifiers live inside a `modifiers`
|
|
75
|
+
// wrapper for both property_declaration and protocol_property_declaration
|
|
76
|
+
// (e.g. `static var shared: P { get }`), so check the wrapper too.
|
|
77
|
+
// `hasKeyword` compares each direct child by `.text` equality: it matches a
|
|
78
|
+
// single-modifier wrapper (`modifiers.text === 'static'`) but fails for a
|
|
79
|
+
// multi-modifier wrapper (`private static` → `modifiers.text === 'private static'`),
|
|
80
|
+
// which `hasModifier` handles by descending into the wrapper's children.
|
|
81
|
+
return (hasKeyword(node, 'static') ||
|
|
82
|
+
hasKeyword(node, 'class') ||
|
|
83
|
+
hasModifier(node, 'modifiers', 'static') ||
|
|
84
|
+
hasModifier(node, 'modifiers', 'class'));
|
|
60
85
|
},
|
|
61
86
|
isReadonly(node) {
|
|
62
87
|
// 'let' = constant/readonly, 'var' = variable
|
|
@@ -4,5 +4,14 @@ import { type SyntaxNode } from '../../utils/ast-helpers.js';
|
|
|
4
4
|
* Decompose a `preproc_include` node into a CaptureMatch with structured
|
|
5
5
|
* import captures. C #include maps to a wildcard import (all symbols
|
|
6
6
|
* from the header are visible).
|
|
7
|
+
*
|
|
8
|
+
* Only literal include paths are emitted as import sources:
|
|
9
|
+
* #include <stdio.h> → system_lib_string
|
|
10
|
+
* #include "local.h" → string_literal
|
|
11
|
+
* A computed include like `#include HEADER_MACRO` carries an `identifier`
|
|
12
|
+
* path node (the macro name, not a header path). Emitting it as an import
|
|
13
|
+
* source produces a garbage literal edge, so we skip it entirely — matching
|
|
14
|
+
* the convention in interpretCImport, which drops imports with no resolvable
|
|
15
|
+
* source (issue #1919 F5).
|
|
7
16
|
*/
|
|
8
17
|
export declare function splitCInclude(node: SyntaxNode): CaptureMatch | null;
|
|
@@ -3,10 +3,20 @@ import { nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
|
|
|
3
3
|
* Decompose a `preproc_include` node into a CaptureMatch with structured
|
|
4
4
|
* import captures. C #include maps to a wildcard import (all symbols
|
|
5
5
|
* from the header are visible).
|
|
6
|
+
*
|
|
7
|
+
* Only literal include paths are emitted as import sources:
|
|
8
|
+
* #include <stdio.h> → system_lib_string
|
|
9
|
+
* #include "local.h" → string_literal
|
|
10
|
+
* A computed include like `#include HEADER_MACRO` carries an `identifier`
|
|
11
|
+
* path node (the macro name, not a header path). Emitting it as an import
|
|
12
|
+
* source produces a garbage literal edge, so we skip it entirely — matching
|
|
13
|
+
* the convention in interpretCImport, which drops imports with no resolvable
|
|
14
|
+
* source (issue #1919 F5).
|
|
6
15
|
*/
|
|
7
16
|
export function splitCInclude(node) {
|
|
8
17
|
// node.type === 'preproc_include'
|
|
9
18
|
// path field: (string_literal (string_content)) | (system_lib_string)
|
|
19
|
+
// | (identifier) ← computed macro include, NOT a header path
|
|
10
20
|
const pathNode = node.childForFieldName?.('path') ?? null;
|
|
11
21
|
if (pathNode === null) {
|
|
12
22
|
// Fallback: scan children
|
|
@@ -23,6 +33,11 @@ export function splitCInclude(node) {
|
|
|
23
33
|
return buildIncludeCapture(node, pathNode);
|
|
24
34
|
}
|
|
25
35
|
function buildIncludeCapture(node, pathNode) {
|
|
36
|
+
// Skip computed includes (`#include MACRO`) — the path is an `identifier`,
|
|
37
|
+
// not a literal header path. Emitting it would create a garbage import.
|
|
38
|
+
if (pathNode.type !== 'string_literal' && pathNode.type !== 'system_lib_string') {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
26
41
|
let raw;
|
|
27
42
|
if (pathNode.type === 'string_literal') {
|
|
28
43
|
// string_literal has children: `"`, string_content, `"`
|
|
@@ -4,6 +4,13 @@ import { type SyntaxNode } from '../../utils/ast-helpers.js';
|
|
|
4
4
|
* Decompose a `preproc_include` node into a CaptureMatch with structured
|
|
5
5
|
* import captures. C++ #include maps to a wildcard import (all symbols
|
|
6
6
|
* from the header are visible). Identical to C's splitCInclude.
|
|
7
|
+
*
|
|
8
|
+
* Only literal include paths are emitted as import sources:
|
|
9
|
+
* #include <map> → system_lib_string
|
|
10
|
+
* #include "User.h" → string_literal
|
|
11
|
+
* A computed include like `#include HEADER_MACRO` carries an `identifier`
|
|
12
|
+
* path node (the macro name, not a header path); we skip it so it never
|
|
13
|
+
* becomes a garbage literal import source (issue #1919 F5).
|
|
7
14
|
*/
|
|
8
15
|
export declare function splitCppInclude(node: SyntaxNode): CaptureMatch | null;
|
|
9
16
|
/**
|
|
@@ -3,6 +3,13 @@ import { nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
|
|
|
3
3
|
* Decompose a `preproc_include` node into a CaptureMatch with structured
|
|
4
4
|
* import captures. C++ #include maps to a wildcard import (all symbols
|
|
5
5
|
* from the header are visible). Identical to C's splitCInclude.
|
|
6
|
+
*
|
|
7
|
+
* Only literal include paths are emitted as import sources:
|
|
8
|
+
* #include <map> → system_lib_string
|
|
9
|
+
* #include "User.h" → string_literal
|
|
10
|
+
* A computed include like `#include HEADER_MACRO` carries an `identifier`
|
|
11
|
+
* path node (the macro name, not a header path); we skip it so it never
|
|
12
|
+
* becomes a garbage literal import source (issue #1919 F5).
|
|
6
13
|
*/
|
|
7
14
|
export function splitCppInclude(node) {
|
|
8
15
|
const pathNode = node.childForFieldName?.('path') ?? null;
|
|
@@ -20,6 +27,11 @@ export function splitCppInclude(node) {
|
|
|
20
27
|
return buildIncludeCapture(node, pathNode);
|
|
21
28
|
}
|
|
22
29
|
function buildIncludeCapture(node, pathNode) {
|
|
30
|
+
// Skip computed includes (`#include MACRO`) — the path is an `identifier`,
|
|
31
|
+
// not a literal header path. Emitting it would create a garbage import.
|
|
32
|
+
if (pathNode.type !== 'string_literal' && pathNode.type !== 'system_lib_string') {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
23
35
|
let raw;
|
|
24
36
|
if (pathNode.type === 'string_literal') {
|
|
25
37
|
const content = pathNode.namedChildren.find((c) => c.type === 'string_content');
|
|
@@ -37,6 +37,46 @@ const DART_SCOPE_QUERY = `
|
|
|
37
37
|
(extension_declaration name: (identifier) @declaration.name) @declaration.class
|
|
38
38
|
(enum_declaration name: (identifier) @declaration.name) @declaration.enum
|
|
39
39
|
|
|
40
|
+
; ── Declarations — type aliases (old-style + new-style function typedefs) ────
|
|
41
|
+
; Both forms parse as type_alias; the name position differs, and a generic
|
|
42
|
+
; <T> parameter list intervenes for the generic variants. Per #1919 review CF2,
|
|
43
|
+
; a generic type_parameters node sits between the name and the next anchor, so
|
|
44
|
+
; the non-generic adjacency patterns silently drop the generic forms. Four
|
|
45
|
+
; standalone patterns (NOT one alternation — the tree-sitter 0.21 hazard drops
|
|
46
|
+
; sibling branches) keep the name capture unambiguous and single-match per form:
|
|
47
|
+
; non-generic old-style typedef int Cmp(int a, int b);
|
|
48
|
+
; children: return-type, NAME, formal_parameter_list
|
|
49
|
+
; generic old-style typedef int Cmp<T>(T a, T b); (CF2)
|
|
50
|
+
; children: return-type, NAME, type_parameters, formal_parameter_list
|
|
51
|
+
; non-generic new-style typedef Pred = bool Function(int);
|
|
52
|
+
; children: NAME, "=", function_type
|
|
53
|
+
; generic new-style typedef Mapper<T> = T Function(T);
|
|
54
|
+
; children: NAME, type_parameters, "=", function_type
|
|
55
|
+
; The alias name is the type_identifier immediately before the param list (old)
|
|
56
|
+
; or before "=" (new); for the generic forms it is the one immediately before
|
|
57
|
+
; the intervening type_parameters. Mirrors Kotlin's @declaration.type_alias
|
|
58
|
+
; rule; the generic scope-extractor maps "type_alias" → TypeAlias.
|
|
59
|
+
(type_alias
|
|
60
|
+
(type_identifier) @declaration.name
|
|
61
|
+
.
|
|
62
|
+
(formal_parameter_list)) @declaration.type_alias
|
|
63
|
+
(type_alias
|
|
64
|
+
(type_identifier) @declaration.name
|
|
65
|
+
.
|
|
66
|
+
(type_parameters)
|
|
67
|
+
.
|
|
68
|
+
(formal_parameter_list)) @declaration.type_alias
|
|
69
|
+
(type_alias
|
|
70
|
+
(type_identifier) @declaration.name
|
|
71
|
+
.
|
|
72
|
+
"=") @declaration.type_alias
|
|
73
|
+
(type_alias
|
|
74
|
+
(type_identifier) @declaration.name
|
|
75
|
+
.
|
|
76
|
+
(type_parameters)
|
|
77
|
+
.
|
|
78
|
+
"=") @declaration.type_alias
|
|
79
|
+
|
|
40
80
|
; ── Declarations — top-level functions (parent is program, not method) ───────
|
|
41
81
|
(program
|
|
42
82
|
(function_signature
|
|
@@ -28,6 +28,7 @@ export function emitKotlinScopeCaptures(sourceText, filePath, cachedTree) {
|
|
|
28
28
|
out.push(...synthesizeKotlinSmartCastBindings(tree.rootNode));
|
|
29
29
|
out.push(...synthesizeKotlinLambdaBindings(tree.rootNode, returnTypes));
|
|
30
30
|
out.push(...synthesizeKotlinInheritanceReferences(tree.rootNode));
|
|
31
|
+
out.push(...synthesizeKotlinSecondaryConstructorDeclarations(tree.rootNode));
|
|
31
32
|
for (const match of getKotlinScopeQuery().matches(tree.rootNode)) {
|
|
32
33
|
const grouped = {};
|
|
33
34
|
// Parallel tag -> captured SyntaxNode map. The query hands us each matched
|
|
@@ -73,6 +74,40 @@ export function emitKotlinScopeCaptures(sourceText, filePath, cachedTree) {
|
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
}
|
|
77
|
+
// Callable references (`::method`, `Type::new`, `obj::m`) — F47 (#1919).
|
|
78
|
+
// The query captures the referenced member as `@reference.name`, an
|
|
79
|
+
// optional receiver type as `@reference.receiver`, and the whole node as
|
|
80
|
+
// `@reference.callable`. Rewrite into a call reference so it participates
|
|
81
|
+
// in call-graph resolution: a bare `::member` resolves as a free call;
|
|
82
|
+
// a `Receiver::member` resolves as a member call against the receiver
|
|
83
|
+
// type. The function/constructor is referenced (not invoked), so no
|
|
84
|
+
// arity/argument metadata is attached.
|
|
85
|
+
if (grouped['@reference.callable'] !== undefined) {
|
|
86
|
+
const nameCap = grouped['@reference.name'];
|
|
87
|
+
const callableNode = groupedNodes['@reference.callable'];
|
|
88
|
+
if (nameCap !== undefined && callableNode !== undefined) {
|
|
89
|
+
const receiverCap = grouped['@reference.receiver'];
|
|
90
|
+
// The anchor Capture must carry the call-form tag as its `name` —
|
|
91
|
+
// the scope-extractor reads `Capture.name` (not the map key) to
|
|
92
|
+
// classify the reference kind, so re-wrap via nodeToCapture rather
|
|
93
|
+
// than reusing the `@reference.callable`-named Capture (whose head
|
|
94
|
+
// `callable` resolves to no ReferenceKind and silently drops it).
|
|
95
|
+
if (receiverCap !== undefined) {
|
|
96
|
+
out.push({
|
|
97
|
+
'@reference.call.member': nodeToCapture('@reference.call.member', callableNode),
|
|
98
|
+
'@reference.name': nameCap,
|
|
99
|
+
'@reference.receiver': receiverCap,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
out.push({
|
|
104
|
+
'@reference.call.free': nodeToCapture('@reference.call.free', callableNode),
|
|
105
|
+
'@reference.name': nameCap,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
76
111
|
if (grouped['@reference.call.free'] !== undefined &&
|
|
77
112
|
grouped['@reference.receiver'] !== undefined) {
|
|
78
113
|
continue;
|
|
@@ -207,6 +242,76 @@ function synthesizeKotlinInheritanceReferences(rootNode) {
|
|
|
207
242
|
}
|
|
208
243
|
return out;
|
|
209
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* The enclosing type name for a node nested in a class/object/companion body.
|
|
247
|
+
* Walks up to the first `class_declaration` / `object_declaration` /
|
|
248
|
+
* `companion_object` ancestor and returns its `type_identifier` name node.
|
|
249
|
+
* Used to qualify a secondary-constructor declaration as `<ClassName>.constructor`.
|
|
250
|
+
*/
|
|
251
|
+
function kotlinEnclosingTypeNameNode(node) {
|
|
252
|
+
for (let cur = node.parent; cur !== null; cur = cur.parent) {
|
|
253
|
+
if (cur.type === 'class_declaration' ||
|
|
254
|
+
cur.type === 'object_declaration' ||
|
|
255
|
+
cur.type === 'companion_object') {
|
|
256
|
+
const nameNode = cur.namedChildren.find((c) => c.type === 'type_identifier');
|
|
257
|
+
return nameNode ?? null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Synthesize a `@declaration.constructor` capture for each Kotlin
|
|
264
|
+
* `secondary_constructor` (issue #1919 review CF1). The structure phase already
|
|
265
|
+
* materializes a `Constructor` graph node (`Constructor:file:Class.constructor#<arity>`),
|
|
266
|
+
* but the registry-primary scope-resolution path had no Constructor *def* in the
|
|
267
|
+
* scope tree — so a call inside the constructor body resolved its caller anchor
|
|
268
|
+
* up to the enclosing Class def, mis-attributing the CALLS edge to the class.
|
|
269
|
+
*
|
|
270
|
+
* Paired with `(secondary_constructor) @scope.function` in query.ts: that rule
|
|
271
|
+
* makes the constructor body its own Function scope; this declaration places a
|
|
272
|
+
* Constructor def in that scope so `pickCallerCallableDef` anchors calls on the
|
|
273
|
+
* Constructor. The def is keyed to match the structure-phase node id:
|
|
274
|
+
* - `@declaration.qualified_name` = `<ClassName>.constructor` so the bridge's
|
|
275
|
+
* qualified key (`<q>:file::Constructor::Class.constructor`) hits the node.
|
|
276
|
+
* - `@declaration.parameter-types` so two same-name secondary constructors are
|
|
277
|
+
* disambiguated by the bridge's parameter-types key (`~Int,Int`), matching
|
|
278
|
+
* the `#<arity>`-suffixed structure node for the overload with the same
|
|
279
|
+
* parameter shape. (The zero-arg overload carries no parameter types and
|
|
280
|
+
* resolves via the qualified/simple key to the `#0` node.)
|
|
281
|
+
*
|
|
282
|
+
* The anchor spans the whole `secondary_constructor` node — same range as the
|
|
283
|
+
* `@scope.function` it pairs with — so the def is owned by that Function scope
|
|
284
|
+
* and the constructor name auto-hoists to the enclosing class scope (exactly the
|
|
285
|
+
* binding shape a normal method declaration produces).
|
|
286
|
+
*/
|
|
287
|
+
function synthesizeKotlinSecondaryConstructorDeclarations(rootNode) {
|
|
288
|
+
const out = [];
|
|
289
|
+
for (const ctorNode of descendantsOfType(rootNode, 'secondary_constructor')) {
|
|
290
|
+
const keyword = ctorNode.namedChildren.find((c) => c.type === 'constructor');
|
|
291
|
+
// The `constructor` keyword is an anonymous token; fall back to the node
|
|
292
|
+
// itself for the name capture position when the named-child lookup misses.
|
|
293
|
+
const nameAnchor = keyword ?? ctorNode;
|
|
294
|
+
const classNameNode = kotlinEnclosingTypeNameNode(ctorNode);
|
|
295
|
+
const qualifiedName = classNameNode !== null ? `${classNameNode.text}.constructor` : 'constructor';
|
|
296
|
+
const match = {
|
|
297
|
+
'@declaration.constructor': nodeToCapture('@declaration.constructor', ctorNode),
|
|
298
|
+
'@declaration.name': syntheticCapture('@declaration.name', nameAnchor, 'constructor'),
|
|
299
|
+
'@declaration.qualified_name': syntheticCapture('@declaration.qualified_name', ctorNode, qualifiedName),
|
|
300
|
+
};
|
|
301
|
+
const arity = computeKotlinArityMetadata(ctorNode);
|
|
302
|
+
if (arity.parameterCount !== undefined) {
|
|
303
|
+
match['@declaration.parameter-count'] = syntheticCapture('@declaration.parameter-count', ctorNode, String(arity.parameterCount));
|
|
304
|
+
}
|
|
305
|
+
if (arity.requiredParameterCount !== undefined) {
|
|
306
|
+
match['@declaration.required-parameter-count'] = syntheticCapture('@declaration.required-parameter-count', ctorNode, String(arity.requiredParameterCount));
|
|
307
|
+
}
|
|
308
|
+
if (arity.parameterTypes !== undefined) {
|
|
309
|
+
match['@declaration.parameter-types'] = syntheticCapture('@declaration.parameter-types', ctorNode, JSON.stringify(arity.parameterTypes));
|
|
310
|
+
}
|
|
311
|
+
out.push(match);
|
|
312
|
+
}
|
|
313
|
+
return out;
|
|
314
|
+
}
|
|
210
315
|
/**
|
|
211
316
|
* The bare simple-name `type_identifier` of a `user_type`. Strips generic
|
|
212
317
|
* type arguments (`Base<T>` → `Base`) and qualifier tails (`pkg.Base` → `Base`)
|
|
@@ -8,6 +8,16 @@ const KOTLIN_SCOPE_QUERY = `
|
|
|
8
8
|
(companion_object) @scope.class
|
|
9
9
|
(function_declaration) @scope.function
|
|
10
10
|
|
|
11
|
+
;; Secondary-constructor body scope (issue #1919 review CF1). A
|
|
12
|
+
;; secondary constructor's "constructor(...) { ... }" body executes statements
|
|
13
|
+
;; just like a method body, so it must be its OWN Function scope — otherwise a
|
|
14
|
+
;; call inside the body resolves its caller anchor up to the enclosing Class
|
|
15
|
+
;; scope (the class's Class def), mis-attributing the CALLS edge to the class
|
|
16
|
+
;; rather than the Constructor. The matching @declaration.constructor is
|
|
17
|
+
;; synthesized in captures.ts (synthesizeKotlinSecondaryConstructorDeclarations)
|
|
18
|
+
;; so this scope owns a Constructor def keyed to the structure-phase node id.
|
|
19
|
+
(secondary_constructor) @scope.function
|
|
20
|
+
|
|
11
21
|
;; Companion-object marker (issue #1756 / U4). Side-channel capture that
|
|
12
22
|
;; lets populateCompanionMembersOnEnclosingClass distinguish a companion
|
|
13
23
|
;; Class scope from a regular Class scope without inspecting ownedDefs.
|
|
@@ -116,6 +126,26 @@ const KOTLIN_SCOPE_QUERY = `
|
|
|
116
126
|
(function_value_parameters)
|
|
117
127
|
[(user_type) (nullable_type) (function_type)] @type-binding.type) @type-binding.return
|
|
118
128
|
|
|
129
|
+
;; References — callable references ("::method", "Type::new", "obj::m") — F47.
|
|
130
|
+
;; A "callable_reference" references a function/constructor as a value (no
|
|
131
|
+
;; call_suffix), so the registry-primary call path never saw it. Real-parse
|
|
132
|
+
;; (issue #1919) shows the canonical shape inside a function body is:
|
|
133
|
+
;; "::topLevelFn" -> (callable_reference :: (simple_identifier)) member only
|
|
134
|
+
;; "String::length" -> (callable_reference (type_identifier) :: (simple_identifier))
|
|
135
|
+
;; "obj::method" -> (callable_reference (type_identifier) :: (simple_identifier))
|
|
136
|
+
;; "Type::new" -> (callable_reference (type_identifier) :: (simple_identifier))
|
|
137
|
+
;; The receiver (real type OR object) is always a "type_identifier"; the
|
|
138
|
+
;; referenced member is the LAST "simple_identifier". One rule with an
|
|
139
|
+
;; optional receiver and an end-anchored member covers all four forms with
|
|
140
|
+
;; exactly one match per callable_reference (no sibling-branch double-match).
|
|
141
|
+
;; (NOTE: a qualified "A.B::m" parses as a nested navigation_expression, not a
|
|
142
|
+
;; callable_reference, and is already captured by the read.member rule below.)
|
|
143
|
+
;; emitKotlinScopeCaptures rewrites this into a free/member call reference.
|
|
144
|
+
(callable_reference
|
|
145
|
+
(type_identifier)? @reference.receiver
|
|
146
|
+
(simple_identifier) @reference.name
|
|
147
|
+
.) @reference.callable
|
|
148
|
+
|
|
119
149
|
;; References — direct calls / constructor syntax
|
|
120
150
|
(call_expression
|
|
121
151
|
(simple_identifier) @reference.name) @reference.call.free
|
|
@@ -166,9 +166,31 @@ function kotlinParameterHasDefaultValue(param) {
|
|
|
166
166
|
}
|
|
167
167
|
return false;
|
|
168
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Member name for a Kotlin method node. A `secondary_constructor`
|
|
171
|
+
* (`constructor(...) { }`) has no name child — its only identity token is
|
|
172
|
+
* the anonymous `constructor` keyword — so it is named "constructor" (F48,
|
|
173
|
+
* issue #1919), matching the @name the KOTLIN_QUERIES structure rule captures
|
|
174
|
+
* off that keyword so method-extractor enrichment keys (`name:line`) align.
|
|
175
|
+
* Multiple secondary constructors collide on this name but are disambiguated
|
|
176
|
+
* downstream by the `#<arity>` ID suffix the worker appends to Constructors.
|
|
177
|
+
*/
|
|
178
|
+
function extractKotlinMethodName(node) {
|
|
179
|
+
if (node.type === 'secondary_constructor')
|
|
180
|
+
return 'constructor';
|
|
181
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
182
|
+
const child = node.namedChild(i);
|
|
183
|
+
if (child?.type === 'simple_identifier')
|
|
184
|
+
return child.text;
|
|
185
|
+
}
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
169
188
|
function extractKotlinParameters(node) {
|
|
170
189
|
const params = [];
|
|
171
|
-
// Kotlin: function_declaration
|
|
190
|
+
// Kotlin: function_declaration / secondary_constructor >
|
|
191
|
+
// function_value_parameters > parameter. Both node types nest the
|
|
192
|
+
// parameter list the same way, so the same walk extracts a secondary
|
|
193
|
+
// constructor's parameters (F48).
|
|
172
194
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
173
195
|
const child = node.namedChild(i);
|
|
174
196
|
if (child && child.type === 'function_value_parameters') {
|
|
@@ -248,17 +270,10 @@ function extractKotlinReturnType(node) {
|
|
|
248
270
|
export const kotlinMethodConfig = {
|
|
249
271
|
language: SupportedLanguages.Kotlin,
|
|
250
272
|
typeDeclarationNodes: ['class_declaration', 'object_declaration', 'companion_object'],
|
|
251
|
-
methodNodeTypes: ['function_declaration'],
|
|
273
|
+
methodNodeTypes: ['function_declaration', 'secondary_constructor'],
|
|
252
274
|
bodyNodeTypes: ['class_body'],
|
|
253
275
|
staticOwnerTypes: new Set(['companion_object', 'object_declaration']),
|
|
254
|
-
extractName
|
|
255
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
256
|
-
const child = node.namedChild(i);
|
|
257
|
-
if (child?.type === 'simple_identifier')
|
|
258
|
-
return child.text;
|
|
259
|
-
}
|
|
260
|
-
return undefined;
|
|
261
|
-
},
|
|
276
|
+
extractName: extractKotlinMethodName,
|
|
262
277
|
extractReturnType: extractKotlinReturnType,
|
|
263
278
|
extractParameters: extractKotlinParameters,
|
|
264
279
|
extractVisibility(node) {
|