ucn 5.5.1 → 5.5.2
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/.claude/skills/ucn/SKILL.md +35 -9
- package/.claude/skills/ucn/references/commands.md +38 -16
- package/.claude/skills/ucn/references/resolution-rules.md +126 -20
- package/.claude/skills/ucn/references/trust-contract.md +5 -3
- package/README.md +30 -21
- package/cli/index.js +1 -1
- package/core/accessors.js +341 -54
- package/core/account.js +74 -20
- package/core/analysis.js +257 -125
- package/core/ast-analysis.js +12 -3
- package/core/brief.js +8 -4
- package/core/build-worker.js +2 -1
- package/core/cache.js +137 -6
- package/core/call-name-cache.js +134 -0
- package/core/callable-scope.js +21 -0
- package/core/callers.js +4726 -940
- package/core/check.js +22 -5
- package/core/class-identity.js +399 -12
- package/core/constructor-calls.js +831 -0
- package/core/constructor-hierarchy.js +147 -0
- package/core/constructor-protocol.js +196 -0
- package/core/contract-membership.js +346 -140
- package/core/cpp-conversion-constraints.js +123 -0
- package/core/cpp-conversion-syntax.js +216 -0
- package/core/cpp-conversions.js +414 -0
- package/core/cpp-scope.js +245 -44
- package/core/deadcode.js +35 -15
- package/core/decorator-binding.js +137 -3
- package/core/enum-reference-syntax.js +134 -0
- package/core/execute.js +17 -1
- package/core/external-macros.js +42 -10
- package/core/go-packages.js +171 -0
- package/core/graph-build.js +2 -0
- package/core/graph.js +75 -59
- package/core/ignore-rules.js +2 -1
- package/core/imports.js +145 -19
- package/core/index-ir.js +41 -4
- package/core/ir.js +27 -6
- package/core/macro-expansion.js +38 -2
- package/core/out-of-class-members.js +182 -0
- package/core/output/analysis-ext.js +4 -2
- package/core/output/analysis.js +89 -20
- package/core/output/graph.js +12 -3
- package/core/output/lines.js +4 -3
- package/core/output/public.js +1 -0
- package/core/output/reporting.js +6 -1
- package/core/output/shared.js +2 -0
- package/core/output/tracing.js +28 -22
- package/core/output-budget.js +2 -2
- package/core/parallel-build.js +52 -1
- package/core/project.js +217 -112
- package/core/provenance-constructor-base.js +161 -0
- package/core/provenance-facts.js +257 -27
- package/core/provenance-overload.js +458 -54
- package/core/provenance-type.js +487 -0
- package/core/provenance.js +234 -15
- package/core/public-command.js +17 -2
- package/core/query-bodies.js +159 -0
- package/core/reflection.js +255 -2
- package/core/rename-token-syntax.js +111 -0
- package/core/reporting.js +13 -7
- package/core/rust-macro-expansion.js +151 -19
- package/core/rust-modules.js +1 -1
- package/core/rust-names.js +607 -0
- package/core/search.js +70 -17
- package/core/shared.js +98 -1
- package/core/source-memo.js +201 -0
- package/core/text-usage-syntax.js +38 -0
- package/core/tracing.js +7 -1
- package/core/type-denotation.js +124 -37
- package/core/type-references.js +615 -246
- package/core/value-references.js +2777 -0
- package/core/verify.js +1097 -281
- package/languages/adapter.js +28 -4
- package/languages/c-family.js +1118 -164
- package/languages/constructor-bases.js +105 -0
- package/languages/cpp-platform.js +26 -0
- package/languages/cpp-site-syntax.js +83 -0
- package/languages/csharp-scope.js +439 -0
- package/languages/csharp.js +428 -68
- package/languages/go.js +385 -207
- package/languages/html.js +4 -2
- package/languages/index.js +397 -2
- package/languages/java-platform.js +226 -0
- package/languages/java-unicode.js +269 -0
- package/languages/java.js +461 -117
- package/languages/javascript.js +418 -66
- package/languages/lexical-scope.js +190 -23
- package/languages/python.js +404 -54
- package/languages/rust.js +510 -82
- package/languages/utils.js +67 -8
- package/mcp/server.js +8 -2
- package/package.json +2 -2
package/core/type-references.js
CHANGED
|
@@ -40,7 +40,33 @@
|
|
|
40
40
|
const path = require('path');
|
|
41
41
|
const { getParser, safeParse, langTraits, LANGUAGES } = require('../languages');
|
|
42
42
|
const { sameNode } = require('../languages/utils');
|
|
43
|
+
const { jsPatternTarget } = require('../languages/lexical-scope');
|
|
43
44
|
const { classKeyOf } = require('./class-identity');
|
|
45
|
+
const { projectHas, sharedRead, containingDeclarations } = require('./provenance-overload');
|
|
46
|
+
const { indexedContent } = require('./source-memo');
|
|
47
|
+
|
|
48
|
+
// Modules read lazily (load cycles), each resolved once: a require() per
|
|
49
|
+
// call in hot paths was measurable (fix #436).
|
|
50
|
+
let lazyCppScopeModuleCache = null;
|
|
51
|
+
const lazyCppScopeModule = () => lazyCppScopeModuleCache || (lazyCppScopeModuleCache = require('./cpp-scope'));
|
|
52
|
+
|
|
53
|
+
/** The namespaces (packages) a language's files declare, with each dotted prefix. */
|
|
54
|
+
function declaredNamespacePrefixes(index, language) {
|
|
55
|
+
return require('./type-denotation').memoizedDerivation(index, `\x01ns-prefixes\x00${language}`, () => {
|
|
56
|
+
const namespaces = new Set();
|
|
57
|
+
for (const entry of index.files.values()) {
|
|
58
|
+
if (entry.language !== language) continue;
|
|
59
|
+
for (const s of entry.symbols || []) {
|
|
60
|
+
if (!s.namespace) continue;
|
|
61
|
+
const parts = s.namespace.split('.');
|
|
62
|
+
for (let i = 1; i <= parts.length; i++) namespaces.add(parts.slice(0, i).join('.'));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return namespaces;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const csharpNamespacePrefixes = index => declaredNamespacePrefixes(index, 'csharp');
|
|
69
|
+
const javaPackagePrefixes = index => declaredNamespacePrefixes(index, 'java');
|
|
44
70
|
|
|
45
71
|
const TYPE_RENAME_KINDS = new Set([
|
|
46
72
|
'class', 'struct', 'interface', 'trait', 'enum', 'record', 'union', 'type',
|
|
@@ -145,10 +171,22 @@ function classifyJava(node) {
|
|
|
145
171
|
if (field === 'name') return skip('variable', { binding: true });
|
|
146
172
|
break;
|
|
147
173
|
case 'formal_parameter': case 'catch_formal_parameter': case 'spread_parameter':
|
|
148
|
-
case 'enhanced_for_statement': case 'resource':
|
|
149
|
-
if (field === 'name'
|
|
174
|
+
case 'enhanced_for_statement': case 'resource':
|
|
175
|
+
if (field === 'name') return skip('parameter', { binding: true });
|
|
176
|
+
break;
|
|
177
|
+
// Pattern variables (fix #420): `o instanceof T v`, `case T v`,
|
|
178
|
+
// `case R(int v, ..)` declare locals whose scope follows the
|
|
179
|
+
// pattern's flow (JLS 6.3.1).
|
|
180
|
+
case 'instanceof_expression':
|
|
181
|
+
if (field === 'name') return skip('pattern', { binding: true });
|
|
182
|
+
break;
|
|
183
|
+
case 'type_pattern': case 'record_pattern_component':
|
|
184
|
+
if (node.type === 'identifier') return skip('pattern', { binding: true });
|
|
150
185
|
break;
|
|
151
|
-
case 'lambda_expression':
|
|
186
|
+
case 'lambda_expression':
|
|
187
|
+
if (field === 'parameters') return skip('parameter', { binding: true });
|
|
188
|
+
break;
|
|
189
|
+
case 'inferred_parameters':
|
|
152
190
|
return skip('parameter', { binding: true });
|
|
153
191
|
case 'type_parameter':
|
|
154
192
|
return skip('type-parameter', { typeParam: true });
|
|
@@ -179,8 +217,20 @@ function classifyJava(node) {
|
|
|
179
217
|
case 'marker_annotation': case 'annotation':
|
|
180
218
|
if (field === 'name') return { role: 'type' };
|
|
181
219
|
break;
|
|
220
|
+
case 'element_value_pair':
|
|
221
|
+
if (field === 'key') return skip('annotation-element');
|
|
222
|
+
break;
|
|
182
223
|
case 'labeled_statement': case 'break_statement': case 'continue_statement':
|
|
183
224
|
return skip('label');
|
|
225
|
+
case 'object_creation_expression': {
|
|
226
|
+
// A qualified instance creation `outer.new Inner()` names a
|
|
227
|
+
// member class of the outer expression's type (JLS 15.9.1), not a
|
|
228
|
+
// type in scope at the site (fix #426).
|
|
229
|
+
const outer = parent.namedChild(0);
|
|
230
|
+
if (outer && field === 'type' && !sameNode(outer, node) &&
|
|
231
|
+
outer.endIndex <= node.startIndex) return { role: 'qualified-creation', outer };
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
184
234
|
default:
|
|
185
235
|
break;
|
|
186
236
|
}
|
|
@@ -188,6 +238,52 @@ function classifyJava(node) {
|
|
|
188
238
|
if (node.type === 'identifier') return { role: 'value' };
|
|
189
239
|
return { role: 'unknown', why: `java:${parent.type}` };
|
|
190
240
|
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The declared type of a Java expression naming a value, read from its
|
|
244
|
+
* declaration in scope (fix #426): `this` is the enclosing class, a simple
|
|
245
|
+
* name the nearest local, parameter or field declaring it. null otherwise.
|
|
246
|
+
*/
|
|
247
|
+
function javaExpressionTypeName(expression) {
|
|
248
|
+
const head = typeNode => {
|
|
249
|
+
if (!typeNode) return null;
|
|
250
|
+
if (typeNode.type === 'generic_type') {
|
|
251
|
+
return head(typeNode.namedChildren.find(child =>
|
|
252
|
+
child.type === 'type_identifier' || child.type === 'scoped_type_identifier'));
|
|
253
|
+
}
|
|
254
|
+
if (typeNode.type === 'scoped_type_identifier') return typeNode.namedChildren.at(-1)?.text || null;
|
|
255
|
+
return typeNode.type === 'type_identifier' ? typeNode.text : null;
|
|
256
|
+
};
|
|
257
|
+
if (expression.type === 'this') {
|
|
258
|
+
const owner = ancestor(expression, JAVA_CLASS_NODES);
|
|
259
|
+
return owner?.childForFieldName('name')?.text || null;
|
|
260
|
+
}
|
|
261
|
+
if (expression.type !== 'identifier') return null;
|
|
262
|
+
const name = expression.text;
|
|
263
|
+
const declares = (declaration) => (declaration.namedChildren || []).some(child =>
|
|
264
|
+
child.type === 'variable_declarator' && child.childForFieldName('name')?.text === name);
|
|
265
|
+
for (let scope = expression.parent; scope; scope = scope.parent) {
|
|
266
|
+
if (scope.type === 'block' || scope.type === 'constructor_body' || scope.type === 'class_body') {
|
|
267
|
+
for (const child of scope.namedChildren || []) {
|
|
268
|
+
if (child.startIndex >= expression.startIndex && scope.type !== 'class_body') break;
|
|
269
|
+
if ((child.type === 'local_variable_declaration' || child.type === 'field_declaration') &&
|
|
270
|
+
declares(child)) return head(child.childForFieldName('type'));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (['method_declaration', 'constructor_declaration', 'lambda_expression'].includes(scope.type)) {
|
|
274
|
+
const params = scope.childForFieldName('parameters');
|
|
275
|
+
for (const param of params?.namedChildren || []) {
|
|
276
|
+
if ((param.type === 'formal_parameter' || param.type === 'spread_parameter') &&
|
|
277
|
+
param.childForFieldName('name')?.text === name) return head(param.childForFieldName('type'));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (JAVA_CLASS_NODES.has(scope.type)) {
|
|
281
|
+
// Fields of an enclosing class are in scope through its body.
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
191
287
|
const JAVA_CLASS_NODES = new Set(['class_declaration', 'enum_declaration', 'record_declaration',
|
|
192
288
|
'interface_declaration']);
|
|
193
289
|
|
|
@@ -242,6 +338,9 @@ function classifyCSharp(node) {
|
|
|
242
338
|
case 'alias_qualified_name':
|
|
243
339
|
if (field === 'name') return { role: 'type', qualifier: parent.childForFieldName('alias'), aliasQualified: true };
|
|
244
340
|
return skip('extern-alias');
|
|
341
|
+
case 'invocation_expression':
|
|
342
|
+
if (field === 'function') return skip('method-call');
|
|
343
|
+
break;
|
|
245
344
|
case 'member_access_expression':
|
|
246
345
|
if (field === 'name') return { role: 'member', receiver: parent.childForFieldName('expression') };
|
|
247
346
|
return { role: 'value' };
|
|
@@ -302,11 +401,24 @@ function classifyJs(node) {
|
|
|
302
401
|
case 'member_expression':
|
|
303
402
|
if (field === 'property') return { role: 'member', receiver: parent.childForFieldName('object') };
|
|
304
403
|
return { role: 'value' };
|
|
305
|
-
case 'required_parameter': case 'optional_parameter':
|
|
306
|
-
|
|
307
|
-
|
|
404
|
+
case 'required_parameter': case 'optional_parameter':
|
|
405
|
+
// The parameter's pattern binds; its default value and type are
|
|
406
|
+
// read (fix #431: `x: number = LIMIT` reads LIMIT).
|
|
407
|
+
if (node.type === 'identifier' && field === 'pattern') return skip('parameter', { binding: true });
|
|
408
|
+
break;
|
|
409
|
+
case 'formal_parameters':
|
|
308
410
|
if (node.type === 'identifier') return skip('parameter', { binding: true });
|
|
309
411
|
break;
|
|
412
|
+
case 'rest_pattern': case 'array_pattern': case 'assignment_pattern':
|
|
413
|
+
case 'object_assignment_pattern': {
|
|
414
|
+
if (node.type !== 'identifier') break;
|
|
415
|
+
// A destructuring target declares a name, or (an assignment
|
|
416
|
+
// pattern, `[a, b] = f()`) writes the binding in scope (fix #431).
|
|
417
|
+
const target = jsPatternTarget(node);
|
|
418
|
+
if (target === 'declaration') return skip('parameter', { binding: true });
|
|
419
|
+
if (target === 'assignment') return { role: 'value', patternWrite: true };
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
310
422
|
case 'variable_declarator':
|
|
311
423
|
if (field === 'name') return skip('variable', { binding: true });
|
|
312
424
|
break;
|
|
@@ -333,6 +445,11 @@ function classifyJs(node) {
|
|
|
333
445
|
if (required) return { role: 'import', requireSource: required, aliased: true };
|
|
334
446
|
return skip('property-key');
|
|
335
447
|
}
|
|
448
|
+
if (parent.type === 'pair_pattern' && field === 'value' && node.type === 'identifier') {
|
|
449
|
+
const target = jsPatternTarget(node);
|
|
450
|
+
if (target === 'declaration') return skip('destructured', { binding: true });
|
|
451
|
+
if (target === 'assignment') return { role: 'value', patternWrite: true };
|
|
452
|
+
}
|
|
336
453
|
break;
|
|
337
454
|
case 'type_parameter':
|
|
338
455
|
if (field === 'name') return skip('type-parameter', { typeParam: true });
|
|
@@ -349,6 +466,8 @@ function classifyJs(node) {
|
|
|
349
466
|
// `const { Widget } = require('./w')` is an import of Widget.
|
|
350
467
|
const required = requireSourceOf(node);
|
|
351
468
|
if (required) return { role: 'import', requireSource: required };
|
|
469
|
+
// `({ a } = obj)` writes the binding in scope (fix #431).
|
|
470
|
+
if (jsPatternTarget(node) === 'assignment') return { role: 'value', patternWrite: true };
|
|
352
471
|
return skip('destructured', { binding: true });
|
|
353
472
|
}
|
|
354
473
|
if (node.type === 'type_identifier') return { role: 'type' };
|
|
@@ -459,7 +578,13 @@ function classifyGo(node) {
|
|
|
459
578
|
if (field === 'name') return { role: 'decl', declNode: parent };
|
|
460
579
|
break;
|
|
461
580
|
case 'qualified_type':
|
|
462
|
-
if (field === 'name')
|
|
581
|
+
if (field === 'name') {
|
|
582
|
+
// In an expression the grammar reads the index of `x[a.b]`
|
|
583
|
+
// as a type argument `a.b` (fix #431): a selector on a value
|
|
584
|
+
// `a` unless `a` names an imported package.
|
|
585
|
+
return { role: 'type', qualifier: parent.childForFieldName('package'),
|
|
586
|
+
...(require('../languages/lexical-scope').goExpressionTypeArgument(parent) && { selectorOperand: true }) };
|
|
587
|
+
}
|
|
463
588
|
return skip('package');
|
|
464
589
|
case 'field_declaration':
|
|
465
590
|
if (node.type === 'field_identifier') return skip('field-name');
|
|
@@ -569,6 +694,9 @@ function classifyRust(node) {
|
|
|
569
694
|
case 'const_item': case 'static_item':
|
|
570
695
|
if (field === 'name') return skip('constant', { binding: true });
|
|
571
696
|
break;
|
|
697
|
+
case 'const_parameter':
|
|
698
|
+
if (field === 'name') return skip('parameter', { binding: true, genericParameter: true });
|
|
699
|
+
break;
|
|
572
700
|
case 'mod_item':
|
|
573
701
|
return skip('module-name');
|
|
574
702
|
case 'label': case 'loop_label': case 'lifetime':
|
|
@@ -619,9 +747,18 @@ function classifyC(node, language) {
|
|
|
619
747
|
case 'qualified_identifier': {
|
|
620
748
|
const scope = parent.childForFieldName('scope');
|
|
621
749
|
if (field === 'name') {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
750
|
+
let qualified = parent;
|
|
751
|
+
while (qualified.parent?.type === 'qualified_identifier' && isField(qualified, 'name')) {
|
|
752
|
+
qualified = qualified.parent;
|
|
753
|
+
}
|
|
754
|
+
const declarator = qualified.parent?.type === 'function_declarator' &&
|
|
755
|
+
isField(qualified, 'declarator') ? qualified.parent : null;
|
|
756
|
+
let declaration = declarator;
|
|
757
|
+
while (declaration?.parent && (isField(declaration, 'declarator') ||
|
|
758
|
+
['reference_declarator', 'parenthesized_declarator', 'attributed_declarator'].includes(declaration.parent.type))) {
|
|
759
|
+
declaration = declaration.parent;
|
|
760
|
+
}
|
|
761
|
+
if (declarator && !declaration.childForFieldName('type') && scope) {
|
|
625
762
|
return { role: 'ctor-qualified', qualifier: scope, qualifiedNode: parent };
|
|
626
763
|
}
|
|
627
764
|
return { role: 'type', cppQualified: true };
|
|
@@ -640,16 +777,26 @@ function classifyC(node, language) {
|
|
|
640
777
|
}
|
|
641
778
|
case 'function_declarator': {
|
|
642
779
|
if (field !== 'declarator') break;
|
|
780
|
+
// `typedef int F(void *data);` names a function type (fix #427).
|
|
781
|
+
if (parent.parent?.type === 'type_definition' && isField(parent, 'declarator')) {
|
|
782
|
+
return { role: 'decl', declNode: parent.parent, typedef: true };
|
|
783
|
+
}
|
|
643
784
|
const classNode = ancestor(node, C_TYPE_SPECIFIERS, new Set(['compound_statement']));
|
|
644
785
|
const body = classNode?.childForFieldName('body');
|
|
645
786
|
// `struct List<K>::Node { Node(..) }` names its class by the
|
|
646
787
|
// qualified name's last segment (fix #396).
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
788
|
+
let className = classNode?.childForFieldName('name');
|
|
789
|
+
while (className && (className.type === 'qualified_identifier' || className.type === 'template_type')) {
|
|
790
|
+
className = className.childForFieldName('name') || className.namedChild(0);
|
|
791
|
+
}
|
|
792
|
+
const classBase = className?.text;
|
|
650
793
|
if (classNode && body && contains(body, node) && classBase === node.text) {
|
|
651
794
|
return { role: 'ctor', classNode };
|
|
652
795
|
}
|
|
796
|
+
// A deduction guide's name is the class template's (fix #427).
|
|
797
|
+
if (language === 'cpp' && require('../languages/c-family').deductionGuideOf(parent.parent)) {
|
|
798
|
+
return { role: 'type' };
|
|
799
|
+
}
|
|
653
800
|
return skip('function', { binding: true });
|
|
654
801
|
}
|
|
655
802
|
case 'field_initializer':
|
|
@@ -676,7 +823,8 @@ function classifyC(node, language) {
|
|
|
676
823
|
if (node.type === 'field_identifier') return skip('field-name');
|
|
677
824
|
break;
|
|
678
825
|
case 'enumerator':
|
|
679
|
-
return skip('enumerator');
|
|
826
|
+
if (field === 'name') return skip('enumerator');
|
|
827
|
+
break;
|
|
680
828
|
case 'type_parameter_declaration': case 'optional_type_parameter_declaration':
|
|
681
829
|
case 'variadic_type_parameter_declaration':
|
|
682
830
|
// `template <typename T = Widget>`: the default is a reference.
|
|
@@ -684,11 +832,21 @@ function classifyC(node, language) {
|
|
|
684
832
|
return skip('type-parameter', { typeParam: true });
|
|
685
833
|
case 'preproc_def': case 'preproc_function_def': case 'preproc_params':
|
|
686
834
|
return skip('macro-name');
|
|
835
|
+
case 'structured_binding_declarator':
|
|
836
|
+
// `auto [a, b] = p;` declares each name (fix #420).
|
|
837
|
+
if (node.type === 'identifier') return skip('variable', { binding: true });
|
|
838
|
+
break;
|
|
839
|
+
case 'reference_declarator':
|
|
840
|
+
// The C++ grammar gives the referenced declarator no field name.
|
|
841
|
+
if (sameNode(parent.namedChild(0), node)) {
|
|
842
|
+
if (node.type === 'identifier') return skip('variable', { binding: true });
|
|
843
|
+
if (node.type === 'field_identifier') return skip('field-name');
|
|
844
|
+
}
|
|
845
|
+
break;
|
|
687
846
|
case 'init_declarator': case 'parameter_declaration': case 'optional_parameter_declaration':
|
|
688
|
-
case 'pointer_declarator': case '
|
|
847
|
+
case 'pointer_declarator': case 'array_declarator':
|
|
689
848
|
case 'declaration':
|
|
690
|
-
if (node.type
|
|
691
|
-
parent.type === 'reference_declarator' || parent.type === 'array_declarator')) {
|
|
849
|
+
if (['identifier', 'field_identifier'].includes(node.type) && field === 'declarator') {
|
|
692
850
|
return skip('variable', { binding: true });
|
|
693
851
|
}
|
|
694
852
|
break;
|
|
@@ -784,16 +942,30 @@ function typeParamScope(node) {
|
|
|
784
942
|
// ── Identity of the pinned type ──────────────────────────────────────────
|
|
785
943
|
|
|
786
944
|
function pinGroupOf(index, def, name) {
|
|
945
|
+
// Several construction witnesses may ask about the same type. Share
|
|
946
|
+
// the pure grouping calculation and all its reads, but give each
|
|
947
|
+
// resolver its own collections: value resolvers extend these groups.
|
|
948
|
+
const key = JSON.stringify(['type-pin-group', name, def]);
|
|
949
|
+
const saved = sharedRead(index, key, view => pinGroupFacts(view, def, name));
|
|
950
|
+
return { ...saved, group: new Set([def, ...saved.group]), all: [...saved.all] };
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function pinGroupFacts(index, def, name) {
|
|
787
954
|
const language = index.files.get(def.file)?.language;
|
|
788
955
|
const family = familyOf(language);
|
|
789
|
-
|
|
790
|
-
|
|
956
|
+
// Different pins of one name inspect the same declarations. Their
|
|
957
|
+
// definition identities depend on the index, not on the selected pin.
|
|
958
|
+
const candidates = sharedRead(index, JSON.stringify(['type-pin-candidates', name, family]), view =>
|
|
959
|
+
(view.symbols.get(name) || []).filter(d => TYPE_DECL_KINDS.has(d.type) &&
|
|
960
|
+
familyOf(view.files.get(d.file)?.language) === family)
|
|
961
|
+
.map(d => ({ def: d, key: classKeyOf(view, d) })));
|
|
962
|
+
const all = candidates.map(candidate => candidate.def);
|
|
791
963
|
const pinKey = classKeyOf(index, def);
|
|
792
|
-
const group = new Set(
|
|
964
|
+
const group = new Set();
|
|
793
965
|
let crossConfig = null;
|
|
794
|
-
for (const
|
|
795
|
-
|
|
796
|
-
if (
|
|
966
|
+
for (const candidate of candidates) {
|
|
967
|
+
const other = candidate.def;
|
|
968
|
+
if (candidate.key === pinKey) {
|
|
797
969
|
group.add(other);
|
|
798
970
|
continue;
|
|
799
971
|
}
|
|
@@ -821,7 +993,7 @@ function pinGroupOf(index, def, name) {
|
|
|
821
993
|
group.add(other);
|
|
822
994
|
continue;
|
|
823
995
|
}
|
|
824
|
-
const { includeClosure } =
|
|
996
|
+
const { includeClosure } = lazyCppScopeModule();
|
|
825
997
|
if (includeClosure(index, def.file).has(other.file) || includeClosure(index, other.file).has(def.file)) {
|
|
826
998
|
group.add(other);
|
|
827
999
|
continue;
|
|
@@ -830,7 +1002,7 @@ function pinGroupOf(index, def, name) {
|
|
|
830
1002
|
if (crossConfig(index, def.file, other.file)) group.add(other);
|
|
831
1003
|
}
|
|
832
1004
|
}
|
|
833
|
-
return { group, all, family, language };
|
|
1005
|
+
return { group: [...group], all, family, language };
|
|
834
1006
|
}
|
|
835
1007
|
|
|
836
1008
|
const CLASS_KEYWORD_KINDS = new Set(['class', 'struct']);
|
|
@@ -841,7 +1013,7 @@ function isHeader(file) {
|
|
|
841
1013
|
}
|
|
842
1014
|
|
|
843
1015
|
function effectiveNs(index, d) {
|
|
844
|
-
const { effectiveNamespace } =
|
|
1016
|
+
const { effectiveNamespace } = lazyCppScopeModule();
|
|
845
1017
|
return effectiveNamespace(index, d) || '';
|
|
846
1018
|
}
|
|
847
1019
|
|
|
@@ -852,6 +1024,101 @@ function rustModulePath(index, d) {
|
|
|
852
1024
|
|
|
853
1025
|
// ── Resolution ───────────────────────────────────────────────────────────
|
|
854
1026
|
|
|
1027
|
+
// Whether a word occurrence of `name` in Rust source is followed, past
|
|
1028
|
+
// whitespace and comments, by `!`: what an invocation's path needs before
|
|
1029
|
+
// its token tree in a source that parses without error.
|
|
1030
|
+
function spelledBeforeBang(source, name) {
|
|
1031
|
+
// ASCII word characters only: anything else around the name (Unicode
|
|
1032
|
+
// identifiers, Unicode whitespace) keeps the file.
|
|
1033
|
+
const word = c => (c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 95;
|
|
1034
|
+
for (let at = source.indexOf(name); at >= 0; at = source.indexOf(name, at + 1)) {
|
|
1035
|
+
if (at > 0 && word(source.charCodeAt(at - 1)) || word(source.charCodeAt(at + name.length))) continue;
|
|
1036
|
+
let i = at + name.length;
|
|
1037
|
+
for (;;) {
|
|
1038
|
+
const c = source.charCodeAt(i);
|
|
1039
|
+
if (c > 127) return true;
|
|
1040
|
+
if (c === 32 || c === 9 || c === 10 || c === 13 || c === 11 || c === 12) { i++; continue; }
|
|
1041
|
+
if (c === 47 && source.charCodeAt(i + 1) === 47) {
|
|
1042
|
+
const end = source.indexOf('\n', i);
|
|
1043
|
+
if (end < 0) { i = source.length; break; }
|
|
1044
|
+
i = end + 1;
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
if (c === 47 && source.charCodeAt(i + 1) === 42) {
|
|
1048
|
+
let depth = 1;
|
|
1049
|
+
i += 2;
|
|
1050
|
+
while (i < source.length && depth > 0) {
|
|
1051
|
+
if (source.charCodeAt(i) === 47 && source.charCodeAt(i + 1) === 42) { depth++; i += 2; }
|
|
1052
|
+
else if (source.charCodeAt(i) === 42 && source.charCodeAt(i + 1) === 47) { depth--; i += 2; }
|
|
1053
|
+
else i++;
|
|
1054
|
+
}
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
break;
|
|
1058
|
+
}
|
|
1059
|
+
if (source.charCodeAt(i) === 33) return true;
|
|
1060
|
+
}
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// The innermost class around a line by the index's declarations.
|
|
1065
|
+
function enclosingClassIn(index, file, line) {
|
|
1066
|
+
const entry = index.files.get(file);
|
|
1067
|
+
let best = null;
|
|
1068
|
+
for (const s of entry?.symbols || []) {
|
|
1069
|
+
if (s.startLine > line || s.endLine < line) continue;
|
|
1070
|
+
const isClass = TYPE_DECL_KINDS.has(s.type) && s.type !== 'type';
|
|
1071
|
+
const candidate = isClass ? s.name : (s.className || null);
|
|
1072
|
+
if (!candidate) continue;
|
|
1073
|
+
if (!best || (s.endLine - s.startLine) < (best.span)) {
|
|
1074
|
+
best = { name: candidate, span: s.endLine - s.startLine };
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return best?.name || null;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// The namespace and class a C/C++ site's lookup starts in: from the site's
|
|
1081
|
+
// own tree node when it has one (no pinned type takes part).
|
|
1082
|
+
function cppSiteContextOf(index, siteNode, siteFile, file, line, syntax = null) {
|
|
1083
|
+
const node = siteNode && siteFile === file && siteNode.startPosition.row + 1 === line
|
|
1084
|
+
? siteNode : null;
|
|
1085
|
+
const { macroNamespacePrefixAt, namespaceAt } = lazyCppScopeModule();
|
|
1086
|
+
if (!node && !syntax) return { namespace: namespaceAt(index, file, line), className: enclosingClassIn(index, file, line) };
|
|
1087
|
+
const facts = syntax || require('../languages/cpp-site-syntax').cppSiteSyntax(node);
|
|
1088
|
+
const { namespaces, memberOf, recovered } = facts;
|
|
1089
|
+
const classes = [...facts.classes];
|
|
1090
|
+
// A class body the raw tree lost (macro-heavy headers, conditional
|
|
1091
|
+
// members): the indexed nested classes around the line (parsed from
|
|
1092
|
+
// the recovered source) still name their owners (fix #396: `union
|
|
1093
|
+
// Data` inside `GenericValue`).
|
|
1094
|
+
if (classes.length > 0) {
|
|
1095
|
+
const symbols = index.files.get(file)?.symbols || [];
|
|
1096
|
+
const around = name => symbols.find(s => s.name === name && TYPE_DECL_KINDS.has(s.type) &&
|
|
1097
|
+
s.startLine <= line && line <= s.endLine);
|
|
1098
|
+
for (let owner = around(classes[0].split('::').pop())?.enclosingType, depth = 0;
|
|
1099
|
+
owner && depth < 8 && !classes.includes(owner); depth++) {
|
|
1100
|
+
classes.unshift(owner);
|
|
1101
|
+
owner = around(owner)?.enclosingType;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
const prefix = macroNamespacePrefixAt(index, file, line);
|
|
1105
|
+
const own = namespaces.join('::');
|
|
1106
|
+
let namespace = prefix && own ? `${prefix}::${own}` : prefix || own;
|
|
1107
|
+
let className = classes.length > 0 ? classes.join('::') : memberOf;
|
|
1108
|
+
// Macro-heavy code can break the raw tree so that an enclosing
|
|
1109
|
+
// namespace block closes early; the indexed declarations around the
|
|
1110
|
+
// line (parsed from the recovered source) still carry it.
|
|
1111
|
+
const indexed = namespaceAt(index, file, line);
|
|
1112
|
+
if (indexed && indexed !== namespace && (namespace === '' || own === '' ||
|
|
1113
|
+
indexed.endsWith(`::${own}`) || indexed.startsWith(`${namespace}::`) ||
|
|
1114
|
+
(recovered && indexed.startsWith(namespace)))) namespace = indexed;
|
|
1115
|
+
// Out-of-line member bodies: the index (parsed from the recovered
|
|
1116
|
+
// source) knows their class even when a macro before the return type
|
|
1117
|
+
// broke the literal head; the head's own qualifier is the fallback.
|
|
1118
|
+
if (classes.length === 0) className = enclosingClassIn(index, file, line) || memberOf;
|
|
1119
|
+
return { namespace, className };
|
|
1120
|
+
}
|
|
1121
|
+
|
|
855
1122
|
class TypeReferenceResolver {
|
|
856
1123
|
constructor(index, def, name) {
|
|
857
1124
|
this.index = index;
|
|
@@ -862,6 +1129,7 @@ class TypeReferenceResolver {
|
|
|
862
1129
|
this.all = all;
|
|
863
1130
|
this.family = family;
|
|
864
1131
|
this.language = language;
|
|
1132
|
+
this.declarationKinds = TYPE_DECL_KINDS;
|
|
865
1133
|
this.pinFiles = new Set([...group].map(d => d.file));
|
|
866
1134
|
this.memo = new Map();
|
|
867
1135
|
this._embedders = undefined;
|
|
@@ -869,6 +1137,41 @@ class TypeReferenceResolver {
|
|
|
869
1137
|
|
|
870
1138
|
inGroup(d) { return this.group.has(d); }
|
|
871
1139
|
|
|
1140
|
+
/**
|
|
1141
|
+
* The Python function scope the pinned class statement binds its name
|
|
1142
|
+
* in (fix #424), when that scope binds the name by other statements
|
|
1143
|
+
* too: one variable, renamed whole (its other bindings are the plan's
|
|
1144
|
+
* alternative bindings), so a reference resolving to that scope names
|
|
1145
|
+
* the pin's variable. Null for module and class-body scopes, which the
|
|
1146
|
+
* module lookup and class-body order decide.
|
|
1147
|
+
*/
|
|
1148
|
+
pythonPinScope(tree) {
|
|
1149
|
+
if (this.family !== 'python' || !tree) return null;
|
|
1150
|
+
if (this._pythonPinScope && this._pythonPinScope.tree === tree) return this._pythonPinScope.scope;
|
|
1151
|
+
let scope = null;
|
|
1152
|
+
const row = (this.def.nameLine || this.def.startLine) - 1;
|
|
1153
|
+
const stack = [tree.rootNode];
|
|
1154
|
+
let classNode = null;
|
|
1155
|
+
while (stack.length > 0 && !classNode) {
|
|
1156
|
+
const node = stack.pop();
|
|
1157
|
+
if (node.startPosition.row > row || node.endPosition.row < row) continue;
|
|
1158
|
+
if (node.type === 'class_definition' && node.childForFieldName('name')?.text === this.name &&
|
|
1159
|
+
node.childForFieldName('name').startPosition.row === row) {
|
|
1160
|
+
classNode = node;
|
|
1161
|
+
break;
|
|
1162
|
+
}
|
|
1163
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) stack.push(node.namedChild(i));
|
|
1164
|
+
}
|
|
1165
|
+
const owner = classNode && bindingScope(classNode, 'python', {});
|
|
1166
|
+
if (owner?.type === 'function_definition') {
|
|
1167
|
+
const { pythonScopeBindingKinds } = require('../languages/lexical-scope');
|
|
1168
|
+
const sites = pythonScopeBindingKinds(classNode.parent, this.name);
|
|
1169
|
+
if (sites.some(site => !sameNode(site.node, classNode.childForFieldName('name')))) scope = owner;
|
|
1170
|
+
}
|
|
1171
|
+
this._pythonPinScope = { tree, scope };
|
|
1172
|
+
return scope;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
872
1175
|
/**
|
|
873
1176
|
* Generic arity written at the token being decided, where arity is part
|
|
874
1177
|
* of a type's identity (trait genericArityIsIdentity, fix #389): `X` is
|
|
@@ -876,9 +1179,14 @@ class TypeReferenceResolver {
|
|
|
876
1179
|
* site is not an identifier token (doc comments).
|
|
877
1180
|
*/
|
|
878
1181
|
siteArity() {
|
|
1182
|
+
if (this.siteSyntax && Number.isInteger(this.siteSyntax.arity)) return this.siteSyntax.arity;
|
|
879
1183
|
const node = this.siteNode;
|
|
880
1184
|
if (!node || node.type !== 'identifier' || !langTraits(this.language)?.genericArityIsIdentity) return null;
|
|
881
1185
|
const parent = node.parent;
|
|
1186
|
+
if (CSHARP_TYPE_DECLS.has(parent?.type) && sameNode(parent.childForFieldName('name'), node)) {
|
|
1187
|
+
const list = parent.namedChildren.find(child => child.type === 'type_parameter_list');
|
|
1188
|
+
return list ? list.namedChildren.filter(child => child.type === 'type_parameter').length : 0;
|
|
1189
|
+
}
|
|
882
1190
|
// A constructor or finalizer is named after its class without type
|
|
883
1191
|
// arguments (fix #390: `Strategy(..)` inside `class Strategy<T>`):
|
|
884
1192
|
// its arity is the enclosing declaration's.
|
|
@@ -911,7 +1219,7 @@ class TypeReferenceResolver {
|
|
|
911
1219
|
/** Type-like definitions of NAME declared in `file`. */
|
|
912
1220
|
fileTypes(file, name = this.name) {
|
|
913
1221
|
return (this.index.symbols.get(name) || []).filter(d => d.file === file &&
|
|
914
|
-
|
|
1222
|
+
this.declarationKinds.has(d.type));
|
|
915
1223
|
}
|
|
916
1224
|
|
|
917
1225
|
/** Container range of a definition inside its own file (innermost
|
|
@@ -925,10 +1233,12 @@ class TypeReferenceResolver {
|
|
|
925
1233
|
}
|
|
926
1234
|
|
|
927
1235
|
_containerOf(d) {
|
|
928
|
-
const entry = this.index.files.get(d.file);
|
|
929
1236
|
let best = null;
|
|
930
|
-
for (const s of
|
|
1237
|
+
for (const s of containingDeclarations(this.index, d) || []) {
|
|
931
1238
|
if (s === d || s.startLine == null || s.endLine == null) continue;
|
|
1239
|
+
if (s.sourceSpan && d.sourceSpan &&
|
|
1240
|
+
(s.sourceSpan.start > d.sourceSpan.start ||
|
|
1241
|
+
s.sourceSpan.end < d.sourceSpan.end)) continue;
|
|
932
1242
|
if (s.startLine > d.startLine || s.endLine < d.endLine) continue;
|
|
933
1243
|
if (s.startLine === d.startLine && s.endLine === d.endLine) continue;
|
|
934
1244
|
if (s.type === 'impl' || s.type === 'field') continue;
|
|
@@ -991,7 +1301,9 @@ class TypeReferenceResolver {
|
|
|
991
1301
|
if (!entry) return { verdict: 'unknown', reason: 'unindexed-file' };
|
|
992
1302
|
if (this.family === 'c') return this.resolveCFamily(file, line, name, null);
|
|
993
1303
|
if (this.family === 'rust') return this.resolveRustName(file, line, name);
|
|
994
|
-
|
|
1304
|
+
// A C# compilation unit can hold several namespace bodies. A type
|
|
1305
|
+
// declared somewhere in the file is not automatically in scope.
|
|
1306
|
+
const local = this.family === 'csharp' ? [] : this.visibleInFile(file, line, name);
|
|
995
1307
|
if (local.length > 0) {
|
|
996
1308
|
// A module-scope assignment of the name rebinds it, unless that
|
|
997
1309
|
// assignment is the type's own declaration (a type alias).
|
|
@@ -1013,7 +1325,16 @@ class TypeReferenceResolver {
|
|
|
1013
1325
|
|
|
1014
1326
|
chase(startFile, name) {
|
|
1015
1327
|
const { _nameBindingReaches } = require('./callers');
|
|
1016
|
-
|
|
1328
|
+
const reach = _nameBindingReaches(this.index, startFile, name, this.pinFiles, 8,
|
|
1329
|
+
{ exactName: true, configurationVerdict: true });
|
|
1330
|
+
// A module binding the name through one alternative of a
|
|
1331
|
+
// configuration item keeps the item's name (fix #424, the #398F
|
|
1332
|
+
// import rule): its importers' and attribute readers' tokens name
|
|
1333
|
+
// the item, never the renamed pin.
|
|
1334
|
+
// Value dependencies still reach the chosen alternative at runtime.
|
|
1335
|
+
// Its importers keep the chooser's name, so they are review sites,
|
|
1336
|
+
// never automatic edits of one alternative's declaration.
|
|
1337
|
+
return reach === 'configuration' ? (this.valueReferences ? 'unknown' : 'no') : reach;
|
|
1017
1338
|
}
|
|
1018
1339
|
|
|
1019
1340
|
moduleFile(entry, module) {
|
|
@@ -1187,29 +1508,12 @@ class TypeReferenceResolver {
|
|
|
1187
1508
|
* the namespace of the indexed types around the line.
|
|
1188
1509
|
*/
|
|
1189
1510
|
csharpSiteNamespace(file, line) {
|
|
1511
|
+
if (this.siteSyntax?.file === file && this.siteSyntax.line === line) {
|
|
1512
|
+
return this.siteSyntax.context.scopes.at(-1)?.namespace || '';
|
|
1513
|
+
}
|
|
1190
1514
|
const node = this.siteNode && this.siteFile === file && this.siteNode.startPosition.row + 1 === line
|
|
1191
1515
|
? this.siteNode : null;
|
|
1192
|
-
if (node)
|
|
1193
|
-
const parts = [];
|
|
1194
|
-
let root = node;
|
|
1195
|
-
for (let current = node.parent; current; current = current.parent) {
|
|
1196
|
-
if (current.type === 'namespace_declaration') {
|
|
1197
|
-
const nameNode = current.childForFieldName('name');
|
|
1198
|
-
if (nameNode) parts.unshift(textOf(nameNode));
|
|
1199
|
-
}
|
|
1200
|
-
root = current;
|
|
1201
|
-
}
|
|
1202
|
-
for (let i = 0; i < root.namedChildCount; i++) {
|
|
1203
|
-
const child = root.namedChild(i);
|
|
1204
|
-
if (child.startIndex > node.startIndex) break;
|
|
1205
|
-
if (child.type === 'file_scoped_namespace_declaration') {
|
|
1206
|
-
const nameNode = child.childForFieldName('name');
|
|
1207
|
-
if (nameNode) parts.unshift(textOf(nameNode));
|
|
1208
|
-
break;
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
if (parts.length > 0) return parts.join('.');
|
|
1212
|
-
}
|
|
1516
|
+
if (node) return require('../languages/csharp-scope').usingScopesOf(node).at(-1)?.namespace || '';
|
|
1213
1517
|
const { siteNamespaceOf } = require('./type-denotation');
|
|
1214
1518
|
return siteNamespaceOf(this.index.files.get(file), line);
|
|
1215
1519
|
}
|
|
@@ -1226,10 +1530,10 @@ class TypeReferenceResolver {
|
|
|
1226
1530
|
const entry = this.index.files.get(file);
|
|
1227
1531
|
const { csharpUsings } = require('./type-denotation');
|
|
1228
1532
|
const candidates = this.all.filter(d => d.name === name && !d.enclosingType);
|
|
1229
|
-
const alias = (entry.importBindings || []).some(b => b.name === name && b.kind === 'using');
|
|
1230
|
-
if (alias) return { verdict: 'no', reason: 'using-alias' };
|
|
1231
1533
|
const site = this.csharpSiteNamespace(file, line);
|
|
1232
1534
|
if (site === undefined) return { verdict: 'unknown', reason: 'unknown-namespace' };
|
|
1535
|
+
const localNested = this.visibleInFile(file, line, name).filter(d => d.enclosingType);
|
|
1536
|
+
if (localNested.length) return this.judge(localNested);
|
|
1233
1537
|
// Member types of an enclosing class come first, declared in any
|
|
1234
1538
|
// `partial` part of it (fix #395: `readonly DepthLimiter _d;` in one
|
|
1235
1539
|
// part of `partial class PropertyValueConverter`, the nested class
|
|
@@ -1250,6 +1554,7 @@ class TypeReferenceResolver {
|
|
|
1250
1554
|
if (members.length > 0) return this.judge(members);
|
|
1251
1555
|
}
|
|
1252
1556
|
}
|
|
1557
|
+
const scope = csharpUsings(this.index, file, site, this.siteNode, this.siteSyntax?.context);
|
|
1253
1558
|
const chain = [];
|
|
1254
1559
|
for (let parts = site ? site.split('.') : []; parts.length > 0; parts = parts.slice(0, -1)) {
|
|
1255
1560
|
chain.push(parts.join('.'));
|
|
@@ -1257,15 +1562,20 @@ class TypeReferenceResolver {
|
|
|
1257
1562
|
chain.push('');
|
|
1258
1563
|
for (const ns of chain) {
|
|
1259
1564
|
const here = candidates.filter(d => (d.namespace || '') === ns);
|
|
1565
|
+
const alias = scope.aliases.has(name) && scope.aliasScopes?.get(name) === ns;
|
|
1566
|
+
if (alias && (here.length || scope.ambiguousAliases.has(name))) {
|
|
1567
|
+
return { verdict: 'unknown', reason: 'ambiguous-type' };
|
|
1568
|
+
}
|
|
1260
1569
|
if (here.length > 0) return this.judge(here);
|
|
1570
|
+
if (alias) return { verdict: 'no', reason: 'using-alias' };
|
|
1571
|
+
// This namespace's imports are considered before its parent's
|
|
1572
|
+
// declarations; using-static directives can provide nested types.
|
|
1573
|
+
const usings = new Set([...scope.namespaces, ...scope.statics].filter(module =>
|
|
1574
|
+
scope.namespaceScopes.get(module)?.has(ns) || scope.staticScopes.get(module)?.has(ns)));
|
|
1575
|
+
const viaUsing = candidates.filter(d => usings.has(d.namespace || '') ||
|
|
1576
|
+
(d.enclosingType && usings.has(`${d.namespace ? d.namespace + '.' : ''}${d.enclosingType}`)));
|
|
1577
|
+
if (viaUsing.length > 0) return this.judge(viaUsing);
|
|
1261
1578
|
}
|
|
1262
|
-
// Using directives resolved from the namespace each is written in
|
|
1263
|
-
// (fix #395: `namespace FluentValidation; using Results;` names
|
|
1264
|
-
// FluentValidation.Results); `using static` also brings nested types.
|
|
1265
|
-
const scope = csharpUsings(this.index, file);
|
|
1266
|
-
const usings = new Set([...scope.namespaces, ...scope.statics]);
|
|
1267
|
-
const viaUsing = candidates.filter(d => usings.has(d.namespace || ''));
|
|
1268
|
-
if (viaUsing.length > 0) return this.judge(viaUsing);
|
|
1269
1579
|
if ([...this.group].some(d => d.enclosingType) && this.inheritsFromPinOwner(file)) {
|
|
1270
1580
|
return { verdict: 'unknown', reason: 'inherited-member-type' };
|
|
1271
1581
|
}
|
|
@@ -1391,19 +1701,20 @@ class TypeReferenceResolver {
|
|
|
1391
1701
|
if (entry?.language !== 'rust') continue;
|
|
1392
1702
|
let content;
|
|
1393
1703
|
try { content = this.index._readFile(file); } catch { continue; }
|
|
1704
|
+
// A cleanly parsed file holds an invocation of the macro only
|
|
1705
|
+
// where its name token is followed by `!` (fix #435: every file
|
|
1706
|
+
// spelling a type-like macro name was parsed and walked).
|
|
1707
|
+
if (!entry.parseErrorRegions && indexedContent(this.index, file, entry, content) &&
|
|
1708
|
+
!spelledBeforeBang(content, macroName)) continue;
|
|
1394
1709
|
const tree = this.index._getParsedTree?.(file, content, 'rust') || safeParse(getParser('rust'), content);
|
|
1395
1710
|
if (!tree) continue;
|
|
1396
1711
|
const rows = new Set(lineNumbers.map(n => n - 1));
|
|
1397
|
-
const
|
|
1398
|
-
|
|
1399
|
-
const
|
|
1400
|
-
if (
|
|
1401
|
-
|
|
1402
|
-
if (macro && macro.text.split('::').pop() === macroName) {
|
|
1403
|
-
sites.push({ file, line: current.startPosition.row + 1 });
|
|
1404
|
-
}
|
|
1712
|
+
for (const current of tree.rootNode.descendantsOfType('macro_invocation')) {
|
|
1713
|
+
if (!rows.has(current.startPosition.row)) continue;
|
|
1714
|
+
const macro = current.childForFieldName('macro');
|
|
1715
|
+
if (macro && macro.text.split('::').pop() === macroName) {
|
|
1716
|
+
sites.push({ file, line: current.startPosition.row + 1 });
|
|
1405
1717
|
}
|
|
1406
|
-
for (let i = current.namedChildCount - 1; i >= 0; i--) stack.push(current.namedChild(i));
|
|
1407
1718
|
}
|
|
1408
1719
|
}
|
|
1409
1720
|
this.memo.set(key, sites);
|
|
@@ -1431,7 +1742,7 @@ class TypeReferenceResolver {
|
|
|
1431
1742
|
rustModuleVerdict(resolved, name, depth) {
|
|
1432
1743
|
const { rustModuleItems } = require('./rust-modules');
|
|
1433
1744
|
const verdicts = [];
|
|
1434
|
-
const items = rustModuleItems(this.index, resolved, name).filter(d =>
|
|
1745
|
+
const items = rustModuleItems(this.index, resolved, name).filter(d => this.declarationKinds.has(d.type));
|
|
1435
1746
|
if (items.length > 0) verdicts.push(this.judge(items));
|
|
1436
1747
|
for (const moduleEntry of resolved.entries) {
|
|
1437
1748
|
const target = this.index.files.get(moduleEntry.file);
|
|
@@ -1501,36 +1812,23 @@ class TypeReferenceResolver {
|
|
|
1501
1812
|
|
|
1502
1813
|
// ── C / C++ ──
|
|
1503
1814
|
|
|
1504
|
-
enclosingClassAt(file, line) {
|
|
1505
|
-
const entry = this.index.files.get(file);
|
|
1506
|
-
let best = null;
|
|
1507
|
-
for (const s of entry?.symbols || []) {
|
|
1508
|
-
if (s.startLine > line || s.endLine < line) continue;
|
|
1509
|
-
const isClass = TYPE_DECL_KINDS.has(s.type) && s.type !== 'type';
|
|
1510
|
-
const candidate = isClass ? s.name : (s.className || null);
|
|
1511
|
-
if (!candidate) continue;
|
|
1512
|
-
if (!best || (s.endLine - s.startLine) < (best.span)) {
|
|
1513
|
-
best = { name: candidate, span: s.endLine - s.startLine };
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
return best?.name || null;
|
|
1517
|
-
}
|
|
1815
|
+
enclosingClassAt(file, line) { return enclosingClassIn(this.index, file, line); }
|
|
1518
1816
|
|
|
1519
1817
|
resolveCFamily(file, line, name, qualifierText, options = {}) {
|
|
1520
1818
|
const entry = this.index.files.get(file);
|
|
1521
|
-
const { includeClosure, resolveQualifier } =
|
|
1819
|
+
const { includeClosure, resolveQualifier } = lazyCppScopeModule();
|
|
1522
1820
|
const visible = includeClosure(this.index, file);
|
|
1523
1821
|
if (entry.language === 'c') {
|
|
1524
1822
|
// C tags (struct/union/enum) live in their own namespace; typedef
|
|
1525
1823
|
// names in the ordinary one.
|
|
1526
1824
|
const tagSite = options.tag != null;
|
|
1527
1825
|
const candidates = this.all.filter(d => d.name === name &&
|
|
1528
|
-
(tagSite ? !cOrdinaryName(d) : cOrdinaryName(d)));
|
|
1826
|
+
(this.valueReferences || (tagSite ? !cOrdinaryName(d) : cOrdinaryName(d))));
|
|
1529
1827
|
const seen = candidates.filter(d => d.file === file || visible.has(d.file));
|
|
1530
1828
|
if (seen.length > 0) return this.judge(seen);
|
|
1531
1829
|
return { verdict: 'unknown', reason: 'not-in-include-closure' };
|
|
1532
1830
|
}
|
|
1533
|
-
const spelling = qualifierText ? `${qualifierText}
|
|
1831
|
+
const spelling = qualifierText ? `${qualifierText}${qualifierText.endsWith('::') ? '' : '::'}${name}` : name;
|
|
1534
1832
|
if (qualifierText && this.def.enclosingType) {
|
|
1535
1833
|
// `Acc<T>::Member` / `Acc<double>::Member` against a member of a
|
|
1536
1834
|
// specialization: the template arguments pick the class.
|
|
@@ -1573,9 +1871,12 @@ class TypeReferenceResolver {
|
|
|
1573
1871
|
const site = this.cppSiteContext(file, line);
|
|
1574
1872
|
const context = { file, line, namespace: site.namespace,
|
|
1575
1873
|
className: qualifierText && qualifierText.startsWith('::') ? null : site.className };
|
|
1576
|
-
const found = resolveQualifier(this.index, context, spelling, visible, 0,
|
|
1874
|
+
const found = resolveQualifier(this.index, context, spelling, visible, 0,
|
|
1875
|
+
{ declarations: true, values: this.valueReferences === true,
|
|
1876
|
+
...(this.valueReferences && this.siteNode && { valueSite: {
|
|
1877
|
+
file: this.siteFile || file, index: this.siteNode.startIndex } }) });
|
|
1577
1878
|
if (found.kind === 'namespace') return { verdict: 'no', reason: 'namespace' };
|
|
1578
|
-
if (found.kind === 'type') {
|
|
1879
|
+
if (found.kind === 'type' || found.kind === 'value') {
|
|
1579
1880
|
const declarations = (found.declarations || []).filter(d => !functionLocal(d) || localInScope(d));
|
|
1580
1881
|
const siblings = this.cppSiblingMembers(declarations);
|
|
1581
1882
|
if (siblings) {
|
|
@@ -1689,7 +1990,7 @@ class TypeReferenceResolver {
|
|
|
1689
1990
|
* from file:line (its injected class name), or null. */
|
|
1690
1991
|
cppBaseNamed(file, line, className, name, depth) {
|
|
1691
1992
|
if (depth > 3) return null;
|
|
1692
|
-
const { includeClosure, resolveQualifier, effectiveNamespace } =
|
|
1993
|
+
const { includeClosure, resolveQualifier, effectiveNamespace } = lazyCppScopeModule();
|
|
1693
1994
|
const { splitParentList } = require('./graph-build');
|
|
1694
1995
|
const visible = includeClosure(this.index, file);
|
|
1695
1996
|
const simple = String(className).split('::').pop();
|
|
@@ -1726,73 +2027,9 @@ class TypeReferenceResolver {
|
|
|
1726
2027
|
* other lines outside every indexed symbol keep their real scope.
|
|
1727
2028
|
*/
|
|
1728
2029
|
cppSiteContext(file, line) {
|
|
1729
|
-
const
|
|
1730
|
-
? this.
|
|
1731
|
-
|
|
1732
|
-
if (!node) return { namespace: namespaceAt(this.index, file, line), className: this.enclosingClassAt(file, line) };
|
|
1733
|
-
const namespaces = [];
|
|
1734
|
-
const classes = [];
|
|
1735
|
-
let memberOf = null;
|
|
1736
|
-
let recovered = false;
|
|
1737
|
-
for (let current = node.parent; current; current = current.parent) {
|
|
1738
|
-
if (current.type === 'ERROR') recovered = true;
|
|
1739
|
-
if (current.type === 'namespace_definition') {
|
|
1740
|
-
const nameNode = current.childForFieldName('name');
|
|
1741
|
-
if (nameNode) namespaces.unshift(textOf(nameNode));
|
|
1742
|
-
} else if (C_TYPE_SPECIFIERS.has(current.type)) {
|
|
1743
|
-
const body = current.childForFieldName('body');
|
|
1744
|
-
const nameNode = current.childForFieldName('name');
|
|
1745
|
-
if (body && contains(body, node) && nameNode) {
|
|
1746
|
-
// Specializations keep their arguments (`Acc<double>`), as
|
|
1747
|
-
// the index names them; an out-of-class nested definition
|
|
1748
|
-
// (`struct List<K>::Node`) is in its owner's scope too
|
|
1749
|
-
// (fix #396).
|
|
1750
|
-
const segments = textOf(nameNode).split('::');
|
|
1751
|
-
classes.unshift(segments.slice(-2).join('::'));
|
|
1752
|
-
}
|
|
1753
|
-
} else if (current.type === 'function_definition' && !memberOf && classes.length === 0) {
|
|
1754
|
-
let declarator = current.childForFieldName('declarator');
|
|
1755
|
-
while (declarator && declarator.type !== 'function_declarator' && declarator.childForFieldName?.('declarator')) {
|
|
1756
|
-
declarator = declarator.childForFieldName('declarator');
|
|
1757
|
-
}
|
|
1758
|
-
const qualified = declarator?.childForFieldName?.('declarator');
|
|
1759
|
-
if (qualified?.type === 'qualified_identifier') {
|
|
1760
|
-
const scope = qualified.childForFieldName('scope');
|
|
1761
|
-
// `List<K>::Node::f`: Node's scope, then List's (fix #396).
|
|
1762
|
-
if (scope) memberOf = qualifiedScopeText(qualified) || textOf(scope).split('::').pop();
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
// A class body the raw tree lost (macro-heavy headers, conditional
|
|
1767
|
-
// members): the indexed nested classes around the line (parsed from
|
|
1768
|
-
// the recovered source) still name their owners (fix #396: `union
|
|
1769
|
-
// Data` inside `GenericValue`).
|
|
1770
|
-
if (classes.length > 0) {
|
|
1771
|
-
const symbols = this.index.files.get(file)?.symbols || [];
|
|
1772
|
-
const around = name => symbols.find(s => s.name === name && TYPE_DECL_KINDS.has(s.type) &&
|
|
1773
|
-
s.startLine <= line && line <= s.endLine);
|
|
1774
|
-
for (let owner = around(classes[0].split('::').pop())?.enclosingType, depth = 0;
|
|
1775
|
-
owner && depth < 8 && !classes.includes(owner); depth++) {
|
|
1776
|
-
classes.unshift(owner);
|
|
1777
|
-
owner = around(owner)?.enclosingType;
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
const prefix = macroNamespacePrefixAt(this.index, file, line);
|
|
1781
|
-
const own = namespaces.join('::');
|
|
1782
|
-
let namespace = prefix && own ? `${prefix}::${own}` : prefix || own;
|
|
1783
|
-
let className = classes.length > 0 ? classes.join('::') : memberOf;
|
|
1784
|
-
// Macro-heavy code can break the raw tree so that an enclosing
|
|
1785
|
-
// namespace block closes early; the indexed declarations around the
|
|
1786
|
-
// line (parsed from the recovered source) still carry it.
|
|
1787
|
-
const indexed = namespaceAt(this.index, file, line);
|
|
1788
|
-
if (indexed && indexed !== namespace && (namespace === '' || own === '' ||
|
|
1789
|
-
indexed.endsWith(`::${own}`) || indexed.startsWith(`${namespace}::`) ||
|
|
1790
|
-
(recovered && indexed.startsWith(namespace)))) namespace = indexed;
|
|
1791
|
-
// Out-of-line member bodies: the index (parsed from the recovered
|
|
1792
|
-
// source) knows their class even when a macro before the return type
|
|
1793
|
-
// broke the literal head; the head's own qualifier is the fallback.
|
|
1794
|
-
if (classes.length === 0) className = this.enclosingClassAt(file, line) || memberOf;
|
|
1795
|
-
return { namespace, className };
|
|
2030
|
+
const syntax = this.siteFile === file && this.siteContextSyntax?.line === line
|
|
2031
|
+
? this.siteContextSyntax.syntax : null;
|
|
2032
|
+
return cppSiteContextOf(this.index, this.siteNode, this.siteFile, file, line, syntax);
|
|
1796
2033
|
}
|
|
1797
2034
|
|
|
1798
2035
|
// ── Qualified names ──
|
|
@@ -1847,6 +2084,21 @@ class TypeReferenceResolver {
|
|
|
1847
2084
|
return { verdict: 'no', reason: 'member' };
|
|
1848
2085
|
}
|
|
1849
2086
|
case 'go': {
|
|
2087
|
+
// A local declared before the selector hides the package
|
|
2088
|
+
// name in its block (fix #420: `conf := conf.Default` then
|
|
2089
|
+
// `conf.Limit` reads a field of the local).
|
|
2090
|
+
if (qualifierNode?.type === 'identifier') {
|
|
2091
|
+
if (!this._goScopes) this._goScopes = new WeakMap();
|
|
2092
|
+
let memo = this._goScopes.get(qualifierNode.tree);
|
|
2093
|
+
if (!memo) { memo = new Map(); this._goScopes.set(qualifierNode.tree, memo); }
|
|
2094
|
+
const { referenceScope } = require('../languages/lexical-scope');
|
|
2095
|
+
const scope = referenceScope(qualifierNode, 'go', memo, text);
|
|
2096
|
+
if (scope === 'unknown') return { verdict: 'unknown', reason: 'qualifier-scope' };
|
|
2097
|
+
if (scope && scope !== 'module' && scope !== 'none') {
|
|
2098
|
+
if (tok.role === 'member') return this.goFieldVerdict(this.siteNode, file);
|
|
2099
|
+
return { verdict: 'no', reason: 'local-binding' };
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
1850
2102
|
const binding = (entry.importBindings || []).find(b => (b.alias || b.name) === text && b.module);
|
|
1851
2103
|
if (!binding) {
|
|
1852
2104
|
if (tok.role === 'member') return this.goFieldVerdict(this.siteNode, file);
|
|
@@ -1941,21 +2193,9 @@ class TypeReferenceResolver {
|
|
|
1941
2193
|
return pythonModulePath(this.index, file, receiver, this.memo);
|
|
1942
2194
|
}
|
|
1943
2195
|
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
const packages = new Set();
|
|
1948
|
-
for (const entry of this.index.files.values()) {
|
|
1949
|
-
if (entry.language !== 'java') continue;
|
|
1950
|
-
for (const s of entry.symbols || []) {
|
|
1951
|
-
if (!s.namespace) continue;
|
|
1952
|
-
const parts = s.namespace.split('.');
|
|
1953
|
-
for (let i = 1; i <= parts.length; i++) packages.add(parts.slice(0, i).join('.'));
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
this.memo.set(key, packages);
|
|
1957
|
-
}
|
|
1958
|
-
return this.memo.get(key);
|
|
2196
|
+
/** Does the project declare Java package `name` or one below it? */
|
|
2197
|
+
javaPackageDeclared(name) {
|
|
2198
|
+
return projectHas(this.index, 'java-package-prefixes', name, javaPackagePrefixes);
|
|
1959
2199
|
}
|
|
1960
2200
|
|
|
1961
2201
|
resolveJavaQualified(file, line, text, tok) {
|
|
@@ -1966,7 +2206,7 @@ class TypeReferenceResolver {
|
|
|
1966
2206
|
return prefix === qualifier;
|
|
1967
2207
|
});
|
|
1968
2208
|
if (byFqnPrefix.length > 0) return this.judge(byFqnPrefix);
|
|
1969
|
-
if (this.
|
|
2209
|
+
if (this.javaPackageDeclared(qualifier)) return { verdict: 'no', reason: 'other-package' };
|
|
1970
2210
|
const last = qualifier.split('.').pop();
|
|
1971
2211
|
const nested = candidates.filter(d => d.enclosingType === last);
|
|
1972
2212
|
if (nested.length > 0) {
|
|
@@ -1984,25 +2224,17 @@ class TypeReferenceResolver {
|
|
|
1984
2224
|
return { verdict: 'unknown', reason: 'qualified-name' };
|
|
1985
2225
|
}
|
|
1986
2226
|
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
const namespaces = new Set();
|
|
1991
|
-
for (const entry of this.index.files.values()) {
|
|
1992
|
-
if (entry.language !== 'csharp') continue;
|
|
1993
|
-
for (const s of entry.symbols || []) {
|
|
1994
|
-
if (!s.namespace) continue;
|
|
1995
|
-
const parts = s.namespace.split('.');
|
|
1996
|
-
for (let i = 1; i <= parts.length; i++) namespaces.add(parts.slice(0, i).join('.'));
|
|
1997
|
-
}
|
|
1998
|
-
}
|
|
1999
|
-
this.memo.set(key, namespaces);
|
|
2000
|
-
}
|
|
2001
|
-
return this.memo.get(key);
|
|
2227
|
+
/** Does the project declare C# namespace `name` or one below it? */
|
|
2228
|
+
csharpNamespaceDeclared(name) {
|
|
2229
|
+
return projectHas(this.index, 'cs-namespace-prefixes', name, csharpNamespacePrefixes);
|
|
2002
2230
|
}
|
|
2003
2231
|
|
|
2004
2232
|
resolveCSharpQualified(file, line, text, tok) {
|
|
2005
|
-
|
|
2233
|
+
// A constructed containing type has an arity at every owner
|
|
2234
|
+
// segment. Erasing those arguments would merge Outer.Inner with
|
|
2235
|
+
// Outer<T>.Inner. Until that owner chain is proven, keep the site
|
|
2236
|
+
// visible without supplying a type-identity witness.
|
|
2237
|
+
if (text.includes('<')) return { verdict: 'unknown', reason: 'generic-containing-type' };
|
|
2006
2238
|
// An `extern alias` qualifier (`Old::Lib.Core.Widget`, fix #401)
|
|
2007
2239
|
// names an assembly reference: a project namespace of that spelling
|
|
2008
2240
|
// may or may not be the assembly's.
|
|
@@ -2014,26 +2246,39 @@ class TypeReferenceResolver {
|
|
|
2014
2246
|
return spelled ? { verdict: 'unknown', reason: 'extern-alias' } : { verdict: 'no', reason: 'external-namespace' };
|
|
2015
2247
|
}
|
|
2016
2248
|
let qualifier = text.replace(/<.*?>/g, '').replace(/^global::/, '');
|
|
2017
|
-
// A using alias names the qualifier's head.
|
|
2249
|
+
// A using alias names the qualifier's head in this namespace body.
|
|
2018
2250
|
const head = qualifier.split('.')[0];
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2021
|
-
|
|
2251
|
+
const { csharpUsings } = require('./type-denotation');
|
|
2252
|
+
const namespace = this.csharpSiteNamespace(file, line);
|
|
2253
|
+
if (namespace === undefined) return { verdict: 'unknown', reason: 'unknown-namespace' };
|
|
2254
|
+
const scope = csharpUsings(this.index, file, namespace, this.siteNode);
|
|
2255
|
+
const global = text.startsWith('global::');
|
|
2022
2256
|
const chain = [];
|
|
2023
|
-
for (let parts =
|
|
2257
|
+
for (let parts = !global && namespace ? namespace.split('.') : []; parts.length > 0; parts = parts.slice(0, -1)) {
|
|
2024
2258
|
chain.push(parts.join('.'));
|
|
2025
2259
|
}
|
|
2026
2260
|
chain.push('');
|
|
2027
2261
|
const candidates = this.all.filter(d => d.name === this.name);
|
|
2028
|
-
const namespaces = this.csharpNamespaces();
|
|
2029
2262
|
for (const ns of chain) {
|
|
2030
2263
|
const full = ns ? `${ns}.${qualifier}` : qualifier;
|
|
2031
2264
|
const hits = candidates.filter(d => {
|
|
2032
2265
|
const prefix = `${d.namespace || ''}${d.enclosingType ? (d.namespace ? '.' : '') + d.enclosingType : ''}`;
|
|
2033
2266
|
return prefix === full;
|
|
2034
2267
|
});
|
|
2268
|
+
const alias = !global && scope.aliasScopes?.get(head) === ns && scope.aliases.get(head);
|
|
2269
|
+
if (alias && (hits.length || scope.ambiguousAliases.has(head))) {
|
|
2270
|
+
return { verdict: 'unknown', reason: 'ambiguous-type' };
|
|
2271
|
+
}
|
|
2035
2272
|
if (hits.length > 0) return this.judge(hits);
|
|
2036
|
-
if (
|
|
2273
|
+
if (this.csharpNamespaceDeclared(full)) return { verdict: 'no', reason: 'other-namespace' };
|
|
2274
|
+
if (alias) {
|
|
2275
|
+
if (scope.aliasSyntax.get(head)?.includes('<')) {
|
|
2276
|
+
return { verdict: 'unknown', reason: 'generic-alias-owner' };
|
|
2277
|
+
}
|
|
2278
|
+
const expanded = [alias, ...qualifier.split('.').slice(1)].join('.');
|
|
2279
|
+
return this.resolveCSharpQualified(file, line, 'global::' + expanded, tok);
|
|
2280
|
+
}
|
|
2281
|
+
if (this.csharpNamespaceDeclared(ns ? `${ns}.${head}` : head)) return { verdict: 'no', reason: 'other-namespace' };
|
|
2037
2282
|
}
|
|
2038
2283
|
const last = qualifier.split('.').pop();
|
|
2039
2284
|
if (candidates.some(d => d.enclosingType === last)) {
|
|
@@ -2198,8 +2443,7 @@ const PY_ANNOTATION_STOP = new Set(['block', 'module', 'class_definition', 'func
|
|
|
2198
2443
|
* Decide every name token on the ground lines of a type rename.
|
|
2199
2444
|
* @returns {{ files: Map<string, {relativePath, lines: Map<number, {edits: number[], reviews: Array<{column, reason}>, skips: number, text: boolean, tokens: number}>}> }}
|
|
2200
2445
|
*/
|
|
2201
|
-
function typeReferenceSites(index, def, name, groundSet) {
|
|
2202
|
-
const resolver = new TypeReferenceResolver(index, def, name);
|
|
2446
|
+
function typeReferenceSites(index, def, name, groundSet, resolver = new TypeReferenceResolver(index, def, name)) {
|
|
2203
2447
|
const files = new Map();
|
|
2204
2448
|
const perFile = groundSet?.perFile || new Map();
|
|
2205
2449
|
const sortedFiles = [...perFile.keys()].sort();
|
|
@@ -2246,6 +2490,8 @@ function scanFile(index, resolver, file, entry, language, lineNumbers) {
|
|
|
2246
2490
|
const name = resolver.name;
|
|
2247
2491
|
let content;
|
|
2248
2492
|
try { content = index._readFile(file); } catch { return null; }
|
|
2493
|
+
const local = resolver.readLocalFile?.(file, entry, language, lineNumbers, content);
|
|
2494
|
+
if (local) return local;
|
|
2249
2495
|
let htmlView = null;
|
|
2250
2496
|
if (language === 'html') {
|
|
2251
2497
|
htmlView = htmlScriptView(content, name);
|
|
@@ -2254,7 +2500,8 @@ function scanFile(index, resolver, file, entry, language, lineNumbers) {
|
|
|
2254
2500
|
const parser = getParser(language);
|
|
2255
2501
|
if (!parser) return null;
|
|
2256
2502
|
const tree = htmlView ? safeParse(parser, htmlView.script)
|
|
2257
|
-
:
|
|
2503
|
+
: (resolver.valueReferences && require('./query-bodies').localQueryTree(index, file, entry, language, name, content)) ||
|
|
2504
|
+
index._getParsedTree?.(file, content, language) || safeParse(parser, content);
|
|
2258
2505
|
const lines = new Map();
|
|
2259
2506
|
const lineOf = row => {
|
|
2260
2507
|
let slot = lines.get(row + 1);
|
|
@@ -2299,15 +2546,20 @@ function scanFile(index, resolver, file, entry, language, lineNumbers) {
|
|
|
2299
2546
|
};
|
|
2300
2547
|
for (const at of occurrences) {
|
|
2301
2548
|
let node = root.descendantForIndex(at, at + name.length);
|
|
2549
|
+
// An extra (a comment) inside an aliased rule can carry the
|
|
2550
|
+
// alias as its type, depending on which traversal first wrapped
|
|
2551
|
+
// the node (fix #420: `{ // x }` read as `interface_body` after a
|
|
2552
|
+
// children walk, as `comment` otherwise): its grammar type is
|
|
2553
|
+
// the stable one.
|
|
2554
|
+
const type = node && (node.isExtra ? node.grammarType || node.type : node.type);
|
|
2302
2555
|
// The smallest node holding the text: a token, a text or macro
|
|
2303
2556
|
// leaf, or (between tokens) a container the walk would not read.
|
|
2304
|
-
|
|
2305
|
-
|
|
2557
|
+
if (node && !TOKEN_TYPES.has(type) && !TEXT_NODE.test(type) &&
|
|
2558
|
+
type !== 'preproc_arg' && !(type === 'ERROR' && node.namedChildCount === 0)) {
|
|
2306
2559
|
node = null;
|
|
2307
2560
|
}
|
|
2308
2561
|
if (!node || seen.has(node.id) || !overlaps(node)) continue;
|
|
2309
2562
|
seen.add(node.id);
|
|
2310
|
-
const type = node.type;
|
|
2311
2563
|
if (TOKEN_TYPES.has(type)) {
|
|
2312
2564
|
const length = node.endIndex - node.startIndex;
|
|
2313
2565
|
if ((length === name.length || length === nameBytes) && node.text === name) found.push(node);
|
|
@@ -2321,9 +2573,10 @@ function scanFile(index, resolver, file, entry, language, lineNumbers) {
|
|
|
2321
2573
|
// code, read as tokens of their own).
|
|
2322
2574
|
if (node.namedChildCount !== 0 && !type.includes('comment')) continue;
|
|
2323
2575
|
mark(node, 'text');
|
|
2324
|
-
if (resolver.family === 'csharp' && type === 'comment' && marks) docComments.push(node);
|
|
2576
|
+
if (!resolver.valueReferences && resolver.family === 'csharp' && type === 'comment' && marks) docComments.push(node);
|
|
2325
2577
|
if (resolver.family === 'python' && type === 'string_content' &&
|
|
2326
|
-
(
|
|
2578
|
+
((!resolver.valueReferences && pythonTypeExpressionString(node, entry)) ||
|
|
2579
|
+
pythonDunderAllEntry(node, name))) found.push(node);
|
|
2327
2580
|
}
|
|
2328
2581
|
return found;
|
|
2329
2582
|
};
|
|
@@ -2336,8 +2589,14 @@ function scanFile(index, resolver, file, entry, language, lineNumbers) {
|
|
|
2336
2589
|
// a persisted blank reads through the recovered tree too.
|
|
2337
2590
|
const persistedBlanks = resolver.family === 'c' && Array.isArray(entry.recoveryBlanks) &&
|
|
2338
2591
|
entry.recoveryBlanks.length > 0 ? entry.recoveryBlanks : null;
|
|
2592
|
+
// A decoration can make a record look like a clean function whose
|
|
2593
|
+
// fields look like local declarations. Recover the pinned value's
|
|
2594
|
+
// enclosing header too, using the same blanks the index verified.
|
|
2595
|
+
const valueDeclarationHeader = resolver.valueReferences && persistedBlanks && resolver.def.file === file &&
|
|
2596
|
+
tokens.some(node => node.startPosition.row + 1 === (resolver.def.nameLine || resolver.def.startLine) &&
|
|
2597
|
+
enclosingHeaderHoldsBlank(node, persistedBlanks));
|
|
2339
2598
|
if (resolver.family === 'c' && ((tree.rootNode.hasError && tokens.some(statementHasError)) ||
|
|
2340
|
-
(persistedBlanks && tokens.some(node => statementHoldsBlank(node, persistedBlanks))))) {
|
|
2599
|
+
(persistedBlanks && tokens.some(node => statementHoldsBlank(node, persistedBlanks))) || valueDeclarationHeader)) {
|
|
2341
2600
|
// Only when the literal parse breaks around a token: the recovery
|
|
2342
2601
|
// re-parses the file.
|
|
2343
2602
|
let module;
|
|
@@ -2393,6 +2652,7 @@ function scanFile(index, resolver, file, entry, language, lineNumbers) {
|
|
|
2393
2652
|
.map(t => ({ node: t.node, scope: bindingScope(t.node, resolver.family, t.role), role: t.role }));
|
|
2394
2653
|
const typeParams = classified.filter(t => t.role.role === 'skip' && t.role.typeParam)
|
|
2395
2654
|
.map(t => ({ node: t.node, scope: typeParamScope(t.node) }));
|
|
2655
|
+
let fileLocalOnly = true;
|
|
2396
2656
|
for (const token of classified) {
|
|
2397
2657
|
const row = token.node.startPosition.row;
|
|
2398
2658
|
const slot = lineOf(row);
|
|
@@ -2404,24 +2664,97 @@ function scanFile(index, resolver, file, entry, language, lineNumbers) {
|
|
|
2404
2664
|
resolver.siteNode = token.node;
|
|
2405
2665
|
resolver.siteFile = file;
|
|
2406
2666
|
// A position no rule decides is a review item, never an edit or a skip.
|
|
2407
|
-
const decision =
|
|
2667
|
+
const decision = (resolver.decideToken
|
|
2668
|
+
? resolver.decideToken(file, entry, token, bindings, typeParams)
|
|
2669
|
+
: decide(resolver, file, entry, token, bindings, typeParams)) ||
|
|
2408
2670
|
{ verdict: 'unknown', reason: 'unmodelled-position' };
|
|
2671
|
+
if (!decision.fileLocalOnly) fileLocalOnly = false;
|
|
2409
2672
|
resolver.siteNode = null;
|
|
2410
2673
|
// An `__all__` entry is a code reference: once edited it is no
|
|
2411
2674
|
// longer a text-only review item.
|
|
2412
2675
|
if (token.role.role === 'dunder-all') slot.stringAnnotation = true;
|
|
2413
|
-
|
|
2676
|
+
// The impact band (fix #428) reads each token's syntactic role and
|
|
2677
|
+
// why a token names something else; a rename reads only verdicts.
|
|
2678
|
+
if (resolver.collectRoles) {
|
|
2679
|
+
// A Python annotation is an expression in a type position.
|
|
2680
|
+
const role = token.role.role === 'value' && resolver.family === 'python' &&
|
|
2681
|
+
ancestor(token.node, PY_ANNOTATION, PY_ANNOTATION_STOP) ? 'type' : token.role.role;
|
|
2682
|
+
(slot.roles ||= new Map()).set(token.node.startPosition.column, role);
|
|
2683
|
+
if (constructionHead(token.node)) (slot.constructionColumns ||= []).push(token.node.startPosition.column);
|
|
2684
|
+
}
|
|
2685
|
+
if (decision.verdict === 'yes') {
|
|
2686
|
+
slot.edits.push(token.node.startPosition.column);
|
|
2687
|
+
// A binding renamed by aliasing its import (`import x as y`).
|
|
2688
|
+
if (decision.importAlias) (slot.importAliases ||= []).push(token.node.startPosition.column);
|
|
2689
|
+
}
|
|
2414
2690
|
else if (decision.verdict === 'unknown') {
|
|
2415
2691
|
slot.reviews.push({ column: token.node.startPosition.column, reason: decision.reason || 'unresolved' });
|
|
2416
2692
|
} else {
|
|
2417
|
-
slot.
|
|
2418
|
-
slot.skipColumns.push(token.node.startPosition.column);
|
|
2693
|
+
skipToken(resolver, slot, token.node.startPosition.column, decision.reason);
|
|
2419
2694
|
}
|
|
2420
2695
|
}
|
|
2421
2696
|
for (const comment of docComments) decideDocCrefs(resolver, file, comment, lineOf(comment.startPosition.row));
|
|
2697
|
+
if (resolver.family === 'c' && !resolver.valueReferences) decideReplacementListCasts(resolver, file, lines);
|
|
2698
|
+
resolver.rememberLocalFile?.(file, lines, fileLocalOnly);
|
|
2422
2699
|
return lines;
|
|
2423
2700
|
}
|
|
2424
2701
|
|
|
2702
|
+
/** A token that names something else: counted, and why when asked. */
|
|
2703
|
+
function skipToken(resolver, slot, column, reason) {
|
|
2704
|
+
slot.skips++;
|
|
2705
|
+
slot.skipColumns.push(column);
|
|
2706
|
+
if (resolver.collectRoles) (slot.skipReasons ||= new Map()).set(column, reason || 'other-declaration');
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
// Expressions whose head names the constructed type (fix #428): `S { .. }`
|
|
2710
|
+
// (Rust struct expressions) and `S(..)` (tuple structs, functional casts).
|
|
2711
|
+
const CONSTRUCTION_HEADS = new Map([
|
|
2712
|
+
['struct_expression', 'name'], ['call_expression', 'function'],
|
|
2713
|
+
]);
|
|
2714
|
+
const HEAD_WRAPPERS = new Set(['scoped_type_identifier', 'scoped_identifier', 'generic_type', 'generic_function']);
|
|
2715
|
+
|
|
2716
|
+
/** Is this token the head of a construction expression? */
|
|
2717
|
+
function constructionHead(node) {
|
|
2718
|
+
let current = node;
|
|
2719
|
+
for (let depth = 0; depth < 4 && current.parent; depth++) {
|
|
2720
|
+
const parent = current.parent;
|
|
2721
|
+
const field = CONSTRUCTION_HEADS.get(parent.type);
|
|
2722
|
+
if (field) return sameNode(parent.childForFieldName(field), current);
|
|
2723
|
+
if (!HEAD_WRAPPERS.has(parent.type)) return false;
|
|
2724
|
+
// A path's last segment names the type; a qualifier does not.
|
|
2725
|
+
const name = parent.childForFieldName('name') || parent.childForFieldName('type');
|
|
2726
|
+
if (name && !sameNode(name, current)) return false;
|
|
2727
|
+
current = parent;
|
|
2728
|
+
}
|
|
2729
|
+
return false;
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
/**
|
|
2733
|
+
* A cast spelled in a C/C++ replacement list (`#define D(t, p) t[(U16)(p)]`,
|
|
2734
|
+
* fix #427): the name is the type its file's lookup finds where the macro is
|
|
2735
|
+
* defined, so a line whose every occurrence of the name is such a cast to
|
|
2736
|
+
* the renamed type is an edit; any other replacement-list text stays a
|
|
2737
|
+
* review item.
|
|
2738
|
+
*/
|
|
2739
|
+
function decideReplacementListCasts(resolver, file, lines) {
|
|
2740
|
+
const { getCachedCalls, _recordIsNoCall } = require('./callers');
|
|
2741
|
+
let records = null;
|
|
2742
|
+
const fileLines = resolver.index._getFileLines(file);
|
|
2743
|
+
const word = new RegExp(`(?<![A-Za-z0-9_$])${resolver.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![A-Za-z0-9_$])`, 'g');
|
|
2744
|
+
for (const [line, slot] of lines) {
|
|
2745
|
+
if (!slot.macroBody || slot.tokens > 0 || slot.reviews.length > 0) continue;
|
|
2746
|
+
records ||= getCachedCalls(resolver.index, file) || [];
|
|
2747
|
+
const casts = records.filter(call => call.line === line && call.name === resolver.name &&
|
|
2748
|
+
call.parenthesizedCallee && call.inMacroBody && Number.isInteger(call.column));
|
|
2749
|
+
if (casts.length === 0) continue;
|
|
2750
|
+
const occurrences = (String(fileLines[line - 1] || '').match(word) || []).length;
|
|
2751
|
+
if (occurrences !== casts.length || !casts.every(call => _recordIsNoCall(resolver.index, file, call))) continue;
|
|
2752
|
+
if (resolver.resolveName(file, line)?.verdict !== 'yes') continue;
|
|
2753
|
+
slot.tokens += casts.length;
|
|
2754
|
+
slot.edits.push(...casts.map(call => call.column));
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2425
2758
|
/**
|
|
2426
2759
|
* C# XML documentation `cref` attributes are references the compiler
|
|
2427
2760
|
* resolves (CS1574 when documentation is generated): each spelling of the
|
|
@@ -2463,20 +2796,20 @@ function decideDocCrefs(resolver, file, comment, slot) {
|
|
|
2463
2796
|
handled = true;
|
|
2464
2797
|
if (verdict.verdict === 'yes') slot.edits.push(column);
|
|
2465
2798
|
else if (verdict.verdict === 'unknown') slot.reviews.push({ column, reason: 'doc-cref' });
|
|
2466
|
-
else
|
|
2467
|
-
|
|
2468
|
-
slot.skipColumns.push(column);
|
|
2469
|
-
}
|
|
2799
|
+
else skipToken(resolver, slot, column, verdict.reason);
|
|
2800
|
+
if (resolver.collectRoles) (slot.roles ||= new Map()).set(column, 'doc-cref');
|
|
2470
2801
|
}
|
|
2471
2802
|
}
|
|
2472
2803
|
if (handled) slot.stringAnnotation = true;
|
|
2473
2804
|
}
|
|
2474
2805
|
|
|
2475
|
-
function shadowOf(token, bindings, space, family) {
|
|
2806
|
+
function shadowOf(token, bindings, space, family, pinScope = null) {
|
|
2476
2807
|
let verdict = null;
|
|
2477
2808
|
for (const binding of bindings) {
|
|
2478
2809
|
if (sameNode(binding.node, token.node)) continue;
|
|
2479
2810
|
if (!binding.scope || !contains(binding.scope, token.node)) continue;
|
|
2811
|
+
// Another binding of the pinned class's own variable (fix #424).
|
|
2812
|
+
if (pinScope && sameNode(binding.scope, pinScope)) continue;
|
|
2480
2813
|
// Type positions are a separate namespace outside Python/JS values.
|
|
2481
2814
|
if (space === 'type' && family !== 'python') continue;
|
|
2482
2815
|
if (binding.role.patternBinding) return 'unknown';
|
|
@@ -2517,20 +2850,6 @@ function statementHasError(node) {
|
|
|
2517
2850
|
return false;
|
|
2518
2851
|
}
|
|
2519
2852
|
|
|
2520
|
-
/** The class path a qualified declarator's scope names, without its
|
|
2521
|
-
* leading namespace-looking segments: `ns::List<K>::Node::f` -> the scope
|
|
2522
|
-
* segments `List<K>::Node` (the last two at most). */
|
|
2523
|
-
function qualifiedScopeText(qualified) {
|
|
2524
|
-
const scopes = [];
|
|
2525
|
-
let current = qualified;
|
|
2526
|
-
while (current?.type === 'qualified_identifier') {
|
|
2527
|
-
const scope = current.childForFieldName('scope');
|
|
2528
|
-
if (scope) scopes.push(textOf(scope));
|
|
2529
|
-
current = current.childForFieldName('name');
|
|
2530
|
-
}
|
|
2531
|
-
return scopes.length > 0 ? scopes.slice(-2).join('::') : null;
|
|
2532
|
-
}
|
|
2533
|
-
|
|
2534
2853
|
/** Does a persisted recovery blank fall inside the declaration or
|
|
2535
2854
|
* statement holding this token? (`blanks`: sorted [start, end] ranges) */
|
|
2536
2855
|
function statementHoldsBlank(node, blanks) {
|
|
@@ -2539,8 +2858,19 @@ function statementHoldsBlank(node, blanks) {
|
|
|
2539
2858
|
if (/(declaration|definition|statement)$/.test(current.type)) { statement = current; break; }
|
|
2540
2859
|
}
|
|
2541
2860
|
if (!statement) return false;
|
|
2542
|
-
|
|
2543
|
-
|
|
2861
|
+
return rangeHoldsBlank(statement.startIndex, statement.endIndex, blanks);
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
function enclosingHeaderHoldsBlank(node, blanks) {
|
|
2865
|
+
for (let owner = node.parent; owner; owner = owner.parent) {
|
|
2866
|
+
const body = owner.childForFieldName('body');
|
|
2867
|
+
if (body && body.startIndex <= node.startIndex &&
|
|
2868
|
+
rangeHoldsBlank(owner.startIndex, body.startIndex, blanks)) return true;
|
|
2869
|
+
}
|
|
2870
|
+
return false;
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
function rangeHoldsBlank(start, end, blanks) {
|
|
2544
2874
|
let lo = 0;
|
|
2545
2875
|
let hi = blanks.length;
|
|
2546
2876
|
while (lo < hi) {
|
|
@@ -2589,7 +2919,7 @@ function macroArgumentOtherUse(resolver, file, node) {
|
|
|
2589
2919
|
: (list.namedChildren || []).filter(child => !child.type.endsWith('comment'));
|
|
2590
2920
|
const index = args.findIndex(arg => sameNode(arg, argument));
|
|
2591
2921
|
if (index < 0 || argument.text !== node.text) return null;
|
|
2592
|
-
const { includeClosure } =
|
|
2922
|
+
const { includeClosure } = lazyCppScopeModule();
|
|
2593
2923
|
const visible = includeClosure(resolver.index, file);
|
|
2594
2924
|
const macros = (resolver.index.symbols.get(callee.text) || []).filter(d => d.type === 'macro' &&
|
|
2595
2925
|
d.functionLike && (d.file === file || visible.has(d.file)));
|
|
@@ -2661,12 +2991,17 @@ function decide(resolver, file, entry, token, bindings, typeParams) {
|
|
|
2661
2991
|
// the class its qualifier spells.
|
|
2662
2992
|
const spelled = cppPrefix(node);
|
|
2663
2993
|
if (spelled == null) return { verdict: 'unknown', reason: 'constructor-owner' };
|
|
2664
|
-
const
|
|
2665
|
-
|
|
2666
|
-
//
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2994
|
+
const { resolveQualifier, includeClosure } = lazyCppScopeModule();
|
|
2995
|
+
const site = resolver.cppSiteContext(file, line);
|
|
2996
|
+
// The qualifier may be an alias of the declaring class. Resolve
|
|
2997
|
+
// its type identity, rather than renaming the alias declaration.
|
|
2998
|
+
const owner = resolveQualifier(resolver.index, { file, line,
|
|
2999
|
+
namespace: site.namespace, className: site.className }, spelled,
|
|
3000
|
+
includeClosure(resolver.index, file));
|
|
3001
|
+
if (owner.kind !== 'type') return { verdict: 'unknown', reason: 'constructor-owner' };
|
|
3002
|
+
const verdict = resolver.judge(owner.classes);
|
|
3003
|
+
return verdict.verdict === 'yes' && !owner.visible
|
|
3004
|
+
? { verdict: 'unknown', reason: 'not-in-include-closure' } : verdict;
|
|
2670
3005
|
}
|
|
2671
3006
|
case 'meminit': {
|
|
2672
3007
|
// A member named like the type is initialized as a member.
|
|
@@ -2677,6 +3012,20 @@ function decide(resolver, file, entry, token, bindings, typeParams) {
|
|
|
2677
3012
|
return resolveUnqualifiedToken(resolver, file, token, bindings, typeParams, 'type');
|
|
2678
3013
|
}
|
|
2679
3014
|
case 'import': return decideImport(resolver, file, entry, token);
|
|
3015
|
+
case 'qualified-creation': {
|
|
3016
|
+
// Only a member class is created through an outer instance.
|
|
3017
|
+
const members = resolver.all.filter(d => d.name === resolver.name && d.enclosingType);
|
|
3018
|
+
if (members.length === 0) return { verdict: 'no', reason: 'not-member-type' };
|
|
3019
|
+
const outerType = javaExpressionTypeName(role.outer);
|
|
3020
|
+
const owner = outerType ? resolver.resolveTypeNamed(file, line, outerType) : 'unknown';
|
|
3021
|
+
if (owner === 'owner') return { verdict: 'yes' };
|
|
3022
|
+
// Another outer type may inherit the member class; only one that
|
|
3023
|
+
// declares its own member class of the name settles it.
|
|
3024
|
+
if (owner === 'other' && members.some(d => !resolver.inGroup(d) && d.enclosingType === outerType)) {
|
|
3025
|
+
return { verdict: 'no', reason: 'other-type' };
|
|
3026
|
+
}
|
|
3027
|
+
return { verdict: 'unknown', reason: 'qualified-creation' };
|
|
3028
|
+
}
|
|
2680
3029
|
case 'member':
|
|
2681
3030
|
return decideMember(resolver, file, token, bindings);
|
|
2682
3031
|
case 'gofield':
|
|
@@ -2689,7 +3038,8 @@ function decide(resolver, file, entry, token, bindings, typeParams) {
|
|
|
2689
3038
|
if (prefix) return resolver.resolveCFamily(file, line, resolver.name, prefix);
|
|
2690
3039
|
}
|
|
2691
3040
|
if (role.qualifier) {
|
|
2692
|
-
return resolver.resolveQualified(file, line, role.qualifier,
|
|
3041
|
+
return resolver.resolveQualified(file, line, role.qualifier,
|
|
3042
|
+
role.aliasQualified ? textOf(role.qualifier) + '::' : null, { role: role.role });
|
|
2693
3043
|
}
|
|
2694
3044
|
if (role.aliasQualified) return { verdict: 'unknown', reason: 'extern-alias' };
|
|
2695
3045
|
if (role.elaborated && family === 'c') {
|
|
@@ -2733,8 +3083,10 @@ function memberShadowOf(resolver, file, node) {
|
|
|
2733
3083
|
// members of the enclosing classes first: a method of that name (C#
|
|
2734
3084
|
// `static LogEvent LogEvent(..)`, a C++ member function) hides the
|
|
2735
3085
|
// type there (fix #393). A constructor is the class's own name.
|
|
2736
|
-
const
|
|
2737
|
-
|
|
3086
|
+
const invokedName = langTraits(resolver.index.files.get(file)?.language)?.isInvokedName;
|
|
3087
|
+
const invokedAt = invokedName ? invokedName(node, { unqualified: true }) : node.parent &&
|
|
3088
|
+
((node.parent.type === 'invocation_expression' || node.parent.type === 'call_expression') &&
|
|
3089
|
+
isField(node, 'function'));
|
|
2738
3090
|
if (invokedAt) {
|
|
2739
3091
|
const scopeNames = new Set(owners);
|
|
2740
3092
|
let level = [...owners];
|
|
@@ -2765,7 +3117,8 @@ function memberShadowOf(resolver, file, node) {
|
|
|
2765
3117
|
function resolveUnqualifiedToken(resolver, file, token, bindings, typeParams, space) {
|
|
2766
3118
|
const family = resolver.family;
|
|
2767
3119
|
if (space === 'value' || family === 'python' || family === 'js') {
|
|
2768
|
-
const shadow = shadowOf(token, bindings, space, family
|
|
3120
|
+
const shadow = shadowOf(token, bindings, space, family,
|
|
3121
|
+
file === resolver.def.file ? resolver.pythonPinScope(token.node.tree) : null);
|
|
2769
3122
|
if (shadow === 'no') return { verdict: 'no', reason: 'local-binding' };
|
|
2770
3123
|
if (shadow === 'unknown') return { verdict: 'unknown', reason: 'local-binding' };
|
|
2771
3124
|
if (shadow === 'module') {
|
|
@@ -2778,6 +3131,16 @@ function resolveUnqualifiedToken(resolver, file, token, bindings, typeParams, sp
|
|
|
2778
3131
|
return { verdict: 'unknown', reason: 'module-rebinding' };
|
|
2779
3132
|
}
|
|
2780
3133
|
}
|
|
3134
|
+
// C and C++ name lookup finds a variable or parameter declared before
|
|
3135
|
+
// the name in an enclosing block before any type (C: one ordinary
|
|
3136
|
+
// namespace; C++: the variable hides the class or alias): `ctx(out,
|
|
3137
|
+
// vargs)` after `auto& vargs = ..` passes the variable (fix #427).
|
|
3138
|
+
if (space === 'type' && family === 'c' && bindings.some(binding =>
|
|
3139
|
+
['variable', 'parameter'].includes(binding.role.why) && !sameNode(binding.node, token.node) &&
|
|
3140
|
+
binding.scope && binding.scope.type !== 'translation_unit' && contains(binding.scope, token.node) &&
|
|
3141
|
+
binding.node.startIndex < token.node.startIndex)) {
|
|
3142
|
+
return { verdict: 'no', reason: 'local-binding' };
|
|
3143
|
+
}
|
|
2781
3144
|
if (space === 'value') {
|
|
2782
3145
|
const member = memberShadowOf(resolver, file, token.node);
|
|
2783
3146
|
if (member) return member;
|
|
@@ -2808,6 +3171,9 @@ function declVerdict(resolver, file, declNode, role = {}, nameLine = null) {
|
|
|
2808
3171
|
// annotated or decorated declarations) is matched there first.
|
|
2809
3172
|
const named = nameLine != null ? resolver.fileTypes(file).filter(d => d.nameLine === nameLine && kindOk(d)) : [];
|
|
2810
3173
|
if (named.length > 0) return resolver.judge(named);
|
|
3174
|
+
const exact = resolver.fileTypes(file).filter(d => d.nameLine == null && kindOk(d) &&
|
|
3175
|
+
d.startLine === startLine && d.endLine === endLine);
|
|
3176
|
+
if (exact.length > 0) return resolver.judge(exact);
|
|
2811
3177
|
const defs = resolver.fileTypes(file).filter(d => d.nameLine == null && (d.endLine === endLine ||
|
|
2812
3178
|
(d.startLine <= startLine + 1 && d.startLine >= startLine - 3 &&
|
|
2813
3179
|
d.endLine >= endLine - 1 && d.endLine <= endLine + 1)) &&
|
|
@@ -3160,10 +3526,8 @@ function decideStringAnnotation(resolver, file, entry, node, slot) {
|
|
|
3160
3526
|
const column = node.startPosition.column + offset;
|
|
3161
3527
|
if (verdict.verdict === 'yes') slot.edits.push(column);
|
|
3162
3528
|
else if (verdict.verdict === 'unknown') slot.reviews.push({ column, reason: verdict.reason || 'string-annotation' });
|
|
3163
|
-
else
|
|
3164
|
-
|
|
3165
|
-
slot.skipColumns.push(column);
|
|
3166
|
-
}
|
|
3529
|
+
else skipToken(resolver, slot, column, verdict.reason);
|
|
3530
|
+
if (resolver.collectRoles) (slot.roles ||= new Map()).set(column, 'string-annotation');
|
|
3167
3531
|
}
|
|
3168
3532
|
slot.stringAnnotation = true;
|
|
3169
3533
|
}
|
|
@@ -3220,9 +3584,14 @@ function csharpAttributeShortSites(index, def, name) {
|
|
|
3220
3584
|
}
|
|
3221
3585
|
|
|
3222
3586
|
module.exports = {
|
|
3587
|
+
cppSiteContextOf,
|
|
3223
3588
|
csharpAttributeShortSites,
|
|
3224
3589
|
TYPE_RENAME_KINDS,
|
|
3225
3590
|
isTypeRenamePin,
|
|
3591
|
+
referenceRole: classify,
|
|
3592
|
+
referenceBindingScope: (node, language, role) => bindingScope(node, familyOf(language), role),
|
|
3593
|
+
referenceTypeParameterScope: typeParamScope,
|
|
3594
|
+
referenceImportVerdict: decideImport,
|
|
3226
3595
|
typeReferenceSites,
|
|
3227
3596
|
TypeReferenceResolver,
|
|
3228
3597
|
familyOf,
|