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
package/README.md
CHANGED
|
@@ -30,6 +30,15 @@ npx ttsc --watch # rebuild on save
|
|
|
30
30
|
|
|
31
31
|
`ttsx` runs a file the way `tsx` or `ts-node` does, but it type-checks the whole project first, so a type error stops the run before anything executes.
|
|
32
32
|
|
|
33
|
+
Preload the same checked runtime when Node or a test runner owns the process:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
node --require ttsc/register src/index.ts
|
|
37
|
+
mocha --require ttsc/register --extension ts,tsx "test/**/*.ts"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`ttsc/register` discovers the nearest `tsconfig.json` for each TypeScript root, applies its plugins, and refuses to execute when the project does not type-check.
|
|
41
|
+
|
|
33
42
|
That covers the CLI. The integrations each have a short guide:
|
|
34
43
|
|
|
35
44
|
- [`@ttsc/unplugin`](https://ttsc.dev/docs/setup/unplugin): Vite, Rollup, Rolldown, esbuild, webpack, Rspack, Next.js, Turbopack, Farm, and Bun.
|
|
@@ -26,6 +26,11 @@ type apiTransformResult struct {
|
|
|
26
26
|
// content can influence a transformed module, so bundler caches invalidate
|
|
27
27
|
// soundly without per-plugin reporting.
|
|
28
28
|
Graph *driver.TransformGraph `json:"graph,omitempty"`
|
|
29
|
+
// HostInputs are absolute native plugin config files evaluated while this
|
|
30
|
+
// generation loaded. JavaScript hosts merge them with descriptor inputs.
|
|
31
|
+
HostInputs []string `json:"hostInputs,omitempty"`
|
|
32
|
+
HostInputHashes map[string]*string `json:"hostInputHashes,omitempty"`
|
|
33
|
+
HostInputRealpaths map[string]*string `json:"hostInputRealpaths,omitempty"`
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
// runAPITransform implements the `api-transform` sub-command. It loads the
|
|
@@ -70,6 +75,9 @@ func runAPITransform(args []string) int {
|
|
|
70
75
|
}
|
|
71
76
|
typescript := map[string]string{}
|
|
72
77
|
var graph *driver.TransformGraph
|
|
78
|
+
var hostInputs []string
|
|
79
|
+
var hostInputHashes map[string]*string
|
|
80
|
+
var hostInputRealpaths map[string]*string
|
|
73
81
|
if prog != nil {
|
|
74
82
|
defer prog.Close()
|
|
75
83
|
// Compute the reference graph before SourceFiles() runs linked plugin
|
|
@@ -81,12 +89,18 @@ func runAPITransform(args []string) int {
|
|
|
81
89
|
typescript[apiOutputKey(cwd, file.FileName())] = file.Text()
|
|
82
90
|
}
|
|
83
91
|
diags = append(diags, prog.Diagnostics()...)
|
|
92
|
+
hostInputs = prog.PluginHostInputs()
|
|
93
|
+
hostInputHashes = prog.PluginHostInputHashes()
|
|
94
|
+
hostInputRealpaths = prog.PluginHostInputRealpaths()
|
|
84
95
|
}
|
|
85
96
|
|
|
86
97
|
result := apiTransformResult{
|
|
87
|
-
Diagnostics:
|
|
88
|
-
|
|
89
|
-
|
|
98
|
+
Diagnostics: make([]apiCompileDiagnostic, 0, len(diags)),
|
|
99
|
+
Graph: graph,
|
|
100
|
+
HostInputs: hostInputs,
|
|
101
|
+
HostInputHashes: hostInputHashes,
|
|
102
|
+
HostInputRealpaths: hostInputRealpaths,
|
|
103
|
+
TypeScript: typescript,
|
|
90
104
|
}
|
|
91
105
|
for _, diag := range diags {
|
|
92
106
|
result.Diagnostics = append(result.Diagnostics, toAPICompileDiagnostic(diag))
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"os/exec"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"runtime"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestAuxiliaryIdentityStateTracksRetargetsNotSourceContents proves the two
|
|
12
|
+
// owners of a selected lexical module path stay separate:
|
|
13
|
+
//
|
|
14
|
+
// 1. ordinary source bytes are owned by graphSession.sourceHashes, so an
|
|
15
|
+
// identity-only auxiliary state must not turn their edit into a reload;
|
|
16
|
+
// 2. retargeting the same lexical symlink or junction must invalidate even
|
|
17
|
+
// when the old and new target bytes agree.
|
|
18
|
+
func TestAuxiliaryIdentityStateTracksRetargetsNotSourceContents(t *testing.T) {
|
|
19
|
+
root := t.TempDir()
|
|
20
|
+
first := filepath.Join(root, "first")
|
|
21
|
+
second := filepath.Join(root, "second")
|
|
22
|
+
if err := os.MkdirAll(first, 0o755); err != nil {
|
|
23
|
+
t.Fatal(err)
|
|
24
|
+
}
|
|
25
|
+
if err := os.MkdirAll(second, 0o755); err != nil {
|
|
26
|
+
t.Fatal(err)
|
|
27
|
+
}
|
|
28
|
+
for _, directory := range []string{first, second} {
|
|
29
|
+
if err := os.WriteFile(filepath.Join(directory, "selection.ts"), []byte("export const value = 1;\n"), 0o644); err != nil {
|
|
30
|
+
t.Fatal(err)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
link := filepath.Join(root, "linked")
|
|
35
|
+
createAuxiliaryDirectoryLink(t, first, link)
|
|
36
|
+
selected := filepath.Join(link, "selection.ts")
|
|
37
|
+
previous := captureDiskStates([]auxiliaryInput{{path: selected, identityOnly: true}})
|
|
38
|
+
initial := previous[selected]
|
|
39
|
+
if !initial.Exists || initial.Realpath == "" || !initial.IdentityOnly {
|
|
40
|
+
t.Fatalf("initial identity state = %+v", initial)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if err := os.WriteFile(filepath.Join(first, "selection.ts"), []byte("export const value = 2;\n"), 0o644); err != nil {
|
|
44
|
+
t.Fatal(err)
|
|
45
|
+
}
|
|
46
|
+
if diskStatesChanged(previous) {
|
|
47
|
+
t.Fatal("selected source contents leaked into identity-only auxiliary invalidation")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if err := os.Remove(link); err != nil {
|
|
51
|
+
t.Fatal(err)
|
|
52
|
+
}
|
|
53
|
+
createAuxiliaryDirectoryLink(t, second, link)
|
|
54
|
+
if !diskStatesChanged(previous) {
|
|
55
|
+
t.Fatal("same-byte lexical link retarget did not invalidate its physical identity")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
merged := compactAuxiliaryInputs([]auxiliaryInput{
|
|
59
|
+
{path: selected, identityOnly: true},
|
|
60
|
+
{path: selected},
|
|
61
|
+
})
|
|
62
|
+
if len(merged) != 1 || merged[0].identityOnly {
|
|
63
|
+
t.Fatalf("content-sensitive duplicate did not win: %+v", merged)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func createAuxiliaryDirectoryLink(t *testing.T, target, link string) {
|
|
68
|
+
t.Helper()
|
|
69
|
+
if runtime.GOOS == "windows" {
|
|
70
|
+
command := exec.Command(
|
|
71
|
+
"node",
|
|
72
|
+
"-e",
|
|
73
|
+
`require("node:fs").symlinkSync(process.argv[1], process.argv[2], "junction")`,
|
|
74
|
+
target,
|
|
75
|
+
link,
|
|
76
|
+
)
|
|
77
|
+
if output, err := command.CombinedOutput(); err != nil {
|
|
78
|
+
t.Skipf("directory junction unavailable on this host: %v: %s", err, output)
|
|
79
|
+
}
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
if err := os.Symlink(target, link); err != nil {
|
|
83
|
+
t.Skipf("directory symlink unavailable on this host: %v", err)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//go:build !windows
|
|
2
|
+
|
|
3
|
+
package main
|
|
4
|
+
|
|
5
|
+
import "path/filepath"
|
|
6
|
+
|
|
7
|
+
func diskRealpath(path string) string {
|
|
8
|
+
resolved, err := filepath.EvalSymlinks(path)
|
|
9
|
+
if err != nil {
|
|
10
|
+
return ""
|
|
11
|
+
}
|
|
12
|
+
if absolute, err := filepath.Abs(resolved); err == nil {
|
|
13
|
+
resolved = absolute
|
|
14
|
+
}
|
|
15
|
+
return filepath.Clean(resolved)
|
|
16
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//go:build windows
|
|
2
|
+
|
|
3
|
+
package main
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
"golang.org/x/sys/windows"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// diskRealpath asks the opened object for its final path. filepath.EvalSymlinks
|
|
13
|
+
// can leave a readable file below a directory junction unresolved on Windows,
|
|
14
|
+
// which would erase the physical identity this state exists to retain.
|
|
15
|
+
func diskRealpath(path string) string {
|
|
16
|
+
pointer, err := windows.UTF16PtrFromString(path)
|
|
17
|
+
if err != nil {
|
|
18
|
+
return ""
|
|
19
|
+
}
|
|
20
|
+
handle, err := windows.CreateFile(
|
|
21
|
+
pointer,
|
|
22
|
+
windows.FILE_READ_ATTRIBUTES,
|
|
23
|
+
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
|
|
24
|
+
nil,
|
|
25
|
+
windows.OPEN_EXISTING,
|
|
26
|
+
windows.FILE_FLAG_BACKUP_SEMANTICS,
|
|
27
|
+
0,
|
|
28
|
+
)
|
|
29
|
+
if err != nil {
|
|
30
|
+
return ""
|
|
31
|
+
}
|
|
32
|
+
defer windows.CloseHandle(handle)
|
|
33
|
+
|
|
34
|
+
buffer := make([]uint16, windows.MAX_PATH)
|
|
35
|
+
for {
|
|
36
|
+
length, err := windows.GetFinalPathNameByHandle(
|
|
37
|
+
handle,
|
|
38
|
+
&buffer[0],
|
|
39
|
+
uint32(len(buffer)),
|
|
40
|
+
0,
|
|
41
|
+
)
|
|
42
|
+
if err != nil {
|
|
43
|
+
return ""
|
|
44
|
+
}
|
|
45
|
+
if length < uint32(len(buffer)) {
|
|
46
|
+
resolved := windows.UTF16ToString(buffer[:length])
|
|
47
|
+
if strings.HasPrefix(resolved, `\\?\UNC\`) {
|
|
48
|
+
resolved = `\\` + strings.TrimPrefix(resolved, `\\?\UNC\`)
|
|
49
|
+
} else {
|
|
50
|
+
resolved = strings.TrimPrefix(resolved, `\\?\`)
|
|
51
|
+
}
|
|
52
|
+
return filepath.Clean(resolved)
|
|
53
|
+
}
|
|
54
|
+
buffer = make([]uint16, length+1)
|
|
55
|
+
}
|
|
56
|
+
}
|
package/cmd/ttscgraph/serve.go
CHANGED
|
@@ -385,9 +385,11 @@ func (s *graphSession) captureState() error {
|
|
|
385
385
|
return err
|
|
386
386
|
}
|
|
387
387
|
inputs := auxiliaryInputs(program, configs, s.cwd)
|
|
388
|
-
|
|
388
|
+
for _, path := range missingRootInputs(configs, sourceHashes) {
|
|
389
|
+
inputs = append(inputs, auxiliaryInput{path: path})
|
|
390
|
+
}
|
|
389
391
|
s.configHashes = configHashes
|
|
390
|
-
s.auxStates = captureDiskStates(
|
|
392
|
+
s.auxStates = captureDiskStates(compactAuxiliaryInputs(inputs))
|
|
391
393
|
s.sourceHashes = sourceHashes
|
|
392
394
|
s.diskDigests = diskDigests
|
|
393
395
|
s.rootFiles = projectRootFilesFromConfigs(configs, false)
|
|
@@ -661,8 +663,19 @@ func changedSources(previous map[string][sha256.Size]byte) (map[string]string, b
|
|
|
661
663
|
}
|
|
662
664
|
|
|
663
665
|
type diskState struct {
|
|
664
|
-
Hash
|
|
665
|
-
Exists
|
|
666
|
+
Hash [sha256.Size]byte
|
|
667
|
+
Exists bool
|
|
668
|
+
Realpath string
|
|
669
|
+
IdentityOnly bool
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// auxiliaryInput distinguishes speculative inputs whose contents select the
|
|
673
|
+
// build universe from the lexical spelling of the source the compiler already
|
|
674
|
+
// selected. The resident source hash owns the latter's contents; this input
|
|
675
|
+
// owns only whether the spelling still reaches the same physical file.
|
|
676
|
+
type auxiliaryInput struct {
|
|
677
|
+
path string
|
|
678
|
+
identityOnly bool
|
|
666
679
|
}
|
|
667
680
|
|
|
668
681
|
// captureDiskStates records the freshness state of speculative resolution
|
|
@@ -671,29 +684,35 @@ type diskState struct {
|
|
|
671
684
|
// Windows), so any path that is neither a readable file nor a directory is
|
|
672
685
|
// recorded as absent instead of failing the snapshot: the recorded state only
|
|
673
686
|
// needs to flip when the candidate becomes resolvable.
|
|
674
|
-
func captureDiskStates(
|
|
675
|
-
states := make(map[string]diskState, len(
|
|
676
|
-
for _,
|
|
677
|
-
|
|
687
|
+
func captureDiskStates(inputs []auxiliaryInput) map[string]diskState {
|
|
688
|
+
states := make(map[string]diskState, len(inputs))
|
|
689
|
+
for _, input := range inputs {
|
|
690
|
+
state := diskState{IdentityOnly: input.identityOnly}
|
|
691
|
+
content, err := os.ReadFile(input.path)
|
|
678
692
|
if err != nil {
|
|
679
|
-
if info, statErr := os.Stat(path); statErr == nil && info.IsDir() {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
states[path] = diskState{}
|
|
693
|
+
if info, statErr := os.Stat(input.path); statErr == nil && info.IsDir() {
|
|
694
|
+
state.Exists = true
|
|
695
|
+
state.Realpath = diskRealpath(input.path)
|
|
683
696
|
}
|
|
697
|
+
states[input.path] = state
|
|
684
698
|
continue
|
|
685
699
|
}
|
|
686
|
-
|
|
700
|
+
state.Exists = true
|
|
701
|
+
state.Realpath = diskRealpath(input.path)
|
|
702
|
+
if !input.identityOnly {
|
|
703
|
+
state.Hash = sha256.Sum256(content)
|
|
704
|
+
}
|
|
705
|
+
states[input.path] = state
|
|
687
706
|
}
|
|
688
707
|
return states
|
|
689
708
|
}
|
|
690
709
|
|
|
691
710
|
func diskStatesChanged(previous map[string]diskState) bool {
|
|
692
|
-
|
|
693
|
-
for path := range previous {
|
|
694
|
-
|
|
711
|
+
inputs := make([]auxiliaryInput, 0, len(previous))
|
|
712
|
+
for path, state := range previous {
|
|
713
|
+
inputs = append(inputs, auxiliaryInput{path: path, identityOnly: state.IdentityOnly})
|
|
695
714
|
}
|
|
696
|
-
current := captureDiskStates(
|
|
715
|
+
current := captureDiskStates(inputs)
|
|
697
716
|
for path, state := range previous {
|
|
698
717
|
if current[path] != state {
|
|
699
718
|
return true
|
|
@@ -702,16 +721,16 @@ func diskStatesChanged(previous map[string]diskState) bool {
|
|
|
702
721
|
return false
|
|
703
722
|
}
|
|
704
723
|
|
|
705
|
-
func auxiliaryInputs(program *driver.Program, configs []*shimtsoptions.ParsedCommandLine, cwd string) []
|
|
706
|
-
inputs := []
|
|
707
|
-
filepath.Join(cwd, ".gitignore"),
|
|
708
|
-
filepath.Join(cwd, ".git", "info", "exclude"),
|
|
709
|
-
filepath.Join(cwd, "package.json"),
|
|
710
|
-
filepath.Join(cwd, "package-lock.json"),
|
|
711
|
-
filepath.Join(cwd, "pnpm-lock.yaml"),
|
|
712
|
-
filepath.Join(cwd, "yarn.lock"),
|
|
713
|
-
filepath.Join(cwd, "bun.lock"),
|
|
714
|
-
filepath.Join(cwd, "bun.lockb"),
|
|
724
|
+
func auxiliaryInputs(program *driver.Program, configs []*shimtsoptions.ParsedCommandLine, cwd string) []auxiliaryInput {
|
|
725
|
+
inputs := []auxiliaryInput{
|
|
726
|
+
{path: filepath.Join(cwd, ".gitignore")},
|
|
727
|
+
{path: filepath.Join(cwd, ".git", "info", "exclude")},
|
|
728
|
+
{path: filepath.Join(cwd, "package.json")},
|
|
729
|
+
{path: filepath.Join(cwd, "package-lock.json")},
|
|
730
|
+
{path: filepath.Join(cwd, "pnpm-lock.yaml")},
|
|
731
|
+
{path: filepath.Join(cwd, "yarn.lock")},
|
|
732
|
+
{path: filepath.Join(cwd, "bun.lock")},
|
|
733
|
+
{path: filepath.Join(cwd, "bun.lockb")},
|
|
715
734
|
}
|
|
716
735
|
for _, source := range program.TSProgram.SourceFiles() {
|
|
717
736
|
file := source.FileName()
|
|
@@ -719,12 +738,18 @@ func auxiliaryInputs(program *driver.Program, configs []*shimtsoptions.ParsedCom
|
|
|
719
738
|
continue
|
|
720
739
|
}
|
|
721
740
|
directory := filepath.Dir(file)
|
|
722
|
-
|
|
741
|
+
for _, path := range appendAncestorInputs(nil, directory, cwd) {
|
|
742
|
+
inputs = append(inputs, auxiliaryInput{path: path})
|
|
743
|
+
}
|
|
723
744
|
for _, reference := range source.ReferencedFiles {
|
|
724
|
-
|
|
745
|
+
for _, path := range driver.FileCandidates(filepath.Join(directory, filepath.FromSlash(reference.FileName))) {
|
|
746
|
+
inputs = append(inputs, auxiliaryInput{path: path})
|
|
747
|
+
}
|
|
725
748
|
}
|
|
726
749
|
for _, reference := range source.TypeReferenceDirectives {
|
|
727
|
-
|
|
750
|
+
for _, path := range driver.TypeReferenceCandidates(configs, directory, cwd, reference.FileName) {
|
|
751
|
+
inputs = append(inputs, auxiliaryInput{path: path})
|
|
752
|
+
}
|
|
728
753
|
}
|
|
729
754
|
for _, specifier := range driver.SourceModuleSpecifiers(source) {
|
|
730
755
|
context := driver.ModuleResolutionContext{
|
|
@@ -738,7 +763,7 @@ func auxiliaryInputs(program *driver.Program, configs []*shimtsoptions.ParsedCom
|
|
|
738
763
|
}
|
|
739
764
|
resolved := program.TSProgram.GetResolvedModuleFromModuleSpecifier(source, specifier)
|
|
740
765
|
if resolved != nil && resolved.IsResolved() {
|
|
741
|
-
|
|
766
|
+
predecessors := driver.ModuleResolutionPredecessors(
|
|
742
767
|
configs,
|
|
743
768
|
directory,
|
|
744
769
|
cwd,
|
|
@@ -746,10 +771,18 @@ func auxiliaryInputs(program *driver.Program, configs []*shimtsoptions.ParsedCom
|
|
|
746
771
|
resolved.ResolvedFileName,
|
|
747
772
|
program.FS.UseCaseSensitiveFileNames(),
|
|
748
773
|
context,
|
|
749
|
-
)
|
|
774
|
+
)
|
|
775
|
+
for _, path := range predecessors {
|
|
776
|
+
inputs = append(inputs, auxiliaryInput{
|
|
777
|
+
path: path,
|
|
778
|
+
identityOnly: sameExistingAuxiliaryPath(path, resolved.ResolvedFileName),
|
|
779
|
+
})
|
|
780
|
+
}
|
|
750
781
|
continue
|
|
751
782
|
}
|
|
752
|
-
|
|
783
|
+
for _, path := range driver.ModuleResolutionCandidates(configs, directory, cwd, specifier.Text(), context) {
|
|
784
|
+
inputs = append(inputs, auxiliaryInput{path: path})
|
|
785
|
+
}
|
|
753
786
|
}
|
|
754
787
|
}
|
|
755
788
|
// Config `types` entries request type packages without any source syntax, so
|
|
@@ -760,10 +793,46 @@ func auxiliaryInputs(program *driver.Program, configs []*shimtsoptions.ParsedCom
|
|
|
760
793
|
continue
|
|
761
794
|
}
|
|
762
795
|
for _, name := range parsed.ParsedConfig.CompilerOptions.Types {
|
|
763
|
-
|
|
796
|
+
for _, path := range driver.TypeReferenceCandidates(configs, parsed.GetCurrentDirectory(), cwd, name) {
|
|
797
|
+
inputs = append(inputs, auxiliaryInput{path: path})
|
|
798
|
+
}
|
|
764
799
|
}
|
|
765
800
|
}
|
|
766
|
-
return
|
|
801
|
+
return compactAuxiliaryInputs(inputs)
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
func sameExistingAuxiliaryPath(left, right string) bool {
|
|
805
|
+
leftInfo, err := os.Stat(left)
|
|
806
|
+
if err != nil {
|
|
807
|
+
return false
|
|
808
|
+
}
|
|
809
|
+
rightInfo, err := os.Stat(right)
|
|
810
|
+
return err == nil && os.SameFile(leftInfo, rightInfo)
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
func compactAuxiliaryInputs(inputs []auxiliaryInput) []auxiliaryInput {
|
|
814
|
+
byPath := make(map[string]auxiliaryInput, len(inputs))
|
|
815
|
+
for _, input := range inputs {
|
|
816
|
+
if strings.TrimSpace(input.path) == "" {
|
|
817
|
+
continue
|
|
818
|
+
}
|
|
819
|
+
if previous, exists := byPath[input.path]; exists {
|
|
820
|
+
// A path that also participates as a manifest or other content-bearing
|
|
821
|
+
// input keeps the stronger content-sensitive contract.
|
|
822
|
+
input.identityOnly = previous.identityOnly && input.identityOnly
|
|
823
|
+
}
|
|
824
|
+
byPath[input.path] = input
|
|
825
|
+
}
|
|
826
|
+
paths := make([]string, 0, len(byPath))
|
|
827
|
+
for path := range byPath {
|
|
828
|
+
paths = append(paths, path)
|
|
829
|
+
}
|
|
830
|
+
sort.Strings(paths)
|
|
831
|
+
output := make([]auxiliaryInput, 0, len(paths))
|
|
832
|
+
for _, path := range paths {
|
|
833
|
+
output = append(output, byPath[path])
|
|
834
|
+
}
|
|
835
|
+
return output
|
|
767
836
|
}
|
|
768
837
|
|
|
769
838
|
func appendAncestorInputs(inputs []string, directory, stop string) []string {
|
package/driver/emit_plugin.go
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package driver
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"context"
|
|
5
4
|
"errors"
|
|
6
5
|
"strings"
|
|
7
6
|
"sync"
|
|
@@ -222,12 +221,30 @@ func (p *Program) EmitWithPluginTransformers(transforms []PluginTransform, write
|
|
|
222
221
|
}
|
|
223
222
|
}
|
|
224
223
|
}
|
|
225
|
-
|
|
224
|
+
// The declaration pass below doubles as this lane's build-information pass,
|
|
225
|
+
// so it also runs for a JavaScript-only `incremental` / `composite` project
|
|
226
|
+
// that has no declarations to write at all.
|
|
227
|
+
if !options.GetEmitDeclarations() && !p.emitsBuildInfo() {
|
|
226
228
|
return nil, nil
|
|
227
229
|
}
|
|
228
230
|
|
|
231
|
+
// What the build information this pass writes does and does not claim.
|
|
232
|
+
//
|
|
233
|
+
// The JavaScript above was hand-assembled and written outside tsgo's
|
|
234
|
+
// emitter, so tsgo's snapshot never saw it happen and records the JS emit of
|
|
235
|
+
// every file as still pending. That error is one-directional and safe: a
|
|
236
|
+
// consumer reading it can only decide to emit again, never to skip a file
|
|
237
|
+
// ttsc actually transformed. Making the record exact would mean running
|
|
238
|
+
// tsgo's own JavaScript emit a second time and discarding its output, paying
|
|
239
|
+
// a full emit to describe work already done. Everything else in the file —
|
|
240
|
+
// the compiler version, the resolved options, per-file versions and
|
|
241
|
+
// signatures, and the declaration state this pass does produce — is accurate.
|
|
242
|
+
//
|
|
243
|
+
// ttsc itself never reads build information back (see
|
|
244
|
+
// `shimcompiler.EmitFreshWithBuildInfo`), so this asymmetry costs a ttsc
|
|
245
|
+
// rebuild nothing.
|
|
229
246
|
var wfMu sync.Mutex
|
|
230
|
-
result := p.
|
|
247
|
+
result := p.emitProgram(shimcompiler.EmitOptions{
|
|
231
248
|
EmitOnly: shimcompiler.EmitOnlyDts,
|
|
232
249
|
WriteFile: func(fileName string, text string, data *shimcompiler.WriteFileData) error {
|
|
233
250
|
wfMu.Lock()
|
package/driver/graph.go
CHANGED
|
@@ -23,9 +23,10 @@ const bundledScheme = "bundled:///"
|
|
|
23
23
|
//
|
|
24
24
|
// - Edges maps each file to its direct resolved references — imports,
|
|
25
25
|
// re-exports, `/// <reference>` targets, type reference directives, and
|
|
26
|
-
// ambient-module declaration files, type-only edges included.
|
|
27
|
-
//
|
|
28
|
-
//
|
|
26
|
+
// ambient-module declaration files, type-only edges included. A leaf file
|
|
27
|
+
// has an empty list so the node and its compiler-time input proof remain
|
|
28
|
+
// explicit. Direct edges are the minimal sufficient statistic; consumers
|
|
29
|
+
// that need a flat per-file list compute the reachability closure themselves.
|
|
29
30
|
// - Globals lists the files that contribute to the global scope (ambient
|
|
30
31
|
// declaration files, script files, global augmentations, `typeRoots`
|
|
31
32
|
// entries). A change to any of them can affect every file.
|
|
@@ -39,10 +40,12 @@ const bundledScheme = "bundled:///"
|
|
|
39
40
|
// map: project-relative slash paths, falling back to slash-normalized
|
|
40
41
|
// absolute paths outside the project root (see TransformOutputKey).
|
|
41
42
|
type TransformGraph struct {
|
|
42
|
-
Edges
|
|
43
|
-
Globals
|
|
44
|
-
Configs
|
|
45
|
-
Candidates
|
|
43
|
+
Edges map[string][]string `json:"edges"`
|
|
44
|
+
Globals []string `json:"globals"`
|
|
45
|
+
Configs []string `json:"configs"`
|
|
46
|
+
Candidates map[string][]string `json:"candidates,omitempty"`
|
|
47
|
+
InputHashes map[string]*string `json:"inputHashes,omitempty"`
|
|
48
|
+
InputRealpaths map[string]*string `json:"inputRealpaths,omitempty"`
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
// NewTransformGraph computes the reference graph of a loaded program, keyed
|
|
@@ -69,16 +72,66 @@ func NewTransformGraph(prog *Program, cwd string) *TransformGraph {
|
|
|
69
72
|
if shimcompiler.FileAffectsGlobalScope(file) {
|
|
70
73
|
graph.Globals = append(graph.Globals, key)
|
|
71
74
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
// Keep leaf modules as empty adjacency-list entries. Besides making the
|
|
76
|
+
// graph's node universe explicit, this lets attachInputProof bind every
|
|
77
|
+
// source file to the bytes TypeScript-Go actually read. Omitting a leaf
|
|
78
|
+
// would let an A-B-A edit during compilation pair B's output with identical
|
|
79
|
+
// pre/post project snapshots for A.
|
|
80
|
+
graph.Edges[key] = referenceTargets(prog, cwd, file)
|
|
76
81
|
}
|
|
77
82
|
sort.Strings(graph.Globals)
|
|
78
83
|
graph.Configs = configChain(prog, cwd)
|
|
84
|
+
graph.attachInputProof(prog, cwd)
|
|
79
85
|
return graph
|
|
80
86
|
}
|
|
81
87
|
|
|
88
|
+
// attachInputProof pairs every graph path with the state the compiler
|
|
89
|
+
// filesystem actually returned while constructing the resident Program. A
|
|
90
|
+
// missing entry means proof was incomplete or contradictory; persistent hosts
|
|
91
|
+
// then keep the fresh result but decline cross-build reuse.
|
|
92
|
+
func (graph *TransformGraph) attachInputProof(prog *Program, cwd string) {
|
|
93
|
+
if prog == nil || prog.inputObserver == nil {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
inputs := map[string]struct{}{}
|
|
97
|
+
for source, targets := range graph.Edges {
|
|
98
|
+
inputs[source] = struct{}{}
|
|
99
|
+
for _, target := range targets {
|
|
100
|
+
inputs[target] = struct{}{}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for _, input := range graph.Globals {
|
|
104
|
+
inputs[input] = struct{}{}
|
|
105
|
+
}
|
|
106
|
+
for _, input := range graph.Configs {
|
|
107
|
+
inputs[input] = struct{}{}
|
|
108
|
+
}
|
|
109
|
+
for source, candidates := range graph.Candidates {
|
|
110
|
+
inputs[source] = struct{}{}
|
|
111
|
+
for _, candidate := range candidates {
|
|
112
|
+
inputs[candidate] = struct{}{}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
hashes := map[string]*string{}
|
|
116
|
+
realpaths := map[string]*string{}
|
|
117
|
+
for input := range inputs {
|
|
118
|
+
file := filepath.FromSlash(input)
|
|
119
|
+
if !filepath.IsAbs(file) {
|
|
120
|
+
file = filepath.Join(cwd, file)
|
|
121
|
+
}
|
|
122
|
+
hash, realpath, ok := prog.inputObserver.proof(file)
|
|
123
|
+
if !ok {
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
hashes[input] = hash
|
|
127
|
+
realpaths[input] = realpath
|
|
128
|
+
}
|
|
129
|
+
if len(hashes) != 0 {
|
|
130
|
+
graph.InputHashes = hashes
|
|
131
|
+
graph.InputRealpaths = realpaths
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
82
135
|
// referenceTargets resolves one file's direct reference set to sorted envelope
|
|
83
136
|
// keys, dropping bundled library files and the file itself.
|
|
84
137
|
func referenceTargets(prog *Program, cwd string, file *ast.SourceFile) []string {
|