* Resolve a Go 2-hop field-chain call `base.field.Method(...)` (#1276): * `target.conn.Exec("insert")` where `func (target *Target) Write()` and * `type Target struct { conn *sql.DB }`. Two inference hops, both read from * source the same way #1108 does: * 1. `base`'s type from the enclosing s
( receiverChain: string, methodName: string, ref: UnresolvedRef, context: ResolutionContext )
| 1929 | * receivers, so the ref stays unresolved instead of name-guessing. |
| 1930 | */ |
| 1931 | function matchGoFieldChainCall( |
| 1932 | receiverChain: string, |
| 1933 | methodName: string, |
| 1934 | ref: UnresolvedRef, |
| 1935 | context: ResolutionContext |
| 1936 | ): ResolvedRef | null { |
| 1937 | const segs = receiverChain.split('.'); |
| 1938 | if (segs.length !== 2 || !segs[0] || !segs[1]) return null; |
| 1939 | const [base, field] = segs; |
| 1940 | |
| 1941 | const baseType = inferLocalReceiverType(base!, ref, context); |
| 1942 | if (!baseType) return null; |
| 1943 | |
| 1944 | const fieldEsc = field!.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
| 1945 | const fieldTypeRe = new RegExp(`\\b${fieldEsc}\\s+\\*?\\[?\\]?([A-Za-z_][\\w.]*)`); |
| 1946 | |
| 1947 | const structs = preferCallSiteFile(context.getNodesByName(baseType), ref.filePath).filter( |
| 1948 | (n) => (n.kind === 'struct' || n.kind === 'class') && n.language === 'go' |
| 1949 | ); |
| 1950 | for (const s of structs) { |
| 1951 | const source = context.readFile(s.filePath); |
| 1952 | if (!source) continue; |
| 1953 | // Only the struct's own declaration lines — a same-named identifier |
| 1954 | // elsewhere in the file can't donate a type. Matched LINE BY LINE with |
| 1955 | // comments stripped: chi's `Mux` has a doc comment reading "the tree |
| 1956 | // router" right above `tree *node`, and a whole-block match captured |
| 1957 | // `router` from the prose instead of `node` from the field. |
| 1958 | const declLines = source.split('\n').slice(Math.max(0, s.startLine - 1), s.endLine); |
| 1959 | for (const rawLine of declLines) { |
| 1960 | const line = rawLine.replace(/\/\/.*$/, '').replace(/\/\*.*?\*\//g, ''); |
| 1961 | const m = line.match(fieldTypeRe); |
| 1962 | if (!m || !m[1]) continue; |
| 1963 | const rawType = m[1]; |
| 1964 | // A package-qualified field type (`http.Handler`, `sql.DB`) is only |
| 1965 | // followed when the package is IN-MODULE: stripping the qualifier and |
| 1966 | // matching the bare name would conflate a stdlib/third-party type with |
| 1967 | // any same-named project type — on chi, `handler http.Handler` bound |
| 1968 | // to an example app's unrelated local `Handler`. That is the exact |
| 1969 | // fabrication this matcher exists to prevent (#1276). |
| 1970 | if (rawType.includes('.')) { |
| 1971 | const pkg = rawType.split('.')[0]!; |
| 1972 | const mod = context.getGoModule?.(); |
| 1973 | const imp = context |
| 1974 | .getImportMappings(s.filePath, 'go') |
| 1975 | .find((i) => i.localName === pkg); |
| 1976 | const inModule = |
| 1977 | !!mod && |
| 1978 | !!imp && |
| 1979 | (imp.source === mod.modulePath || imp.source.startsWith(mod.modulePath + '/')); |
| 1980 | if (!inModule) continue; |
| 1981 | } |
| 1982 | // Unexported (lowercase) types are idiomatic Go and stay eligible — |
| 1983 | // chi's `mx.tree.FindRoute()` chains through `tree *node`. A |
| 1984 | // mis-capture is harmless: resolveMethodOnType only returns a |
| 1985 | // validated `<type>::<method>` match. |
| 1986 | const fieldType = rawType.split('.').pop(); |
| 1987 | if (!fieldType || !/^[A-Za-z_]/.test(fieldType) || GO_BUILTIN_FIELD_TYPES.has(fieldType)) continue; |
| 1988 | const resolved = resolveMethodOnType(fieldType, methodName, ref, context, 0.85, 'instance-method'); |
no test coverage detected