ucn 5.5.0 → 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 +41 -27
- package/cli/index.js +6 -1
- package/core/accessors.js +341 -54
- package/core/account.js +106 -31
- package/core/analysis.js +266 -127
- package/core/ast-analysis.js +12 -3
- package/core/brief.js +8 -4
- package/core/build-worker.js +2 -1
- package/core/cache.js +144 -6
- package/core/call-name-cache.js +134 -0
- package/core/callable-scope.js +21 -0
- package/core/callers.js +5111 -1024
- 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 +228 -114
- 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 +1202 -157
- package/core/rust-modules.js +21 -3
- 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 +157 -46
- 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 +548 -89
- package/languages/utils.js +67 -8
- package/mcp/server.js +8 -2
- package/package.json +2 -2
package/languages/go.js
CHANGED
|
@@ -108,7 +108,7 @@ function _processFunction(node, functions, processedRanges, lines) {
|
|
|
108
108
|
const child = node.namedChild(i);
|
|
109
109
|
if (child.type === 'identifier') names.push(child);
|
|
110
110
|
}
|
|
111
|
-
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
111
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
|
|
112
112
|
for (const nameNode of names) {
|
|
113
113
|
const isExported = /^[A-Z]/.test(nameNode.text);
|
|
114
114
|
functions.push({
|
|
@@ -117,6 +117,7 @@ function _processFunction(node, functions, processedRanges, lines) {
|
|
|
117
117
|
paramsStructured: parseStructuredParams(paramsNode, 'go'),
|
|
118
118
|
startLine,
|
|
119
119
|
endLine,
|
|
120
|
+
sourceSpan,
|
|
120
121
|
indent,
|
|
121
122
|
modifiers: isExported ? ['export'] : [],
|
|
122
123
|
isFunctionVariable: true,
|
|
@@ -135,7 +136,7 @@ function _processFunction(node, functions, processedRanges, lines) {
|
|
|
135
136
|
const paramsNode = node.childForFieldName('parameters');
|
|
136
137
|
|
|
137
138
|
if (nameNode) {
|
|
138
|
-
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
139
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
|
|
139
140
|
const returnType = extractReturnType(node);
|
|
140
141
|
const returnedFunctionResult = extractReturnedFunctionResult(node);
|
|
141
142
|
const docstring = extractGoDocstring(lines, startLine);
|
|
@@ -148,6 +149,7 @@ function _processFunction(node, functions, processedRanges, lines) {
|
|
|
148
149
|
paramsStructured: parseStructuredParams(paramsNode, 'go'),
|
|
149
150
|
startLine,
|
|
150
151
|
endLine,
|
|
152
|
+
sourceSpan,
|
|
151
153
|
indent,
|
|
152
154
|
modifiers: isExported ? ['export'] : [],
|
|
153
155
|
...(returnType && { returnType }),
|
|
@@ -169,7 +171,7 @@ function _processFunction(node, functions, processedRanges, lines) {
|
|
|
169
171
|
const receiverNode = node.childForFieldName('receiver');
|
|
170
172
|
|
|
171
173
|
if (nameNode) {
|
|
172
|
-
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
174
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
|
|
173
175
|
const receiver = extractReceiver(receiverNode);
|
|
174
176
|
const returnType = extractReturnType(node);
|
|
175
177
|
const returnedFunctionResult = extractReturnedFunctionResult(node);
|
|
@@ -182,6 +184,7 @@ function _processFunction(node, functions, processedRanges, lines) {
|
|
|
182
184
|
paramsStructured: parseStructuredParams(paramsNode, 'go'),
|
|
183
185
|
startLine,
|
|
184
186
|
endLine,
|
|
187
|
+
sourceSpan,
|
|
185
188
|
indent,
|
|
186
189
|
isMethod: true,
|
|
187
190
|
receiver,
|
|
@@ -342,7 +345,7 @@ function _processClass(node, types, processedRanges, lines) {
|
|
|
342
345
|
const typeNode = spec.childForFieldName('type');
|
|
343
346
|
|
|
344
347
|
if (nameNode && typeNode) {
|
|
345
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
348
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
346
349
|
const name = nameNode.text;
|
|
347
350
|
const docstring = extractGoDocstring(lines, startLine);
|
|
348
351
|
const typeParams = extractTypeParams(spec);
|
|
@@ -376,6 +379,7 @@ function _processClass(node, types, processedRanges, lines) {
|
|
|
376
379
|
name,
|
|
377
380
|
startLine,
|
|
378
381
|
endLine,
|
|
382
|
+
sourceSpan,
|
|
379
383
|
...(nameLine !== startLine && { nameLine }),
|
|
380
384
|
type: typeKind,
|
|
381
385
|
members,
|
|
@@ -406,12 +410,13 @@ function _processClass(node, types, processedRanges, lines) {
|
|
|
406
410
|
// type of another package (fix #378).
|
|
407
411
|
const aliasQualifier = aliased?.type === 'qualified_type'
|
|
408
412
|
? aliased.childForFieldName('package')?.text : null;
|
|
409
|
-
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
413
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
|
|
410
414
|
const nameLine = nameNode.startPosition.row + 1;
|
|
411
415
|
types.push({
|
|
412
416
|
name: nameNode.text,
|
|
413
417
|
startLine,
|
|
414
418
|
endLine,
|
|
419
|
+
sourceSpan,
|
|
415
420
|
...(nameLine !== startLine && { nameLine }),
|
|
416
421
|
type: 'type',
|
|
417
422
|
members: [],
|
|
@@ -467,6 +472,25 @@ function _blockHasIota(constDecl) {
|
|
|
467
472
|
* Returns true if node was matched, false otherwise
|
|
468
473
|
*/
|
|
469
474
|
function _processState(node, objects, lines) {
|
|
475
|
+
// Package variables and constants are bindings even when unexported.
|
|
476
|
+
// A grouped specification can declare several names with one value list.
|
|
477
|
+
if (['const_declaration', 'var_declaration'].includes(node.type) &&
|
|
478
|
+
node.parent?.type === 'source_file') {
|
|
479
|
+
const specs = node.namedChildren.flatMap(child => child.type === 'var_spec_list'
|
|
480
|
+
? child.namedChildren : [child]);
|
|
481
|
+
for (const spec of specs) {
|
|
482
|
+
if (!['const_spec', 'var_spec'].includes(spec.type)) continue;
|
|
483
|
+
for (let i = 0; i < spec.childCount; i++) {
|
|
484
|
+
if (spec.fieldNameForChild(i) !== 'name') continue;
|
|
485
|
+
const name = spec.child(i);
|
|
486
|
+
if (name.type !== 'identifier' || name.text === '_') continue;
|
|
487
|
+
objects.push({ name: name.text, startLine: name.startPosition.row + 1,
|
|
488
|
+
endLine: spec.endPosition.row + 1,
|
|
489
|
+
...(node.type === 'const_declaration' && { isConst: true }) });
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
470
494
|
if (node.type === 'const_declaration') {
|
|
471
495
|
const isIotaBlock = _blockHasIota(node);
|
|
472
496
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -478,14 +502,14 @@ function _processState(node, objects, lines) {
|
|
|
478
502
|
const name = nameNode.text;
|
|
479
503
|
|
|
480
504
|
if (valueNode && _isCompositeLiteral(valueNode) && GO_STATE_PATTERN.test(name)) {
|
|
481
|
-
const { startLine, endLine } = nodeToLocation(spec, lines);
|
|
482
|
-
objects.push({ name, startLine, endLine });
|
|
505
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(spec, lines);
|
|
506
|
+
objects.push({ name, startLine, endLine, sourceSpan });
|
|
483
507
|
} else if (isIotaBlock && /^[A-Z]/.test(name)) {
|
|
484
|
-
const { startLine, endLine } = nodeToLocation(spec, lines);
|
|
485
|
-
objects.push({ name, startLine, endLine, isConst: true });
|
|
508
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(spec, lines);
|
|
509
|
+
objects.push({ name, startLine, endLine, sourceSpan, isConst: true });
|
|
486
510
|
} else if (_isGoExportedName(name)) {
|
|
487
|
-
const { startLine, endLine } = nodeToLocation(spec, lines);
|
|
488
|
-
objects.push({ name, startLine, endLine, isConst: true });
|
|
511
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(spec, lines);
|
|
512
|
+
objects.push({ name, startLine, endLine, sourceSpan, isConst: true });
|
|
489
513
|
}
|
|
490
514
|
}
|
|
491
515
|
}
|
|
@@ -502,11 +526,11 @@ function _processState(node, objects, lines) {
|
|
|
502
526
|
if (nameNode) {
|
|
503
527
|
const name = nameNode.text;
|
|
504
528
|
if (valueNode && _isCompositeLiteral(valueNode) && GO_STATE_PATTERN.test(name)) {
|
|
505
|
-
const { startLine, endLine } = nodeToLocation(spec, lines);
|
|
506
|
-
objects.push({ name, startLine, endLine });
|
|
529
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(spec, lines);
|
|
530
|
+
objects.push({ name, startLine, endLine, sourceSpan });
|
|
507
531
|
} else if (_isGoExportedName(name)) {
|
|
508
|
-
const { startLine, endLine } = nodeToLocation(spec, lines);
|
|
509
|
-
objects.push({ name, startLine, endLine });
|
|
532
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(spec, lines);
|
|
533
|
+
objects.push({ name, startLine, endLine, sourceSpan });
|
|
510
534
|
}
|
|
511
535
|
}
|
|
512
536
|
}
|
|
@@ -582,18 +606,22 @@ function extractStructFields(structNode, codeOrLines) {
|
|
|
582
606
|
for (let i = 0; i < fieldListNode.namedChildCount; i++) {
|
|
583
607
|
const field = fieldListNode.namedChild(i);
|
|
584
608
|
if (field.type === 'field_declaration') {
|
|
585
|
-
const { startLine, endLine } = nodeToLocation(field, codeOrLines);
|
|
586
|
-
const
|
|
609
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(field, codeOrLines);
|
|
610
|
+
const nameNodes = field.childrenForFieldName('name');
|
|
587
611
|
const typeNode = field.childForFieldName('type');
|
|
588
612
|
|
|
589
|
-
if (
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
613
|
+
if (nameNodes.length > 0) {
|
|
614
|
+
// `Keys, Values []string` declares every name (fix #430).
|
|
615
|
+
for (const nameNode of nameNodes) {
|
|
616
|
+
fields.push({
|
|
617
|
+
name: nameNode.text,
|
|
618
|
+
startLine,
|
|
619
|
+
endLine,
|
|
620
|
+
sourceSpan,
|
|
621
|
+
memberType: 'field',
|
|
622
|
+
...(typeNode && { fieldType: typeNode.text })
|
|
623
|
+
});
|
|
624
|
+
}
|
|
597
625
|
} else if (typeNode) {
|
|
598
626
|
// Embedded field: has type but no name (e.g., `Base` in `type Child struct { Base; Name string }`)
|
|
599
627
|
// Use the type name as the field name
|
|
@@ -607,6 +635,7 @@ function extractStructFields(structNode, codeOrLines) {
|
|
|
607
635
|
name: embeddedName,
|
|
608
636
|
startLine,
|
|
609
637
|
endLine,
|
|
638
|
+
sourceSpan,
|
|
610
639
|
memberType: 'field',
|
|
611
640
|
embedded: true,
|
|
612
641
|
fieldType: typeNode.text
|
|
@@ -627,7 +656,7 @@ function extractInterfaceMembers(interfaceNode, codeOrLines) {
|
|
|
627
656
|
const child = interfaceNode.namedChild(i);
|
|
628
657
|
// tree-sitter Go uses method_elem (or method_spec in older versions)
|
|
629
658
|
if (child.type === 'method_elem' || child.type === 'method_spec') {
|
|
630
|
-
const { startLine, endLine } = nodeToLocation(child, codeOrLines);
|
|
659
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(child, codeOrLines);
|
|
631
660
|
// Name is in a field_identifier child
|
|
632
661
|
let nameText = null;
|
|
633
662
|
let paramsText = null;
|
|
@@ -678,6 +707,7 @@ function extractInterfaceMembers(interfaceNode, codeOrLines) {
|
|
|
678
707
|
name: nameText,
|
|
679
708
|
startLine,
|
|
680
709
|
endLine,
|
|
710
|
+
sourceSpan,
|
|
681
711
|
memberType: 'field',
|
|
682
712
|
embedded: true,
|
|
683
713
|
fieldType: nameText
|
|
@@ -687,6 +717,7 @@ function extractInterfaceMembers(interfaceNode, codeOrLines) {
|
|
|
687
717
|
name: nameText,
|
|
688
718
|
startLine,
|
|
689
719
|
endLine,
|
|
720
|
+
sourceSpan,
|
|
690
721
|
memberType: 'method',
|
|
691
722
|
...(paramsText !== null && { params: paramsText }),
|
|
692
723
|
...(paramsNode && { paramsStructured: parseStructuredParams(paramsNode, 'go') }),
|
|
@@ -696,7 +727,7 @@ function extractInterfaceMembers(interfaceNode, codeOrLines) {
|
|
|
696
727
|
}
|
|
697
728
|
} else if (child.type === 'type_identifier' || child.type === 'qualified_type') {
|
|
698
729
|
// Standalone type identifier inside interface body — embedded interface
|
|
699
|
-
const { startLine, endLine } = nodeToLocation(child, codeOrLines);
|
|
730
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(child, codeOrLines);
|
|
700
731
|
let embName = child.text;
|
|
701
732
|
const dotIdx = embName.indexOf('.');
|
|
702
733
|
if (dotIdx >= 0) embName = embName.slice(dotIdx + 1);
|
|
@@ -704,6 +735,7 @@ function extractInterfaceMembers(interfaceNode, codeOrLines) {
|
|
|
704
735
|
name: embName,
|
|
705
736
|
startLine,
|
|
706
737
|
endLine,
|
|
738
|
+
sourceSpan,
|
|
707
739
|
memberType: 'field',
|
|
708
740
|
embedded: true,
|
|
709
741
|
fieldType: child.text
|
|
@@ -714,7 +746,7 @@ function extractInterfaceMembers(interfaceNode, codeOrLines) {
|
|
|
714
746
|
for (let j = 0; j < child.namedChildCount; j++) {
|
|
715
747
|
const sub = child.namedChild(j);
|
|
716
748
|
if (sub.type === 'type_identifier' || sub.type === 'qualified_type') {
|
|
717
|
-
const { startLine, endLine } = nodeToLocation(sub, codeOrLines);
|
|
749
|
+
const { startLine, endLine, sourceSpan } = nodeToLocation(sub, codeOrLines);
|
|
718
750
|
let embName = sub.text;
|
|
719
751
|
const dotIdx = embName.indexOf('.');
|
|
720
752
|
if (dotIdx >= 0) embName = embName.slice(dotIdx + 1);
|
|
@@ -722,6 +754,7 @@ function extractInterfaceMembers(interfaceNode, codeOrLines) {
|
|
|
722
754
|
name: embName,
|
|
723
755
|
startLine,
|
|
724
756
|
endLine,
|
|
757
|
+
sourceSpan,
|
|
725
758
|
memberType: 'field',
|
|
726
759
|
embedded: true,
|
|
727
760
|
fieldType: sub.text
|
|
@@ -1514,6 +1547,61 @@ const GO_BUILTINS = new Set([
|
|
|
1514
1547
|
// Value positions of an instantiated generic function value (fix #378).
|
|
1515
1548
|
const GENERIC_VALUE_PARENTS = new Set(['argument_list', 'literal_element', 'expression_list']);
|
|
1516
1549
|
|
|
1550
|
+
// A value in argument position (fix #425): a call argument, or the operand
|
|
1551
|
+
// of a generic instantiation read as a conversion (`conv[[]uint16](Uint16s)`
|
|
1552
|
+
// with `type conv[T any] func(..)` converts the function value Uint16s, and
|
|
1553
|
+
// tree-sitter reads every `X[T](v)` that way).
|
|
1554
|
+
function isArgumentValue(node) {
|
|
1555
|
+
const parent = node?.parent;
|
|
1556
|
+
if (!parent) return false;
|
|
1557
|
+
if (parent.type === 'argument_list') return true;
|
|
1558
|
+
return parent.type === 'type_conversion_expression' &&
|
|
1559
|
+
parent.childForFieldName('type')?.type === 'generic_type' &&
|
|
1560
|
+
sameNode(parent.childForFieldName('operand'), node);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
// A value an instantiated generic function denotes (fix #378, #425): an
|
|
1564
|
+
// argument, a literal element, or a value list (right-hand side, var spec
|
|
1565
|
+
// value, return).
|
|
1566
|
+
function isGenericValuePosition(node) {
|
|
1567
|
+
const parent = node?.parent;
|
|
1568
|
+
if (!parent) return false;
|
|
1569
|
+
if (isArgumentValue(node)) return true;
|
|
1570
|
+
if (!GENERIC_VALUE_PARENTS.has(parent.type)) return false;
|
|
1571
|
+
return parent.type !== 'expression_list' || isValueExpressionList(parent);
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
// Predeclared types no function value converts to.
|
|
1575
|
+
const GO_NON_FUNCTION_TYPES = new Set(['bool', 'string', 'error', 'byte', 'rune', 'uintptr',
|
|
1576
|
+
'int', 'int8', 'int16', 'int32', 'int64', 'uint', 'uint8', 'uint16', 'uint32', 'uint64',
|
|
1577
|
+
'float32', 'float64', 'complex64', 'complex128']);
|
|
1578
|
+
|
|
1579
|
+
/**
|
|
1580
|
+
* Is a positional element of a slice, array or map literal of a predeclared
|
|
1581
|
+
* non-function element type (`[]string{r.Name}`, fix #425)? Such an element
|
|
1582
|
+
* is never a method value. Nested element literals (`[][]string{{..}}`)
|
|
1583
|
+
* descend one element type per level; struct literals and named element
|
|
1584
|
+
* types are undecided (false).
|
|
1585
|
+
*/
|
|
1586
|
+
function holdsNoFunctionValue(literalElement) {
|
|
1587
|
+
let depth = 0;
|
|
1588
|
+
let n = literalElement.parent;
|
|
1589
|
+
while (n?.type === 'literal_value' && n.parent?.type === 'literal_element' &&
|
|
1590
|
+
n.parent.parent?.type === 'literal_value') {
|
|
1591
|
+
depth++;
|
|
1592
|
+
n = n.parent.parent;
|
|
1593
|
+
}
|
|
1594
|
+
if (n?.type !== 'literal_value' || n.parent?.type !== 'composite_literal') return false;
|
|
1595
|
+
let type = n.parent.childForFieldName('type');
|
|
1596
|
+
for (let i = 0; i <= depth; i++) {
|
|
1597
|
+
if (type?.type === 'slice_type' || type?.type === 'array_type' ||
|
|
1598
|
+
type?.type === 'implicit_length_array_type') type = type.childForFieldName('element');
|
|
1599
|
+
else if (type?.type === 'map_type') type = type.childForFieldName('value');
|
|
1600
|
+
else return false;
|
|
1601
|
+
}
|
|
1602
|
+
return type?.type === 'type_identifier' && GO_NON_FUNCTION_TYPES.has(type.text);
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1517
1605
|
// An expression_list holding VALUES: the right side of `=` / `:=`, a var
|
|
1518
1606
|
// spec's values, or a return list (never an assignment's left side).
|
|
1519
1607
|
function isValueExpressionList(list) {
|
|
@@ -1741,6 +1829,8 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1741
1829
|
const packageTypeQualifiers = new Map();
|
|
1742
1830
|
// Names whose scope type is a New*-prefix GUESS (fix #266) — per scope
|
|
1743
1831
|
const scopeGuesses = new Map();
|
|
1832
|
+
// Open type switches whose binding is typed per clause (fix #425).
|
|
1833
|
+
const typeSwitches = [];
|
|
1744
1834
|
// Container-typed variables (slice/map/array declared TYPE TEXT) for
|
|
1745
1835
|
// range-element typing (fix #300, mux-measured): `for _, route := range
|
|
1746
1836
|
// r.routes` types route from the field's declared []*Route. Element
|
|
@@ -1963,6 +2053,109 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1963
2053
|
}
|
|
1964
2054
|
return null;
|
|
1965
2055
|
};
|
|
2056
|
+
// A parenthesized selector operand (fix #425): `(*T).M`, `(T).M` and
|
|
2057
|
+
// `(*pkg.T).M` are method expressions that name type T's method M as
|
|
2058
|
+
// `T.M` does (Go spec, Method expressions); `(*p).M` and `(&v).M` select
|
|
2059
|
+
// the method of a value. The operand reduces to the name it holds, an
|
|
2060
|
+
// identifier or a package-qualified name, resolved like the unwrapped
|
|
2061
|
+
// form; a written instantiated type (`(*Pair[int, string]).M`) names its
|
|
2062
|
+
// generic type. null for any other parenthesized expression.
|
|
2063
|
+
const methodOperandOf = (operandNode) => {
|
|
2064
|
+
if (operandNode?.type !== 'parenthesized_expression') return null;
|
|
2065
|
+
let n = operandNode;
|
|
2066
|
+
const unparen = () => {
|
|
2067
|
+
while (n?.type === 'parenthesized_expression') n = n.namedChildCount === 1 ? n.namedChild(0) : null;
|
|
2068
|
+
};
|
|
2069
|
+
unparen();
|
|
2070
|
+
if (n?.type === 'unary_expression' && ['*', '&'].includes(n.child(0)?.type)) {
|
|
2071
|
+
n = n.childForFieldName('operand');
|
|
2072
|
+
unparen();
|
|
2073
|
+
}
|
|
2074
|
+
if (n?.type === 'type_instantiation_expression') {
|
|
2075
|
+
let t = n.childForFieldName('type');
|
|
2076
|
+
if (t?.type === 'pointer_type') t = t.namedChildCount === 1 ? t.namedChild(0) : null;
|
|
2077
|
+
if (t?.type === 'type_identifier') return { node: t, name: t.text, writtenType: true };
|
|
2078
|
+
if (t?.type === 'qualified_type') {
|
|
2079
|
+
const pkg = t.childForFieldName('package');
|
|
2080
|
+
const name = t.childForFieldName('name');
|
|
2081
|
+
return pkg && name && isPackageQualifier(pkg, pkg.text)
|
|
2082
|
+
? { node: t, name: name.text, qualifier: pkg.text, writtenType: true } : null;
|
|
2083
|
+
}
|
|
2084
|
+
return null;
|
|
2085
|
+
}
|
|
2086
|
+
if (n?.type === 'identifier') return { node: n, name: n.text };
|
|
2087
|
+
if (n?.type === 'selector_expression') {
|
|
2088
|
+
const pkg = n.childForFieldName('operand');
|
|
2089
|
+
const name = n.childForFieldName('field');
|
|
2090
|
+
if (pkg?.type === 'identifier' && name && isPackageQualifier(pkg, pkg.text)) {
|
|
2091
|
+
return { node: n, name: name.text, qualifier: pkg.text, packageNode: pkg };
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
return null;
|
|
2095
|
+
};
|
|
2096
|
+
// A selector VALUE (fix #425: one record shape for every value position,
|
|
2097
|
+
// which used to repeat it per position): `obj.method` (a method value),
|
|
2098
|
+
// `pkg.Func` (a package-qualified function reference), `T.M` / `(*T).M`
|
|
2099
|
+
// (a method expression) and `(&T{..}).M`. A receiver no local binds
|
|
2100
|
+
// names a package-level declaration (`receiverPackageLevel`): the
|
|
2101
|
+
// package's type of that name makes the value a method expression.
|
|
2102
|
+
const selectorValueRecord = (valueNode, extra = {}, bindingEnclosing = false) => {
|
|
2103
|
+
const fieldNode = valueNode.childForFieldName('field');
|
|
2104
|
+
let operandNode = valueNode.childForFieldName('operand');
|
|
2105
|
+
if (!fieldNode || !operandNode) return null;
|
|
2106
|
+
const methodOperand = methodOperandOf(operandNode);
|
|
2107
|
+
let qualifiedOperand = null;
|
|
2108
|
+
if (methodOperand?.qualifier) qualifiedOperand = methodOperand;
|
|
2109
|
+
else if (methodOperand && !methodOperand.writtenType) operandNode = methodOperand.node;
|
|
2110
|
+
const receiver = qualifiedOperand ? undefined : operandNode.type === 'identifier'
|
|
2111
|
+
? operandNode.text : methodOperand?.writtenType ? methodOperand.name : undefined;
|
|
2112
|
+
// `pkg.Func` as a value is a package-qualified function
|
|
2113
|
+
// reference, never a method value (fix #378).
|
|
2114
|
+
const pkgValue = !methodOperand && isPackageQualifier(operandNode, receiver);
|
|
2115
|
+
let receiverTypeEvidence;
|
|
2116
|
+
let receiverType = receiver && !methodOperand?.writtenType
|
|
2117
|
+
? getReceiverType(receiver, operandNode) : undefined;
|
|
2118
|
+
let receiverTypeQualifier = receiverType
|
|
2119
|
+
? getReceiverTypeQualifier(receiver, operandNode) : undefined;
|
|
2120
|
+
// Composite-literal receiver (fix #298): the literal's type is
|
|
2121
|
+
// compiler-true, never guessed.
|
|
2122
|
+
if (!receiver && !receiverType && !qualifiedOperand) {
|
|
2123
|
+
const lit = literalReceiverInfo(operandNode);
|
|
2124
|
+
if (lit) {
|
|
2125
|
+
receiverType = lit.receiverType;
|
|
2126
|
+
receiverTypeEvidence = typeOrigin('constructor', operandNode);
|
|
2127
|
+
receiverTypeQualifier = lit.receiverTypeQualifier;
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
const packageLevel = !!(receiver && !pkgValue && !receiverType &&
|
|
2131
|
+
(methodOperand?.writtenType || !isShadowedByLocal(operandNode, receiver)) &&
|
|
2132
|
+
!importAliases.has(receiver));
|
|
2133
|
+
const enclosingFunction = bindingEnclosing && receiver && !methodOperand?.writtenType
|
|
2134
|
+
? getCurrentEnclosingFunction(operandNode, receiver)
|
|
2135
|
+
: getCurrentEnclosingFunction();
|
|
2136
|
+
return {
|
|
2137
|
+
name: fieldNode.text,
|
|
2138
|
+
// #223 name-node convention: the field's own line.
|
|
2139
|
+
line: fieldNode.startPosition.row + 1,
|
|
2140
|
+
column: fieldNode.startPosition.column,
|
|
2141
|
+
isMethod: !pkgValue,
|
|
2142
|
+
...(pkgValue && { isFunctionReference: true }),
|
|
2143
|
+
receiver,
|
|
2144
|
+
...(receiverType && { receiverType, ...(receiverTypeEvidence ? { receiverTypeSource: 'constructor', receiverTypeEvidence } : getReceiverType(receiver, valueNode, true) || { receiverTypeSource: 'unknown' }) }),
|
|
2145
|
+
...(receiverTypeQualifier && { receiverTypeQualifier }),
|
|
2146
|
+
...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
2147
|
+
...(packageLevel && { receiverPackageLevel: true }),
|
|
2148
|
+
...(qualifiedOperand && {
|
|
2149
|
+
receiverRoot: qualifiedOperand.qualifier,
|
|
2150
|
+
receiverField: qualifiedOperand.name,
|
|
2151
|
+
receiverRootIsModule: true,
|
|
2152
|
+
}),
|
|
2153
|
+
enclosingFunction,
|
|
2154
|
+
isPotentialCallback: true,
|
|
2155
|
+
uncertain: false,
|
|
2156
|
+
...extra,
|
|
2157
|
+
};
|
|
2158
|
+
};
|
|
1966
2159
|
// Is the FIRST scope-chain hit for this variable a New*-prefix GUESS
|
|
1967
2160
|
// (fix #266, viper-measured)? `registry := NewCodecRegistry()` types
|
|
1968
2161
|
// registry as 'CodecRegistry' by NAME CONVENTION — the actual return
|
|
@@ -2351,6 +2544,64 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
2351
2544
|
}
|
|
2352
2545
|
}
|
|
2353
2546
|
|
|
2547
|
+
// Type switch bindings (fix #425): in `switch v := x.(type)`, a
|
|
2548
|
+
// clause listing exactly one type gives v that type; a clause of
|
|
2549
|
+
// several types and the default clause give v the type of x (Go
|
|
2550
|
+
// spec, Type switches). The binding is the switch's own: the outer
|
|
2551
|
+
// meaning of the name returns after the switch (onLeave).
|
|
2552
|
+
if (node.type === 'type_switch_statement' && functionStack.length > 0) {
|
|
2553
|
+
const alias = node.childForFieldName('alias');
|
|
2554
|
+
const id = alias?.type === 'expression_list' && alias.namedChildCount === 1
|
|
2555
|
+
? alias.namedChild(0) : alias;
|
|
2556
|
+
if (id?.type === 'identifier' && id.text !== '_') {
|
|
2557
|
+
const scopeKey = functionStack[functionStack.length - 1].startLine;
|
|
2558
|
+
const typeMap = scopeTypes.get(scopeKey);
|
|
2559
|
+
const qualifiers = scopeTypeQualifiers.get(scopeKey);
|
|
2560
|
+
const value = node.childForFieldName('value');
|
|
2561
|
+
const valueType = value?.type === 'identifier' && !isGuessedType(value.text)
|
|
2562
|
+
? getReceiverType(value.text, value) : undefined;
|
|
2563
|
+
typeSwitches.push({
|
|
2564
|
+
node, name: id.text, scopeKey, typeMap, qualifiers,
|
|
2565
|
+
valueType,
|
|
2566
|
+
valueQualifier: valueType ? getReceiverTypeQualifier(value.text, value) : undefined,
|
|
2567
|
+
valueEvidence: valueType ? getReceiverType(value.text, value, true) : undefined,
|
|
2568
|
+
saved: typeMap?.has(id.text) ? {
|
|
2569
|
+
type: typeMap.get(id.text), origin: typeMap.origins.get(id.text),
|
|
2570
|
+
qualifier: qualifiers?.get(id.text),
|
|
2571
|
+
guessed: !!scopeGuesses.get(scopeKey)?.has(id.text),
|
|
2572
|
+
} : null,
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
if ((node.type === 'type_case' || node.type === 'default_case') &&
|
|
2577
|
+
typeSwitches.length > 0 && sameNode(typeSwitches.at(-1).node, node.parent)) {
|
|
2578
|
+
const sw = typeSwitches.at(-1);
|
|
2579
|
+
const types = node.type === 'type_case' ? node.childrenForFieldName('type') : [];
|
|
2580
|
+
let typeName = null;
|
|
2581
|
+
let qualifier = null;
|
|
2582
|
+
let origin = null;
|
|
2583
|
+
if (types.length === 1) {
|
|
2584
|
+
typeName = extractTypeName(types[0]);
|
|
2585
|
+
qualifier = typeName ? extractTypeQualifier(types[0]) : null;
|
|
2586
|
+
origin = { source: 'type-assertion', node: types[0] };
|
|
2587
|
+
} else if (sw.valueType) {
|
|
2588
|
+
typeName = sw.valueType;
|
|
2589
|
+
qualifier = sw.valueQualifier || null;
|
|
2590
|
+
}
|
|
2591
|
+
if (sw.typeMap) {
|
|
2592
|
+
if (typeName) {
|
|
2593
|
+
if (origin) sw.typeMap.set(sw.name, typeName, origin.source, origin.node);
|
|
2594
|
+
else sw.typeMap.set(sw.name, typeName, sw.valueEvidence?.receiverTypeEvidence || 'flow');
|
|
2595
|
+
if (qualifier) sw.qualifiers?.set(sw.name, qualifier);
|
|
2596
|
+
else sw.qualifiers?.delete(sw.name);
|
|
2597
|
+
} else {
|
|
2598
|
+
sw.typeMap.delete(sw.name);
|
|
2599
|
+
sw.qualifiers?.delete(sw.name);
|
|
2600
|
+
}
|
|
2601
|
+
scopeGuesses.get(sw.scopeKey)?.delete(sw.name);
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2354
2605
|
// Track local variable types from composite literals and typed assignments
|
|
2355
2606
|
// e.g., s := &Status{...} → s has type Status
|
|
2356
2607
|
// registry := Registry{...} → registry has type Registry
|
|
@@ -2734,6 +2985,10 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
2734
2985
|
// Method or package call: obj.Method() or pkg.Func()
|
|
2735
2986
|
const fieldNode = funcNode.childForFieldName('field');
|
|
2736
2987
|
let operandNode = funcNode.childForFieldName('operand');
|
|
2988
|
+
// `(*T).M(t, ..)`, `(*p).M(..)` and `(*pkg.T).M(..)` receive
|
|
2989
|
+
// like `T.M(t, ..)`, `p.M(..)` and `pkg.T.M(..)` (fix #425).
|
|
2990
|
+
const methodOperand = methodOperandOf(operandNode);
|
|
2991
|
+
if (methodOperand && !methodOperand.writtenType) operandNode = methodOperand.node;
|
|
2737
2992
|
// fix #381: `c.Load()` after `c := s.config` receives like
|
|
2738
2993
|
// `s.config.Load()`.
|
|
2739
2994
|
if (operandNode?.type === 'identifier' && !importAliases.has(operandNode.text) &&
|
|
@@ -2743,17 +2998,19 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
2743
2998
|
}
|
|
2744
2999
|
|
|
2745
3000
|
if (fieldNode) {
|
|
2746
|
-
const
|
|
3001
|
+
const writtenReceiver = methodOperand?.writtenType && !methodOperand.qualifier
|
|
3002
|
+
? methodOperand.name : undefined;
|
|
3003
|
+
const receiver = operandNode?.type === 'identifier' ? operandNode.text : writtenReceiver;
|
|
2747
3004
|
// Distinguish pkg.Func() (package-qualified) from obj.Method()
|
|
2748
3005
|
// If receiver is a known import alias, this is a package call, not a method call
|
|
2749
3006
|
// A local binding shadows a same-named import inside its
|
|
2750
3007
|
// scope (`fs := NewMemMapFs(); fs.Chown(...)` in a file
|
|
2751
3008
|
// importing "io/fs"): that call is a METHOD call on the
|
|
2752
3009
|
// local, never the package function (fix #360).
|
|
2753
|
-
const isPkgCall =
|
|
2754
|
-
!isShadowedByLocal(operandNode, receiver);
|
|
3010
|
+
const isPkgCall = !writtenReceiver && !methodOperand && receiver &&
|
|
3011
|
+
importAliases.has(receiver) && !isShadowedByLocal(operandNode, receiver);
|
|
2755
3012
|
let receiverTypeEvidence;
|
|
2756
|
-
let receiverType = (!isPkgCall && receiver)
|
|
3013
|
+
let receiverType = (!isPkgCall && receiver && !writtenReceiver)
|
|
2757
3014
|
? getReceiverType(receiver, operandNode) : undefined;
|
|
2758
3015
|
let receiverTypeQualifier = receiverType
|
|
2759
3016
|
? getReceiverTypeQualifier(receiver, operandNode) : undefined;
|
|
@@ -2764,7 +3021,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
2764
3021
|
// package (fix #399): typed at query time. So is an
|
|
2765
3022
|
// untyped package-level variable of this file, whose
|
|
2766
3023
|
// value may come from another package (fix #400).
|
|
2767
|
-
const receiverPackageScope = !!(receiver && !isPkgCall && !receiverType &&
|
|
3024
|
+
const receiverPackageScope = !!(receiver && !writtenReceiver && !isPkgCall && !receiverType &&
|
|
2768
3025
|
!receiverIndexedSource &&
|
|
2769
3026
|
(!fileLevelNames().has(receiver) || fileVarNames().has(receiver)) &&
|
|
2770
3027
|
!isShadowedByLocal(operandNode, receiver));
|
|
@@ -2989,52 +3246,12 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
2989
3246
|
}
|
|
2990
3247
|
|
|
2991
3248
|
// Detect function references passed as arguments: dc.worker passed to UntilWithContext(ctx, dc.worker, ...)
|
|
2992
|
-
// selector_expression inside argument_list (not inside call_expression as the function)
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
const operandNode = node.childForFieldName('operand');
|
|
2999
|
-
if (fieldNode && operandNode) {
|
|
3000
|
-
const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
|
|
3001
|
-
// `pkg.Func` as a value is a package-qualified function
|
|
3002
|
-
// reference, never a method value (fix #378).
|
|
3003
|
-
const pkgValue = isPackageQualifier(operandNode, receiver);
|
|
3004
|
-
let receiverTypeEvidence;
|
|
3005
|
-
let receiverType = receiver
|
|
3006
|
-
? getReceiverType(receiver, operandNode) : undefined;
|
|
3007
|
-
let receiverTypeQualifier = receiverType
|
|
3008
|
-
? getReceiverTypeQualifier(receiver, operandNode) : undefined;
|
|
3009
|
-
// Composite-literal receiver (fix #298): the literal's
|
|
3010
|
-
// type is compiler-true, never guessed.
|
|
3011
|
-
if (!receiver && !receiverType) {
|
|
3012
|
-
const lit = literalReceiverInfo(operandNode);
|
|
3013
|
-
if (lit) {
|
|
3014
|
-
receiverType = lit.receiverType;
|
|
3015
|
-
receiverTypeEvidence = typeOrigin('constructor', operandNode);
|
|
3016
|
-
receiverTypeQualifier = lit.receiverTypeQualifier;
|
|
3017
|
-
}
|
|
3018
|
-
}
|
|
3019
|
-
const enclosingFunction = receiver
|
|
3020
|
-
? getCurrentEnclosingFunction(operandNode, receiver)
|
|
3021
|
-
: getCurrentEnclosingFunction();
|
|
3022
|
-
calls.push({
|
|
3023
|
-
name: fieldNode.text,
|
|
3024
|
-
line: fieldNode.startPosition.row + 1,
|
|
3025
|
-
column: fieldNode.startPosition.column,
|
|
3026
|
-
isMethod: !pkgValue,
|
|
3027
|
-
...(pkgValue && { isFunctionReference: true }),
|
|
3028
|
-
receiver,
|
|
3029
|
-
...(receiverType && { receiverType, ...(receiverTypeEvidence ? { receiverTypeSource: 'constructor', receiverTypeEvidence } : getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
|
|
3030
|
-
...(receiverTypeQualifier && { receiverTypeQualifier }),
|
|
3031
|
-
...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
3032
|
-
enclosingFunction,
|
|
3033
|
-
isPotentialCallback: true,
|
|
3034
|
-
uncertain: false
|
|
3035
|
-
});
|
|
3036
|
-
}
|
|
3037
|
-
}
|
|
3249
|
+
// selector_expression inside argument_list (not inside call_expression as the function),
|
|
3250
|
+
// or the operand of a conversion to a generic function type (fix #425:
|
|
3251
|
+
// `anyFieldC[[]uint16](Uint16s)` where anyFieldC is a type).
|
|
3252
|
+
if (node.type === 'selector_expression' && isArgumentValue(node)) {
|
|
3253
|
+
const record = selectorValueRecord(node, {}, true);
|
|
3254
|
+
if (record) calls.push(record);
|
|
3038
3255
|
}
|
|
3039
3256
|
|
|
3040
3257
|
// Instantiated generic function VALUES (fix #378): `id[int]` /
|
|
@@ -3043,18 +3260,37 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
3043
3260
|
// The same syntax indexes a slice or map: a locally bound operand is
|
|
3044
3261
|
// an index, and a package-level non-function never matches a
|
|
3045
3262
|
// function pin (Go forbids a same-package var and func of one name).
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3263
|
+
// Several type arguments (`Map[int, string]`, `lo.Map[int, string]`)
|
|
3264
|
+
// parse as a type_instantiation_expression (fix #425), which only
|
|
3265
|
+
// instantiates.
|
|
3266
|
+
if ((node.type === 'index_expression' || node.type === 'type_instantiation_expression') &&
|
|
3267
|
+
isGenericValuePosition(node)) {
|
|
3268
|
+
let nameNode = null;
|
|
3269
|
+
let qualifierNode = null;
|
|
3270
|
+
if (node.type === 'index_expression') {
|
|
3271
|
+
const operandNode = node.childForFieldName('operand');
|
|
3272
|
+
if (operandNode?.type === 'identifier') nameNode = operandNode;
|
|
3273
|
+
else if (operandNode?.type === 'selector_expression') {
|
|
3274
|
+
qualifierNode = operandNode.childForFieldName('operand');
|
|
3275
|
+
nameNode = operandNode.childForFieldName('field');
|
|
3276
|
+
}
|
|
3277
|
+
} else {
|
|
3278
|
+
const typeNode = node.childForFieldName('type');
|
|
3279
|
+
if (typeNode?.type === 'type_identifier') nameNode = typeNode;
|
|
3280
|
+
else if (typeNode?.type === 'qualified_type') {
|
|
3281
|
+
qualifierNode = typeNode.childForFieldName('package');
|
|
3282
|
+
nameNode = typeNode.childForFieldName('name');
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
if (nameNode && !qualifierNode) {
|
|
3286
|
+
const name = nameNode.text;
|
|
3051
3287
|
if (!GO_SKIP_IDENTS.has(name) && !GO_BUILTINS.has(name) &&
|
|
3052
|
-
!importAliases.has(name) && !isShadowedByLocal(
|
|
3288
|
+
!importAliases.has(name) && !isShadowedByLocal(nameNode, name) &&
|
|
3053
3289
|
!isFuncTypedParam(name) && !getLocalClosure(name)) {
|
|
3054
3290
|
calls.push({
|
|
3055
3291
|
name,
|
|
3056
|
-
line:
|
|
3057
|
-
column:
|
|
3292
|
+
line: nameNode.startPosition.row + 1,
|
|
3293
|
+
column: nameNode.startPosition.column,
|
|
3058
3294
|
isMethod: false,
|
|
3059
3295
|
isFunctionReference: true,
|
|
3060
3296
|
isPotentialCallback: true,
|
|
@@ -3062,15 +3298,14 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
3062
3298
|
uncertain: false,
|
|
3063
3299
|
});
|
|
3064
3300
|
}
|
|
3065
|
-
} else if (
|
|
3066
|
-
const
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
if (fieldNode && isPackageQualifier(qualifier, receiver)) {
|
|
3301
|
+
} else if (nameNode && qualifierNode) {
|
|
3302
|
+
const receiver = ['identifier', 'package_identifier'].includes(qualifierNode.type)
|
|
3303
|
+
? qualifierNode.text : undefined;
|
|
3304
|
+
if (isPackageQualifier(qualifierNode, receiver)) {
|
|
3070
3305
|
calls.push({
|
|
3071
|
-
name:
|
|
3072
|
-
line:
|
|
3073
|
-
column:
|
|
3306
|
+
name: nameNode.text,
|
|
3307
|
+
line: nameNode.startPosition.row + 1,
|
|
3308
|
+
column: nameNode.startPosition.column,
|
|
3074
3309
|
isMethod: false,
|
|
3075
3310
|
receiver,
|
|
3076
3311
|
isFunctionReference: true,
|
|
@@ -3084,7 +3319,8 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
3084
3319
|
|
|
3085
3320
|
// Detect plain identifier function references passed as arguments:
|
|
3086
3321
|
// e.g., r.GET("/users", listUsers) — listUsers is a plain identifier in argument_list
|
|
3087
|
-
|
|
3322
|
+
// (or converted: `conv[[]uint16](Uint16s)`, fix #425)
|
|
3323
|
+
if (node.type === 'identifier' && isArgumentValue(node)) {
|
|
3088
3324
|
const name = node.text;
|
|
3089
3325
|
if (!GO_SKIP_IDENTS.has(name) && !importAliases.has(name)) {
|
|
3090
3326
|
const enclosingFunction = getCurrentEnclosingFunction();
|
|
@@ -3121,43 +3357,8 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
3121
3357
|
for (const rhs of rhsNodes) {
|
|
3122
3358
|
// selector_expression: sched.schedulePod
|
|
3123
3359
|
if (rhs.type === 'selector_expression') {
|
|
3124
|
-
const
|
|
3125
|
-
|
|
3126
|
-
if (fieldNode && operandNode) {
|
|
3127
|
-
const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
|
|
3128
|
-
// `pkg.Func` as a value is a package-qualified function
|
|
3129
|
-
// reference, never a method value (fix #378).
|
|
3130
|
-
const pkgValue = isPackageQualifier(operandNode, receiver);
|
|
3131
|
-
let receiverTypeEvidence;
|
|
3132
|
-
let receiverType = receiver
|
|
3133
|
-
? getReceiverType(receiver, operandNode) : undefined;
|
|
3134
|
-
let receiverTypeQualifier = receiverType
|
|
3135
|
-
? getReceiverTypeQualifier(receiver, operandNode) : undefined;
|
|
3136
|
-
// Composite-literal receiver (fix #298).
|
|
3137
|
-
if (!receiver && !receiverType) {
|
|
3138
|
-
const lit = literalReceiverInfo(operandNode);
|
|
3139
|
-
if (lit) {
|
|
3140
|
-
receiverType = lit.receiverType;
|
|
3141
|
-
receiverTypeEvidence = typeOrigin('constructor', operandNode);
|
|
3142
|
-
receiverTypeQualifier = lit.receiverTypeQualifier;
|
|
3143
|
-
}
|
|
3144
|
-
}
|
|
3145
|
-
const enclosingFunction = getCurrentEnclosingFunction();
|
|
3146
|
-
calls.push({
|
|
3147
|
-
name: fieldNode.text,
|
|
3148
|
-
line: fieldNode.startPosition.row + 1,
|
|
3149
|
-
column: fieldNode.startPosition.column,
|
|
3150
|
-
isMethod: !pkgValue,
|
|
3151
|
-
...(pkgValue && { isFunctionReference: true }),
|
|
3152
|
-
receiver,
|
|
3153
|
-
...(receiverType && { receiverType, ...(receiverTypeEvidence ? { receiverTypeSource: 'constructor', receiverTypeEvidence } : getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
|
|
3154
|
-
...(receiverTypeQualifier && { receiverTypeQualifier }),
|
|
3155
|
-
...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
3156
|
-
enclosingFunction,
|
|
3157
|
-
isPotentialCallback: true,
|
|
3158
|
-
uncertain: false
|
|
3159
|
-
});
|
|
3160
|
-
}
|
|
3360
|
+
const record = selectorValueRecord(rhs);
|
|
3361
|
+
if (record) calls.push(record);
|
|
3161
3362
|
}
|
|
3162
3363
|
// Plain identifier: schedulePod (standalone function reference)
|
|
3163
3364
|
if (rhs.type === 'identifier') {
|
|
@@ -3194,44 +3395,20 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
3194
3395
|
: (exprList ? [exprList] : []);
|
|
3195
3396
|
for (const val of values) {
|
|
3196
3397
|
if (val.type !== 'selector_expression') continue;
|
|
3197
|
-
const
|
|
3198
|
-
|
|
3199
|
-
if (!fieldNode || !operandNode) continue;
|
|
3200
|
-
const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
|
|
3201
|
-
// `pkg.Func` as a value is a package-qualified function
|
|
3202
|
-
// reference, never a method value (fix #378).
|
|
3203
|
-
const pkgValue = isPackageQualifier(operandNode, receiver);
|
|
3204
|
-
let receiverTypeEvidence;
|
|
3205
|
-
let receiverType = receiver
|
|
3206
|
-
? getReceiverType(receiver, operandNode) : undefined;
|
|
3207
|
-
let receiverTypeQualifier = receiverType
|
|
3208
|
-
? getReceiverTypeQualifier(receiver, operandNode) : undefined;
|
|
3209
|
-
if (!receiver && !receiverType) {
|
|
3210
|
-
const lit = literalReceiverInfo(operandNode);
|
|
3211
|
-
if (lit) {
|
|
3212
|
-
receiverType = lit.receiverType;
|
|
3213
|
-
receiverTypeEvidence = typeOrigin('constructor', operandNode);
|
|
3214
|
-
receiverTypeQualifier = lit.receiverTypeQualifier;
|
|
3215
|
-
}
|
|
3216
|
-
}
|
|
3217
|
-
calls.push({
|
|
3218
|
-
name: fieldNode.text,
|
|
3219
|
-
// #223 name-node convention: the field's own line.
|
|
3220
|
-
line: fieldNode.startPosition.row + 1,
|
|
3221
|
-
column: fieldNode.startPosition.column,
|
|
3222
|
-
isMethod: !pkgValue,
|
|
3223
|
-
...(pkgValue && { isFunctionReference: true }),
|
|
3224
|
-
receiver,
|
|
3225
|
-
...(receiverType && { receiverType, ...(receiverTypeEvidence ? { receiverTypeSource: 'constructor', receiverTypeEvidence } : getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
|
|
3226
|
-
...(receiverTypeQualifier && { receiverTypeQualifier }),
|
|
3227
|
-
...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
3228
|
-
enclosingFunction: getCurrentEnclosingFunction(),
|
|
3229
|
-
isPotentialCallback: true,
|
|
3230
|
-
uncertain: false
|
|
3231
|
-
});
|
|
3398
|
+
const record = selectorValueRecord(val);
|
|
3399
|
+
if (record) calls.push(record);
|
|
3232
3400
|
}
|
|
3233
3401
|
}
|
|
3234
3402
|
|
|
3403
|
+
// Selector values as positional composite-literal elements (fix #425,
|
|
3404
|
+
// zap: `[]withAny{(*SugaredLogger).With}`, a struct literal's
|
|
3405
|
+
// `(*Logger).With` field value): the same values as keyed ones.
|
|
3406
|
+
if (node.type === 'selector_expression' && node.parent?.type === 'literal_element' &&
|
|
3407
|
+
node.parent.parent?.type !== 'keyed_element' && !holdsNoFunctionValue(node.parent)) {
|
|
3408
|
+
const record = selectorValueRecord(node);
|
|
3409
|
+
if (record) calls.push(record);
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3235
3412
|
// Detect function references in composite literal fields:
|
|
3236
3413
|
// ResourceEventHandlerFuncs{AddFunc: addNodeToCache, UpdateFunc: updateNode}
|
|
3237
3414
|
// keyed_element → literal_element(key) ":" literal_element(value)
|
|
@@ -3288,45 +3465,11 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
3288
3465
|
}
|
|
3289
3466
|
}
|
|
3290
3467
|
if (valueNode.type === 'selector_expression') {
|
|
3291
|
-
const
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
// reference, never a method value (fix #378).
|
|
3297
|
-
const pkgValue = isPackageQualifier(operandNode, receiver);
|
|
3298
|
-
let receiverTypeEvidence;
|
|
3299
|
-
let receiverType = receiver
|
|
3300
|
-
? getReceiverType(receiver, operandNode) : undefined;
|
|
3301
|
-
let receiverTypeQualifier = receiverType
|
|
3302
|
-
? getReceiverTypeQualifier(receiver, operandNode) : undefined;
|
|
3303
|
-
// Composite-literal receiver (fix #298).
|
|
3304
|
-
if (!receiver && !receiverType) {
|
|
3305
|
-
const lit = literalReceiverInfo(operandNode);
|
|
3306
|
-
if (lit) {
|
|
3307
|
-
receiverType = lit.receiverType;
|
|
3308
|
-
receiverTypeEvidence = typeOrigin('constructor', operandNode);
|
|
3309
|
-
receiverTypeQualifier = lit.receiverTypeQualifier;
|
|
3310
|
-
}
|
|
3311
|
-
}
|
|
3312
|
-
const enclosingFunction = getCurrentEnclosingFunction();
|
|
3313
|
-
calls.push({
|
|
3314
|
-
name: fieldNode.text,
|
|
3315
|
-
line: fieldNode.startPosition.row + 1,
|
|
3316
|
-
column: fieldNode.startPosition.column,
|
|
3317
|
-
isMethod: !pkgValue,
|
|
3318
|
-
...(pkgValue && { isFunctionReference: true }),
|
|
3319
|
-
receiver,
|
|
3320
|
-
...(receiverType && { receiverType, ...(receiverTypeEvidence ? { receiverTypeSource: 'constructor', receiverTypeEvidence } : getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
|
|
3321
|
-
...(receiverTypeQualifier && { receiverTypeQualifier }),
|
|
3322
|
-
...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
3323
|
-
enclosingFunction,
|
|
3324
|
-
isPotentialCallback: true,
|
|
3325
|
-
uncertain: false,
|
|
3326
|
-
...(compositeType && { compositeType }),
|
|
3327
|
-
...(fieldName && { fieldName }),
|
|
3328
|
-
});
|
|
3329
|
-
}
|
|
3468
|
+
const record = selectorValueRecord(valueNode, {
|
|
3469
|
+
...(compositeType && { compositeType }),
|
|
3470
|
+
...(fieldName && { fieldName }),
|
|
3471
|
+
});
|
|
3472
|
+
if (record) calls.push(record);
|
|
3330
3473
|
}
|
|
3331
3474
|
// Inline closure: RunE: func(cmd *cobra.Command, args []string) { ... }
|
|
3332
3475
|
// Mark the enclosing function as the entry point (the closure itself has no name)
|
|
@@ -3353,6 +3496,25 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
3353
3496
|
return true;
|
|
3354
3497
|
}, {
|
|
3355
3498
|
onLeave: (node) => {
|
|
3499
|
+
if (node.type === 'type_switch_statement' && typeSwitches.length > 0 &&
|
|
3500
|
+
sameNode(typeSwitches.at(-1).node, node)) {
|
|
3501
|
+
const sw = typeSwitches.pop();
|
|
3502
|
+
if (sw.typeMap) {
|
|
3503
|
+
if (sw.saved) {
|
|
3504
|
+
sw.typeMap.set(sw.name, sw.saved.type, sw.saved.origin || 'unknown');
|
|
3505
|
+
if (sw.saved.qualifier) sw.qualifiers?.set(sw.name, sw.saved.qualifier);
|
|
3506
|
+
else sw.qualifiers?.delete(sw.name);
|
|
3507
|
+
if (sw.saved.guessed) {
|
|
3508
|
+
let gset = scopeGuesses.get(sw.scopeKey);
|
|
3509
|
+
if (!gset) scopeGuesses.set(sw.scopeKey, gset = new Set());
|
|
3510
|
+
gset.add(sw.name);
|
|
3511
|
+
}
|
|
3512
|
+
} else {
|
|
3513
|
+
sw.typeMap.delete(sw.name);
|
|
3514
|
+
sw.qualifiers?.delete(sw.name);
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3356
3518
|
if (isFunctionNode(node)) {
|
|
3357
3519
|
const leaving = functionStack.pop();
|
|
3358
3520
|
if (leaving) {
|
|
@@ -3664,6 +3826,21 @@ function findUsagesInCode(code, name, parser, tree, options = {}) {
|
|
|
3664
3826
|
return usages;
|
|
3665
3827
|
}
|
|
3666
3828
|
|
|
3829
|
+
/**
|
|
3830
|
+
* Does a local binding hide the receiver of the selector whose field token
|
|
3831
|
+
* starts at (line, column) (fix #425: `pkg.F` names an import only when no
|
|
3832
|
+
* local binds `pkg`)? true / false; null when no such selector is there.
|
|
3833
|
+
*/
|
|
3834
|
+
function selectorReceiverIsLocal(tree, line, column) {
|
|
3835
|
+
const point = { row: line - 1, column };
|
|
3836
|
+
const field = tree?.rootNode?.descendantForPosition(point);
|
|
3837
|
+
if (field?.type !== 'field_identifier' || field.parent?.type !== 'selector_expression') return null;
|
|
3838
|
+
const operand = field.parent.childForFieldName('operand');
|
|
3839
|
+
if (operand?.type !== 'identifier') return null;
|
|
3840
|
+
const scope = referenceScope(operand, 'go');
|
|
3841
|
+
return !!scope && typeof scope !== 'string';
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3667
3844
|
/**
|
|
3668
3845
|
* Classify a Go symbol as a runtime entry point of a specific kind.
|
|
3669
3846
|
* Returns 'test' | 'main' | null.
|
|
@@ -3720,6 +3897,7 @@ module.exports = {
|
|
|
3720
3897
|
findImportsInCode,
|
|
3721
3898
|
findExportsInCode,
|
|
3722
3899
|
findUsagesInCode,
|
|
3900
|
+
selectorReceiverIsLocal,
|
|
3723
3901
|
isPlatformConcreteCall,
|
|
3724
3902
|
isPlatformConcreteType,
|
|
3725
3903
|
isEntryPoint,
|