ttsc 0.28.4 → 0.28.6

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.
Files changed (38) hide show
  1. package/cmd/ttsc/api_compile.go +6 -4
  2. package/cmd/ttsc/api_transform.go +6 -4
  3. package/cmd/ttsc/build.go +7 -6
  4. package/cmd/ttscgraph/serve.go +70 -77
  5. package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +26 -0
  6. package/cmd/ttscgraph/serve_session_tracks_all_module_specifier_forms_test.go +1 -2
  7. package/driver/graph.go +67 -35
  8. package/driver/input_observation.go +305 -115
  9. package/driver/input_observation_test.go +203 -0
  10. package/driver/program.go +28 -4
  11. package/driver/resolution_candidates.go +247 -741
  12. package/driver/resolution_candidates_test.go +341 -0
  13. package/lib/compiler/internal/project/readProjectConfig.js +9 -7
  14. package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
  15. package/lib/compiler/internal/runBuild.js +1 -0
  16. package/lib/compiler/internal/runBuild.js.map +1 -1
  17. package/lib/compiler/internal/sharedHostHelpers.d.ts +4 -0
  18. package/lib/compiler/internal/sharedHostHelpers.js +11 -1
  19. package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
  20. package/lib/compiler/internal/startResidentTransform.js +1 -0
  21. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  22. package/lib/compiler/internal/transformProjectInMemory.js +165 -8
  23. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  24. package/lib/internal/projectInputPathIdentity.js +8 -7
  25. package/lib/internal/projectInputPathIdentity.js.map +1 -1
  26. package/lib/structures/ITtscCompilerTransformation.d.ts +57 -19
  27. package/package.json +8 -8
  28. package/shim/compiler/incremental.go +52 -0
  29. package/shim/compiler/resolution.go +444 -0
  30. package/shim/core/shim.go +15 -1
  31. package/src/compiler/internal/project/readProjectConfig.ts +9 -7
  32. package/src/compiler/internal/runBuild.ts +2 -0
  33. package/src/compiler/internal/sharedHostHelpers.ts +14 -0
  34. package/src/compiler/internal/startResidentTransform.ts +2 -0
  35. package/src/compiler/internal/transformProjectInMemory.ts +193 -8
  36. package/src/internal/projectInputPathIdentity.ts +8 -7
  37. package/src/structures/ITtscCompilerTransformation.ts +65 -19
  38. package/utility/host.go +8 -7
@@ -21,6 +21,7 @@ import { resolveTsgo } from "./resolveTsgo";
21
21
  import { appendBuildOutput, normalizeBuildOutput } from "./runBuild";
