ttsc 0.26.2 → 0.27.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.
- package/README.md +9 -0
- package/cmd/ttsc/api_transform.go +17 -3
- package/cmd/ttscgraph/auxiliary_identity_state_tracks_retargets_not_source_contents_test.go +85 -0
- package/cmd/ttscgraph/disk_realpath_other.go +16 -0
- package/cmd/ttscgraph/disk_realpath_windows.go +56 -0
- package/cmd/ttscgraph/serve.go +104 -35
- package/driver/emit_plugin.go +20 -3
- package/driver/graph.go +64 -11
- package/driver/input_observation.go +273 -0
- package/driver/input_observation_test.go +149 -0
- package/driver/plugins.go +384 -4
- package/driver/program.go +54 -1
- package/driver/resolution_candidates.go +13 -5
- package/driver/rewrite.go +45 -2
- package/lib/TtscCompiler.d.ts +12 -11
- package/lib/TtscCompiler.js +44 -29
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/compiler/internal/captureProcessOutput.js +2 -2
- package/lib/compiler/internal/captureProcessOutput.js.map +1 -1
- package/lib/compiler/internal/compileProjectInMemory.js +4 -3
- package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
- package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +2 -0
- package/lib/compiler/internal/resolveEmittedJavaScript.js +3 -0
- package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +1 -1
- package/lib/compiler/internal/runBuild.js +80 -34
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/runSingleFileEmit.js +2 -2
- package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
- package/lib/compiler/internal/sharedHostHelpers.d.ts +28 -0
- package/lib/compiler/internal/sharedHostHelpers.js +39 -0
- package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.js +12 -4
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.d.ts +3 -0
- package/lib/compiler/internal/transformProjectInMemory.js +239 -35
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/flags/schema.js +12 -5
- package/lib/flags/schema.js.map +1 -1
- package/lib/internal/createCanonicalTempDirectory.d.ts +17 -0
- package/lib/internal/createCanonicalTempDirectory.js +42 -0
- package/lib/internal/createCanonicalTempDirectory.js.map +1 -0
- package/lib/internal/projectInputPathIdentity.d.ts +3 -0
- package/lib/internal/projectInputPathIdentity.js +11 -5
- package/lib/internal/projectInputPathIdentity.js.map +1 -1
- package/lib/internal/resolveNodeBinary.d.ts +14 -0
- package/lib/internal/resolveNodeBinary.js +150 -0
- package/lib/internal/resolveNodeBinary.js.map +1 -0
- package/lib/internal/resolveSafeCacheCleanupTargets.d.ts +15 -0
- package/lib/internal/resolveSafeCacheCleanupTargets.js +61 -0
- package/lib/internal/resolveSafeCacheCleanupTargets.js.map +1 -0
- package/lib/launcher/internal/prepareExecution.d.ts +3 -0
- package/lib/launcher/internal/prepareExecution.js +42 -14
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/runTtsc.js +25 -13
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.js +18 -3
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +2 -0
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +37 -1
- package/lib/launcher/internal/runtimeHooks.js +653 -62
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/plugin/internal/buildSourcePlugin.d.ts +62 -8
- package/lib/plugin/internal/buildSourcePlugin.js +754 -72
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.d.ts +17 -5
- package/lib/plugin/internal/loadProjectPlugins.js +1339 -77
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/register.d.ts +1 -0
- package/lib/register.js +46 -0
- package/lib/register.js.map +1 -0
- package/lib/structures/ITtscCompilerContext.d.ts +6 -3
- package/lib/structures/ITtscCompilerTransformation.d.ts +31 -0
- package/lib/structures/ITtscPlugin.d.ts +33 -0
- package/package.json +16 -9
- package/shim/checker/go.mod +9 -0
- package/shim/checker/go.sum +22 -0
- package/shim/checker/shim.go +75 -0
- package/shim/compiler/incremental.go +45 -8
- package/src/TtscCompiler.ts +55 -33
- package/src/compiler/internal/captureProcessOutput.ts +3 -2
- package/src/compiler/internal/compileProjectInMemory.ts +4 -3
- package/src/compiler/internal/resolveEmittedJavaScript.ts +6 -0
- package/src/compiler/internal/runBuild.ts +102 -39
- package/src/compiler/internal/runSingleFileEmit.ts +2 -2
- package/src/compiler/internal/sharedHostHelpers.ts +44 -0
- package/src/compiler/internal/startResidentTransform.ts +12 -3
- package/src/compiler/internal/transformProjectInMemory.ts +410 -39
- package/src/flags/schema.ts +12 -5
- package/src/internal/createCanonicalTempDirectory.ts +53 -0
- package/src/internal/projectInputPathIdentity.ts +17 -4
- package/src/internal/resolveNodeBinary.ts +173 -0
- package/src/internal/resolveSafeCacheCleanupTargets.ts +85 -0
- package/src/launcher/internal/prepareExecution.ts +50 -14
- package/src/launcher/internal/runTtsc.ts +24 -13
- package/src/launcher/internal/runTtscserver.ts +18 -3
- package/src/launcher/internal/runTtsx.ts +2 -0
- package/src/launcher/internal/runtimeHooks.ts +823 -66
- package/src/plugin/internal/buildSourcePlugin.ts +984 -79
- package/src/plugin/internal/loadProjectPlugins.ts +1718 -104
- package/src/register.ts +47 -0
- package/src/structures/ITtscCompilerContext.ts +6 -3
- package/src/structures/ITtscCompilerTransformation.ts +39 -0
- package/src/structures/ITtscPlugin.ts +36 -0
- package/tools/shim_audit/baseline.json +0 -1
- package/utility/host.go +9 -3
- package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +0 -5
- package/lib/internal/assertSafeExplicitCacheDirectory.js +0 -26
- package/lib/internal/assertSafeExplicitCacheDirectory.js.map +0 -1
- package/src/internal/assertSafeExplicitCacheDirectory.ts +0 -29
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
package driver
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"crypto/sha256"
|
|
5
|
+
"encoding/hex"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"sync"
|
|
8
|
+
|
|
9
|
+
shimtspath "github.com/microsoft/typescript-go/shim/tspath"
|
|
10
|
+
"github.com/microsoft/typescript-go/shim/vfs"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
var observedDirectoryDigest = func() string {
|
|
14
|
+
digest := sha256.Sum256([]byte("ttsc:host-input:directory\x00"))
|
|
15
|
+
return hex.EncodeToString(digest[:])
|
|
16
|
+
}()
|
|
17
|
+
|
|
18
|
+
type observedInputKind uint8
|
|
19
|
+
|
|
20
|
+
const (
|
|
21
|
+
observedInputMissing observedInputKind = iota
|
|
22
|
+
observedInputFile
|
|
23
|
+
observedInputDirectory
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
type observedInput struct {
|
|
27
|
+
contentHash *string
|
|
28
|
+
kind observedInputKind
|
|
29
|
+
realpath *string
|
|
30
|
+
unstable bool
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// inputObservationFS records the exact disk state returned through the
|
|
34
|
+
// compiler filesystem. A later transform envelope can therefore prove which
|
|
35
|
+
// bytes and resolution-candidate states produced its resident Program instead
|
|
36
|
+
// of attaching post-compile disk hashes to an earlier result.
|
|
37
|
+
type inputObservationFS struct {
|
|
38
|
+
vfs.FS
|
|
39
|
+
caseSensitive bool
|
|
40
|
+
mu sync.Mutex
|
|
41
|
+
observations map[string]observedInput
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func newInputObservationFS(inner vfs.FS) *inputObservationFS {
|
|
45
|
+
return &inputObservationFS{
|
|
46
|
+
FS: inner,
|
|
47
|
+
caseSensitive: inner.UseCaseSensitiveFileNames(),
|
|
48
|
+
observations: map[string]observedInput{},
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func (fs *inputObservationFS) FileExists(path string) bool {
|
|
53
|
+
exists := fs.FS.FileExists(path)
|
|
54
|
+
if exists {
|
|
55
|
+
// Existence participates in resolution, but only ReadFile returns bytes
|
|
56
|
+
// that can influence the resident Program. Do not duplicate every
|
|
57
|
+
// resolver probe with an eager file read.
|
|
58
|
+
fs.observe(path, observedInput{
|
|
59
|
+
kind: observedInputFile,
|
|
60
|
+
realpath: fs.currentRealpath(path),
|
|
61
|
+
})
|
|
62
|
+
} else {
|
|
63
|
+
fs.observe(path, observedInput{kind: observedInputMissing})
|
|
64
|
+
}
|
|
65
|
+
return exists
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func (fs *inputObservationFS) ReadFile(path string) (string, bool) {
|
|
69
|
+
contents, ok := fs.FS.ReadFile(path)
|
|
70
|
+
if ok {
|
|
71
|
+
digest := sha256.Sum256([]byte(contents))
|
|
72
|
+
hash := hex.EncodeToString(digest[:])
|
|
73
|
+
fs.observe(path, observedInput{
|
|
74
|
+
contentHash: &hash,
|
|
75
|
+
kind: observedInputFile,
|
|
76
|
+
realpath: fs.currentRealpath(path),
|
|
77
|
+
})
|
|
78
|
+
} else {
|
|
79
|
+
fs.observe(path, observedInput{kind: observedInputMissing})
|
|
80
|
+
}
|
|
81
|
+
return contents, ok
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func (fs *inputObservationFS) DirectoryExists(path string) bool {
|
|
85
|
+
exists := fs.FS.DirectoryExists(path)
|
|
86
|
+
if exists {
|
|
87
|
+
fs.observe(path, observedInput{
|
|
88
|
+
kind: observedInputDirectory,
|
|
89
|
+
realpath: fs.currentRealpath(path),
|
|
90
|
+
})
|
|
91
|
+
} else {
|
|
92
|
+
fs.observe(path, observedInput{kind: observedInputMissing})
|
|
93
|
+
}
|
|
94
|
+
return exists
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func (fs *inputObservationFS) GetAccessibleEntries(path string) vfs.Entries {
|
|
98
|
+
// DirectoryExists records directory identity on the ordinary compiler path.
|
|
99
|
+
// An empty listing alone cannot distinguish a missing directory from an
|
|
100
|
+
// existing empty one without issuing an extra stat for every enumeration.
|
|
101
|
+
return fs.FS.GetAccessibleEntries(path)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
func (fs *inputObservationFS) Stat(path string) vfs.FileInfo {
|
|
105
|
+
info := fs.FS.Stat(path)
|
|
106
|
+
if info == nil {
|
|
107
|
+
fs.observe(path, observedInput{kind: observedInputMissing})
|
|
108
|
+
} else if info.IsDir() {
|
|
109
|
+
fs.observe(path, observedInput{
|
|
110
|
+
kind: observedInputDirectory,
|
|
111
|
+
realpath: fs.currentRealpath(path),
|
|
112
|
+
})
|
|
113
|
+
} else {
|
|
114
|
+
fs.observe(path, observedInput{
|
|
115
|
+
kind: observedInputFile,
|
|
116
|
+
realpath: fs.currentRealpath(path),
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
return info
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
func (fs *inputObservationFS) Realpath(path string) string {
|
|
123
|
+
realpath := fs.FS.Realpath(path)
|
|
124
|
+
if realpath != "" {
|
|
125
|
+
resolved := filepath.Clean(realpath)
|
|
126
|
+
fs.observeRealpath(path, &resolved)
|
|
127
|
+
}
|
|
128
|
+
return realpath
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
func (fs *inputObservationFS) currentRealpath(path string) *string {
|
|
132
|
+
realpath := fs.FS.Realpath(path)
|
|
133
|
+
if realpath == "" {
|
|
134
|
+
return nil
|
|
135
|
+
}
|
|
136
|
+
resolved := filepath.Clean(realpath)
|
|
137
|
+
return &resolved
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func (fs *inputObservationFS) observationKey(path string) string {
|
|
141
|
+
if !filepath.IsAbs(path) {
|
|
142
|
+
return ""
|
|
143
|
+
}
|
|
144
|
+
return shimtspath.GetCanonicalFileName(
|
|
145
|
+
shimtspath.NormalizePath(path),
|
|
146
|
+
fs.caseSensitive,
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
func (fs *inputObservationFS) observe(path string, next observedInput) {
|
|
151
|
+
key := fs.observationKey(path)
|
|
152
|
+
if key == "" {
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
fs.mu.Lock()
|
|
156
|
+
defer fs.mu.Unlock()
|
|
157
|
+
keys := []string{key}
|
|
158
|
+
// A compiler read can arrive through an 8.3, case-variant, or already-real
|
|
159
|
+
// spelling while resolution recorded the selected lexical alias. Index the
|
|
160
|
+
// returned bytes by the final physical path too, so proof can join the two
|
|
161
|
+
// observations without another disk read.
|
|
162
|
+
if next.contentHash != nil && next.realpath != nil {
|
|
163
|
+
physicalKey := fs.observationKey(*next.realpath)
|
|
164
|
+
if physicalKey != "" && physicalKey != key {
|
|
165
|
+
keys = append(keys, physicalKey)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
for _, observationKey := range keys {
|
|
169
|
+
fs.mergeObservation(observationKey, next)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// mergeObservation merges one observation key while the caller holds fs.mu.
|
|
174
|
+
func (fs *inputObservationFS) mergeObservation(key string, next observedInput) {
|
|
175
|
+
previous, found := fs.observations[key]
|
|
176
|
+
if !found {
|
|
177
|
+
fs.observations[key] = next
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
if previous.unstable || next.unstable || previous.kind != next.kind || !sameOptionalString(previous.realpath, next.realpath) {
|
|
181
|
+
previous.unstable = true
|
|
182
|
+
previous.contentHash = nil
|
|
183
|
+
previous.realpath = nil
|
|
184
|
+
fs.observations[key] = previous
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
if previous.contentHash != nil && next.contentHash != nil && *previous.contentHash != *next.contentHash {
|
|
188
|
+
previous.unstable = true
|
|
189
|
+
previous.contentHash = nil
|
|
190
|
+
previous.realpath = nil
|
|
191
|
+
fs.observations[key] = previous
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
if previous.contentHash == nil && next.contentHash != nil {
|
|
195
|
+
previous.contentHash = next.contentHash
|
|
196
|
+
}
|
|
197
|
+
fs.observations[key] = previous
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
func (fs *inputObservationFS) observeRealpath(path string, realpath *string) {
|
|
201
|
+
key := fs.observationKey(path)
|
|
202
|
+
if key == "" {
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
fs.mu.Lock()
|
|
206
|
+
defer fs.mu.Unlock()
|
|
207
|
+
previous, found := fs.observations[key]
|
|
208
|
+
if !found {
|
|
209
|
+
// A realpath by itself cannot prove whether the path was a file or
|
|
210
|
+
// directory. The ordinary resolver probes record that state separately.
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
if previous.unstable || !sameOptionalString(previous.realpath, realpath) {
|
|
214
|
+
previous.unstable = true
|
|
215
|
+
previous.contentHash = nil
|
|
216
|
+
previous.realpath = nil
|
|
217
|
+
fs.observations[key] = previous
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
func sameOptionalString(left, right *string) bool {
|
|
222
|
+
if left == nil || right == nil {
|
|
223
|
+
return left == nil && right == nil
|
|
224
|
+
}
|
|
225
|
+
return *left == *right
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// proof returns a stable compiler-time state. A nil hash/realpath with ok=true
|
|
229
|
+
// is an explicit JSON null for a path observed missing; ok=false means the
|
|
230
|
+
// compiler never produced a complete, internally consistent observation.
|
|
231
|
+
func (fs *inputObservationFS) proof(path string) (hash, realpath *string, ok bool) {
|
|
232
|
+
key := fs.observationKey(path)
|
|
233
|
+
if key == "" {
|
|
234
|
+
return nil, nil, false
|
|
235
|
+
}
|
|
236
|
+
fs.mu.Lock()
|
|
237
|
+
observation, found := fs.observations[key]
|
|
238
|
+
// TypeScript-Go can probe a lexical symlink candidate and then read the
|
|
239
|
+
// selected source by its physical filename. Reuse that exact observed read
|
|
240
|
+
// for the alias instead of issuing an eager duplicate read from FileExists.
|
|
241
|
+
if found && !observation.unstable && observation.kind == observedInputFile && observation.contentHash == nil && observation.realpath != nil {
|
|
242
|
+
targetKey := fs.observationKey(*observation.realpath)
|
|
243
|
+
target, targetFound := fs.observations[targetKey]
|
|
244
|
+
if targetFound && !target.unstable && target.kind == observedInputFile && target.contentHash != nil && sameOptionalString(target.realpath, observation.realpath) {
|
|
245
|
+
targetHash := *target.contentHash
|
|
246
|
+
observation.contentHash = &targetHash
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
fs.mu.Unlock()
|
|
250
|
+
if !found || observation.unstable {
|
|
251
|
+
return nil, nil, false
|
|
252
|
+
}
|
|
253
|
+
switch observation.kind {
|
|
254
|
+
case observedInputMissing:
|
|
255
|
+
return nil, nil, true
|
|
256
|
+
case observedInputDirectory:
|
|
257
|
+
hash := observedDirectoryDigest
|
|
258
|
+
if observation.realpath == nil {
|
|
259
|
+
return nil, nil, false
|
|
260
|
+
}
|
|
261
|
+
realpath := *observation.realpath
|
|
262
|
+
return &hash, &realpath, true
|
|
263
|
+
case observedInputFile:
|
|
264
|
+
if observation.contentHash == nil || observation.realpath == nil {
|
|
265
|
+
return nil, nil, false
|
|
266
|
+
}
|
|
267
|
+
hash := *observation.contentHash
|
|
268
|
+
realpath := *observation.realpath
|
|
269
|
+
return &hash, &realpath, true
|
|
270
|
+
default:
|
|
271
|
+
return nil, nil, false
|
|
272
|
+
}
|
|
273
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
package driver
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"crypto/sha256"
|
|
5
|
+
"encoding/hex"
|
|
6
|
+
"os"
|
|
7
|
+
"os/exec"
|
|
8
|
+
"path/filepath"
|
|
9
|
+
"runtime"
|
|
10
|
+
"testing"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
func TestInputObservationFSRejectsRestoredContent(t *testing.T) {
|
|
14
|
+
root := t.TempDir()
|
|
15
|
+
file := filepath.Join(root, "external.d.ts")
|
|
16
|
+
if err := os.WriteFile(file, []byte("A"), 0o644); err != nil {
|
|
17
|
+
t.Fatal(err)
|
|
18
|
+
}
|
|
19
|
+
observed := newInputObservationFS(DefaultFS())
|
|
20
|
+
if contents, ok := observed.ReadFile(file); !ok || contents != "A" {
|
|
21
|
+
t.Fatalf("first read = %q, %v", contents, ok)
|
|
22
|
+
}
|
|
23
|
+
if err := os.WriteFile(file, []byte("B"), 0o644); err != nil {
|
|
24
|
+
t.Fatal(err)
|
|
25
|
+
}
|
|
26
|
+
if contents, ok := observed.ReadFile(file); !ok || contents != "B" {
|
|
27
|
+
t.Fatalf("second read = %q, %v", contents, ok)
|
|
28
|
+
}
|
|
29
|
+
if err := os.WriteFile(file, []byte("A"), 0o644); err != nil {
|
|
30
|
+
t.Fatal(err)
|
|
31
|
+
}
|
|
32
|
+
if _, _, ok := observed.proof(file); ok {
|
|
33
|
+
t.Fatal("A-B-A observation unexpectedly produced reusable proof")
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func TestInputObservationFSProvesReadBytesAndMissingCandidates(t *testing.T) {
|
|
38
|
+
root := t.TempDir()
|
|
39
|
+
file := filepath.Join(root, "selected.ts")
|
|
40
|
+
missing := filepath.Join(root, "superseding.ts")
|
|
41
|
+
contents := "export const selected = true;\n"
|
|
42
|
+
if err := os.WriteFile(file, []byte(contents), 0o644); err != nil {
|
|
43
|
+
t.Fatal(err)
|
|
44
|
+
}
|
|
45
|
+
observed := newInputObservationFS(DefaultFS())
|
|
46
|
+
if !observed.FileExists(file) {
|
|
47
|
+
t.Fatal("selected file was not found")
|
|
48
|
+
}
|
|
49
|
+
if _, ok := observed.ReadFile(file); !ok {
|
|
50
|
+
t.Fatal("selected file was not read")
|
|
51
|
+
}
|
|
52
|
+
if observed.FileExists(missing) {
|
|
53
|
+
t.Fatal("missing candidate unexpectedly exists")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
digest := sha256.Sum256([]byte(contents))
|
|
57
|
+
wantHash := hex.EncodeToString(digest[:])
|
|
58
|
+
hash, realpath, ok := observed.proof(file)
|
|
59
|
+
if !ok || hash == nil || *hash != wantHash {
|
|
60
|
+
t.Fatalf("selected proof hash = %v, %v", hash, ok)
|
|
61
|
+
}
|
|
62
|
+
wantRealpath, err := filepath.EvalSymlinks(file)
|
|
63
|
+
if err != nil {
|
|
64
|
+
t.Fatal(err)
|
|
65
|
+
}
|
|
66
|
+
if realpath == nil || filepath.Clean(*realpath) != filepath.Clean(wantRealpath) {
|
|
67
|
+
t.Fatalf("selected proof realpath = %v, want %q", realpath, wantRealpath)
|
|
68
|
+
}
|
|
69
|
+
hash, realpath, ok = observed.proof(missing)
|
|
70
|
+
if !ok || hash != nil || realpath != nil {
|
|
71
|
+
t.Fatalf("missing proof = %v, %v, %v", hash, realpath, ok)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func TestInputObservationFSHashesCompilerDecodedText(t *testing.T) {
|
|
76
|
+
root := t.TempDir()
|
|
77
|
+
file := filepath.Join(root, "bom.ts")
|
|
78
|
+
text := "export const value = true;\n"
|
|
79
|
+
raw := append([]byte{0xef, 0xbb, 0xbf}, []byte(text)...)
|
|
80
|
+
if err := os.WriteFile(file, raw, 0o644); err != nil {
|
|
81
|
+
t.Fatal(err)
|
|
82
|
+
}
|
|
83
|
+
observed := newInputObservationFS(DefaultFS())
|
|
84
|
+
contents, ok := observed.ReadFile(file)
|
|
85
|
+
if !ok || contents != text {
|
|
86
|
+
t.Fatalf("compiler read = %q, %v; want decoded text", contents, ok)
|
|
87
|
+
}
|
|
88
|
+
digest := sha256.Sum256([]byte(text))
|
|
89
|
+
hash, _, ok := observed.proof(file)
|
|
90
|
+
if !ok || hash == nil || *hash != hex.EncodeToString(digest[:]) {
|
|
91
|
+
t.Fatalf("decoded proof hash = %v, %v", hash, ok)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func TestInputObservationFSJoinsSelectedAliasProbeToPhysicalRead(t *testing.T) {
|
|
96
|
+
root := t.TempDir()
|
|
97
|
+
target := filepath.Join(root, "target")
|
|
98
|
+
if err := os.Mkdir(target, 0o755); err != nil {
|
|
99
|
+
t.Fatal(err)
|
|
100
|
+
}
|
|
101
|
+
physical := filepath.Join(target, "value.js")
|
|
102
|
+
contents := "export const value = true;\n"
|
|
103
|
+
if err := os.WriteFile(physical, []byte(contents), 0o644); err != nil {
|
|
104
|
+
t.Fatal(err)
|
|
105
|
+
}
|
|
106
|
+
alias := filepath.Join(root, "alias")
|
|
107
|
+
if runtime.GOOS == "windows" {
|
|
108
|
+
command := exec.Command(
|
|
109
|
+
"node",
|
|
110
|
+
"-e",
|
|
111
|
+
`require("node:fs").symlinkSync(process.argv[1], process.argv[2], "junction")`,
|
|
112
|
+
target,
|
|
113
|
+
alias,
|
|
114
|
+
)
|
|
115
|
+
if output, err := command.CombinedOutput(); err != nil {
|
|
116
|
+
t.Skipf("directory junction unavailable on this host: %v: %s", err, output)
|
|
117
|
+
}
|
|
118
|
+
} else if err := os.Symlink(target, alias); err != nil {
|
|
119
|
+
t.Skipf("directory symlink unavailable on this host: %v", err)
|
|
120
|
+
}
|
|
121
|
+
lexical := filepath.Join(alias, "value.js")
|
|
122
|
+
observed := newInputObservationFS(DefaultFS())
|
|
123
|
+
if !observed.FileExists(lexical) {
|
|
124
|
+
t.Fatal("selected lexical alias was not found")
|
|
125
|
+
}
|
|
126
|
+
if _, ok := observed.ReadFile(physical); !ok {
|
|
127
|
+
t.Fatal("selected physical source was not read")
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
digest := sha256.Sum256([]byte(contents))
|
|
131
|
+
hash, realpath, ok := observed.proof(lexical)
|
|
132
|
+
if !ok || hash == nil || *hash != hex.EncodeToString(digest[:]) {
|
|
133
|
+
t.Fatalf("alias proof hash = %v, %v", hash, ok)
|
|
134
|
+
}
|
|
135
|
+
if realpath == nil {
|
|
136
|
+
t.Fatalf("alias proof realpath = nil, want %q", physical)
|
|
137
|
+
}
|
|
138
|
+
actualInfo, err := os.Stat(*realpath)
|
|
139
|
+
if err != nil {
|
|
140
|
+
t.Fatal(err)
|
|
141
|
+
}
|
|
142
|
+
expectedInfo, err := os.Stat(physical)
|
|
143
|
+
if err != nil {
|
|
144
|
+
t.Fatal(err)
|
|
145
|
+
}
|
|
146
|
+
if !os.SameFile(actualInfo, expectedInfo) {
|
|
147
|
+
t.Fatalf("alias proof realpath = %q, want %q", *realpath, physical)
|
|
148
|
+
}
|
|
149
|
+
}
|