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.
Files changed (44) hide show
  1. package/dist/core/ingestion/export-detection.js +0 -5
  2. package/dist/core/ingestion/field-extractors/configs/dart.js +1 -4
  3. package/dist/core/ingestion/field-extractors/configs/php.js +1 -2
  4. package/dist/core/ingestion/field-extractors/configs/swift.js +1 -1
  5. package/dist/core/ingestion/field-extractors/typescript.js +0 -29
  6. package/dist/core/ingestion/import-resolvers/python.d.ts +1 -1
  7. package/dist/core/ingestion/import-resolvers/utils.d.ts +1 -1
  8. package/dist/core/ingestion/languages/cpp/arity-metadata.js +2 -8
  9. package/dist/core/ingestion/languages/cpp/captures.js +1 -1
  10. package/dist/core/ingestion/languages/csharp/captures.js +17 -27
  11. package/dist/core/ingestion/languages/java/captures.js +4 -1
  12. package/dist/core/ingestion/languages/php/captures.js +12 -22
  13. package/dist/core/ingestion/languages/php/import-decomposer.js +2 -33
  14. package/dist/core/ingestion/languages/php/receiver-binding.js +8 -4
  15. package/dist/core/ingestion/languages/python/captures.js +28 -10
  16. package/dist/core/ingestion/languages/python/depends-references.js +1 -1
  17. package/dist/core/ingestion/languages/python/import-target.d.ts +4 -5
  18. package/dist/core/ingestion/languages/python/import-target.js +108 -16
  19. package/dist/core/ingestion/languages/python/index-stats.d.ts +18 -0
  20. package/dist/core/ingestion/languages/python/index-stats.js +25 -0
  21. package/dist/core/ingestion/languages/python/scope-resolver.js +7 -6
  22. package/dist/core/ingestion/languages/ruby/captures.js +41 -24
  23. package/dist/core/ingestion/languages/rust/captures.js +17 -13
  24. package/dist/core/ingestion/languages/rust/range-binding.js +2 -3
  25. package/dist/core/ingestion/languages/typescript/receiver-binding.js +0 -1
  26. package/dist/core/ingestion/method-extractors/configs/dart.js +0 -1
  27. package/dist/core/ingestion/method-extractors/configs/php.js +0 -1
  28. package/dist/core/ingestion/type-env.js +0 -2
  29. package/dist/core/ingestion/type-extractors/c-cpp.js +8 -10
  30. package/dist/core/ingestion/type-extractors/csharp.js +18 -34
  31. package/dist/core/ingestion/type-extractors/go.js +2 -10
  32. package/dist/core/ingestion/type-extractors/jvm.js +6 -5
  33. package/dist/core/ingestion/type-extractors/php.js +1 -1
  34. package/dist/core/ingestion/type-extractors/python.js +1 -1
  35. package/dist/core/ingestion/type-extractors/ruby.js +2 -2
  36. package/dist/core/ingestion/type-extractors/rust.js +0 -16
  37. package/dist/core/ingestion/type-extractors/shared.d.ts +2 -2
  38. package/dist/core/ingestion/type-extractors/shared.js +25 -11
  39. package/dist/core/ingestion/type-extractors/swift.js +5 -5
  40. package/dist/core/ingestion/type-extractors/typescript.js +1 -1
  41. package/dist/core/ingestion/utils/ast-helpers.d.ts +14 -0
  42. package/dist/core/ingestion/utils/ast-helpers.js +16 -0
  43. package/dist/core/ingestion/variable-extractors/configs/dart.js +0 -9
  44. 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', 'struct_declaration', 'protocol_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: Set<string>): string | null;
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: Set<string>): string | null;
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 === 'variadic_parameter') {
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' || inner.type === 'rvalue_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 { findNodeAtRange, nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
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 stmtCapture = grouped['@import.statement'];
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 anchor = grouped['@reference.read.member'];
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 anchor = grouped['@scope.function'];
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 anchor = grouped[declTag];
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 anchor = grouped[callTag];
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 anchor = grouped['@declaration.class'] ?? grouped['@declaration.record'];
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
- return node.childForFieldName('name') ?? node.firstNamedChild;
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 !== 'comment');
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 { findNodeAtRange, nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
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 stmtCapture = grouped['@import.statement'];
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 anchor = grouped['@scope.function'];
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 anchor = grouped[declTag];
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 anchor = grouped[callTag];
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 1: explicit alias_clause wrapper (older grammar versions).
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 1: explicit alias_clause wrapper (older grammar versions).
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 === 'anonymous_class_declaration')
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
- const bodyNode = fnNode.childForFieldName('body') ??
106
- // arrow_function: body is the expression after `=>`
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 { findNodeAtRange, nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
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
- // Decompose multi-name imports. Both `import_statement` and
70
- // `import_from_statement` share the matched range, so we try the
71
- // `from` form first and fall back to plain.
72
- const stmtCapture = grouped['@import.statement'];
73
- const stmtNode = findNodeAtRange(tree.rootNode, stmtCapture.range, 'import_from_statement') ??
74
- findNodeAtRange(tree.rootNode, stmtCapture.range, 'import_statement');
75
- if (stmtNode !== null) {
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
- const fnNode = findNodeAtRange(tree.rootNode, grouped['@scope.function'].range, 'function_definition');
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
- const fnNode = findNodeAtRange(tree.rootNode, anchorCap.range, 'function_definition');
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') ?? param.childForFieldName('default');
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
- /** Mutable `Set` because the legacy `resolvePythonImportInternal`
16
- * chain downstream is typed to accept `Set<string>`. Callers that
17
- * only hold a `ReadonlySet` should copy via `new Set(...)` at the
18
- * adapter boundary. */
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;