* Read `filePath` and fold its `extends` chain into a single set of * effective options. Parents are applied first and the nearest config * wins — tsc replaces `paths` wholesale rather than merging it. * * `stack` holds the configs currently being resolved, so a cycle * (`a extends b extends a`
( filePath: string, stack: Set<string>, depth: number )
| 173 | * (`a extends b extends a`) stops instead of recursing forever. |
| 174 | */ |
| 175 | function loadEffectiveOptions( |
| 176 | filePath: string, |
| 177 | stack: Set<string>, |
| 178 | depth: number |
| 179 | ): EffectiveOptions | null { |
| 180 | const abs = path.resolve(filePath); |
| 181 | if (stack.has(abs) || depth > MAX_EXTENDS_DEPTH) { |
| 182 | logDebug('path-aliases: extends chain cycle or too deep', { filePath: abs, depth }); |
| 183 | return null; |
| 184 | } |
| 185 | const raw = readTsconfigLike(abs); |
| 186 | if (!raw) return null; |
| 187 | |
| 188 | stack.add(abs); |
| 189 | const dir = path.dirname(abs); |
| 190 | const effective: EffectiveOptions = {}; |
| 191 | |
| 192 | const parents = typeof raw.extends === 'string' ? [raw.extends] : (raw.extends ?? []); |
| 193 | for (const spec of parents) { |
| 194 | if (typeof spec !== 'string') continue; |
| 195 | const target = resolveExtendsTarget(spec, dir); |
| 196 | if (!target) { |
| 197 | logDebug('path-aliases: unresolved extends', { from: abs, spec }); |
| 198 | continue; |
| 199 | } |
| 200 | const inherited = loadEffectiveOptions(target, stack, depth + 1); |
| 201 | if (!inherited) continue; |
| 202 | if (inherited.baseUrl !== undefined) effective.baseUrl = inherited.baseUrl; |
| 203 | if (inherited.paths !== undefined) { |
| 204 | effective.paths = inherited.paths; |
| 205 | effective.pathsDir = inherited.pathsDir; |
| 206 | } |
| 207 | } |
| 208 | stack.delete(abs); |
| 209 | |
| 210 | const co = raw.compilerOptions ?? {}; |
| 211 | // Both are relative to the file that declared them, not to whichever |
| 212 | // config started the chain. |
| 213 | if (typeof co.baseUrl === 'string') effective.baseUrl = path.resolve(dir, co.baseUrl); |
| 214 | if (co.paths && typeof co.paths === 'object') { |
| 215 | effective.paths = co.paths; |
| 216 | effective.pathsDir = dir; |
| 217 | } |
| 218 | return effective; |
| 219 | } |
| 220 | |
| 221 | function readTsconfigLike(filePath: string): RawTsconfig | null { |
| 222 | try { |
no test coverage detected