@ttsc/unplugin 0.19.3 → 0.20.0

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.
@@ -107,10 +107,10 @@ function extendsSpecifiers(extended) {
107
107
  }
108
108
  /**
109
109
  * Resolve an `extends` specifier to an absolute config path using TypeScript's
110
- * rules: absolute paths and relative specifiers get `.json` /
111
- * `tsconfig.json`-directory fallbacks; bare specifiers go through Node's module
112
- * resolver scoped to the declaring config. Returns `null` instead of throwing;
113
- * the compiler reports unresolvable `extends` itself.
110
+ * rules: absolute paths and relative specifiers get an exact-file / `.json`
111
+ * fallback; bare specifiers go through Node's module resolver scoped to the
112
+ * declaring config. Returns `null` instead of throwing; the compiler reports
113
+ * unresolvable `extends` itself.
114
114
  */
115
115
  function resolveExtendsConfig(tsconfig, specifier) {
116
116
  if (path.isAbsolute(specifier)) {
@@ -185,21 +185,29 @@ function isBarePackageRoot(specifier) {
185
185
  return !specifier.includes("/");
186
186
  }
187
187
  /**
188
- * Try an on-disk `extends` location as-is, with `.json` appended, and as a
189
- * directory containing `tsconfig.json`. Returns the first existing match.
188
+ * Try an on-disk `extends` location as-is and, unless it already ends in
189
+ * `.json`, with that extension appended. A directory is never a config file and
190
+ * cannot be expanded to `tsconfig.json` or a double `.json` suffix.
190
191
  */
191
192
  function resolveExistingExtendsPath(location) {
192
- for (const candidate of new Set([
193
- location,
194
- `${location}.json`,
195
- path.join(location, "tsconfig.json"),
196
- ])) {
197
- if (fs.existsSync(candidate)) {
193
+ const candidates = location.endsWith(".json")
194
+ ? [location]
195
+ : [location, `${location}.json`];
196
+ for (const candidate of candidates) {
197
+ if (isFile(candidate)) {
198
198
  return resolveRealPath(candidate);
199
199
  }
200
200
  }
201
201
  return null;
202
202
  }
203
+ function isFile(location) {
204
+ try {
205
+ return fs.statSync(location).isFile();
206
+ }
207
+ catch {
208
+ return false;
209
+ }
210
+ }
203
211
  /**
204
212
  * Return true when `specifier` is a relative path reference: `.`, `..`, or a
205
213
  * string starting with `./`, `../`, `.\\`, or `..\\`.
@@ -1 +1 @@
1
- {"version":3,"file":"tsconfigPaths.mjs","sources":["../../src/core/tsconfigPaths.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tsconfigPaths.mjs","sources":["../../src/core/tsconfigPaths.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttsc/unplugin",
3
- "version": "0.19.3",
3
+ "version": "0.20.0",
4
4
  "description": "Bundler adapters for ttsc plugins.",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib/index.mjs",
@@ -95,7 +95,7 @@
95
95
  "unplugin": "^2.3.11"
96
96
  },
97
97
  "peerDependencies": {
98
- "ttsc": "0.19.3"
98
+ "ttsc": "^0.20.0"
99
99
  },
100
100
  "devDependencies": {
101
101
  "@rollup/plugin-commonjs": "^29.0.2",
@@ -108,7 +108,7 @@
108
108
  "tslib": "^2.8.1",
109
109
  "typescript": "^7.0.2",
110
110
  "vite": "^7.1.12",
111
- "ttsc": "0.19.3"
111
+ "ttsc": "0.20.0"
112
112
  },
113
113
  "repository": {
114
114
  "type": "git",
@@ -53,7 +53,7 @@ export interface TtscTransformAlias {
53
53
  export interface TtscCachedProjectTransform {
54
54
  /**
55
55
  * SHA-256 hash of every input the compiler reported outside the project walk
56
- * (keyed by absolute path), captured at the time of the transform.
56
+ * (keyed by filesystem identity), captured at the time of the transform.
57
57
  *
58
58
  * The project walk cannot see files outside the project root or under ignored
59
59
  * directories (`node_modules` declarations, monorepo sibling sources,
@@ -65,6 +65,12 @@ export interface TtscCachedProjectTransform {
65
65
  * `buildStart` and never replay across edits.
66
66
  */
67
67
  externalInputHashes?: Record<string, string>;
68
+ /**
69
+ * Original absolute spellings of {@link externalInputHashes} inputs. These
70
+ * stay separate from their identity keys so validation reads the paths the
71
+ * compiler reported rather than a normalized replacement spelling.
72
+ */
73
+ externalInputPaths?: string[];
68
74
  /**
69
75
  * SHA-256 hash of each project-relative input path at the time of the
70
76
  * transform.
@@ -101,22 +107,27 @@ export function createTtscTransformCache(): TtscTransformCache {
101
107
  return new Map();
102
108
  }
103
109
 
110
+ /** Cached case-insensitivity probes for existing macOS filesystem locations. */
111
+ const CASE_INSENSITIVE_FILESYSTEMS = new Map<string, boolean>();
112
+
104
113
  /**
105
114
  * Hooks the bundler adapter passes into {@link transformTtsc} so transform
106
- * side-channels (currently the plugin-reported dependency list) reach the
107
- * bundler without leaking extra fields on the returned `TransformResult`.
115
+ * side-channels (plugin-reported dependencies and host resolution candidates)
116
+ * reach the bundler without leaking extra fields on the returned
117
+ * `TransformResult`.
108
118
  */
109
119
  export interface TtscTransformHooks {
110
120
  /**
111
121
  * Invoked once per absolute watch-input path derived for the transformed file
112
122
  * `F`: the plugin-reported `dependencies[F]` list unioned with the host-owned
113
123
  * reference graph's contribution — the reachability closure of `graph.edges`
114
- * from `F`, the `graph.globals` files, and the `graph.configs` chain or,
115
- * for a file the envelope declared `dependenciesComplete`, only
116
- * `dependencies[F]` and the universal `graph.configs` chain. Adapters forward
117
- * this to the bundler's `addWatchFile` so type-only inputs participate in
118
- * watch-mode and persistent-cache invalidation. See {@link selectWatchInputs}
119
- * for the exact derivation.
124
+ * from `F`, the `graph.globals` files, the `graph.configs` chain, and missing
125
+ * higher-priority `graph.candidates` — or, for a file the envelope declared
126
+ * `dependenciesComplete`, only `dependencies[F]`, `graph.candidates`, and the
127
+ * universal `graph.configs` chain. Adapters forward this to the bundler's
128
+ * `addWatchFile` so type-only inputs participate in watch-mode and
129
+ * persistent-cache invalidation. See {@link selectWatchInputs} for the exact
130
+ * derivation.
120
131
  */
121
132
  addWatchFile?: (file: string) => void;
122
133
  /**
@@ -313,7 +324,7 @@ function evictGeneration(
313
324
  * Forward every derived watch input for `file` to the adapter's `addWatchFile`
314
325
  * hook: the plugin-reported `dependencies[file]` list unioned with the
315
326
  * host-owned reference graph's contribution (`reach(edges, file)`, `globals`,
316
- * `configs`).
327
+ * `configs`, and resolution candidates).
317
328
  *
318
329
  * Envelope keys mirror the `typescript` keys (project-relative); values may be
319
330
  * project-relative or absolute. Every path is absolutized against the project
@@ -344,7 +355,8 @@ function notifyWatchInputs(
344
355
  *
345
356
  * By default the derivation is a union: `dependencies[file] ∪ reach(edges,
346
357
  * file) ∪ globals ∪ configs`. The plugin-reported list can only widen the
347
- * host-owned language-semantic bound, never narrow it.
358
+ * host-owned language-semantic bound, never narrow it. Resolution candidates
359
+ * remain part of that host-owned bound in both modes.
348
360
  *
349
361
  * An envelope that lists `file` in `dependenciesComplete` narrows it to
350
362
  * `dependencies[file] ∪ configs`: the plugin declared its reported list the
@@ -368,8 +380,8 @@ function selectWatchInputs(props: {
368
380
  const seen = new Set<string>();
369
381
  const excluded = new Set(
370
382
  props.temporaryTsconfig === undefined
371
- ? [props.file]
372
- : [props.file, path.resolve(props.temporaryTsconfig)],
383
+ ? [pathIdentityKey(props.file)]
384
+ : [pathIdentityKey(props.file), pathIdentityKey(props.temporaryTsconfig)],
373
385
  );
374
386
  for (const absolute of [
375
387
  ...selectFileDependencies(props),
@@ -377,16 +389,56 @@ function selectWatchInputs(props: {
377
389
  ...props,
378
390
  complete: declaresCompleteDependencies(props) && !isVolatileFile(props),
379
391
  }),
392
+ ...selectResolutionCandidateInputs(props),
380
393
  ]) {
381
- if (excluded.has(absolute) || seen.has(absolute)) {
394
+ const identity = pathIdentityKey(absolute);
395
+ if (excluded.has(identity) || seen.has(identity)) {
382
396
  continue;
383
397
  }
384
- seen.add(absolute);
398
+ seen.add(identity);
385
399
  output.push(absolute);
386
400
  }
387
401
  return output;
388
402
  }
389
403
 
404
+ /**
405
+ * Return the module-resolution paths that can supersede a currently resolved
406
+ * module reachable from `file`. They remain host-owned even when a plugin
407
+ * declares `dependenciesComplete`: plugin code cannot vouch for a compiler
408
+ * resolution change that occurs without any plugin input changing.
409
+ */
410
+ function selectResolutionCandidateInputs(props: {
411
+ file: string;
412
+ projectRoot: string;
413
+ result: ITtscCompilerTransformation;
414
+ }): string[] {
415
+ if (props.result.type === "exception") {
416
+ return [];
417
+ }
418
+ const graph = props.result.graph;
419
+ if (graph === undefined || graph.candidates === undefined) {
420
+ return [];
421
+ }
422
+ const reachable = new Set(
423
+ selectReachableSources(props.projectRoot, props.file, graph).map(
424
+ pathIdentityKey,
425
+ ),
426
+ );
427
+ const output: string[] = [];
428
+ for (const [source, candidates] of Object.entries(graph.candidates)) {
429
+ if (
430
+ !reachable.has(
431
+ pathIdentityKey(path.resolve(props.projectRoot, source)),
432
+ ) ||
433
+ !Array.isArray(candidates)
434
+ ) {
435
+ continue;
436
+ }
437
+ output.push(...selectListedFiles(props.projectRoot, candidates));
438
+ }
439
+ return output;
440
+ }
441
+
390
442
  /**
391
443
  * Flatten the host-owned reference graph for one file into absolute paths.
392
444
  *
@@ -438,26 +490,29 @@ function selectReachableEdges(
438
490
  if (!Array.isArray(targets)) {
439
491
  continue;
440
492
  }
441
- edges.set(
442
- path.resolve(projectRoot, source),
443
- targets
493
+ const identity = pathIdentityKey(path.resolve(projectRoot, source));
494
+ const entries = edges.get(identity) ?? [];
495
+ entries.push(
496
+ ...targets
444
497
  .filter(
445
498
  (target): target is string =>
446
499
  typeof target === "string" && target.length !== 0,
447
500
  )
448
501
  .map((target) => path.resolve(projectRoot, target)),
449
502
  );
503
+ edges.set(identity, entries);
450
504
  }
451
505
  const output: string[] = [];
452
- const visited = new Set<string>([file]);
506
+ const visited = new Set<string>([pathIdentityKey(file)]);
453
507
  const queue = [file];
454
508
  while (queue.length !== 0) {
455
509
  const current = queue.pop()!;
456
- for (const target of edges.get(current) ?? []) {
457
- if (visited.has(target)) {
510
+ for (const target of edges.get(pathIdentityKey(current)) ?? []) {
511
+ const identity = pathIdentityKey(target);
512
+ if (visited.has(identity)) {
458
513
  continue;
459
514
  }
460
- visited.add(target);
515
+ visited.add(identity);
461
516
  queue.push(target);
462
517
  output.push(target);
463
518
  }
@@ -465,6 +520,54 @@ function selectReachableEdges(
465
520
  return output;
466
521
  }
467
522
 
523
+ /**
524
+ * Return the source files whose direct graph edges are reachable from `file`,
525
+ * including `file` itself. Resolution candidates belong to importers rather
526
+ * than targets, so this is intentionally distinct from selectReachableEdges.
527
+ */
528
+ function selectReachableSources(
529
+ projectRoot: string,
530
+ file: string,
531
+ graph: ITtscCompilerTransformation.IReferenceGraph,
532
+ ): string[] {
533
+ const edges = new Map<string, string[]>();
534
+ const spellings = new Map<string, string>();
535
+ for (const [source, targets] of Object.entries(graph.edges ?? {})) {
536
+ if (!Array.isArray(targets)) {
537
+ continue;
538
+ }
539
+ const absolute = path.resolve(projectRoot, source);
540
+ const identity = pathIdentityKey(absolute);
541
+ spellings.set(identity, absolute);
542
+ const entries = edges.get(identity) ?? [];
543
+ entries.push(
544
+ ...targets
545
+ .filter(
546
+ (target): target is string =>
547
+ typeof target === "string" && target.length !== 0,
548
+ )
549
+ .map((target) => path.resolve(projectRoot, target)),
550
+ );
551
+ edges.set(identity, entries);
552
+ }
553
+ const output = [file];
554
+ const visited = new Set<string>([pathIdentityKey(file)]);
555
+ const queue = [file];
556
+ while (queue.length !== 0) {
557
+ const current = queue.pop()!;
558
+ for (const target of edges.get(pathIdentityKey(current)) ?? []) {
559
+ const identity = pathIdentityKey(target);
560
+ if (visited.has(identity)) {
561
+ continue;
562
+ }
563
+ visited.add(identity);
564
+ queue.push(target);
565
+ output.push(spellings.get(identity) ?? target);
566
+ }
567
+ }
568
+ return output;
569
+ }
570
+
468
571
  /**
469
572
  * Absolutize one graph string list (`globals`, `configs`), skipping members a
470
573
  * malformed envelope section may carry. Duplicates survive; the caller
@@ -534,7 +637,8 @@ function declaresFile(
534
637
  (entry) =>
535
638
  typeof entry === "string" &&
536
639
  entry.length !== 0 &&
537
- path.resolve(props.projectRoot, entry) === props.file,
640
+ pathIdentityKey(path.resolve(props.projectRoot, entry)) ===
641
+ pathIdentityKey(props.file),
538
642
  );
539
643
  }
540
644
 
@@ -560,7 +664,10 @@ function selectFileDependencies(props: {
560
664
  let entries = dependencies[key];
561
665
  if (entries === undefined) {
562
666
  for (const [candidate, candidateEntries] of Object.entries(dependencies)) {
563
- if (path.resolve(props.projectRoot, candidate) === props.file) {
667
+ if (
668
+ pathIdentityKey(path.resolve(props.projectRoot, candidate)) ===
669
+ pathIdentityKey(props.file)
670
+ ) {
564
671
  entries = candidateEntries;
565
672
  break;
566
673
  }
@@ -576,10 +683,11 @@ function selectFileDependencies(props: {
576
683
  continue;
577
684
  }
578
685
  const absolute = path.resolve(props.projectRoot, entry);
579
- if (absolute === props.file || seen.has(absolute)) {
686
+ const identity = pathIdentityKey(absolute);
687
+ if (identity === pathIdentityKey(props.file) || seen.has(identity)) {
580
688
  continue;
581
689
  }
582
- seen.add(absolute);
690
+ seen.add(identity);
583
691
  output.push(absolute);
584
692
  }
585
693
  return output;
@@ -678,7 +786,9 @@ function matchesCachedSource(
678
786
  const externalHashes = cached.externalInputHashes ?? {};
679
787
  return sameHashes(
680
788
  externalHashes,
681
- collectExternalInputHashes(Object.keys(externalHashes)),
789
+ collectExternalInputHashes(
790
+ cached.externalInputPaths ?? Object.keys(externalHashes),
791
+ ),
682
792
  );
683
793
  }
684
794
 
@@ -773,7 +883,7 @@ function listProjectInputFiles(root: string): string[] {
773
883
  * only the reference graph can prove relevant.
774
884
  */
775
885
  export function isProjectWalkPath(root: string, file: string): boolean {
776
- const relative = path.relative(path.resolve(root), path.resolve(file));
886
+ const relative = path.relative(pathIdentityKey(root), pathIdentityKey(file));
777
887
  if (
778
888
  relative.length === 0 ||
779
889
  relative === ".." ||
@@ -789,20 +899,26 @@ export function isProjectWalkPath(root: string, file: string): boolean {
789
899
 
790
900
  /**
791
901
  * Hash a list of absolute out-of-walk input paths: content SHA-256 for a
792
- * readable file, a stable `missing` marker otherwise. The marker is state, not
793
- * an error a recorded input disappearing (or reappearing) must change the
794
- * comparison exactly like a content edit. Exported so `@ttsc/metro` can re-hash
795
- * its recorded snapshot with identical semantics at cache-key time.
902
+ * readable file, a stable `missing` marker otherwise. Keys use filesystem
903
+ * identity so case-only spellings share one snapshot entry, while reads retain
904
+ * the original path supplied by the compiler. The marker is state, not an error
905
+ * a recorded input disappearing (or reappearing) must change the comparison
906
+ * exactly like a content edit. Exported so `@ttsc/metro` can re-hash its
907
+ * recorded snapshot with identical semantics at cache-key time.
796
908
  */
797
909
  export function collectExternalInputHashes(
798
910
  paths: readonly string[],
799
911
  ): Record<string, string> {
800
912
  const hashes: Record<string, string> = {};
801
913
  for (const file of paths) {
914
+ const identity = pathIdentityKey(file);
915
+ if (identity in hashes) {
916
+ continue;
917
+ }
802
918
  try {
803
- hashes[file] = hashText(fs.readFileSync(file));
919
+ hashes[identity] = hashText(fs.readFileSync(file));
804
920
  } catch {
805
- hashes[file] = "missing";
921
+ hashes[identity] = "missing";
806
922
  }
807
923
  }
808
924
  return hashes;
@@ -813,7 +929,9 @@ export function collectExternalInputHashes(
813
929
  * union of every reference-graph member (edge keys and targets, globals, the
814
930
  * config chain) and every plugin-reported dependency, minus everything the
815
931
  * project walk already hashes and the disposed temp-dir tsconfig. These are the
816
- * inputs {@link matchesCachedSource}'s walk cannot see.
932
+ * inputs {@link matchesCachedSource}'s walk cannot see. Resolution candidates
933
+ * that are still missing remain in this set even under the project root: the
934
+ * first walk cannot hash a file that has not been created yet.
817
935
  *
818
936
  * A `dependenciesComplete` declaration deliberately does not narrow this set,
819
937
  * unlike the per-file watch derivation. This cache replays one whole envelope,
@@ -833,6 +951,7 @@ function selectExternalInputPaths(props: {
833
951
  return [];
834
952
  }
835
953
  const members: string[] = [];
954
+ const resolutionCandidates = new Set<string>();
836
955
  const graph = props.result.graph;
837
956
  if (graph !== undefined) {
838
957
  for (const [source, targets] of Object.entries(graph.edges ?? {})) {
@@ -846,6 +965,19 @@ function selectExternalInputPaths(props: {
846
965
  members.push(...listed);
847
966
  }
848
967
  }
968
+ for (const candidates of Object.values(graph.candidates ?? {})) {
969
+ if (!Array.isArray(candidates)) {
970
+ continue;
971
+ }
972
+ for (const candidate of candidates) {
973
+ if (typeof candidate !== "string" || candidate.length === 0) {
974
+ continue;
975
+ }
976
+ const absolute = path.resolve(props.projectRoot, candidate);
977
+ members.push(candidate);
978
+ resolutionCandidates.add(pathIdentityKey(absolute));
979
+ }
980
+ }
849
981
  }
850
982
  for (const entries of Object.values(props.result.dependencies ?? {})) {
851
983
  if (Array.isArray(entries)) {
@@ -855,7 +987,7 @@ function selectExternalInputPaths(props: {
855
987
  const excluded =
856
988
  props.temporaryTsconfig === undefined
857
989
  ? undefined
858
- : path.resolve(props.temporaryTsconfig);
990
+ : pathIdentityKey(props.temporaryTsconfig);
859
991
  const output: string[] = [];
860
992
  const seen = new Set<string>();
861
993
  for (const member of members) {
@@ -863,14 +995,17 @@ function selectExternalInputPaths(props: {
863
995
  continue;
864
996
  }
865
997
  const absolute = path.resolve(props.projectRoot, member);
998
+ const identity = pathIdentityKey(absolute);
999
+ const missingCandidate =
1000
+ resolutionCandidates.has(identity) && !fs.existsSync(absolute);
866
1001
  if (
867
- absolute === excluded ||
868
- seen.has(absolute) ||
869
- isProjectWalkPath(props.projectRoot, absolute)
1002
+ identity === excluded ||
1003
+ seen.has(identity) ||
1004
+ (!missingCandidate && isProjectWalkPath(props.projectRoot, absolute))
870
1005
  ) {
871
1006
  continue;
872
1007
  }
873
- seen.add(absolute);
1008
+ seen.add(identity);
874
1009
  output.push(absolute);
875
1010
  }
876
1011
  output.sort();
@@ -939,13 +1074,17 @@ async function transformProject(props: {
939
1074
  }).transform();
940
1075
  const temporaryTsconfig =
941
1076
  configured.path === props.tsconfig ? undefined : configured.path;
1077
+ const externalInputPaths = selectExternalInputPaths({
1078
+ projectRoot,
1079
+ result,
1080
+ temporaryTsconfig,
1081
+ });
942
1082
  return {
943
1083
  // Capture the out-of-walk input hashes while the generation is fresh so
944
1084
  // cache validation can re-check them; computed before dispose so the
945
1085
  // exclusion of the temp-dir tsconfig is the only reason it never keys.
946
- externalInputHashes: collectExternalInputHashes(
947
- selectExternalInputPaths({ projectRoot, result, temporaryTsconfig }),
948
- ),
1086
+ externalInputHashes: collectExternalInputHashes(externalInputPaths),
1087
+ externalInputPaths,
949
1088
  inputHashes: collectInputHashes({
950
1089
  currentFile: props.currentFile,
951
1090
  currentSource: props.currentSource,
@@ -1181,7 +1320,7 @@ function createTransformCacheKey(props: {
1181
1320
  aliasPaths: props.aliasPaths,
1182
1321
  compilerOptions: props.compilerOptions,
1183
1322
  plugins: props.plugins,
1184
- tsconfig: path.resolve(props.tsconfig),
1323
+ tsconfig: pathIdentityKey(props.tsconfig),
1185
1324
  });
1186
1325
  }
1187
1326
 
@@ -1257,7 +1396,10 @@ function selectTransformedSource(props: {
1257
1396
  }
1258
1397
  // Slow path: resolve each candidate to an absolute path for comparison.
1259
1398
  for (const [candidate, source] of Object.entries(props.result.typescript)) {
1260
- if (path.resolve(props.projectRoot, candidate) === props.file) {
1399
+ if (
1400
+ pathIdentityKey(path.resolve(props.projectRoot, candidate)) ===
1401
+ pathIdentityKey(props.file)
1402
+ ) {
1261
1403
  return source;
1262
1404
  }
1263
1405
  }
@@ -1357,7 +1499,65 @@ function resolveTsconfig(file: string, tsconfig?: string): string {
1357
1499
  }
1358
1500
 
1359
1501
  function toProjectKey(root: string, file: string): string {
1360
- return normalizePath(path.relative(root, file));
1502
+ return normalizePath(
1503
+ path.relative(pathIdentityKey(root), pathIdentityKey(file)),
1504
+ );
1505
+ }
1506
+
1507
+ /**
1508
+ * Build a comparison key for a path without changing the spelling handed to a
1509
+ * filesystem or bundler. Windows is case-insensitive; macOS is probed per
1510
+ * existing filesystem location so case-sensitive volumes keep distinct paths.
1511
+ */
1512
+ export function pathIdentityKey(file: string): string {
1513
+ const absolute = path.resolve(file);
1514
+ return filesystemIsCaseInsensitive(absolute)
1515
+ ? absolute.toLowerCase()
1516
+ : absolute;
1517
+ }
1518
+
1519
+ function filesystemIsCaseInsensitive(file: string): boolean {
1520
+ if (process.platform === "win32") {
1521
+ return true;
1522
+ }
1523
+ if (process.platform !== "darwin") {
1524
+ return false;
1525
+ }
1526
+ let existing = file;
1527
+ while (!fs.existsSync(existing)) {
1528
+ const parent = path.dirname(existing);
1529
+ if (parent === existing) {
1530
+ return false;
1531
+ }
1532
+ existing = parent;
1533
+ }
1534
+ let resolved: string;
1535
+ try {
1536
+ resolved = fs.realpathSync.native(existing);
1537
+ } catch {
1538
+ return false;
1539
+ }
1540
+ const cached = CASE_INSENSITIVE_FILESYSTEMS.get(resolved);
1541
+ if (cached !== undefined) {
1542
+ return cached;
1543
+ }
1544
+ const alternate = togglePathCase(resolved);
1545
+ const insensitive = alternate !== undefined && fs.existsSync(alternate);
1546
+ CASE_INSENSITIVE_FILESYSTEMS.set(resolved, insensitive);
1547
+ return insensitive;
1548
+ }
1549
+
1550
+ function togglePathCase(file: string): string | undefined {
1551
+ for (let index = file.length - 1; index >= 0; --index) {
1552
+ const character = file[index]!;
1553
+ if (character >= "a" && character <= "z") {
1554
+ return `${file.slice(0, index)}${character.toUpperCase()}${file.slice(index + 1)}`;
1555
+ }
1556
+ if (character >= "A" && character <= "Z") {
1557
+ return `${file.slice(0, index)}${character.toLowerCase()}${file.slice(index + 1)}`;
1558
+ }
1559
+ }
1560
+ return undefined;
1361
1561
  }
1362
1562
 
1363
1563
  function normalizePath(file: string): string {
@@ -130,10 +130,10 @@ function extendsSpecifiers(extended: unknown): string[] {
130
130
 
131
131
  /**
132
132
  * Resolve an `extends` specifier to an absolute config path using TypeScript's
133
- * rules: absolute paths and relative specifiers get `.json` /
134
- * `tsconfig.json`-directory fallbacks; bare specifiers go through Node's module
135
- * resolver scoped to the declaring config. Returns `null` instead of throwing;
136
- * the compiler reports unresolvable `extends` itself.
133
+ * rules: absolute paths and relative specifiers get an exact-file / `.json`
134
+ * fallback; bare specifiers go through Node's module resolver scoped to the
135
+ * declaring config. Returns `null` instead of throwing; the compiler reports
136
+ * unresolvable `extends` itself.
137
137
  */
138
138
  function resolveExtendsConfig(
139
139
  tsconfig: string,
@@ -221,22 +221,30 @@ function isBarePackageRoot(specifier: string): boolean {
221
221
  }
222
222
 
223
223
  /**
224
- * Try an on-disk `extends` location as-is, with `.json` appended, and as a
225
- * directory containing `tsconfig.json`. Returns the first existing match.
224
+ * Try an on-disk `extends` location as-is and, unless it already ends in
225
+ * `.json`, with that extension appended. A directory is never a config file and
226
+ * cannot be expanded to `tsconfig.json` or a double `.json` suffix.
226
227
  */
227
228
  function resolveExistingExtendsPath(location: string): string | null {
228
- for (const candidate of new Set([
229
- location,
230
- `${location}.json`,
231
- path.join(location, "tsconfig.json"),
232
- ])) {
233
- if (fs.existsSync(candidate)) {
229
+ const candidates = location.endsWith(".json")
230
+ ? [location]
231
+ : [location, `${location}.json`];
232
+ for (const candidate of candidates) {
233
+ if (isFile(candidate)) {
234
234
  return resolveRealPath(candidate);
235
235
  }
236
236
  }
237
237
  return null;
238
238
  }
239
239
 
240
+ function isFile(location: string): boolean {
241
+ try {
242
+ return fs.statSync(location).isFile();
243
+ } catch {
244
+ return false;
245
+ }
246
+ }
247
+
240
248
  /**
241
249
  * Return true when `specifier` is a relative path reference: `.`, `..`, or a
242
250
  * string starting with `./`, `../`, `.\\`, or `..\\`.