gitnexus 1.6.6-rc.100 → 1.6.6-rc.102
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/export-detection.js +0 -5
- package/dist/core/ingestion/field-extractors/configs/dart.js +1 -4
- package/dist/core/ingestion/field-extractors/configs/php.js +1 -2
- package/dist/core/ingestion/field-extractors/configs/swift.js +1 -1
- package/dist/core/ingestion/field-extractors/typescript.js +0 -29
- package/dist/core/ingestion/import-resolvers/python.d.ts +1 -1
- package/dist/core/ingestion/import-resolvers/utils.d.ts +1 -1
- package/dist/core/ingestion/languages/cpp/arity-metadata.js +2 -8
- package/dist/core/ingestion/languages/cpp/captures.js +1 -1
- package/dist/core/ingestion/languages/csharp/captures.js +17 -27
- package/dist/core/ingestion/languages/java/captures.js +4 -1
- package/dist/core/ingestion/languages/php/captures.js +12 -22
- package/dist/core/ingestion/languages/php/import-decomposer.js +2 -33
- package/dist/core/ingestion/languages/php/receiver-binding.js +8 -4
- package/dist/core/ingestion/languages/python/captures.js +28 -10
- package/dist/core/ingestion/languages/python/depends-references.js +1 -1
- package/dist/core/ingestion/languages/python/import-target.d.ts +4 -5
- package/dist/core/ingestion/languages/python/import-target.js +108 -16
- package/dist/core/ingestion/languages/python/index-stats.d.ts +18 -0
- package/dist/core/ingestion/languages/python/index-stats.js +25 -0
- package/dist/core/ingestion/languages/python/scope-resolver.js +7 -6
- package/dist/core/ingestion/languages/ruby/captures.js +41 -24
- package/dist/core/ingestion/languages/rust/captures.js +17 -13
- package/dist/core/ingestion/languages/rust/range-binding.js +2 -3
- package/dist/core/ingestion/languages/typescript/receiver-binding.js +0 -1
- package/dist/core/ingestion/method-extractors/configs/dart.js +0 -1
- package/dist/core/ingestion/method-extractors/configs/php.js +0 -1
- package/dist/core/ingestion/type-env.js +0 -2
- package/dist/core/ingestion/type-extractors/c-cpp.js +8 -10
- package/dist/core/ingestion/type-extractors/csharp.js +18 -34
- package/dist/core/ingestion/type-extractors/go.js +2 -10
- package/dist/core/ingestion/type-extractors/jvm.js +6 -5
- package/dist/core/ingestion/type-extractors/php.js +1 -1
- package/dist/core/ingestion/type-extractors/python.js +1 -1
- package/dist/core/ingestion/type-extractors/ruby.js +2 -2
- package/dist/core/ingestion/type-extractors/rust.js +0 -16
- package/dist/core/ingestion/type-extractors/shared.d.ts +2 -2
- package/dist/core/ingestion/type-extractors/shared.js +25 -11
- package/dist/core/ingestion/type-extractors/swift.js +5 -5
- package/dist/core/ingestion/type-extractors/typescript.js +1 -1
- package/dist/core/ingestion/utils/ast-helpers.d.ts +14 -0
- package/dist/core/ingestion/utils/ast-helpers.js +16 -0
- package/dist/core/ingestion/variable-extractors/configs/dart.js +0 -9
- package/package.json +1 -1
|
@@ -62,11 +62,6 @@ const CSHARP_DECL_TYPES = new Set([
|
|
|
62
62
|
'struct_declaration',
|
|
63
63
|
'enum_declaration',
|
|
64
64
|
'record_declaration',
|
|
65
|
-
// tree-sitter-c-sharp absorbs 'record struct' and 'record class' into
|
|
66
|
-
// record_declaration — these two node types are listed defensively but
|
|
67
|
-
// never emitted by the grammar in practice (verified against ^0.23.1).
|
|
68
|
-
'record_struct_declaration',
|
|
69
|
-
'record_class_declaration',
|
|
70
65
|
'delegate_declaration',
|
|
71
66
|
'property_declaration',
|
|
72
67
|
'field_declaration',
|
|
@@ -42,10 +42,7 @@ export const dartConfig = {
|
|
|
42
42
|
// declaration > type_identifier (first named child usually)
|
|
43
43
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
44
44
|
const child = node.namedChild(i);
|
|
45
|
-
if (child &&
|
|
46
|
-
(child.type === 'type_identifier' ||
|
|
47
|
-
child.type === 'generic_type' ||
|
|
48
|
-
child.type === 'function_type')) {
|
|
45
|
+
if (child && (child.type === 'type_identifier' || child.type === 'function_type')) {
|
|
49
46
|
return extractSimpleTypeName(child) ?? child.text?.trim();
|
|
50
47
|
}
|
|
51
48
|
}
|
|
@@ -46,8 +46,7 @@ export const phpConfig = {
|
|
|
46
46
|
child.type === 'named_type' ||
|
|
47
47
|
child.type === 'optional_type' ||
|
|
48
48
|
child.type === 'primitive_type' ||
|
|
49
|
-
child.type === 'intersection_type'
|
|
50
|
-
child.type === 'nullable_type') {
|
|
49
|
+
child.type === 'intersection_type') {
|
|
51
50
|
return extractSimpleTypeName(child) ?? child.text?.trim();
|
|
52
51
|
}
|
|
53
52
|
}
|
|
@@ -17,7 +17,7 @@ const SWIFT_VIS = new Set([
|
|
|
17
17
|
*/
|
|
18
18
|
export const swiftConfig = {
|
|
19
19
|
language: SupportedLanguages.Swift,
|
|
20
|
-
typeDeclarationNodes: ['class_declaration', '
|
|
20
|
+
typeDeclarationNodes: ['class_declaration', 'protocol_declaration'],
|
|
21
21
|
fieldNodeTypes: ['property_declaration'],
|
|
22
22
|
bodyNodeTypes: ['class_body', 'protocol_body'],
|
|
23
23
|
defaultVisibility: 'internal',
|
|
@@ -70,17 +70,6 @@ export class TypeScriptFieldExtractor extends BaseFieldExtractor {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
// Check for modifier node (tree-sitter typescript may group these)
|
|
74
|
-
const modifiers = node.childForFieldName('modifiers');
|
|
75
|
-
if (modifiers) {
|
|
76
|
-
for (let i = 0; i < modifiers.childCount; i++) {
|
|
77
|
-
const modifier = modifiers.child(i);
|
|
78
|
-
const modText = modifier?.text.trim();
|
|
79
|
-
if (modText && TypeScriptFieldExtractor.VISIBILITY_MODIFIERS.has(modText)) {
|
|
80
|
-
return modText;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
73
|
// TypeScript class members are public by default
|
|
85
74
|
return 'public';
|
|
86
75
|
}
|
|
@@ -94,15 +83,6 @@ export class TypeScriptFieldExtractor extends BaseFieldExtractor {
|
|
|
94
83
|
return true;
|
|
95
84
|
}
|
|
96
85
|
}
|
|
97
|
-
const modifiers = node.childForFieldName('modifiers');
|
|
98
|
-
if (modifiers) {
|
|
99
|
-
for (let i = 0; i < modifiers.childCount; i++) {
|
|
100
|
-
const modifier = modifiers.child(i);
|
|
101
|
-
if (modifier && modifier.text === 'static') {
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
86
|
return false;
|
|
107
87
|
}
|
|
108
88
|
/**
|
|
@@ -115,15 +95,6 @@ export class TypeScriptFieldExtractor extends BaseFieldExtractor {
|
|
|
115
95
|
return true;
|
|
116
96
|
}
|
|
117
97
|
}
|
|
118
|
-
const modifiers = node.childForFieldName('modifiers');
|
|
119
|
-
if (modifiers) {
|
|
120
|
-
for (let i = 0; i < modifiers.childCount; i++) {
|
|
121
|
-
const modifier = modifiers.child(i);
|
|
122
|
-
if (modifier && modifier.text === 'readonly') {
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
98
|
return false;
|
|
128
99
|
}
|
|
129
100
|
/**
|
|
@@ -19,4 +19,4 @@
|
|
|
19
19
|
*
|
|
20
20
|
* Returns null to let the caller fall through to suffixResolve.
|
|
21
21
|
*/
|
|
22
|
-
export declare function resolvePythonImportInternal(currentFile: string, importPath: string, allFiles:
|
|
22
|
+
export declare function resolvePythonImportInternal(currentFile: string, importPath: string, allFiles: ReadonlySet<string>): string | null;
|
|
@@ -8,7 +8,7 @@ export declare const EXTENSIONS: string[];
|
|
|
8
8
|
* Try to match a path (with extensions) against the known file set.
|
|
9
9
|
* Returns the matched file path or null.
|
|
10
10
|
*/
|
|
11
|
-
export declare function tryResolveWithExtensions(basePath: string, allFiles:
|
|
11
|
+
export declare function tryResolveWithExtensions(basePath: string, allFiles: ReadonlySet<string>): string | null;
|
|
12
12
|
/**
|
|
13
13
|
* Build a suffix index for O(1) endsWith lookups.
|
|
14
14
|
* Maps every possible path suffix to its original file path.
|
|
@@ -23,7 +23,6 @@ export function computeCppDeclarationArity(node) {
|
|
|
23
23
|
continue;
|
|
24
24
|
if (child.type === 'parameter_declaration' ||
|
|
25
25
|
child.type === 'optional_parameter_declaration' ||
|
|
26
|
-
child.type === 'variadic_parameter' ||
|
|
27
26
|
child.type === 'variadic_parameter_declaration') {
|
|
28
27
|
params.push(child);
|
|
29
28
|
}
|
|
@@ -47,8 +46,7 @@ export function computeCppDeclarationArity(node) {
|
|
|
47
46
|
// token in tree-sitter-cpp, detected via `hasEllipsis` above.
|
|
48
47
|
// C++ parameter packs: `template<typename... Ts> void foo(Ts... args)` —
|
|
49
48
|
// detected as `variadic_parameter_declaration`.
|
|
50
|
-
const isVariadic = hasEllipsis ||
|
|
51
|
-
params.some((p) => p.type === 'variadic_parameter' || p.type === 'variadic_parameter_declaration');
|
|
49
|
+
const isVariadic = hasEllipsis || params.some((p) => p.type === 'variadic_parameter_declaration');
|
|
52
50
|
const optionalCount = params.filter((p) => p.type === 'optional_parameter_declaration').length;
|
|
53
51
|
const requiredCount = params.filter((p) => p.type === 'parameter_declaration' ||
|
|
54
52
|
// variadic_parameter_declaration with a name is a parameter pack — counts as one
|
|
@@ -57,11 +55,7 @@ export function computeCppDeclarationArity(node) {
|
|
|
57
55
|
const types = [];
|
|
58
56
|
const typeClasses = [];
|
|
59
57
|
for (const p of params) {
|
|
60
|
-
if (p.type === '
|
|
61
|
-
types.push('...');
|
|
62
|
-
typeClasses.push(unknownTypeClass('...'));
|
|
63
|
-
}
|
|
64
|
-
else if (p.type === 'variadic_parameter_declaration') {
|
|
58
|
+
if (p.type === 'variadic_parameter_declaration') {
|
|
65
59
|
// Parameter pack: treated as variadic
|
|
66
60
|
types.push('...');
|
|
67
61
|
typeClasses.push(unknownTypeClass('...'));
|
|
@@ -1251,7 +1251,7 @@ function lookupAdlIdentifierType(identNode) {
|
|
|
1251
1251
|
inner = next;
|
|
1252
1252
|
continue;
|
|
1253
1253
|
}
|
|
1254
|
-
if (inner.type === 'reference_declarator'
|
|
1254
|
+
if (inner.type === 'reference_declarator') {
|
|
1255
1255
|
// reference_declarator has a single child (the inner declarator).
|
|
1256
1256
|
let next = null;
|
|
1257
1257
|
for (let j = 0; j < inner.namedChildCount; j++) {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Pure given the input source text. No I/O, no globals consulted.
|
|
17
17
|
*/
|
|
18
|
-
import {
|
|
18
|
+
import { nodeIfType, nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
|
|
19
19
|
import { splitUsingDirective } from './import-decomposer.js';
|
|
20
20
|
import { computeCsharpArityMetadata } from './arity-metadata.js';
|
|
21
21
|
import { synthesizeCsharpReceiverBinding } from './receiver-binding.js';
|
|
@@ -91,9 +91,16 @@ export function emitCsharpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
91
91
|
// `@`; we put it back so the central extractor's prefix lookups
|
|
92
92
|
// (`@scope.`, `@declaration.`, …) work.
|
|
93
93
|
const grouped = {};
|
|
94
|
+
// Parallel tag -> captured SyntaxNode map: the query hands us each matched
|
|
95
|
+
// node as c.node, so anchors resolve via a type-guarded lookup (nodeIfType)
|
|
96
|
+
// instead of re-deriving them with findNodeAtRange(tree.rootNode, ...) per
|
|
97
|
+
// match — the O(matches x rootChildren) root-walk fixed for go #1915 /
|
|
98
|
+
// python #1918, mirrored here.
|
|
99
|
+
const nodeMap = {};
|
|
94
100
|
for (const c of m.captures) {
|
|
95
101
|
const tag = '@' + c.name;
|
|
96
102
|
grouped[tag] = nodeToCapture(tag, c.node);
|
|
103
|
+
nodeMap[tag] = c.node;
|
|
97
104
|
}
|
|
98
105
|
if (Object.keys(grouped).length === 0)
|
|
99
106
|
continue;
|
|
@@ -101,8 +108,7 @@ export function emitCsharpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
101
108
|
// the kind/source/name/alias markers it consumes. Raw query match
|
|
102
109
|
// only carries the @import.statement anchor.
|
|
103
110
|
if (grouped['@import.statement'] !== undefined) {
|
|
104
|
-
const
|
|
105
|
-
const stmtNode = findNodeAtRange(tree.rootNode, stmtCapture.range, 'using_directive');
|
|
111
|
+
const stmtNode = nodeIfType(nodeMap['@import.statement'], 'using_directive');
|
|
106
112
|
if (stmtNode !== null) {
|
|
107
113
|
const decomposed = splitUsingDirective(stmtNode);
|
|
108
114
|
if (decomposed !== null) {
|
|
@@ -116,8 +122,7 @@ export function emitCsharpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
116
122
|
continue;
|
|
117
123
|
}
|
|
118
124
|
if (grouped['@reference.read.member'] !== undefined) {
|
|
119
|
-
const
|
|
120
|
-
const memberNode = findNodeAtRange(tree.rootNode, anchor.range, 'member_access_expression');
|
|
125
|
+
const memberNode = nodeIfType(nodeMap['@reference.read.member'], 'member_access_expression');
|
|
121
126
|
if (memberNode === null || !shouldEmitReadMember(memberNode)) {
|
|
122
127
|
continue;
|
|
123
128
|
}
|
|
@@ -130,8 +135,7 @@ export function emitCsharpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
130
135
|
// `@scope.function` matches.
|
|
131
136
|
if (grouped['@scope.function'] !== undefined) {
|
|
132
137
|
out.push(grouped);
|
|
133
|
-
const
|
|
134
|
-
const fnNode = findFunctionNode(tree.rootNode, anchor.range);
|
|
138
|
+
const fnNode = nodeIfType(nodeMap['@scope.function'], ...FUNCTION_NODE_TYPES);
|
|
135
139
|
if (fnNode !== null) {
|
|
136
140
|
for (const synth of synthesizeCsharpReceiverBinding(fnNode)) {
|
|
137
141
|
out.push(synth);
|
|
@@ -145,8 +149,7 @@ export function emitCsharpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
145
149
|
// the first tag that matches.
|
|
146
150
|
const declTag = FUNCTION_DECL_TAGS.find((t) => grouped[t] !== undefined);
|
|
147
151
|
if (declTag !== undefined) {
|
|
148
|
-
const
|
|
149
|
-
const fnNode = findFunctionNode(tree.rootNode, anchor.range);
|
|
152
|
+
const fnNode = nodeIfType(nodeMap[declTag], ...FUNCTION_NODE_TYPES);
|
|
150
153
|
if (fnNode !== null) {
|
|
151
154
|
const arity = computeCsharpArityMetadata(fnNode);
|
|
152
155
|
if (arity.parameterCount !== undefined) {
|
|
@@ -168,9 +171,7 @@ export function emitCsharpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
168
171
|
// resolution.
|
|
169
172
|
const callTag = ['@reference.call.free', '@reference.call.member', '@reference.call.constructor'].find((t) => grouped[t] !== undefined);
|
|
170
173
|
if (callTag !== undefined && grouped['@reference.arity'] === undefined) {
|
|
171
|
-
const
|
|
172
|
-
const callNode = findNodeAtRange(tree.rootNode, anchor.range, 'invocation_expression') ??
|
|
173
|
-
findNodeAtRange(tree.rootNode, anchor.range, 'object_creation_expression');
|
|
174
|
+
const callNode = nodeIfType(nodeMap[callTag], 'invocation_expression', 'object_creation_expression');
|
|
174
175
|
if (callNode !== null) {
|
|
175
176
|
const argList = callNode.childForFieldName('arguments');
|
|
176
177
|
const args = argList === null
|
|
@@ -195,9 +196,7 @@ export function emitCsharpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
195
196
|
// `new User(...)` resolves to a Constructor def in memberByOwner.
|
|
196
197
|
if (grouped['@declaration.class'] !== undefined ||
|
|
197
198
|
grouped['@declaration.record'] !== undefined) {
|
|
198
|
-
const
|
|
199
|
-
const typeNode = findNodeAtRange(tree.rootNode, anchor.range, 'class_declaration') ??
|
|
200
|
-
findNodeAtRange(tree.rootNode, anchor.range, 'record_declaration');
|
|
199
|
+
const typeNode = nodeIfType(nodeMap['@declaration.class'] ?? nodeMap['@declaration.record'], 'class_declaration', 'record_declaration');
|
|
201
200
|
if (typeNode !== null) {
|
|
202
201
|
const synth = synthesizePrimaryConstructor(typeNode);
|
|
203
202
|
if (synth !== null)
|
|
@@ -243,7 +242,9 @@ function terminalTypeNameNode(node) {
|
|
|
243
242
|
case 'qualified_name':
|
|
244
243
|
return node.lastNamedChild;
|
|
245
244
|
case 'generic_name':
|
|
246
|
-
|
|
245
|
+
// generic_name has no `name` field (verified by real parse, #1920); the
|
|
246
|
+
// base identifier is the first named child.
|
|
247
|
+
return node.firstNamedChild;
|
|
247
248
|
default:
|
|
248
249
|
return null;
|
|
249
250
|
}
|
|
@@ -333,14 +334,3 @@ function inferArgType(argNode) {
|
|
|
333
334
|
return '';
|
|
334
335
|
}
|
|
335
336
|
}
|
|
336
|
-
/** Find the first C# function-like node at the given range. The
|
|
337
|
-
* declaration anchor range covers the whole method/constructor/etc.
|
|
338
|
-
* node, but the tag alone doesn't tell us which node type. */
|
|
339
|
-
function findFunctionNode(rootNode, range) {
|
|
340
|
-
for (const nodeType of FUNCTION_NODE_TYPES) {
|
|
341
|
-
const n = findNodeAtRange(rootNode, range, nodeType);
|
|
342
|
-
if (n !== null)
|
|
343
|
-
return n;
|
|
344
|
-
}
|
|
345
|
-
return null;
|
|
346
|
-
}
|
|
@@ -132,9 +132,12 @@ export function emitJavaScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
132
132
|
findNodeAtRange(tree.rootNode, anchor.range, 'object_creation_expression');
|
|
133
133
|
if (callNode !== null) {
|
|
134
134
|
const argList = callNode.childForFieldName('arguments');
|
|
135
|
+
// Exclude interleaved comments — tree-sitter-java emits `block_comment` /
|
|
136
|
+
// `line_comment` as named children of argument_list, which would inflate
|
|
137
|
+
// arity (and arity feeds call-processor symbol-ID generation). #1920
|
|
135
138
|
const args = argList === null
|
|
136
139
|
? []
|
|
137
|
-
: argList.namedChildren.filter((c) => c !== null && c.type !== '
|
|
140
|
+
: argList.namedChildren.filter((c) => c !== null && c.type !== 'block_comment' && c.type !== 'line_comment');
|
|
138
141
|
grouped['@reference.arity'] = syntheticCapture('@reference.arity', callNode, String(args.length));
|
|
139
142
|
const argTypes = args.map((arg) => inferArgType(arg));
|
|
140
143
|
grouped['@reference.parameter-types'] = syntheticCapture('@reference.parameter-types', callNode, JSON.stringify(argTypes));
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
*
|
|
31
31
|
* Pure given the input source text. No I/O, no globals consulted.
|
|
32
32
|
*/
|
|
33
|
-
import {
|
|
33
|
+
import { nodeIfType, nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
|
|
34
34
|
import { splitNamespaceUseDeclaration } from './import-decomposer.js';
|
|
35
35
|
import { computePhpArityMetadata } from './arity-metadata.js';
|
|
36
36
|
import { synthesizePhpReceiverBinding } from './receiver-binding.js';
|
|
@@ -89,9 +89,16 @@ export function emitPhpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
89
89
|
// Group captures by their tag name. Tree-sitter strips the leading
|
|
90
90
|
// `@`; we put it back so the central extractor's prefix lookups work.
|
|
91
91
|
const grouped = {};
|
|
92
|
+
// Parallel tag -> captured SyntaxNode map: the query hands us each matched
|
|
93
|
+
// node as c.node, so anchors resolve via a type-guarded lookup (nodeIfType)
|
|
94
|
+
// instead of re-deriving them with findNodeAtRange(tree.rootNode, ...) per
|
|
95
|
+
// match — the O(matches x rootChildren) root-walk fixed for go #1915 /
|
|
96
|
+
// python #1918, mirrored here.
|
|
97
|
+
const nodeMap = {};
|
|
92
98
|
for (const c of m.captures) {
|
|
93
99
|
const tag = '@' + c.name;
|
|
94
100
|
grouped[tag] = nodeToCapture(tag, c.node);
|
|
101
|
+
nodeMap[tag] = c.node;
|
|
95
102
|
}
|
|
96
103
|
if (Object.keys(grouped).length === 0)
|
|
97
104
|
continue;
|
|
@@ -157,8 +164,7 @@ export function emitPhpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
157
164
|
// Decompose each `namespace_use_declaration` so `interpretPhpImport`
|
|
158
165
|
// sees the kind/source/name/alias markers it consumes.
|
|
159
166
|
if (grouped['@import.statement'] !== undefined) {
|
|
160
|
-
const
|
|
161
|
-
const stmtNode = findNodeAtRange(tree.rootNode, stmtCapture.range, 'namespace_use_declaration');
|
|
167
|
+
const stmtNode = nodeIfType(nodeMap['@import.statement'], 'namespace_use_declaration');
|
|
162
168
|
if (stmtNode !== null) {
|
|
163
169
|
const decomposed = splitNamespaceUseDeclaration(stmtNode);
|
|
164
170
|
if (decomposed.length > 0) {
|
|
@@ -175,8 +181,7 @@ export function emitPhpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
175
181
|
// non-static method-like. Mirrors C#'s `this` / `base` synthesis.
|
|
176
182
|
if (grouped['@scope.function'] !== undefined) {
|
|
177
183
|
out.push(grouped);
|
|
178
|
-
const
|
|
179
|
-
const fnNode = findFunctionNode(tree.rootNode, anchor.range);
|
|
184
|
+
const fnNode = nodeIfType(nodeMap['@scope.function'], ...FUNCTION_NODE_TYPES);
|
|
180
185
|
if (fnNode !== null) {
|
|
181
186
|
for (const synth of synthesizePhpReceiverBinding(fnNode)) {
|
|
182
187
|
out.push(synth);
|
|
@@ -196,8 +201,7 @@ export function emitPhpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
196
201
|
// registry can narrow overloads.
|
|
197
202
|
const declTag = FUNCTION_DECL_TAGS.find((t) => grouped[t] !== undefined);
|
|
198
203
|
if (declTag !== undefined) {
|
|
199
|
-
const
|
|
200
|
-
const fnNode = findFunctionNode(tree.rootNode, anchor.range);
|
|
204
|
+
const fnNode = nodeIfType(nodeMap[declTag], ...FUNCTION_NODE_TYPES);
|
|
201
205
|
if (fnNode !== null) {
|
|
202
206
|
const arity = computePhpArityMetadata(fnNode);
|
|
203
207
|
if (arity.parameterCount !== undefined) {
|
|
@@ -217,12 +221,7 @@ export function emitPhpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
217
221
|
// lines 149-186). PHP needs this for arity-based dispatch (Cluster H).
|
|
218
222
|
const callTag = ['@reference.call.free', '@reference.call.member', '@reference.call.constructor'].find((t) => grouped[t] !== undefined);
|
|
219
223
|
if (callTag !== undefined && grouped['@reference.arity'] === undefined) {
|
|
220
|
-
const
|
|
221
|
-
const callNode = findNodeAtRange(tree.rootNode, anchor.range, 'function_call_expression') ??
|
|
222
|
-
findNodeAtRange(tree.rootNode, anchor.range, 'member_call_expression') ??
|
|
223
|
-
findNodeAtRange(tree.rootNode, anchor.range, 'nullsafe_member_call_expression') ??
|
|
224
|
-
findNodeAtRange(tree.rootNode, anchor.range, 'scoped_call_expression') ??
|
|
225
|
-
findNodeAtRange(tree.rootNode, anchor.range, 'object_creation_expression');
|
|
224
|
+
const callNode = nodeIfType(nodeMap[callTag], 'function_call_expression', 'member_call_expression', 'nullsafe_member_call_expression', 'scoped_call_expression', 'object_creation_expression');
|
|
226
225
|
if (callNode !== null) {
|
|
227
226
|
const argList = callNode.childForFieldName('arguments');
|
|
228
227
|
const args = [];
|
|
@@ -244,15 +243,6 @@ export function emitPhpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
244
243
|
}
|
|
245
244
|
return out;
|
|
246
245
|
}
|
|
247
|
-
/** Find the first PHP function-like node at the given range. */
|
|
248
|
-
function findFunctionNode(rootNode, range) {
|
|
249
|
-
for (const nodeType of FUNCTION_NODE_TYPES) {
|
|
250
|
-
const n = findNodeAtRange(rootNode, range, nodeType);
|
|
251
|
-
if (n !== null)
|
|
252
|
-
return n;
|
|
253
|
-
}
|
|
254
|
-
return null;
|
|
255
|
-
}
|
|
256
246
|
// ─── PHP receiver normalization ──────────────────────────────────────────────
|
|
257
247
|
/**
|
|
258
248
|
* Normalize a PHP receiver expression so the language-agnostic
|
|
@@ -97,23 +97,7 @@ function parseUseClause(clause, qualifier) {
|
|
|
97
97
|
const source = qualName.text.trim();
|
|
98
98
|
if (source === '')
|
|
99
99
|
return null;
|
|
100
|
-
// Strategy
|
|
101
|
-
const aliasClause = findNamedChild(clause, 'alias_clause');
|
|
102
|
-
if (aliasClause !== null) {
|
|
103
|
-
// alias_clause: "as" name
|
|
104
|
-
const aliasName = findNamedChild(aliasClause, 'name') ?? aliasClause.firstNamedChild;
|
|
105
|
-
const alias = aliasName?.text.trim() ?? '';
|
|
106
|
-
if (alias === '')
|
|
107
|
-
return null;
|
|
108
|
-
return {
|
|
109
|
-
kind: 'alias',
|
|
110
|
-
source,
|
|
111
|
-
name: alias,
|
|
112
|
-
alias,
|
|
113
|
-
atNode: clause,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
// Strategy 2: bare sibling `name` node after the qualified_name.
|
|
100
|
+
// Strategy: bare sibling `name` node after the qualified_name.
|
|
117
101
|
// tree-sitter-php (≥ 0.22) emits `use Foo\Bar as Baz` as:
|
|
118
102
|
// namespace_use_clause
|
|
119
103
|
// qualified_name "Foo\Bar"
|
|
@@ -197,22 +181,7 @@ function parseInnerClause(clause, prefix, qualifier) {
|
|
|
197
181
|
if (innerPath === '')
|
|
198
182
|
return null;
|
|
199
183
|
const source = prefix !== '' ? `${prefix}\\${innerPath}` : innerPath;
|
|
200
|
-
// Strategy
|
|
201
|
-
const aliasClause = findNamedChild(clause, 'alias_clause');
|
|
202
|
-
if (aliasClause !== null) {
|
|
203
|
-
const aliasName = findNamedChild(aliasClause, 'name') ?? aliasClause.firstNamedChild;
|
|
204
|
-
const alias = aliasName?.text.trim() ?? '';
|
|
205
|
-
if (alias === '')
|
|
206
|
-
return null;
|
|
207
|
-
return {
|
|
208
|
-
kind: 'alias',
|
|
209
|
-
source,
|
|
210
|
-
name: alias,
|
|
211
|
-
alias,
|
|
212
|
-
atNode: clause,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
// Strategy 2: bare sibling `name` node after the qualified_name (tree-sitter-php ≥ 0.22).
|
|
184
|
+
// Strategy: bare sibling `name` node after the qualified_name (tree-sitter-php ≥ 0.22).
|
|
216
185
|
if (clause.namedChildCount >= 2) {
|
|
217
186
|
const lastChild = clause.namedChild(clause.namedChildCount - 1);
|
|
218
187
|
if (lastChild !== null && lastChild !== qualName && lastChild.type === 'name') {
|
|
@@ -24,6 +24,11 @@ const TYPE_DECL_NODE_TYPES = new Set([
|
|
|
24
24
|
'interface_declaration',
|
|
25
25
|
'trait_declaration',
|
|
26
26
|
'enum_declaration',
|
|
27
|
+
// tree-sitter-php node for `new class {...}` (real node is `anonymous_class`,
|
|
28
|
+
// not `anonymous_class_declaration`). Included so the enclosing-type walk
|
|
29
|
+
// stops AT the anon class and the guard below skips it (otherwise a method in
|
|
30
|
+
// an anon class nested in a named class would mis-bind $this to the outer class).
|
|
31
|
+
'anonymous_class',
|
|
27
32
|
]);
|
|
28
33
|
const FUNCTION_NODE_TYPES = new Set([
|
|
29
34
|
'method_declaration',
|
|
@@ -94,7 +99,7 @@ export function synthesizePhpReceiverBinding(fnNode) {
|
|
|
94
99
|
if (enclosingType === null)
|
|
95
100
|
return [];
|
|
96
101
|
// Anonymous class — skip (no stable name).
|
|
97
|
-
if (enclosingType.type === '
|
|
102
|
+
if (enclosingType.type === 'anonymous_class')
|
|
98
103
|
return [];
|
|
99
104
|
const enclosingName = typeName(enclosingType);
|
|
100
105
|
if (enclosingName === null)
|
|
@@ -102,9 +107,8 @@ export function synthesizePhpReceiverBinding(fnNode) {
|
|
|
102
107
|
// Anchor the synthesized captures to the method body (compound_statement)
|
|
103
108
|
// so they land inside the function scope, not at the class scope.
|
|
104
109
|
// For interface/abstract methods that have no body, skip.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
fnNode.childForFieldName('return_value');
|
|
110
|
+
// tree-sitter-php arrow_function also exposes its expression via the `body` field.
|
|
111
|
+
const bodyNode = fnNode.childForFieldName('body');
|
|
108
112
|
if (bodyNode === null)
|
|
109
113
|
return [];
|
|
110
114
|
const out = [];
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Pure given the input source text. No I/O, no globals consulted.
|
|
17
17
|
*/
|
|
18
|
-
import {
|
|
18
|
+
import { nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
|
|
19
19
|
import { splitImportStatement } from './import-decomposer.js';
|
|
20
20
|
import { getPythonParser, getPythonScopeQuery } from './query.js';
|
|
21
21
|
import { synthesizeReceiverTypeBinding } from './receiver-binding.js';
|
|
@@ -59,20 +59,30 @@ export function emitPythonScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
59
59
|
// `@`; we put it back so the central extractor's prefix lookups
|
|
60
60
|
// (`@scope.`, `@declaration.`, …) work.
|
|
61
61
|
const grouped = {};
|
|
62
|
+
// Parallel tag -> captured SyntaxNode map. The tree-sitter query already
|
|
63
|
+
// hands us each matched node as `c.node`, so anchor nodes can be used
|
|
64
|
+
// directly (or via a bounded LOCAL walk) instead of re-deriving them with
|
|
65
|
+
// `findNodeAtRange(tree.rootNode, ...)`, which scanned all of root's named
|
|
66
|
+
// children on every match -> O(matches x rootChildren). That was the #1848
|
|
67
|
+
// hotpath in Go (fixed in eaf0a305); the same shape lived here in Python.
|
|
68
|
+
const nodeMap = {};
|
|
62
69
|
for (const c of m.captures) {
|
|
63
70
|
const tag = '@' + c.name;
|
|
64
71
|
grouped[tag] = nodeToCapture(tag, c.node);
|
|
72
|
+
nodeMap[tag] = c.node;
|
|
65
73
|
}
|
|
66
74
|
if (Object.keys(grouped).length === 0)
|
|
67
75
|
continue;
|
|
68
76
|
if (grouped['@import.statement'] !== undefined) {
|
|
69
|
-
//
|
|
70
|
-
// `import_from_statement`
|
|
71
|
-
// `
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
// `@import.statement` is captured directly ON the `import_statement` /
|
|
78
|
+
// `import_from_statement` node (query: `(import_statement) @import.statement`
|
|
79
|
+
// and `(import_from_statement) @import.statement`), so the captured node IS
|
|
80
|
+
// the one the old findNodeAtRange re-derived. `splitImportStatement`
|
|
81
|
+
// dispatches on those two types; a captured node of any other type would
|
|
82
|
+
// have made the old range+type lookup return null -> the defensive raw
|
|
83
|
+
// fallback, which the type guard below reproduces exactly.
|
|
84
|
+
const stmtNode = nodeMap['@import.statement'];
|
|
85
|
+
if (stmtNode.type === 'import_from_statement' || stmtNode.type === 'import_statement') {
|
|
76
86
|
for (const piece of splitImportStatement(stmtNode))
|
|
77
87
|
out.push(piece);
|
|
78
88
|
}
|
|
@@ -84,7 +94,11 @@ export function emitPythonScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
84
94
|
}
|
|
85
95
|
if (grouped['@scope.function'] !== undefined) {
|
|
86
96
|
out.push(grouped);
|
|
87
|
-
|
|
97
|
+
// `@scope.function` is captured directly on the `function_definition`
|
|
98
|
+
// node (query: `(function_definition) @scope.function`), so it IS the
|
|
99
|
+
// node the old findNodeAtRange re-derived at that range.
|
|
100
|
+
const scopeNode = nodeMap['@scope.function'];
|
|
101
|
+
const fnNode = scopeNode.type === 'function_definition' ? scopeNode : null;
|
|
88
102
|
if (fnNode !== null) {
|
|
89
103
|
const synth = synthesizeReceiverTypeBinding(fnNode);
|
|
90
104
|
if (synth !== null)
|
|
@@ -100,7 +114,11 @@ export function emitPythonScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
100
114
|
// The anchor range is the function_definition itself — we resolve
|
|
101
115
|
// the node and pipe it through the arity helper.
|
|
102
116
|
const anchorCap = grouped['@declaration.function'];
|
|
103
|
-
|
|
117
|
+
// `@declaration.function` is captured directly on the `function_definition`
|
|
118
|
+
// node (query: `(function_definition name: (identifier) @declaration.name)
|
|
119
|
+
// @declaration.function`), so use the captured node, not a root re-walk.
|
|
120
|
+
const anchorNode = nodeMap['@declaration.function'];
|
|
121
|
+
const fnNode = anchorNode.type === 'function_definition' ? anchorNode : null;
|
|
104
122
|
if (fnNode !== null) {
|
|
105
123
|
if (pythonFunctionDefinitionLabel(fnNode, 'Function') === 'Method') {
|
|
106
124
|
delete grouped['@declaration.function'];
|
|
@@ -27,7 +27,7 @@ export function synthesizeDependsReferences(fnNode) {
|
|
|
27
27
|
if (param.type !== 'typed_default_parameter' && param.type !== 'default_parameter') {
|
|
28
28
|
continue;
|
|
29
29
|
}
|
|
30
|
-
const defaultValue = param.childForFieldName('value')
|
|
30
|
+
const defaultValue = param.childForFieldName('value');
|
|
31
31
|
if (defaultValue === null)
|
|
32
32
|
continue;
|
|
33
33
|
const callNode = defaultValue.type === 'call' ? defaultValue : null;
|
|
@@ -12,10 +12,9 @@
|
|
|
12
12
|
import type { ParsedImport, WorkspaceIndex } from '../../../../_shared/index.js';
|
|
13
13
|
export interface PythonResolveContext {
|
|
14
14
|
readonly fromFile: string;
|
|
15
|
-
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
readonly allFilePaths: Set<string>;
|
|
15
|
+
/** `ReadonlySet` so the orchestrator's stable run-level set flows straight
|
|
16
|
+
* through to `getPythonFileIndex`'s `WeakMap` key (built once per run, not
|
|
17
|
+
* copied per import). The whole resolver chain only reads the set. */
|
|
18
|
+
readonly allFilePaths: ReadonlySet<string>;
|
|
20
19
|
}
|
|
21
20
|
export declare function resolvePythonImportTarget(parsedImport: ParsedImport, workspaceIndex: WorkspaceIndex): string | null;
|