* Resolve a Rust module path (segments WITHOUT the leaf symbol) to the file of * the last module segment — `crate::a::b` → ` /a/b.rs` (or `.../b/mod.rs`). * Anchors on `crate` / `self` / `super`; a bare path is tried crate-relative.
( segments: string[], fromFile: string, context: ResolutionContext )
| 2147 | * Anchors on `crate` / `self` / `super`; a bare path is tried crate-relative. |
| 2148 | */ |
| 2149 | function resolveRustModuleFile( |
| 2150 | segments: string[], |
| 2151 | fromFile: string, |
| 2152 | context: ResolutionContext |
| 2153 | ): string | null { |
| 2154 | if (segments.length === 0) return null; |
| 2155 | const projectRoot = context.getProjectRoot(); |
| 2156 | const fromAbs = path.join(projectRoot, fromFile); |
| 2157 | const toRel = (p: string) => path.relative(projectRoot, p).replace(/\\/g, '/'); |
| 2158 | |
| 2159 | // Walk a sequence of module segments down from `startDir`, mapping each to a |
| 2160 | // `<seg>.rs` or `<seg>/mod.rs` file. Returns the leaf module's file, or null |
| 2161 | // if `startDir` is null or any segment has no file on disk. |
| 2162 | const resolveUnder = (startDir: string | null, rest: string[]): string | null => { |
| 2163 | if (!startDir) return null; |
| 2164 | let dir = startDir; |
| 2165 | let targetFile: string | null = null; |
| 2166 | for (const seg of rest) { |
| 2167 | if (seg === 'self' || seg === 'crate' || seg === 'super') continue; |
| 2168 | const asFile = toRel(path.join(dir, seg + '.rs')); |
| 2169 | const asMod = toRel(path.join(dir, seg, 'mod.rs')); |
| 2170 | if (context.fileExists(asFile)) targetFile = asFile; |
| 2171 | else if (context.fileExists(asMod)) targetFile = asMod; |
| 2172 | else return null; |
| 2173 | dir = path.join(dir, seg); |
| 2174 | } |
| 2175 | return targetFile; |
| 2176 | }; |
| 2177 | |
| 2178 | const first = segments[0]!; |
| 2179 | if (first === 'crate') { |
| 2180 | return resolveUnder(rustCrateRootDir(fromAbs, context), segments.slice(1)); |
| 2181 | } |
| 2182 | if (first === 'self') { |
| 2183 | return resolveUnder(rustSelfModuleDir(fromAbs), segments.slice(1)); |
| 2184 | } |
| 2185 | if (first === 'super') { |
| 2186 | let supers = 0; |
| 2187 | while (segments[supers] === 'super') supers++; |
| 2188 | let dir: string | null = rustSelfModuleDir(fromAbs); |
| 2189 | for (let s = 0; s < supers && dir; s++) dir = path.dirname(dir); |
| 2190 | return resolveUnder(dir, segments.slice(supers)); |
| 2191 | } |
| 2192 | // Bare path. In expression position (`submodule::item()` — the router-assembly |
| 2193 | // and general cross-module-call pattern) the prefix is a SUBMODULE of the |
| 2194 | // current module, i.e. 2018 `self::`-relative — so try self-relative FIRST. |
| 2195 | // Fall back to crate-relative for 2015-edition / crate-root items. External |
| 2196 | // crate paths (`serde::de::Error`) miss both and fall through to name-matching. |
| 2197 | return ( |
| 2198 | resolveUnder(rustSelfModuleDir(fromAbs), segments) ?? |
| 2199 | resolveUnder(rustCrateRootDir(fromAbs, context), segments) |
| 2200 | ); |
| 2201 | } |
| 2202 | |
| 2203 | /** |
| 2204 | * Resolve a Java/Kotlin reference whose receiver is the simple name of |
no test coverage detected