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