ucn 5.1.1 → 5.2.1
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 +26 -5
- package/.claude/skills/ucn/references/commands.md +5 -5
- package/README.md +42 -15
- package/core/accessors.js +183 -0
- package/core/analysis.js +46 -0
- package/core/ast-analysis.js +104 -0
- package/core/cache.js +32 -1
- package/core/callers.js +1255 -64
- package/core/command-contracts.js +13 -13
- package/core/deadcode.js +41 -2
- package/core/execute.js +4 -1
- package/core/graph-build.js +6 -0
- package/core/graph.js +72 -5
- package/core/index-ir.js +13 -2
- package/core/ir.js +5 -3
- package/core/output/analysis.js +30 -1
- package/core/output/graph.js +28 -8
- package/core/output/public.js +4 -0
- package/core/output/refactoring.js +31 -2
- package/core/output/reporting.js +7 -0
- package/core/project.js +32 -0
- package/core/search.js +9 -0
- package/core/verify.js +1168 -38
- package/languages/c-family.js +239 -26
- package/languages/csharp.js +40 -4
- package/languages/go.js +473 -71
- package/languages/javascript.js +86 -4
- package/languages/python.js +392 -101
- package/languages/rust.js +87 -4
- package/languages/utils.js +11 -0
- package/mcp/server.js +99 -103
- package/mcp/stdio-server.js +296 -0
- package/package.json +10 -8
package/languages/go.js
CHANGED
|
@@ -227,6 +227,11 @@ function _processClass(node, types, processedRanges, lines) {
|
|
|
227
227
|
|
|
228
228
|
const embeddedBases = members
|
|
229
229
|
.filter(m => m.embedded)
|
|
230
|
+
// The inheritance graph is keyed by project type names.
|
|
231
|
+
// Keep the historical bare name here so promoted methods
|
|
232
|
+
// remain connected; the field symbol separately retains
|
|
233
|
+
// the qualified authored type (`io.Reader`) for consumers
|
|
234
|
+
// which must distinguish an open external method set.
|
|
230
235
|
.map(m => m.name);
|
|
231
236
|
|
|
232
237
|
types.push({
|
|
@@ -641,9 +646,11 @@ const GO_BUILTINS = new Set([
|
|
|
641
646
|
/**
|
|
642
647
|
* Variable receiving this call's result (fix #207 return-type flow):
|
|
643
648
|
* bb := balancer.Get(n) → { assignedTo: 'bb' }
|
|
644
|
-
* x, err := pkg.Make() → { assignedTo: 'x', assignedTuple: true
|
|
645
|
-
*
|
|
646
|
-
*
|
|
649
|
+
* x, err := pkg.Make() → { assignedTo: 'x', assignedTuple: true,
|
|
650
|
+
* assignedTupleTargets: [{name:'x', index:0},
|
|
651
|
+
* {name:'err', index:1}] }
|
|
652
|
+
* (tuple unpack — the flow map pairs every named
|
|
653
|
+
* target with its declared return position)
|
|
647
654
|
* y = q() → { assignedTo: 'y' } (plain `=` only — `+=` etc.
|
|
648
655
|
* don't bind the call's type to the variable)
|
|
649
656
|
* a, b := g(), h() → parallel assignment: each call pairs with its
|
|
@@ -679,19 +686,24 @@ function goAssignmentTargetOf(callNode) {
|
|
|
679
686
|
return target?.type === 'identifier' && target.text !== '_'
|
|
680
687
|
? { assignedTo: target.text } : undefined;
|
|
681
688
|
}
|
|
682
|
-
const target = names[0];
|
|
683
|
-
if (target?.type !== 'identifier' || target.text === '_') return undefined;
|
|
684
689
|
if (names.length > 1) {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
return {
|
|
693
|
-
|
|
690
|
+
const targets = names
|
|
691
|
+
.map((item, index) => ({ item, index }))
|
|
692
|
+
.filter(({ item }) => item.type === 'identifier' && item.text !== '_')
|
|
693
|
+
.map(({ item, index }) => ({ name: item.text, index }));
|
|
694
|
+
if (targets.length === 0) return undefined;
|
|
695
|
+
const [target, ...restTargets] = targets;
|
|
696
|
+
const rest = restTargets.map(item => item.name);
|
|
697
|
+
return {
|
|
698
|
+
assignedTo: target.name,
|
|
699
|
+
assignedTuple: true,
|
|
700
|
+
assignedTupleIndex: target.index,
|
|
701
|
+
assignedTupleTargets: targets,
|
|
702
|
+
...(rest.length > 0 && { assignedTupleRest: rest }),
|
|
703
|
+
};
|
|
694
704
|
}
|
|
705
|
+
const target = names[0];
|
|
706
|
+
if (target?.type !== 'identifier' || target.text === '_') return undefined;
|
|
695
707
|
return { assignedTo: target.text };
|
|
696
708
|
}
|
|
697
709
|
|
|
@@ -727,7 +739,10 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
727
739
|
};
|
|
728
740
|
// Skip common non-function identifiers when detecting callback arguments
|
|
729
741
|
const GO_SKIP_IDENTS = new Set(['nil', 'true', 'false', 'err', 'ctx', 'context', 'iota']);
|
|
730
|
-
// Track local
|
|
742
|
+
// Track local closures per function scope. The declared result belongs
|
|
743
|
+
// to the lexical value (not to a package-level function with the same
|
|
744
|
+
// name), but it is still compiler-grade assignment-flow evidence.
|
|
745
|
+
// scopeStartLine -> Map<name, { returnType?: string }>
|
|
731
746
|
const closureScopes = new Map();
|
|
732
747
|
// Track variable -> type mappings per function scope (scopeStartLine -> Map<varName, typeName>)
|
|
733
748
|
const scopeTypes = new Map();
|
|
@@ -741,6 +756,12 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
741
756
|
const packageTypeQualifiers = new Map();
|
|
742
757
|
// Names whose scope type is a New*-prefix GUESS (fix #266) — per scope
|
|
743
758
|
const scopeGuesses = new Map();
|
|
759
|
+
// Container-typed variables (slice/map/array declared TYPE TEXT) for
|
|
760
|
+
// range-element typing (fix #300, mux-measured): `for _, route := range
|
|
761
|
+
// r.routes` types route from the field's declared []*Route. Element
|
|
762
|
+
// types are compiler-true — Go range yields the container's element.
|
|
763
|
+
const scopeContainerTypes = new Map(); // scopeStartLine -> Map<name, typeText>
|
|
764
|
+
const packageContainerTypes = new Map();
|
|
744
765
|
// Track function-typed parameter names per scope (scopeStartLine -> Set<name>)
|
|
745
766
|
const funcParamScopes = new Map();
|
|
746
767
|
|
|
@@ -804,6 +825,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
804
825
|
const typeMap = new Map();
|
|
805
826
|
const typeQualifierMap = new Map();
|
|
806
827
|
const funcParamNames = new Set();
|
|
828
|
+
const containerMap = new Map();
|
|
807
829
|
|
|
808
830
|
// Method receiver: func (f *Framework) Method()
|
|
809
831
|
if (node.type === 'method_declaration') {
|
|
@@ -844,6 +866,10 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
844
866
|
if (nameNodes.length === 0) continue;
|
|
845
867
|
if (typeNode.type === 'function_type') {
|
|
846
868
|
for (const nn of nameNodes) funcParamNames.add(nn.text);
|
|
869
|
+
} else if (CONTAINER_TYPE_NODES.has(typeNode.type)) {
|
|
870
|
+
// Container-typed parameter (fix #300): the range
|
|
871
|
+
// VALUE var over it gets the element type.
|
|
872
|
+
for (const nn of nameNodes) containerMap.set(nn.text, typeNode.text);
|
|
847
873
|
} else {
|
|
848
874
|
const typeName = extractTypeName(typeNode);
|
|
849
875
|
if (typeName) {
|
|
@@ -858,7 +884,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
858
884
|
}
|
|
859
885
|
}
|
|
860
886
|
|
|
861
|
-
return { typeMap, typeQualifierMap, funcParamNames };
|
|
887
|
+
return { typeMap, typeQualifierMap, funcParamNames, containerMap };
|
|
862
888
|
};
|
|
863
889
|
|
|
864
890
|
// Helper to extract function name from a function node
|
|
@@ -884,13 +910,13 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
884
910
|
: null;
|
|
885
911
|
};
|
|
886
912
|
|
|
887
|
-
//
|
|
888
|
-
const
|
|
913
|
+
// Resolve a local closure through the lexical function-scope chain.
|
|
914
|
+
const getLocalClosure = (name) => {
|
|
889
915
|
for (let i = functionStack.length - 1; i >= 0; i--) {
|
|
890
916
|
const scope = closureScopes.get(functionStack[i].startLine);
|
|
891
|
-
if (scope?.has(name)) return
|
|
917
|
+
if (scope?.has(name)) return scope.get(name);
|
|
892
918
|
}
|
|
893
|
-
return
|
|
919
|
+
return null;
|
|
894
920
|
};
|
|
895
921
|
|
|
896
922
|
// Check if name is a function-typed parameter (e.g., match func(Object) bool)
|
|
@@ -920,6 +946,37 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
920
946
|
return refNode && isShadowedByLocal(refNode, varName)
|
|
921
947
|
? undefined : packageTypeQualifiers.get(varName);
|
|
922
948
|
};
|
|
949
|
+
// Compiler-true receiver type from a composite-literal receiver
|
|
950
|
+
// expression (fix #298, websocket-measured — the #220(7) typing-sources
|
|
951
|
+
// family): `(&Kit{...}).Run`, `(&net.Dialer{}).DialContext(...)`,
|
|
952
|
+
// `Kit{}.Walk(...)`. Unwraps parens and the & — the literal's TYPE is the
|
|
953
|
+
// receiver's dynamic type. Anonymous types (slice/map/array/struct
|
|
954
|
+
// literals) return null: they never receive project methods.
|
|
955
|
+
const literalReceiverInfo = (operandNode) => {
|
|
956
|
+
let n = operandNode;
|
|
957
|
+
if (n?.type === 'parenthesized_expression') {
|
|
958
|
+
n = n.namedChildCount > 0 ? n.namedChild(0) : null;
|
|
959
|
+
}
|
|
960
|
+
if (n?.type === 'unary_expression') {
|
|
961
|
+
n = n.namedChildCount > 0 ? n.namedChild(0) : null;
|
|
962
|
+
}
|
|
963
|
+
if (!n || n.type !== 'composite_literal') return null;
|
|
964
|
+
let typeNode = n.childForFieldName('type');
|
|
965
|
+
if (typeNode?.type === 'generic_type') {
|
|
966
|
+
typeNode = typeNode.namedChildCount > 0 ? typeNode.namedChild(0) : null;
|
|
967
|
+
}
|
|
968
|
+
if (typeNode?.type === 'qualified_type') {
|
|
969
|
+
const pkg = typeNode.childForFieldName('package')?.text;
|
|
970
|
+
const name = typeNode.childForFieldName('name')?.text;
|
|
971
|
+
return name
|
|
972
|
+
? { receiverType: name, ...(pkg && { receiverTypeQualifier: pkg }) }
|
|
973
|
+
: null;
|
|
974
|
+
}
|
|
975
|
+
if (typeNode?.type === 'type_identifier') {
|
|
976
|
+
return { receiverType: typeNode.text };
|
|
977
|
+
}
|
|
978
|
+
return null;
|
|
979
|
+
};
|
|
923
980
|
// Is the FIRST scope-chain hit for this variable a New*-prefix GUESS
|
|
924
981
|
// (fix #266, viper-measured)? `registry := NewCodecRegistry()` types
|
|
925
982
|
// registry as 'CodecRegistry' by NAME CONVENTION — the actual return
|
|
@@ -935,6 +992,66 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
935
992
|
}
|
|
936
993
|
return false;
|
|
937
994
|
};
|
|
995
|
+
// Range-element typing helpers (fix #300). Container type TEXT for a
|
|
996
|
+
// variable declared as a slice/map/array; element extraction from that
|
|
997
|
+
// text; and a lazily built same-file struct→fields map so a selector
|
|
998
|
+
// iterable (`range r.routes`) resolves through the receiver's struct.
|
|
999
|
+
const lookupContainerType = (varName) => {
|
|
1000
|
+
for (let i = functionStack.length - 1; i >= 0; i--) {
|
|
1001
|
+
const m = scopeContainerTypes.get(functionStack[i].startLine);
|
|
1002
|
+
if (m?.has(varName)) return m.get(varName);
|
|
1003
|
+
}
|
|
1004
|
+
return packageContainerTypes.get(varName);
|
|
1005
|
+
};
|
|
1006
|
+
const CONTAINER_TYPE_NODES = new Set(['slice_type', 'map_type', 'array_type']);
|
|
1007
|
+
const containerElementType = (typeText) => {
|
|
1008
|
+
if (!typeText) return null;
|
|
1009
|
+
const t = String(typeText).trim();
|
|
1010
|
+
let m = t.match(/^\[\d*\]\s*(.+)$/s); // []T and [N]T
|
|
1011
|
+
if (!m) m = t.match(/^map\[[^\]]+\]\s*(.+)$/s); // map[K]V → V
|
|
1012
|
+
if (!m) return null;
|
|
1013
|
+
const el = m[1].trim().replace(/^\*/, '').trim();
|
|
1014
|
+
const qm = el.match(/^([A-Za-z_]\w*)\.([A-Za-z_]\w*)$/);
|
|
1015
|
+
if (qm) return { type: qm[2], qualifier: qm[1] };
|
|
1016
|
+
return /^[A-Za-z_]\w*$/.test(el) ? { type: el, qualifier: null } : null;
|
|
1017
|
+
};
|
|
1018
|
+
let structFieldsCache = null;
|
|
1019
|
+
const getStructFields = () => {
|
|
1020
|
+
if (structFieldsCache) return structFieldsCache;
|
|
1021
|
+
structFieldsCache = new Map();
|
|
1022
|
+
const stack = [tree.rootNode];
|
|
1023
|
+
while (stack.length > 0) {
|
|
1024
|
+
const n = stack.pop();
|
|
1025
|
+
if (n.type === 'type_declaration') {
|
|
1026
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
1027
|
+
const spec = n.namedChild(i);
|
|
1028
|
+
if (spec.type !== 'type_spec') continue;
|
|
1029
|
+
const nameNode = spec.childForFieldName('name');
|
|
1030
|
+
const typeNode = spec.childForFieldName('type');
|
|
1031
|
+
if (!nameNode || typeNode?.type !== 'struct_type') continue;
|
|
1032
|
+
const fields = new Map();
|
|
1033
|
+
for (let j = 0; j < typeNode.namedChildCount; j++) {
|
|
1034
|
+
const list = typeNode.namedChild(j);
|
|
1035
|
+
if (list.type !== 'field_declaration_list') continue;
|
|
1036
|
+
for (let k = 0; k < list.namedChildCount; k++) {
|
|
1037
|
+
const fd = list.namedChild(k);
|
|
1038
|
+
if (fd.type !== 'field_declaration') continue;
|
|
1039
|
+
const ftype = fd.childForFieldName('type');
|
|
1040
|
+
if (!ftype) continue;
|
|
1041
|
+
for (let x = 0; x < fd.namedChildCount; x++) {
|
|
1042
|
+
const fn = fd.namedChild(x);
|
|
1043
|
+
if (fn.type === 'field_identifier') fields.set(fn.text, ftype.text);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
structFieldsCache.set(nameNode.text, fields);
|
|
1048
|
+
}
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
for (let i = 0; i < n.namedChildCount; i++) stack.push(n.namedChild(i));
|
|
1052
|
+
}
|
|
1053
|
+
return structFieldsCache;
|
|
1054
|
+
};
|
|
938
1055
|
|
|
939
1056
|
// fix #203 (Go): is a bare-identifier function REFERENCE shadowed by an
|
|
940
1057
|
// enclosing func-literal/function parameter, method receiver, range/init
|
|
@@ -1043,14 +1160,61 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1043
1160
|
endLine: node.endPosition.row + 1
|
|
1044
1161
|
};
|
|
1045
1162
|
functionStack.push(entry);
|
|
1046
|
-
const { typeMap, typeQualifierMap, funcParamNames } = buildScopeTypeMap(node);
|
|
1163
|
+
const { typeMap, typeQualifierMap, funcParamNames, containerMap } = buildScopeTypeMap(node);
|
|
1047
1164
|
scopeTypes.set(entry.startLine, typeMap);
|
|
1048
1165
|
scopeTypeQualifiers.set(entry.startLine, typeQualifierMap);
|
|
1166
|
+
scopeContainerTypes.set(entry.startLine, containerMap || new Map());
|
|
1049
1167
|
if (funcParamNames.size > 0) {
|
|
1050
1168
|
funcParamScopes.set(entry.startLine, funcParamNames);
|
|
1051
1169
|
}
|
|
1052
1170
|
}
|
|
1053
1171
|
|
|
1172
|
+
// Range-element typing (fix #300, mux-measured): `for _, route :=
|
|
1173
|
+
// range r.routes` — the VALUE variable's compile-time type is the
|
|
1174
|
+
// container's element type. Sources: a selector iterable whose root
|
|
1175
|
+
// is a typed var and whose field's declaring struct lives in this
|
|
1176
|
+
// file (declared field text `[]*Route`), or an identifier iterable
|
|
1177
|
+
// declared/parameterized with a container type. Only the two-var
|
|
1178
|
+
// form's SECOND variable is typed (single-var range yields the
|
|
1179
|
+
// index/key); untypeable shapes stay untyped — never guessed.
|
|
1180
|
+
if (node.type === 'range_clause' && functionStack.length > 0) {
|
|
1181
|
+
const left = node.childForFieldName('left');
|
|
1182
|
+
const right = node.childForFieldName('right');
|
|
1183
|
+
const vars = left && left.type === 'expression_list'
|
|
1184
|
+
? Array.from({ length: left.namedChildCount }, (_, i) => left.namedChild(i))
|
|
1185
|
+
: [];
|
|
1186
|
+
const valueVar = vars.length === 2 && vars[1].type === 'identifier' &&
|
|
1187
|
+
vars[1].text !== '_' ? vars[1].text : null;
|
|
1188
|
+
if (valueVar && right) {
|
|
1189
|
+
let containerText = null;
|
|
1190
|
+
if (right.type === 'selector_expression') {
|
|
1191
|
+
const operand = right.childForFieldName('operand');
|
|
1192
|
+
const fieldN = right.childForFieldName('field');
|
|
1193
|
+
if (operand?.type === 'identifier' && fieldN &&
|
|
1194
|
+
!isGuessedType(operand.text)) {
|
|
1195
|
+
const rootType = getReceiverType(operand.text, operand);
|
|
1196
|
+
if (rootType) {
|
|
1197
|
+
containerText = getStructFields().get(rootType)?.get(fieldN.text) || null;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
} else if (right.type === 'identifier') {
|
|
1201
|
+
containerText = lookupContainerType(right.text);
|
|
1202
|
+
}
|
|
1203
|
+
const el = containerElementType(containerText);
|
|
1204
|
+
if (el) {
|
|
1205
|
+
const scopeKey = functionStack[functionStack.length - 1].startLine;
|
|
1206
|
+
const typeMap = scopeTypes.get(scopeKey);
|
|
1207
|
+
if (typeMap) {
|
|
1208
|
+
typeMap.set(valueVar, el.type);
|
|
1209
|
+
const qualifiers = scopeTypeQualifiers.get(scopeKey);
|
|
1210
|
+
if (el.qualifier) qualifiers?.set(valueVar, el.qualifier);
|
|
1211
|
+
else qualifiers?.delete(valueVar);
|
|
1212
|
+
scopeGuesses.get(scopeKey)?.delete(valueVar);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1054
1218
|
// Track local variable types from composite literals and typed assignments
|
|
1055
1219
|
// e.g., s := &Status{...} → s has type Status
|
|
1056
1220
|
// registry := Registry{...} → registry has type Registry
|
|
@@ -1076,6 +1240,13 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1076
1240
|
// &Type{...} or Type{...}
|
|
1077
1241
|
if (val.type === 'composite_literal') {
|
|
1078
1242
|
const typeNode = val.childForFieldName('type');
|
|
1243
|
+
if (typeNode && CONTAINER_TYPE_NODES.has(typeNode.type)) {
|
|
1244
|
+
// xs := []*Route{...} — container literal
|
|
1245
|
+
// feeds range-element typing (fix #300).
|
|
1246
|
+
scopeContainerTypes.get(scopeKey)
|
|
1247
|
+
?.set(names[vi], typeNode.text);
|
|
1248
|
+
continue;
|
|
1249
|
+
}
|
|
1079
1250
|
typeName = extractTypeName(typeNode);
|
|
1080
1251
|
typeQualifier = extractTypeQualifier(typeNode);
|
|
1081
1252
|
} else if (val.type === 'unary_expression' && val.childCount > 0) {
|
|
@@ -1130,6 +1301,24 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1130
1301
|
}
|
|
1131
1302
|
}
|
|
1132
1303
|
}
|
|
1304
|
+
} else if (val.type === 'type_assertion_expression') {
|
|
1305
|
+
// d, ok := dialer.(proxy.ContextDialer) — the
|
|
1306
|
+
// asserted type IS d's static type (fix #298,
|
|
1307
|
+
// websocket-measured: `return d.DialContext, nil`
|
|
1308
|
+
// through the assertion). Compiler-true.
|
|
1309
|
+
const typeNode = val.childForFieldName('type');
|
|
1310
|
+
typeName = extractTypeName(typeNode);
|
|
1311
|
+
typeQualifier = extractTypeQualifier(typeNode);
|
|
1312
|
+
} else if (val.type === 'identifier') {
|
|
1313
|
+
// Var-copy: d := cstDialer — Go is statically
|
|
1314
|
+
// typed, so the copy shares the source variable's
|
|
1315
|
+
// type (fix #298; guess-ness propagates).
|
|
1316
|
+
typeName = getReceiverType(val.text, val) || null;
|
|
1317
|
+
if (typeName) {
|
|
1318
|
+
typeQualifier =
|
|
1319
|
+
getReceiverTypeQualifier(val.text, val) || null;
|
|
1320
|
+
typeGuessed = isGuessedType(val.text);
|
|
1321
|
+
}
|
|
1133
1322
|
}
|
|
1134
1323
|
if (typeName) {
|
|
1135
1324
|
typeMap.set(names[vi], typeName);
|
|
@@ -1161,11 +1350,46 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1161
1350
|
const varQualifierMap = scopeKey == null
|
|
1162
1351
|
? packageTypeQualifiers : scopeTypeQualifiers.get(scopeKey);
|
|
1163
1352
|
if (varTypeMap) {
|
|
1353
|
+
const varContainerMap = scopeKey == null
|
|
1354
|
+
? packageContainerTypes : scopeContainerTypes.get(scopeKey);
|
|
1164
1355
|
const recordSpec = (spec) => {
|
|
1165
1356
|
if (spec.type !== 'var_spec') return;
|
|
1166
|
-
const
|
|
1167
|
-
|
|
1168
|
-
|
|
1357
|
+
const declaredType = spec.childForFieldName('type');
|
|
1358
|
+
// Container-typed declaration (fix #300): `var routes
|
|
1359
|
+
// []*Route` feeds range-element typing.
|
|
1360
|
+
if (declaredType && CONTAINER_TYPE_NODES.has(declaredType.type) &&
|
|
1361
|
+
varContainerMap) {
|
|
1362
|
+
for (let j = 0; j < spec.namedChildCount; j++) {
|
|
1363
|
+
const id = spec.namedChild(j);
|
|
1364
|
+
if (id.type === 'identifier') {
|
|
1365
|
+
varContainerMap.set(id.text, declaredType.text);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
let typeName = extractTypeName(spec.childForFieldName('type'));
|
|
1371
|
+
let qualifier = typeName
|
|
1372
|
+
? extractTypeQualifier(spec.childForFieldName('type')) : null;
|
|
1373
|
+
if (!typeName) {
|
|
1374
|
+
// var cstDialer = Dialer{...} — the initializer's
|
|
1375
|
+
// composite-literal type IS the variable's type
|
|
1376
|
+
// (fix #298, websocket-measured: `d := cstDialer`
|
|
1377
|
+
// copies then call DialContext). Single-value specs
|
|
1378
|
+
// only; multi-value pairing stays untyped.
|
|
1379
|
+
const valueNode = spec.childForFieldName('value');
|
|
1380
|
+
let init = valueNode?.type === 'expression_list' &&
|
|
1381
|
+
valueNode.namedChildCount === 1
|
|
1382
|
+
? valueNode.namedChild(0) : null;
|
|
1383
|
+
if (init?.type === 'unary_expression') {
|
|
1384
|
+
init = init.namedChildCount > 0 ? init.namedChild(0) : null;
|
|
1385
|
+
}
|
|
1386
|
+
if (init?.type === 'composite_literal') {
|
|
1387
|
+
const tn = init.childForFieldName('type');
|
|
1388
|
+
typeName = extractTypeName(tn);
|
|
1389
|
+
qualifier = extractTypeQualifier(tn);
|
|
1390
|
+
}
|
|
1391
|
+
if (!typeName) return;
|
|
1392
|
+
}
|
|
1169
1393
|
for (let j = 0; j < spec.namedChildCount; j++) {
|
|
1170
1394
|
const id = spec.namedChild(j);
|
|
1171
1395
|
if (id.type === 'identifier') {
|
|
@@ -1188,52 +1412,66 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1188
1412
|
}
|
|
1189
1413
|
}
|
|
1190
1414
|
|
|
1191
|
-
// Track local closures: atoi := func(...) { ... } or
|
|
1415
|
+
// Track local closures: atoi := func(...) { ... } or
|
|
1416
|
+
// var handler = func(...) { ... }. Pair each lexical name with the
|
|
1417
|
+
// literal's declared result so an invocation can type its assignment
|
|
1418
|
+
// without pretending it calls a same-named package symbol.
|
|
1192
1419
|
if (node.type === 'short_var_declaration' || node.type === 'var_declaration') {
|
|
1193
|
-
|
|
1194
|
-
const hasFunc = (n) => {
|
|
1420
|
+
const firstFuncLiteral = (n) => {
|
|
1195
1421
|
if (!n) return false;
|
|
1196
|
-
if (n.type === 'func_literal') return
|
|
1422
|
+
if (n.type === 'func_literal') return n;
|
|
1197
1423
|
for (let i = 0; i < n.childCount; i++) {
|
|
1198
|
-
|
|
1424
|
+
const found = firstFuncLiteral(n.child(i));
|
|
1425
|
+
if (found) return found;
|
|
1199
1426
|
}
|
|
1200
|
-
return
|
|
1427
|
+
return null;
|
|
1201
1428
|
};
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1429
|
+
const entries = [];
|
|
1430
|
+
const pairNamesWithValues = (left, right) => {
|
|
1431
|
+
if (!left || !right) return;
|
|
1432
|
+
const names = left.type === 'expression_list'
|
|
1433
|
+
? left.namedChildren.filter(child => child.type === 'identifier')
|
|
1434
|
+
: left.type === 'identifier' ? [left] : [];
|
|
1435
|
+
const values = right.type === 'expression_list'
|
|
1436
|
+
? right.namedChildren : [right];
|
|
1437
|
+
for (let i = 0; i < names.length; i++) {
|
|
1438
|
+
const literal = firstFuncLiteral(values[i] ||
|
|
1439
|
+
(values.length === 1 ? values[0] : null));
|
|
1440
|
+
if (literal) entries.push({
|
|
1441
|
+
name: names[i].text,
|
|
1442
|
+
returnType: extractReturnType(literal),
|
|
1443
|
+
});
|
|
1213
1444
|
}
|
|
1445
|
+
};
|
|
1446
|
+
if (node.type === 'short_var_declaration') {
|
|
1447
|
+
pairNamesWithValues(
|
|
1448
|
+
node.childForFieldName('left'),
|
|
1449
|
+
node.childForFieldName('right'));
|
|
1214
1450
|
} else {
|
|
1215
|
-
// var_declaration: check per-spec so only names with
|
|
1451
|
+
// var_declaration: check per-spec so only names paired with
|
|
1452
|
+
// func_literal values are tracked.
|
|
1216
1453
|
// Handle both: var x = func(){} (var_declaration > var_spec)
|
|
1217
1454
|
// and: var (\n x = func(){} \n) (var_declaration > var_spec_list > var_spec)
|
|
1218
|
-
const
|
|
1455
|
+
const collectClosureEntries = (parent) => {
|
|
1219
1456
|
for (let i = 0; i < parent.namedChildCount; i++) {
|
|
1220
1457
|
const child = parent.namedChild(i);
|
|
1221
|
-
if (child.type === 'var_spec'
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
}
|
|
1458
|
+
if (child.type === 'var_spec') {
|
|
1459
|
+
pairNamesWithValues(
|
|
1460
|
+
child.childForFieldName('name'),
|
|
1461
|
+
child.childForFieldName('value'));
|
|
1226
1462
|
} else if (child.type === 'var_spec_list') {
|
|
1227
|
-
|
|
1463
|
+
collectClosureEntries(child);
|
|
1228
1464
|
}
|
|
1229
1465
|
}
|
|
1230
1466
|
};
|
|
1231
|
-
|
|
1467
|
+
collectClosureEntries(node);
|
|
1232
1468
|
}
|
|
1233
|
-
if (
|
|
1469
|
+
if (entries.length > 0 && functionStack.length > 0) {
|
|
1234
1470
|
const scopeKey = functionStack[functionStack.length - 1].startLine;
|
|
1235
|
-
if (!closureScopes.has(scopeKey)) closureScopes.set(scopeKey, new
|
|
1236
|
-
for (const
|
|
1471
|
+
if (!closureScopes.has(scopeKey)) closureScopes.set(scopeKey, new Map());
|
|
1472
|
+
for (const entry of entries) {
|
|
1473
|
+
closureScopes.get(scopeKey).set(entry.name, entry);
|
|
1474
|
+
}
|
|
1237
1475
|
}
|
|
1238
1476
|
}
|
|
1239
1477
|
|
|
@@ -1269,8 +1507,42 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1269
1507
|
const callName = funcNode.text;
|
|
1270
1508
|
// Skip Go built-in function calls
|
|
1271
1509
|
if (GO_BUILTINS.has(callName)) return true;
|
|
1272
|
-
//
|
|
1273
|
-
|
|
1510
|
+
// Local closures shadow package-level functions. Keep an
|
|
1511
|
+
// internal-only call record when their declared result can
|
|
1512
|
+
// type an assignment; the sentinel name prevents semantic
|
|
1513
|
+
// caller matching against a package symbol of the same name.
|
|
1514
|
+
const localClosure = getLocalClosure(callName);
|
|
1515
|
+
if (localClosure) {
|
|
1516
|
+
if (assigned && localClosure.returnType) {
|
|
1517
|
+
calls.push({
|
|
1518
|
+
name: '<local-closure>',
|
|
1519
|
+
localCallName: callName,
|
|
1520
|
+
localValueCall: true,
|
|
1521
|
+
returnTypeHint: localClosure.returnType,
|
|
1522
|
+
line: node.startPosition.row + 1,
|
|
1523
|
+
column: funcNode.startPosition.column,
|
|
1524
|
+
callStart: node.startIndex,
|
|
1525
|
+
callEnd: node.endIndex,
|
|
1526
|
+
isMethod: false,
|
|
1527
|
+
argCount,
|
|
1528
|
+
...(argSpread && { argSpread: true }),
|
|
1529
|
+
assignedTo: assigned.assignedTo,
|
|
1530
|
+
...(assigned.assignedTuple && { assignedTuple: true }),
|
|
1531
|
+
...(assigned.assignedTupleIndex != null && {
|
|
1532
|
+
assignedTupleIndex: assigned.assignedTupleIndex,
|
|
1533
|
+
}),
|
|
1534
|
+
...(assigned.assignedTupleTargets && {
|
|
1535
|
+
assignedTupleTargets: assigned.assignedTupleTargets,
|
|
1536
|
+
}),
|
|
1537
|
+
...(assigned.assignedTupleRest && {
|
|
1538
|
+
assignedTupleRest: assigned.assignedTupleRest,
|
|
1539
|
+
}),
|
|
1540
|
+
enclosingFunction,
|
|
1541
|
+
uncertain: false,
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
return true;
|
|
1545
|
+
}
|
|
1274
1546
|
// Skip calls to function-typed parameters (e.g., match func(Object) bool)
|
|
1275
1547
|
// These are local parameter invocations, not calls to global functions
|
|
1276
1548
|
if (isFuncTypedParam(callName)) return true;
|
|
@@ -1280,12 +1552,21 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1280
1552
|
calls.push({
|
|
1281
1553
|
name: callName,
|
|
1282
1554
|
line: node.startPosition.row + 1,
|
|
1555
|
+
column: funcNode.startPosition.column,
|
|
1556
|
+
callStart: node.startIndex,
|
|
1557
|
+
callEnd: node.endIndex,
|
|
1283
1558
|
isMethod: false,
|
|
1284
1559
|
argCount,
|
|
1285
1560
|
...(argSpread && { argSpread: true }),
|
|
1286
1561
|
...(assigned && { assignedTo: assigned.assignedTo }),
|
|
1287
1562
|
...(assigned?.assignedTuple && { assignedTuple: true }),
|
|
1288
|
-
|
|
1563
|
+
...(assigned?.assignedTupleIndex != null && {
|
|
1564
|
+
assignedTupleIndex: assigned.assignedTupleIndex,
|
|
1565
|
+
}),
|
|
1566
|
+
...(assigned?.assignedTupleTargets && {
|
|
1567
|
+
assignedTupleTargets: assigned.assignedTupleTargets,
|
|
1568
|
+
}),
|
|
1569
|
+
...(assigned?.assignedTupleRest && { assignedTupleRest: assigned.assignedTupleRest }),
|
|
1289
1570
|
enclosingFunction,
|
|
1290
1571
|
uncertain,
|
|
1291
1572
|
...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp })
|
|
@@ -1300,10 +1581,19 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1300
1581
|
// Distinguish pkg.Func() (package-qualified) from obj.Method()
|
|
1301
1582
|
// If receiver is a known import alias, this is a package call, not a method call
|
|
1302
1583
|
const isPkgCall = receiver && importAliases.has(receiver);
|
|
1303
|
-
|
|
1584
|
+
let receiverType = (!isPkgCall && receiver)
|
|
1304
1585
|
? getReceiverType(receiver, operandNode) : undefined;
|
|
1305
|
-
|
|
1586
|
+
let receiverTypeQualifier = receiverType
|
|
1306
1587
|
? getReceiverTypeQualifier(receiver, operandNode) : undefined;
|
|
1588
|
+
// Composite-literal receiver (fix #298):
|
|
1589
|
+
// (&Kit{...}).Run(...) — compiler-true type, never guessed.
|
|
1590
|
+
if (!receiver && !receiverType) {
|
|
1591
|
+
const lit = literalReceiverInfo(operandNode);
|
|
1592
|
+
if (lit) {
|
|
1593
|
+
receiverType = lit.receiverType;
|
|
1594
|
+
receiverTypeQualifier = lit.receiverTypeQualifier;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1307
1597
|
// fix #202: one-hop declared-field receivers — h.inner.Run().
|
|
1308
1598
|
// receiverRoot/Field/RootType let findCallers hop to the
|
|
1309
1599
|
// field's declared struct-field type cross-file.
|
|
@@ -1338,14 +1628,34 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1338
1628
|
// declared return (*pflag.FlagSet → external → routed).
|
|
1339
1629
|
// Package-qualified producers (os.CreateTemp().Name())
|
|
1340
1630
|
// carry the qualifier for strict import-package resolution.
|
|
1341
|
-
let receiverCall, receiverCallIsMethod, receiverCallReceiver,
|
|
1631
|
+
let receiverCall, receiverCallIsMethod, receiverCallReceiver,
|
|
1632
|
+
receiverCallLine, receiverCallStart, receiverCallEnd,
|
|
1633
|
+
receiverCallResultType, receiverCallResultTypeQualifier;
|
|
1342
1634
|
if (!receiver && !receiverFieldName && operandNode?.type === 'call_expression') {
|
|
1343
1635
|
const prodFunc = operandNode.childForFieldName('function');
|
|
1636
|
+
receiverCallStart = operandNode.startIndex;
|
|
1637
|
+
receiverCallEnd = operandNode.endIndex;
|
|
1344
1638
|
if (prodFunc?.type === 'identifier') {
|
|
1345
1639
|
receiverCall = prodFunc.text;
|
|
1346
1640
|
// Producer link (fix #258): plain-call records carry
|
|
1347
1641
|
// the call node's start line
|
|
1348
1642
|
receiverCallLine = operandNode.startPosition.row + 1;
|
|
1643
|
+
// Go's builtin new has an exact static result:
|
|
1644
|
+
// `new(Route)` is `*Route`. The builtin itself is
|
|
1645
|
+
// intentionally absent from the semantic call
|
|
1646
|
+
// index, so carry the result type on its consumer.
|
|
1647
|
+
if (receiverCall === 'new') {
|
|
1648
|
+
const prodArgs = operandNode.childForFieldName('arguments');
|
|
1649
|
+
const typeArg = prodArgs?.namedChild(0);
|
|
1650
|
+
if (typeArg) {
|
|
1651
|
+
const authored = typeArg.text.replace(/^\*+/, '').trim();
|
|
1652
|
+
const parts = authored.split('.');
|
|
1653
|
+
receiverCallResultType = parts.pop();
|
|
1654
|
+
if (parts.length > 0) {
|
|
1655
|
+
receiverCallResultTypeQualifier = parts.join('.');
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1349
1659
|
} else if (prodFunc?.type === 'selector_expression') {
|
|
1350
1660
|
const pf = prodFunc.childForFieldName('field');
|
|
1351
1661
|
const po = prodFunc.childForFieldName('operand');
|
|
@@ -1372,6 +1682,9 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1372
1682
|
// set and the oracles key by the name's line; Go was
|
|
1373
1683
|
// the only parser still using the call node's start.
|
|
1374
1684
|
line: fieldNode.startPosition.row + 1,
|
|
1685
|
+
column: fieldNode.startPosition.column,
|
|
1686
|
+
callStart: node.startIndex,
|
|
1687
|
+
callEnd: node.endIndex,
|
|
1375
1688
|
isMethod: !isPkgCall,
|
|
1376
1689
|
receiver,
|
|
1377
1690
|
...(receiverType && { receiverType }),
|
|
@@ -1386,10 +1699,22 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1386
1699
|
...(receiverCallIsMethod && { receiverCallIsMethod: true }),
|
|
1387
1700
|
...(receiverCallReceiver && { receiverCallReceiver }),
|
|
1388
1701
|
...(receiverCallLine && { receiverCallLine }),
|
|
1702
|
+
...(receiverCallStart != null && { receiverCallStart }),
|
|
1703
|
+
...(receiverCallEnd != null && { receiverCallEnd }),
|
|
1704
|
+
...(receiverCallResultType && { receiverCallResultType }),
|
|
1705
|
+
...(receiverCallResultTypeQualifier && {
|
|
1706
|
+
receiverCallResultTypeQualifier,
|
|
1707
|
+
}),
|
|
1389
1708
|
argCount,
|
|
1390
1709
|
...(argSpread && { argSpread: true }),
|
|
1391
1710
|
...(assigned && { assignedTo: assigned.assignedTo }),
|
|
1392
1711
|
...(assigned?.assignedTuple && { assignedTuple: true }),
|
|
1712
|
+
...(assigned?.assignedTupleIndex != null && {
|
|
1713
|
+
assignedTupleIndex: assigned.assignedTupleIndex,
|
|
1714
|
+
}),
|
|
1715
|
+
...(assigned?.assignedTupleTargets && {
|
|
1716
|
+
assignedTupleTargets: assigned.assignedTupleTargets,
|
|
1717
|
+
}),
|
|
1393
1718
|
...(assigned?.assignedTupleRest && { assignedTupleRest: assigned.assignedTupleRest }),
|
|
1394
1719
|
enclosingFunction,
|
|
1395
1720
|
uncertain,
|
|
@@ -1437,6 +1762,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1437
1762
|
calls.push({
|
|
1438
1763
|
name: typeName,
|
|
1439
1764
|
line: node.startPosition.row + 1,
|
|
1765
|
+
column: (typeNode.childForFieldName?.('name') || typeNode).startPosition.column,
|
|
1440
1766
|
isMethod: false,
|
|
1441
1767
|
isConstructor: true,
|
|
1442
1768
|
...(typeQualifier && { receiver: typeQualifier }),
|
|
@@ -1457,19 +1783,29 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1457
1783
|
const operandNode = node.childForFieldName('operand');
|
|
1458
1784
|
if (fieldNode && operandNode) {
|
|
1459
1785
|
const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
|
|
1460
|
-
|
|
1786
|
+
let receiverType = receiver
|
|
1461
1787
|
? getReceiverType(receiver, operandNode) : undefined;
|
|
1462
|
-
|
|
1788
|
+
let receiverTypeQualifier = receiverType
|
|
1463
1789
|
? getReceiverTypeQualifier(receiver, operandNode) : undefined;
|
|
1790
|
+
// Composite-literal receiver (fix #298): the literal's
|
|
1791
|
+
// type is compiler-true, never guessed.
|
|
1792
|
+
if (!receiver && !receiverType) {
|
|
1793
|
+
const lit = literalReceiverInfo(operandNode);
|
|
1794
|
+
if (lit) {
|
|
1795
|
+
receiverType = lit.receiverType;
|
|
1796
|
+
receiverTypeQualifier = lit.receiverTypeQualifier;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1464
1799
|
const enclosingFunction = getCurrentEnclosingFunction();
|
|
1465
1800
|
calls.push({
|
|
1466
1801
|
name: fieldNode.text,
|
|
1467
|
-
line:
|
|
1802
|
+
line: fieldNode.startPosition.row + 1,
|
|
1803
|
+
column: fieldNode.startPosition.column,
|
|
1468
1804
|
isMethod: true,
|
|
1469
1805
|
receiver,
|
|
1470
1806
|
...(receiverType && { receiverType }),
|
|
1471
1807
|
...(receiverTypeQualifier && { receiverTypeQualifier }),
|
|
1472
|
-
...(receiverType && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
1808
|
+
...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
1473
1809
|
enclosingFunction,
|
|
1474
1810
|
isPotentialCallback: true,
|
|
1475
1811
|
uncertain: false
|
|
@@ -1487,6 +1823,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1487
1823
|
calls.push({
|
|
1488
1824
|
name,
|
|
1489
1825
|
line: node.startPosition.row + 1,
|
|
1826
|
+
column: node.startPosition.column,
|
|
1490
1827
|
isMethod: false,
|
|
1491
1828
|
isFunctionReference: true,
|
|
1492
1829
|
isPotentialCallback: true,
|
|
@@ -1517,19 +1854,28 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1517
1854
|
const operandNode = rhs.childForFieldName('operand');
|
|
1518
1855
|
if (fieldNode && operandNode) {
|
|
1519
1856
|
const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
|
|
1520
|
-
|
|
1857
|
+
let receiverType = receiver
|
|
1521
1858
|
? getReceiverType(receiver, operandNode) : undefined;
|
|
1522
|
-
|
|
1859
|
+
let receiverTypeQualifier = receiverType
|
|
1523
1860
|
? getReceiverTypeQualifier(receiver, operandNode) : undefined;
|
|
1861
|
+
// Composite-literal receiver (fix #298).
|
|
1862
|
+
if (!receiver && !receiverType) {
|
|
1863
|
+
const lit = literalReceiverInfo(operandNode);
|
|
1864
|
+
if (lit) {
|
|
1865
|
+
receiverType = lit.receiverType;
|
|
1866
|
+
receiverTypeQualifier = lit.receiverTypeQualifier;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1524
1869
|
const enclosingFunction = getCurrentEnclosingFunction();
|
|
1525
1870
|
calls.push({
|
|
1526
1871
|
name: fieldNode.text,
|
|
1527
|
-
line:
|
|
1872
|
+
line: fieldNode.startPosition.row + 1,
|
|
1873
|
+
column: fieldNode.startPosition.column,
|
|
1528
1874
|
isMethod: true,
|
|
1529
1875
|
receiver,
|
|
1530
1876
|
...(receiverType && { receiverType }),
|
|
1531
1877
|
...(receiverTypeQualifier && { receiverTypeQualifier }),
|
|
1532
|
-
...(receiverType && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
1878
|
+
...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
1533
1879
|
enclosingFunction,
|
|
1534
1880
|
isPotentialCallback: true,
|
|
1535
1881
|
uncertain: false
|
|
@@ -1544,6 +1890,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1544
1890
|
calls.push({
|
|
1545
1891
|
name,
|
|
1546
1892
|
line: rhs.startPosition.row + 1,
|
|
1893
|
+
column: rhs.startPosition.column,
|
|
1547
1894
|
isMethod: false,
|
|
1548
1895
|
isFunctionReference: true,
|
|
1549
1896
|
isPotentialCallback: true,
|
|
@@ -1557,6 +1904,51 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1557
1904
|
}
|
|
1558
1905
|
}
|
|
1559
1906
|
|
|
1907
|
+
// Method values in RETURN position (fix #298, websocket-measured):
|
|
1908
|
+
// `return (&httpProxyDialer{...}).DialContext, nil` is the
|
|
1909
|
+
// compile-breaking reference a rename sweep must see — it was
|
|
1910
|
+
// invisible (no record at all). Selector values only; plain
|
|
1911
|
+
// identifier returns (`return err`) stay unemitted — every returned
|
|
1912
|
+
// variable would join the calls cache (classified-deferred).
|
|
1913
|
+
if (node.type === 'return_statement') {
|
|
1914
|
+
const exprList = node.namedChildCount > 0 ? node.namedChild(0) : null;
|
|
1915
|
+
const values = exprList?.type === 'expression_list'
|
|
1916
|
+
? exprList.namedChildren
|
|
1917
|
+
: (exprList ? [exprList] : []);
|
|
1918
|
+
for (const val of values) {
|
|
1919
|
+
if (val.type !== 'selector_expression') continue;
|
|
1920
|
+
const fieldNode = val.childForFieldName('field');
|
|
1921
|
+
const operandNode = val.childForFieldName('operand');
|
|
1922
|
+
if (!fieldNode || !operandNode) continue;
|
|
1923
|
+
const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
|
|
1924
|
+
let receiverType = receiver
|
|
1925
|
+
? getReceiverType(receiver, operandNode) : undefined;
|
|
1926
|
+
let receiverTypeQualifier = receiverType
|
|
1927
|
+
? getReceiverTypeQualifier(receiver, operandNode) : undefined;
|
|
1928
|
+
if (!receiver && !receiverType) {
|
|
1929
|
+
const lit = literalReceiverInfo(operandNode);
|
|
1930
|
+
if (lit) {
|
|
1931
|
+
receiverType = lit.receiverType;
|
|
1932
|
+
receiverTypeQualifier = lit.receiverTypeQualifier;
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
calls.push({
|
|
1936
|
+
name: fieldNode.text,
|
|
1937
|
+
// #223 name-node convention: the field's own line.
|
|
1938
|
+
line: fieldNode.startPosition.row + 1,
|
|
1939
|
+
column: fieldNode.startPosition.column,
|
|
1940
|
+
isMethod: true,
|
|
1941
|
+
receiver,
|
|
1942
|
+
...(receiverType && { receiverType }),
|
|
1943
|
+
...(receiverTypeQualifier && { receiverTypeQualifier }),
|
|
1944
|
+
...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
1945
|
+
enclosingFunction: getCurrentEnclosingFunction(),
|
|
1946
|
+
isPotentialCallback: true,
|
|
1947
|
+
uncertain: false
|
|
1948
|
+
});
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1560
1952
|
// Detect function references in composite literal fields:
|
|
1561
1953
|
// ResourceEventHandlerFuncs{AddFunc: addNodeToCache, UpdateFunc: updateNode}
|
|
1562
1954
|
// keyed_element → literal_element(key) ":" literal_element(value)
|
|
@@ -1600,6 +1992,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1600
1992
|
calls.push({
|
|
1601
1993
|
name,
|
|
1602
1994
|
line: valueNode.startPosition.row + 1,
|
|
1995
|
+
column: valueNode.startPosition.column,
|
|
1603
1996
|
isMethod: false,
|
|
1604
1997
|
isFunctionReference: true,
|
|
1605
1998
|
isPotentialCallback: true,
|
|
@@ -1616,19 +2009,28 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1616
2009
|
const operandNode = valueNode.childForFieldName('operand');
|
|
1617
2010
|
if (fieldNode && operandNode) {
|
|
1618
2011
|
const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
|
|
1619
|
-
|
|
2012
|
+
let receiverType = receiver
|
|
1620
2013
|
? getReceiverType(receiver, operandNode) : undefined;
|
|
1621
|
-
|
|
2014
|
+
let receiverTypeQualifier = receiverType
|
|
1622
2015
|
? getReceiverTypeQualifier(receiver, operandNode) : undefined;
|
|
2016
|
+
// Composite-literal receiver (fix #298).
|
|
2017
|
+
if (!receiver && !receiverType) {
|
|
2018
|
+
const lit = literalReceiverInfo(operandNode);
|
|
2019
|
+
if (lit) {
|
|
2020
|
+
receiverType = lit.receiverType;
|
|
2021
|
+
receiverTypeQualifier = lit.receiverTypeQualifier;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
1623
2024
|
const enclosingFunction = getCurrentEnclosingFunction();
|
|
1624
2025
|
calls.push({
|
|
1625
2026
|
name: fieldNode.text,
|
|
1626
|
-
line:
|
|
2027
|
+
line: fieldNode.startPosition.row + 1,
|
|
2028
|
+
column: fieldNode.startPosition.column,
|
|
1627
2029
|
isMethod: true,
|
|
1628
2030
|
receiver,
|
|
1629
2031
|
...(receiverType && { receiverType }),
|
|
1630
2032
|
...(receiverTypeQualifier && { receiverTypeQualifier }),
|
|
1631
|
-
...(receiverType && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
2033
|
+
...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
1632
2034
|
enclosingFunction,
|
|
1633
2035
|
isPotentialCallback: true,
|
|
1634
2036
|
uncertain: false,
|