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.
- package/cmd/ttsc/api_compile.go +6 -4
- package/cmd/ttsc/api_transform.go +6 -4
- package/cmd/ttsc/build.go +7 -6
- package/cmd/ttscgraph/serve.go +70 -77
- package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +26 -0
- package/cmd/ttscgraph/serve_session_tracks_all_module_specifier_forms_test.go +1 -2
- package/driver/graph.go +67 -35
- package/driver/input_observation.go +305 -115
- package/driver/input_observation_test.go +203 -0
- package/driver/program.go +28 -4
- package/driver/resolution_candidates.go +247 -741
- package/driver/resolution_candidates_test.go +341 -0
- package/lib/compiler/internal/project/readProjectConfig.js +9 -7
- package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
- package/lib/compiler/internal/runBuild.js +1 -0
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/sharedHostHelpers.d.ts +4 -0
- package/lib/compiler/internal/sharedHostHelpers.js +11 -1
- package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.js +1 -0
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.js +165 -8
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/internal/projectInputPathIdentity.js +8 -7
- package/lib/internal/projectInputPathIdentity.js.map +1 -1
- package/lib/structures/ITtscCompilerTransformation.d.ts +57 -19
- package/package.json +8 -8
- package/shim/compiler/incremental.go +52 -0
- package/shim/compiler/resolution.go +444 -0
- package/shim/core/shim.go +15 -1
- package/src/compiler/internal/project/readProjectConfig.ts +9 -7
- package/src/compiler/internal/runBuild.ts +2 -0
- package/src/compiler/internal/sharedHostHelpers.ts +14 -0
- package/src/compiler/internal/startResidentTransform.ts +2 -0
- package/src/compiler/internal/transformProjectInMemory.ts +193 -8
- package/src/internal/projectInputPathIdentity.ts +8 -7
- package/src/structures/ITtscCompilerTransformation.ts +65 -19
- 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
|
|
212
|
-
ForceNoEmit
|
|
213
|
-
OutDir
|
|
214
|
-
|
|
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) {
|