22
22
  import {
23
23
  assertSharedHostCompatibility,
24
+ clearInheritedSemanticConfigPath,
24
25
  clearInheritedTsgoArgs,
25
26
  inheritedSidecarEnv,
26
27
  linkedTransformPlugins,
@@ -622,6 +623,7 @@ function nativePluginEnv(
622
623
  // This lane forwards no tsgo argv of its own, so anything inherited belongs
623
624
  // to an outer ttsc run and must not reach these sidecars.
624
625
  clearInheritedTsgoArgs(env, options.env);
626
+ clearInheritedSemanticConfigPath(env, options.env);
625
627
  if (plugin?.stage === "transform") {
626
628
  const linked = linkedTransformPlugins(nativePlugins ?? []);
627
629
  if (linked.length !== 0) {
@@ -809,11 +811,9 @@ function parseGraphEdges(value: unknown): Record<string, string[]> | undefined {
809
811
  * compiler input proof. A section carrying nothing usable collapses to
810
812
  * `undefined`.
811
813
  *
812
- * `candidates` is the one optional member, so an empty one is left off the
813
- * result instead of being materialized as `{}`. The host omits the key when it
814
- * has no superseding candidate to report, and a consumer that narrows on the
815
- * declared optional type must see the same shape whether it reads the decoded
816
- * envelope or the wire.
814
+ * Optional resolver-input members are left off when empty. A consumer that
815
+ * narrows on their declared optional types therefore sees the same shape from
816
+ * the decoded envelope and the wire.
817
817
  */
818
818
  function parseReferenceGraph(
819
819
  value: unknown,
@@ -827,23 +827,34 @@ function parseReferenceGraph(
827
827
  edges?: unknown;
828
828
  globals?: unknown;
829
829
  inputHashes?: unknown;
830
+ inputObservations?: unknown;
830
831
  inputProofFailures?: unknown;
831
832
  inputRealpaths?: unknown;
833
+ resolutionInputs?: unknown;
832
834
  };
833
835
  const candidates = parseDependencyLists(section.candidates) ?? {};
834
836
  const edges = parseGraphEdges(section.edges) ?? {};
835
837
  const globals = parseFileList(section.globals) ?? [];
836
838
  const configs = parseFileList(section.configs) ?? [];
839
+ const resolutionInputs = parseFileList(section.resolutionInputs) ?? [];
837
840
  const inputHashes = parseGraphInputHashes(section.inputHashes);
838
- const inputProofFailures = parseGraphInputProofFailures(
841
+ const parsedInputObservations = parseGraphInputObservations(
842
+ section.inputObservations,
843
+ );
844
+ const reportedInputProofFailures = parseGraphInputProofFailures(
839
845
  section.inputProofFailures,
840
846
  );
847
+ const inputProofFailures = {
848
+ ...reportedInputProofFailures,
849
+ ...parsedInputObservations.failures,
850
+ };
841
851
  const inputRealpaths = parseGraphInputRealpaths(section.inputRealpaths);
842
852
  if (
843
853
  Object.keys(candidates).length === 0 &&
844
854
  Object.keys(edges).length === 0 &&
845
855
  globals.length === 0 &&
846
- configs.length === 0
856
+ configs.length === 0 &&
857
+ resolutionInputs.length === 0
847
858
  ) {
848
859
  return undefined;
849
860
  }
@@ -852,12 +863,186 @@ function parseReferenceGraph(
852
863
  configs,
853
864
  edges,
854
865
  globals,
866
+ ...(resolutionInputs.length === 0 ? {} : { resolutionInputs }),
855
867
  ...(inputHashes === undefined ? {} : { inputHashes }),
856
- ...(inputProofFailures === undefined ? {} : { inputProofFailures }),
868
+ ...(parsedInputObservations.observations === undefined
869
+ ? {}
870
+ : { inputObservations: parsedInputObservations.observations }),
871
+ ...(Object.keys(inputProofFailures).length === 0
872
+ ? {}
873
+ : { inputProofFailures }),
857
874
  ...(inputRealpaths === undefined ? {} : { inputRealpaths }),
858
875
  };
859
876
  }
860
877
 
878
+ /**
879
+ * Parse graph-keyed predicate observations and retain malformed entries as
880
+ * failures.
881
+ */
882
+ function parseGraphInputObservations(value: unknown): {
883
+ failures?: Record<string, string>;
884
+ observations?: Record<string, ITtscCompilerTransformation.IInputObservation>;
885
+ } {
886
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
887
+ return {};
888
+ }
889
+ const failures: Record<string, string> = {};
890
+ const observations: Record<
891
+ string,
892
+ ITtscCompilerTransformation.IInputObservation
893
+ > = {};
894
+ for (const [file, entry] of Object.entries(value)) {
895
+ if (file.length === 0) continue;
896
+ const parsed = parseGraphInputObservation(entry);
897
+ if (parsed === "malformed" || parsed === "conflicting") {
898
+ failures[file] = `${parsed}-observation`;
899
+ } else {
900
+ observations[file] = parsed;
901
+ }
902
+ }
903
+ return {
904
+ ...(Object.keys(failures).length === 0 ? {} : { failures }),
905
+ ...(Object.keys(observations).length === 0 ? {} : { observations }),
906
+ };
907
+ }
908
+
909
+ /** Parse one predicate observation without guessing around a malformed member. */
910
+ function parseGraphInputObservation(
911
+ value: unknown,
912
+ ): ITtscCompilerTransformation.IInputObservation | "conflicting" | "malformed" {
913
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
914
+ return "malformed";
915
+ }
916
+ const entry = value as Record<string, unknown>;
917
+ const observation: ITtscCompilerTransformation.IInputObservation = {};
918
+ if (Object.prototype.hasOwnProperty.call(entry, "accessibleEntries")) {
919
+ const accessible = entry.accessibleEntries;
920
+ if (
921
+ typeof accessible !== "object" ||
922
+ accessible === null ||
923
+ Array.isArray(accessible)
924
+ ) {
925
+ return "malformed";
926
+ }
927
+ const lists = accessible as Record<string, unknown>;
928
+ if (
929
+ !Array.isArray(lists.directories) ||
930
+ !lists.directories.every(
931
+ (name): name is string => typeof name === "string",
932
+ ) ||
933
+ !Array.isArray(lists.files) ||
934
+ !lists.files.every((name): name is string => typeof name === "string")
935
+ ) {
936
+ return "malformed";
937
+ }
938
+ observation.accessibleEntries = {
939
+ directories: [...lists.directories],
940
+ files: [...lists.files],
941
+ };
942
+ }
943
+ if (Object.prototype.hasOwnProperty.call(entry, "fileExists")) {
944
+ if (typeof entry.fileExists !== "boolean") return "malformed";
945
+ observation.fileExists = entry.fileExists;
946
+ }
947
+ if (Object.prototype.hasOwnProperty.call(entry, "directoryExists")) {
948
+ if (typeof entry.directoryExists !== "boolean") return "malformed";
949
+ observation.directoryExists = entry.directoryExists;
950
+ }
951
+ if (Object.prototype.hasOwnProperty.call(entry, "stat")) {
952
+ if (!["directory", "file", "missing"].includes(entry.stat as string)) {
953
+ return "malformed";
954
+ }
955
+ observation.stat = entry.stat as "directory" | "file" | "missing";
956
+ }
957
+ if (Object.prototype.hasOwnProperty.call(entry, "readFile")) {
958
+ const read = entry.readFile;
959
+ if (typeof read !== "object" || read === null || Array.isArray(read)) {
960
+ return "malformed";
961
+ }
962
+ const result = read as Record<string, unknown>;
963
+ if (result.ok === false && result.hash === undefined) {
964
+ observation.readFile = { ok: false };
965
+ } else if (
966
+ result.ok === true &&
967
+ typeof result.hash === "string" &&
968
+ /^[0-9a-f]{64}$/.test(result.hash)
969
+ ) {
970
+ observation.readFile = { hash: result.hash, ok: true };
971
+ } else {
972
+ return "malformed";
973
+ }
974
+ }
975
+ if (Object.prototype.hasOwnProperty.call(entry, "realpath")) {
976
+ const realpath = entry.realpath;
977
+ if (
978
+ typeof realpath !== "object" ||
979
+ realpath === null ||
980
+ Array.isArray(realpath)
981
+ ) {
982
+ return "malformed";
983
+ }
984
+ const result = realpath as Record<string, unknown>;
985
+ if (result.ok === false && result.path === undefined) {
986
+ observation.realpath = { ok: false };
987
+ } else if (
988
+ result.ok === true &&
989
+ typeof result.path === "string" &&
990
+ path.isAbsolute(result.path)
991
+ ) {
992
+ observation.realpath = { ok: true, path: path.resolve(result.path) };
993
+ } else {
994
+ return "malformed";
995
+ }
996
+ }
997
+ if (Object.keys(observation).length === 0) return "malformed";
998
+ return graphInputObservationCompatible(observation)
999
+ ? observation
1000
+ : "conflicting";
1001
+ }
1002
+
1003
+ /** Whether one predicate set can describe a single stable filesystem state. */
1004
+ function graphInputObservationCompatible(
1005
+ observation: ITtscCompilerTransformation.IInputObservation,
1006
+ ): boolean {
1007
+ const { accessibleEntries, directoryExists, fileExists, readFile, stat } =
1008
+ observation;
1009
+ const hasAccessibleEntries =
1010
+ accessibleEntries !== undefined &&
1011
+ (accessibleEntries.directories.length !== 0 ||
1012
+ accessibleEntries.files.length !== 0);
1013
+ if (
1014
+ hasAccessibleEntries &&
1015
+ (fileExists === true ||
1016
+ directoryExists === false ||
1017
+ (stat !== undefined && stat !== "directory") ||
1018
+ readFile?.ok === true)
1019
+ ) {
1020
+ return false;
1021
+ }
1022
+ if (fileExists === true && directoryExists === true) return false;
1023
+ if (
1024
+ stat === "directory" &&
1025
+ (fileExists === true || directoryExists === false)
1026
+ ) {
1027
+ return false;
1028
+ }
1029
+ if (stat === "file" && (fileExists === false || directoryExists === true)) {
1030
+ return false;
1031
+ }
1032
+ if (stat === "missing" && (fileExists === true || directoryExists === true)) {
1033
+ return false;
1034
+ }
1035
+ if (
1036
+ readFile?.ok === true &&
1037
+ (fileExists === false ||
1038
+ directoryExists === true ||
1039
+ (stat !== undefined && stat !== "file"))
1040
+ ) {
1041
+ return false;
1042
+ }
1043
+ return true;
1044
+ }
1045
+
861
1046
  /** Parse graph-keyed machine-readable compiler proof-failure reasons. */
862
1047
  function parseGraphInputProofFailures(
863
1048
  value: unknown,
@@ -52,7 +52,7 @@ export function createFilesystemPathIdentityContext(
52
52
  const realpaths = new Map<string, CachedRealpath>();
53
53
  const sensitivities = new Map<string, boolean>();
54
54
  const platform = operations.platform ?? process.platform;
55
- const pathApi = platform === "win32" ? path.win32 : path;
55
+ const pathApi = platform === "win32" ? path.win32 : path.posix;
56
56
  const throwOnRealpathError = operations.throwOnRealpathError ?? true;
57
57
  const realpath = operations.realpath ?? physicalRealpath;
58
58
  const lstat = operations.lstat ?? fs.lstatSync;
@@ -150,7 +150,7 @@ export function isFilesystemPathIdentityWithin(
150
150
  platform: NodeJS.Platform = process.platform,
151
151
  ): boolean {
152
152
  if (candidate === root) return true;
153
- const separator = platform === "win32" ? path.win32.sep : path.sep;
153
+ const separator = platform === "win32" ? path.win32.sep : path.posix.sep;
154
154
  return candidate.startsWith(
155
155
  root.endsWith(separator) ? root : `${root}${separator}`,
156
156
  );
@@ -160,7 +160,7 @@ export function resolveFilesystemPath(
160
160
  location: string,
161
161
  platform: NodeJS.Platform = process.platform,
162
162
  ): string {
163
- const pathApi = platform === "win32" ? path.win32 : path;
163
+ const pathApi = platform === "win32" ? path.win32 : path.posix;
164
164
  if (platform !== "win32") {
165
165
  return pathApi.resolve(location);
166
166
  }
@@ -224,6 +224,7 @@ function filesystemDirectoryIsCaseSensitive(
224
224
  realpath(location: string): string;
225
225
  },
226
226
  ): boolean {
227
+ const pathApi = platform === "win32" ? path.win32 : path.posix;
227
228
  let entries: string[];
228
229
  try {
229
230
  entries = operations.readdir(directory);
@@ -242,7 +243,7 @@ function filesystemDirectoryIsCaseSensitive(
242
243
  const alternate = alternateCase(name);
243
244
  if (alternate === name) continue;
244
245
  try {
245
- operations.lstat(path.join(directory, alternate));
246
+ operations.lstat(pathApi.join(directory, alternate));
246
247
  return false;
247
248
  } catch (error) {
248
249
  if (isMissingFilesystemEntry(error)) {
@@ -261,13 +262,13 @@ function filesystemDirectoryIsCaseSensitive(
261
262
  // case-sensitive volume.
262
263
  let current = resolveFilesystemPath(directory, platform);
263
264
  while (true) {
264
- const parent = path.dirname(current);
265
+ const parent = pathApi.dirname(current);
265
266
  if (parent === current) break;
266
- const name = path.basename(current);
267
+ const name = pathApi.basename(current);
267
268
  const alternate = alternateCase(name);
268
269
  if (alternate !== name) {
269
270
  try {
270
- operations.realpath(path.join(parent, alternate));
271
+ operations.realpath(pathApi.join(parent, alternate));
271
272
  return false;
272
273
  } catch (error) {
273
274
  if (isMissingFilesystemEntry(error) === false) throw error;
@@ -50,27 +50,36 @@ export namespace ITtscCompilerTransformation {
50
50
  configs: string[];
51
51
 
52
52
  /**
53
- * Resolution candidates that would outrank the selected target, keyed by
54
- * importing file. The compiler already resolved the import to a graph edge;
55
- * a listed path can be absent now or can be an existing unsuccessful probe,
56
- * and its appearance or change can alter that edge without editing the
57
- * importer or a recorded file.
58
- *
59
- * Hosts watch and hash these paths in addition to realized graph members. A
60
- * candidate strictly below the selected target is deliberately absent: its
61
- * creation cannot change resolution and must not invalidate a cache.
53
+ * Exact lexical filesystem inputs consulted by TypeScript-Go's module,
54
+ * type-reference, and triple-slash path resolution, keyed by source file.
55
+ * Hosts watch and prove these paths in addition to realized graph members.
62
56
  *
63
57
  * A host with nothing to report leaves the whole property out rather than
64
- * sending an empty map, so `undefined` and "no superseding candidate" are
65
- * the same observation on the wire and after decoding.
58
+ * sending an empty map, so `undefined` and "no resolver input" are the same
59
+ * observation on the wire and after decoding.
66
60
  */
67
61
  candidates?: Record<string, string[]>;
68
62
 
69
63
  /**
70
- * SHA-256/null states returned by the compiler filesystem while this graph
71
- * was being resolved, keyed in the same vocabulary as {@link edges}.
72
- * Persistent hosts use them to reject a graph whose external file or
73
- * superseding candidate changed during compilation.
64
+ * Automatic type discovery and resolution inputs that affect every source,
65
+ * including effective type-root directory membership.
66
+ */
67
+ resolutionInputs?: string[];
68
+
69
+ /**
70
+ * Exact filesystem predicates observed while the compiler constructed this
71
+ * graph. Each property is independent: `fileExists: false` and
72
+ * `directoryExists: true` describe one stable directory and do not
73
+ * conflict. Consumers re-run only the predicates present in an entry, so a
74
+ * failed file candidate is never reinterpreted as generic path absence.
75
+ */
76
+ inputObservations?: Record<string, IInputObservation>;
77
+
78
+ /**
79
+ * Legacy SHA-256/null projection of compiler filesystem observations, keyed
80
+ * in the same vocabulary as {@link edges}. Current consumers prefer
81
+ * {@link inputObservations} for candidate predicates and retain this pair
82
+ * for strict realized-file content validation.
74
83
  */
75
84
  inputHashes?: Record<string, string | null>;
76
85
 
@@ -78,14 +87,51 @@ export namespace ITtscCompilerTransformation {
78
87
  inputRealpaths?: Record<string, string | null>;
79
88
 
80
89
  /**
81
- * Why the compiler could not produce a paired proof for a graph member.
82
- * Values are stable machine-readable reason codes such as `content-changed`
83
- * or `realpath-unavailable`; consumers use them only for diagnostics and
84
- * still treat the missing proof as authoritative refusal.
90
+ * Why the compiler could not produce a trustworthy proof for a graph
91
+ * member. Values are stable machine-readable reason codes such as
92
+ * `content-changed` or `realpath-unavailable`; consumers use them only for
93
+ * diagnostics and still treat the missing proof as authoritative refusal.
85
94
  */
86
95
  inputProofFailures?: Record<string, string>;
87
96
  }
88
97
 
98
+ /** Predicate-preserving compiler filesystem proof for one lexical path. */
99
+ export interface IInputObservation {
100
+ /** Result returned by `GetAccessibleEntries`, preserving both name lists. */
101
+ accessibleEntries?: {
102
+ directories: string[];
103
+ files: string[];
104
+ };
105
+
106
+ /** Result returned by `DirectoryExists`, when the compiler called it. */
107
+ directoryExists?: boolean;
108
+
109
+ /** Result returned by `FileExists`, when the compiler called it. */
110
+ fileExists?: boolean;
111
+
112
+ /** Result returned by `ReadFile`, including decoded-text content identity. */
113
+ readFile?:
114
+ | { ok: false }
115
+ | {
116
+ hash: string;
117
+ ok: true;
118
+ };
119
+
120
+ /** Result returned by `Realpath` or captured beside a successful predicate. */
121
+ realpath?:
122
+ | { ok: false }
123
+ | {
124
+ ok: true;
125
+ path: string;
126
+ };
127
+
128
+ /**
129
+ * Result returned by `Stat`, using the compiler's file-versus-directory
130
+ * view.
131
+ */
132
+ stat?: "directory" | "file" | "missing";
133
+ }
134
+
89
135
  /** Successful source-to-source transformation result. */
90
136
  export interface ISuccess {
91
137
  /** Indicates that transformation completed without diagnostics. */
package/utility/host.go CHANGED
@@ -307,13 +307,14 @@ func loadUtilityProgram(opts hostOptions) (*driver.Program, []driver.PluginEntry
307
307
  defer restoreEnv()
308
308
 
309
309
  prog, diags, err := driver.LoadProgram(opts.cwd, opts.tsconfig, driver.LoadProgramOptions{
310
- ForceEmit: opts.emit,
311
- ForceNoEmit: opts.noEmit,
312
- OutDir: opts.outDir,
313
- SingleThreaded: opts.singleThreaded,
314
- Checkers: opts.checkers,
315
- TsgoArgs: opts.tsgoArgs,
316
- FS: opts.fs,
310
+ ForceEmit: opts.emit,
311
+ ForceNoEmit: opts.noEmit,
312
+ OutDir: opts.outDir,
313
+ SemanticConfigPath: os.Getenv(driver.SemanticConfigPathEnv),
314
+ SingleThreaded: opts.singleThreaded,
315
+ Checkers: opts.checkers,
316
+ TsgoArgs: opts.tsgoArgs,
317
+ FS: opts.fs,
317
318
  })
318
319
  if err != nil {
319
320
  fmt.Fprintf(opts.stderr, "ttsc utility: %v\n", err)