ttsc 0.28.5 → 0.29.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.
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
@@ -75,6 +75,209 @@ func TestInputObservationFSProvesReadBytesAndMissingCandidates(t *testing.T) {
75
75
  }
76
76
  }
77
77
 
78
+ func TestInputObservationFSPreservesPredicateSemantics(t *testing.T) {
79
+ t.Run("compatible-file-and-directory-predicates", testInputObservationFSKeepsFileAndDirectoryPredicatesIndependent)
80
+ t.Run("every-repeated-predicate-change", testInputObservationFSRejectsEveryRepeatedPredicateChange)
81
+ t.Run("impossible-predicate-sets", testInputObservationFSRejectsImpossiblePredicateSets)
82
+ t.Run("observed-candidate-failure", testTransformGraphReportsObservedCandidateFailure)
83
+ t.Run("rich-speculative-proof", testTransformGraphKeepsRichSpeculativeProof)
84
+ }
85
+
86
+ func testInputObservationFSKeepsFileAndDirectoryPredicatesIndependent(t *testing.T) {
87
+ root := t.TempDir()
88
+ directory := filepath.Join(root, "punycode.js")
89
+ if err := os.Mkdir(directory, 0o755); err != nil {
90
+ t.Fatal(err)
91
+ }
92
+ observed := newInputObservationFS(DefaultFS())
93
+ if observed.FileExists(directory) {
94
+ t.Fatal("directory unexpectedly satisfied FileExists")
95
+ }
96
+ if !observed.DirectoryExists(directory) {
97
+ t.Fatal("directory did not satisfy DirectoryExists")
98
+ }
99
+
100
+ proof, failure := observed.predicateProof(directory)
101
+ if failure != "" {
102
+ t.Fatalf("compatible predicates failed proof: %q", failure)
103
+ }
104
+ if proof.FileExists == nil || *proof.FileExists {
105
+ t.Fatalf("FileExists proof = %#v, want false", proof.FileExists)
106
+ }
107
+ if proof.DirectoryExists == nil || !*proof.DirectoryExists {
108
+ t.Fatalf("DirectoryExists proof = %#v, want true", proof.DirectoryExists)
109
+ }
110
+ if proof.Realpath == nil || !proof.Realpath.OK || !filepath.IsAbs(proof.Realpath.Path) {
111
+ t.Fatalf("directory realpath proof = %#v, want absolute success", proof.Realpath)
112
+ }
113
+ hash, realpath, legacyFailure := observed.proof(directory)
114
+ if legacyFailure != "" || hash == nil || *hash != observedDirectoryDigest || realpath == nil {
115
+ t.Fatalf("legacy directory projection = %v, %v, failure %q", hash, realpath, legacyFailure)
116
+ }
117
+ }
118
+
119
+ func testInputObservationFSRejectsEveryRepeatedPredicateChange(t *testing.T) {
120
+ root := t.TempDir()
121
+ cases := []struct {
122
+ name string
123
+ first TransformInputObservation
124
+ second TransformInputObservation
125
+ expected inputProofFailure
126
+ }{
127
+ {
128
+ name: "accessible-entries",
129
+ first: TransformInputObservation{
130
+ AccessibleEntries: &TransformInputEntriesObservation{Directories: []string{"alpha"}, Files: []string{}},
131
+ },
132
+ second: TransformInputObservation{
133
+ AccessibleEntries: &TransformInputEntriesObservation{Directories: []string{"alpha", "beta"}, Files: []string{}},
134
+ },
135
+ expected: inputProofAccessibleEntriesChanged,
136
+ },
137
+ {
138
+ name: "directory-exists",
139
+ first: TransformInputObservation{DirectoryExists: boolPointer(false)},
140
+ second: TransformInputObservation{DirectoryExists: boolPointer(true)},
141
+ expected: inputProofDirectoryExistsChanged,
142
+ },
143
+ {
144
+ name: "file-exists",
145
+ first: TransformInputObservation{FileExists: boolPointer(false)},
146
+ second: TransformInputObservation{FileExists: boolPointer(true)},
147
+ expected: inputProofFileExistsChanged,
148
+ },
149
+ {
150
+ name: "read-availability",
151
+ first: TransformInputObservation{ReadFile: &TransformInputReadObservation{OK: false}},
152
+ second: TransformInputObservation{ReadFile: &TransformInputReadObservation{OK: true, Hash: "a"}},
153
+ expected: inputProofContentChanged,
154
+ },
155
+ {
156
+ name: "read-content",
157
+ first: TransformInputObservation{ReadFile: &TransformInputReadObservation{OK: true, Hash: "a"}},
158
+ second: TransformInputObservation{ReadFile: &TransformInputReadObservation{OK: true, Hash: "b"}},
159
+ expected: inputProofContentChanged,
160
+ },
161
+ {
162
+ name: "realpath",
163
+ first: TransformInputObservation{Realpath: &TransformInputRealpathObservation{OK: false}},
164
+ second: TransformInputObservation{Realpath: &TransformInputRealpathObservation{OK: true, Path: filepath.Join(root, "target")}},
165
+ expected: inputProofRealpathChanged,
166
+ },
167
+ {
168
+ name: "stat",
169
+ first: TransformInputObservation{Stat: stringPointer("missing")},
170
+ second: TransformInputObservation{Stat: stringPointer("file")},
171
+ expected: inputProofStatChanged,
172
+ },
173
+ }
174
+ for _, entry := range cases {
175
+ t.Run(entry.name, func(t *testing.T) {
176
+ observed := newInputObservationFS(DefaultFS())
177
+ key := observed.observationKey(filepath.Join(root, entry.name))
178
+ observed.mergeObservation(key, observedInput{proof: entry.first})
179
+ observed.mergeObservation(key, observedInput{proof: entry.second})
180
+ if failure := observed.observations[key].failure; failure != entry.expected {
181
+ t.Fatalf("failure = %q, want %q", failure, entry.expected)
182
+ }
183
+ })
184
+ }
185
+ }
186
+
187
+ func testInputObservationFSRejectsImpossiblePredicateSets(t *testing.T) {
188
+ root := t.TempDir()
189
+ cases := []struct {
190
+ name string
191
+ first TransformInputObservation
192
+ second TransformInputObservation
193
+ }{
194
+ {
195
+ name: "file-and-directory",
196
+ first: TransformInputObservation{FileExists: boolPointer(true)},
197
+ second: TransformInputObservation{DirectoryExists: boolPointer(true)},
198
+ },
199
+ {
200
+ name: "missing-file-with-readable-content",
201
+ first: TransformInputObservation{FileExists: boolPointer(false)},
202
+ second: TransformInputObservation{ReadFile: &TransformInputReadObservation{OK: true, Hash: "a"}},
203
+ },
204
+ {
205
+ name: "entries-from-a-missing-directory",
206
+ first: TransformInputObservation{AccessibleEntries: &TransformInputEntriesObservation{
207
+ Directories: []string{"package"},
208
+ Files: []string{},
209
+ }},
210
+ second: TransformInputObservation{DirectoryExists: boolPointer(false)},
211
+ },
212
+ }
213
+ for _, entry := range cases {
214
+ t.Run(entry.name, func(t *testing.T) {
215
+ observed := newInputObservationFS(DefaultFS())
216
+ key := observed.observationKey(filepath.Join(root, entry.name))
217
+ observed.mergeObservation(key, observedInput{proof: entry.first})
218
+ observed.mergeObservation(key, observedInput{proof: entry.second})
219
+ if failure := observed.observations[key].failure; failure != inputProofPredicateConflict {
220
+ t.Fatalf("failure = %q, want %q", failure, inputProofPredicateConflict)
221
+ }
222
+ })
223
+ }
224
+ }
225
+
226
+ func testTransformGraphReportsObservedCandidateFailure(t *testing.T) {
227
+ root := t.TempDir()
228
+ raced := filepath.ToSlash(filepath.Join("node_modules", "pkg.ts"))
229
+ unobserved := filepath.ToSlash(filepath.Join("node_modules", "pkg.tsx"))
230
+ observed := newInputObservationFS(DefaultFS())
231
+ key := observed.observationKey(filepath.Join(root, filepath.FromSlash(raced)))
232
+ observed.mergeObservation(key, observedInput{
233
+ proof: TransformInputObservation{FileExists: boolPointer(false)},
234
+ })
235
+ observed.mergeObservation(key, observedInput{
236
+ proof: TransformInputObservation{FileExists: boolPointer(true)},
237
+ })
238
+ graph := TransformGraph{
239
+ Candidates: map[string][]string{"src/main.ts": {raced, unobserved}},
240
+ Configs: []string{},
241
+ Edges: map[string][]string{"src/main.ts": {}},
242
+ Globals: []string{},
243
+ }
244
+ graph.attachInputProof(&Program{inputObserver: observed}, root)
245
+ if failure := graph.InputProofFailures[raced]; failure != string(inputProofFileExistsChanged) {
246
+ t.Fatalf("observed candidate failure = %q, want %q", failure, inputProofFileExistsChanged)
247
+ }
248
+ if failure, found := graph.InputProofFailures[unobserved]; found {
249
+ t.Fatalf("wholly unobserved candidate failure = %q", failure)
250
+ }
251
+ }
252
+
253
+ func testTransformGraphKeepsRichSpeculativeProof(t *testing.T) {
254
+ root := t.TempDir()
255
+ candidate := filepath.ToSlash(filepath.Join("node_modules", "pkg.ts"))
256
+ observed := newInputObservationFS(DefaultFS())
257
+ key := observed.observationKey(filepath.Join(root, filepath.FromSlash(candidate)))
258
+ observed.mergeObservation(key, observedInput{
259
+ proof: TransformInputObservation{FileExists: boolPointer(true)},
260
+ })
261
+ graph := TransformGraph{
262
+ Candidates: map[string][]string{"src/main.ts": {candidate}},
263
+ Configs: []string{},
264
+ Edges: map[string][]string{"src/main.ts": {}},
265
+ Globals: []string{},
266
+ }
267
+ graph.attachInputProof(&Program{inputObserver: observed}, root)
268
+ proof, found := graph.InputObservations[candidate]
269
+ if !found || proof.FileExists == nil || !*proof.FileExists {
270
+ t.Fatalf("rich speculative proof = %#v, %v; want FileExists true", proof, found)
271
+ }
272
+ if failure, found := graph.InputProofFailures[candidate]; found {
273
+ t.Fatalf("legacy projection failure superseded rich proof: %q", failure)
274
+ }
275
+ }
276
+
277
+ func stringPointer(value string) *string {
278
+ return &value
279
+ }
280
+
78
281
  func TestInputObservationFSHashesCompilerDecodedText(t *testing.T) {
79
282
  root := t.TempDir()
80
283
  file := filepath.Join(root, "bom.ts")
package/driver/program.go CHANGED
@@ -18,6 +18,11 @@ import (
18
18
  "github.com/microsoft/typescript-go/shim/vfs"
19
19
  )
20
20
 
21
+ // SemanticConfigPathEnv carries the user-authored config path when an embedder
22
+ // parses a disposable generated wrapper whose location must not become the
23
+ // Program's semantic project root.
24
+ const SemanticConfigPathEnv = "TTSC_SEMANTIC_CONFIG_PATH"
25
+
21
26
  // Diagnostic is the compilation diagnostic shape ttsc passes around. Kept
22
27
  // dependency-free (no shim types) so callers can render or inspect freely.
23
28
  //
@@ -208,10 +213,14 @@ type Program struct {
208
213
  // the program. `ForceEmit` is used by `ttsc --emit` and runtime compilation
209
214
  // so execution still works when the project defaults to `noEmit`.
210
215
  type LoadProgramOptions struct {
211
- ForceEmit bool
212
- ForceNoEmit bool
213
- OutDir string
214
- SourcePreamble string
216
+ ForceEmit bool
217
+ ForceNoEmit bool
218
+ OutDir string
219
+ // SemanticConfigPath restores the user-authored config as the semantic
220
+ // project owner after parsing a disposable generated wrapper. Native command
221
+ // entry points set it explicitly; nested driver calls do not inherit it.
222
+ SemanticConfigPath string
223
+ SourcePreamble string
215
224
  // SingleThreaded forces TypeScript-Go's single-threaded mode (one checker,
216
225
  // serial parse/check/emit), mirroring `tsgo --singleThreaded`.
217
226
  SingleThreaded bool
@@ -404,6 +413,9 @@ func LoadProgram(cwd, tsconfigPath string, options LoadProgramOptions) (*Program
404
413
  if len(diags) > 0 {
405
414
  return nil, diags, nil
406
415
  }
416
+ if err := applySemanticConfigPath(parsed, options.SemanticConfigPath); err != nil {
417
+ return nil, nil, err
418
+ }
407
419
  if options.ForceNoEmit {
408
420
  forceNoEmit(parsed)
409
421
  }
@@ -432,6 +444,18 @@ func LoadProgram(cwd, tsconfigPath string, options LoadProgramOptions) (*Program
432
444
  return prog, nil, nil
433
445
  }
434
446
 
447
+ func applySemanticConfigPath(parsed *tsoptions.ParsedCommandLine, semanticConfigPath string) error {
448
+ configured := strings.TrimSpace(semanticConfigPath)
449
+ if configured == "" {
450
+ return nil
451
+ }
452
+ if !filepath.IsAbs(configured) {
453
+ return fmt.Errorf("driver: semantic config path must be absolute: %s", configured)
454
+ }
455
+ parsed.ParsedConfig.CompilerOptions.ConfigFilePath = tspath.ResolvePath(configured)
456
+ return nil
457
+ }
458
+
435
459
  // forceEmit clears noEmit and emitDeclarationOnly so the program always
436
460
  // produces JavaScript output regardless of the tsconfig settings.
437
461
  func forceEmit(parsed *tsoptions.ParsedCommandLine) {