ttsc 0.28.5 → 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
@@ -10,6 +10,7 @@ import (
10
10
  "encoding/json"
11
11
  "flag"
12
12
  "fmt"
13
+ "os"
13
14
 
14
15
  shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
15
16
 
@@ -66,10 +67,11 @@ func runAPICompile(args []string) int {
66
67
  }
67
68
 
68
69
  prog, diags, err := driver.LoadProgram(cwd, *tsconfigPath, driver.LoadProgramOptions{
69
- ForceEmit: true,
70
- SingleThreaded: *singleThreaded,
71
- Checkers: *checkers,
72
- TsgoArgs: tsgoArgs,
70
+ ForceEmit: true,
71
+ SemanticConfigPath: os.Getenv(driver.SemanticConfigPathEnv),
72
+ SingleThreaded: *singleThreaded,
73
+ Checkers: *checkers,
74
+ TsgoArgs: tsgoArgs,
73
75
  })
74
76
  if err != nil {
75
77
  fmt.Fprintf(stderr, "ttsc api-compile: %v\n", err)
@@ -10,6 +10,7 @@ import (
10
10
  "encoding/json"
11
11
  "flag"
12
12
  "fmt"
13
+ "os"
13
14
 
14
15
  "github.com/samchon/ttsc/packages/ttsc/driver"
15
16
  cwdutil "github.com/samchon/ttsc/packages/ttsc/internal/cwd"
@@ -71,10 +72,11 @@ func runAPITransform(args []string) int {
71
72
  }
72
73
 
73
74
  prog, diags, err := driver.LoadProgram(cwd, *tsconfigPath, driver.LoadProgramOptions{
74
- ForceNoEmit: true,
75
- SingleThreaded: *singleThreaded,
76
- Checkers: *checkers,
77
- TsgoArgs: tsgoArgs,
75
+ ForceNoEmit: true,
76
+ SemanticConfigPath: os.Getenv(driver.SemanticConfigPathEnv),
77
+ SingleThreaded: *singleThreaded,
78
+ Checkers: *checkers,
79
+ TsgoArgs: tsgoArgs,
78
80
  })
79
81
  if err != nil {
80
82
  fmt.Fprintf(stderr, "ttsc api-transform: %v\n", err)
package/cmd/ttsc/build.go CHANGED
@@ -69,12 +69,13 @@ func runBuild(args []string) int {
69
69
  }
70
70
 
71
71
  prog, diags, err := driver.LoadProgram(cwd, *tsconfigPath, driver.LoadProgramOptions{
72
- ForceEmit: *emit,
73
- ForceNoEmit: *noEmit,
74
- OutDir: *outDir,
75
- SingleThreaded: *singleThreaded,
76
- Checkers: *checkers,
77
- TsgoArgs: tsgoArgs,
72
+ ForceEmit: *emit,
73
+ ForceNoEmit: *noEmit,
74
+ OutDir: *outDir,
75
+ SemanticConfigPath: os.Getenv(driver.SemanticConfigPathEnv),
76
+ SingleThreaded: *singleThreaded,
77
+ Checkers: *checkers,
78
+ TsgoArgs: tsgoArgs,
78
79
  })
79
80
  if err != nil {
80
81
  fmt.Fprintf(stderr, "ttsc: %v\n", err)
@@ -10,6 +10,7 @@ import (
10
10
  "io"
11
11
  "os"
12
12
  "path/filepath"
13
+ "runtime"
13
14
  "slices"
14
15
  "sort"
15
16
  "strings"
@@ -574,7 +575,7 @@ func (s *graphSession) captureState() error {
574
575
  if err != nil {
575
576
  return err
576
577
  }
577
- inputs := auxiliaryInputs(program, configs, s.cwd)
578
+ inputs := auxiliaryInputs(program, s.cwd)
578
579
  for _, path := range missingRootInputs(configs, sourceHashes) {
579
580
  inputs = append(inputs, auxiliaryInput{path: path})
580
581
  }
@@ -847,23 +848,25 @@ func changedSources(previous map[string][sha256.Size]byte) (map[string]string, b
847
848
  }
848
849
 
849
850
  type diskState struct {
850
- Hash [sha256.Size]byte
851
- Exists bool
852
- Realpath string
853
- IdentityOnly bool
851
+ DirectoryEntries bool
852
+ Hash [sha256.Size]byte
853
+ Exists bool
854
+ Realpath string
855
+ IdentityOnly bool
854
856
  }
855
857
 
856
- // auxiliaryInput distinguishes speculative inputs whose contents select the
858
+ // auxiliaryInput distinguishes resolver inputs whose contents select the
857
859
  // build universe from the lexical spelling of the source the compiler already
858
860
  // selected. The resident source hash owns the latter's contents; this input
859
861
  // owns only whether the spelling still reaches the same physical file.
860
862
  type auxiliaryInput struct {
861
- path string
862
- identityOnly bool
863
+ directoryEntries bool
864
+ path string
865
+ identityOnly bool
863
866
  }
864
867
 
865
- // captureDiskStates records the freshness state of speculative resolution
866
- // candidates. Most candidates do not exist, and a module specifier can name a
868
+ // captureDiskStates records the freshness state of resolver inputs. Many do
869
+ // not exist, and a module specifier can name a
867
870
  // path the host OS cannot even parse (`./style.css?inline`, a `data:` URL on
868
871
  // Windows), so any path that is neither a readable file nor a directory is
869
872
  // recorded as absent instead of failing the snapshot: the recorded state only
@@ -871,12 +874,18 @@ type auxiliaryInput struct {
871
874
  func captureDiskStates(inputs []auxiliaryInput) map[string]diskState {
872
875
  states := make(map[string]diskState, len(inputs))
873
876
  for _, input := range inputs {
874
- state := diskState{IdentityOnly: input.identityOnly}
877
+ state := diskState{
878
+ DirectoryEntries: input.directoryEntries,
879
+ IdentityOnly: input.identityOnly,
880
+ }
875
881
  content, err := os.ReadFile(input.path)
876
882
  if err != nil {
877
883
  if info, statErr := os.Stat(input.path); statErr == nil && info.IsDir() {
878
884
  state.Exists = true
879
885
  state.Realpath = diskRealpath(input.path)
886
+ if input.directoryEntries {
887
+ state.Hash = accessibleDirectoryEntriesHash(input.path)
888
+ }
880
889
  }
881
890
  states[input.path] = state
882
891
  continue
@@ -894,7 +903,11 @@ func captureDiskStates(inputs []auxiliaryInput) map[string]diskState {
894
903
  func diskStatesChanged(previous map[string]diskState) bool {
895
904
  inputs := make([]auxiliaryInput, 0, len(previous))
896
905
  for path, state := range previous {
897
- inputs = append(inputs, auxiliaryInput{path: path, identityOnly: state.IdentityOnly})
906
+ inputs = append(inputs, auxiliaryInput{
907
+ directoryEntries: state.DirectoryEntries,
908
+ identityOnly: state.IdentityOnly,
909
+ path: path,
910
+ })
898
911
  }
899
912
  current := captureDiskStates(inputs)
900
913
  for path, state := range previous {
@@ -905,7 +918,7 @@ func diskStatesChanged(previous map[string]diskState) bool {
905
918
  return false
906
919
  }
907
920
 
908
- func auxiliaryInputs(program *driver.Program, configs []*shimtsoptions.ParsedCommandLine, cwd string) []auxiliaryInput {
921
+ func auxiliaryInputs(program *driver.Program, cwd string) []auxiliaryInput {
909
922
  inputs := []auxiliaryInput{
910
923
  {path: filepath.Join(cwd, ".gitignore")},
911
924
  {path: filepath.Join(cwd, ".git", "info", "exclude")},
@@ -925,75 +938,17 @@ func auxiliaryInputs(program *driver.Program, configs []*shimtsoptions.ParsedCom
925
938
  for _, path := range appendAncestorInputs(nil, directory, cwd) {
926
939
  inputs = append(inputs, auxiliaryInput{path: path})
927
940
  }
928
- for _, reference := range source.ReferencedFiles {
929
- for _, path := range driver.FileCandidates(filepath.Join(directory, filepath.FromSlash(reference.FileName))) {
930
- inputs = append(inputs, auxiliaryInput{path: path})
931
- }
932
- }
933
- for _, reference := range source.TypeReferenceDirectives {
934
- for _, path := range driver.TypeReferenceCandidates(configs, directory, cwd, reference.FileName) {
935
- inputs = append(inputs, auxiliaryInput{path: path})
936
- }
937
- }
938
- for _, specifier := range driver.SourceModuleSpecifiers(source) {
939
- context := driver.ModuleResolutionContext{
940
- Mode: program.TSProgram.GetModeForUsageLocation(source, specifier),
941
- }
942
- for _, parsed := range configs {
943
- if parsed != nil && parsed.ParsedConfig != nil && parsed.ParsedConfig.CompilerOptions != nil {
944
- context.Options = parsed.ParsedConfig.CompilerOptions
945
- break
946
- }
947
- }
948
- resolved := program.TSProgram.GetResolvedModuleFromModuleSpecifier(source, specifier)
949
- if resolved != nil && resolved.IsResolved() {
950
- predecessors := driver.ModuleResolutionPredecessors(
951
- configs,
952
- directory,
953
- cwd,
954
- specifier.Text(),
955
- resolved.ResolvedFileName,
956
- program.FS.UseCaseSensitiveFileNames(),
957
- context,
958
- )
959
- for _, path := range predecessors {
960
- inputs = append(inputs, auxiliaryInput{
961
- path: path,
962
- identityOnly: sameExistingAuxiliaryPath(path, resolved.ResolvedFileName),
963
- })
964
- }
965
- continue
966
- }
967
- for _, path := range driver.ModuleResolutionCandidates(configs, directory, cwd, specifier.Text(), context) {
968
- inputs = append(inputs, auxiliaryInput{path: path})
969
- }
970
- }
971
941
  }
972
- // Config `types` entries request type packages without any source syntax, so
973
- // a missing one (e.g. a generated typeRoots package) must contribute the same
974
- // candidates as a triple-slash type directive.
975
- for _, parsed := range configs {
976
- if parsed == nil || parsed.ParsedConfig == nil || parsed.ParsedConfig.CompilerOptions == nil {
977
- continue
978
- }
979
- for _, name := range parsed.ParsedConfig.CompilerOptions.Types {
980
- for _, path := range driver.TypeReferenceCandidates(configs, parsed.GetCurrentDirectory(), cwd, name) {
981
- inputs = append(inputs, auxiliaryInput{path: path})
982
- }
983
- }
942
+ for _, input := range driver.ObserveProgramResolutions(program, cwd).Inputs {
943
+ inputs = append(inputs, auxiliaryInput{
944
+ directoryEntries: input.DirectoryEntries,
945
+ identityOnly: input.IdentityOnly,
946
+ path: input.Path,
947
+ })
984
948
  }
985
949
  return compactAuxiliaryInputs(inputs)
986
950
  }
987
951
 
988
- func sameExistingAuxiliaryPath(left, right string) bool {
989
- leftInfo, err := os.Stat(left)
990
- if err != nil {
991
- return false
992
- }
993
- rightInfo, err := os.Stat(right)
994
- return err == nil && os.SameFile(leftInfo, rightInfo)
995
- }
996
-
997
952
  func compactAuxiliaryInputs(inputs []auxiliaryInput) []auxiliaryInput {
998
953
  byPath := make(map[string]auxiliaryInput, len(inputs))
999
954
  for _, input := range inputs {
@@ -1001,6 +956,7 @@ func compactAuxiliaryInputs(inputs []auxiliaryInput) []auxiliaryInput {
1001
956
  continue
1002
957
  }
1003
958
  if previous, exists := byPath[input.path]; exists {
959
+ input.directoryEntries = previous.directoryEntries || input.directoryEntries
1004
960
  // A path that also participates as a manifest or other content-bearing
1005
961
  // input keeps the stronger content-sensitive contract.
1006
962
  input.identityOnly = previous.identityOnly && input.identityOnly
@@ -1019,6 +975,43 @@ func compactAuxiliaryInputs(inputs []auxiliaryInput) []auxiliaryInput {
1019
975
  return output
1020
976
  }
1021
977
 
978
+ // accessibleDirectoryEntriesHash mirrors TypeScript-Go's accessible-entry
979
+ // classification and sorted name order for a directory enumeration input.
980
+ func accessibleDirectoryEntriesHash(directory string) [sha256.Size]byte {
981
+ hash := sha256.New()
982
+ entries, err := os.ReadDir(directory)
983
+ if err == nil {
984
+ for _, entry := range entries {
985
+ kind := byte(0)
986
+ switch {
987
+ case entry.Type().IsDir():
988
+ kind = 'D'
989
+ case entry.Type().IsRegular():
990
+ kind = 'F'
991
+ case entry.Type()&os.ModeSymlink != 0 || (runtime.GOOS == "windows" && entry.Type()&os.ModeIrregular != 0):
992
+ if info, statErr := os.Stat(filepath.Join(directory, entry.Name())); statErr == nil {
993
+ if info.IsDir() {
994
+ kind = 'D'
995
+ } else if info.Mode().IsRegular() {
996
+ kind = 'F'
997
+ }
998
+ }
999
+ default:
1000
+ continue
1001
+ }
1002
+ if kind == 0 {
1003
+ continue
1004
+ }
1005
+ _, _ = hash.Write([]byte{kind})
1006
+ _, _ = hash.Write([]byte(entry.Name()))
1007
+ _, _ = hash.Write([]byte{0})
1008
+ }
1009
+ }
1010
+ var digest [sha256.Size]byte
1011
+ copy(digest[:], hash.Sum(nil))
1012
+ return digest
1013
+ }
1014
+
1022
1015
  func appendAncestorInputs(inputs []string, directory, stop string) []string {
1023
1016
  stop = filepath.Clean(stop)
1024
1017
  for current := filepath.Clean(directory); ; current = filepath.Dir(current) {
@@ -220,6 +220,32 @@ func TestServeSessionReloadsSupersedingModuleCandidates(t *testing.T) {
220
220
  }
221
221
  })
222
222
  }
223
+
224
+ t.Run("automatic_type_directory_membership", func(t *testing.T) {
225
+ root := t.TempDir()
226
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
227
+ "compilerOptions": { "module": "commonjs", "target": "ES2022", "types": ["*"] },
228
+ "files": ["src/main.ts"]
229
+ }`)
230
+ writeGraphFile(t, filepath.Join(root, "src", "main.ts"), "export const value = 1;\n")
231
+ session, err := newGraphSession(root, "tsconfig.json")
232
+ if err != nil {
233
+ t.Fatal(err)
234
+ }
235
+ defer session.Close()
236
+ if _, _, _, err := session.Snapshot(); err != nil {
237
+ t.Fatal(err)
238
+ }
239
+
240
+ writeGraphFile(t, filepath.Join(root, "node_modules", "@types", "generated", "index.d.ts"), "declare const generatedAmbient: string;\n")
241
+ dump, mode, changed, err := session.Snapshot()
242
+ if err != nil {
243
+ t.Fatal(err)
244
+ }
245
+ if dump == nil || mode != serveModeReload || !changed {
246
+ t.Fatalf("automatic type package appearance = dump:%v mode:%q changed:%v", dump != nil, mode, changed)
247
+ }
248
+ })
223
249
  }
224
250
 
225
251
  func hasDumpNodeAt(dump graph.Dump, name, fileSuffix string) bool {
@@ -19,7 +19,6 @@ import legacy = require("./legacy");
19
19
  export { exported } from "./exported";
20
20
  export type Deferred = import("./types").Deferred;
21
21
  export const lazy = import("./lazy");
22
- export const common = require("./common");
23
22
  export const result = legacy();
24
23
  `)
25
24
 
@@ -29,7 +28,7 @@ export const result = legacy();
29
28
  }
30
29
  defer session.Close()
31
30
 
32
- for _, name := range []string{"legacy.ts", "exported.ts", "types.ts", "lazy.ts", "common.ts"} {
31
+ for _, name := range []string{"legacy.ts", "exported.ts", "types.ts", "lazy.ts"} {
33
32
  candidate := filepath.Join(root, "src", name)
34
33
  state, ok := session.auxStates[candidate]
35
34
  if !ok {
package/driver/graph.go CHANGED
@@ -32,26 +32,31 @@ const bundledScheme = "bundled:///"
32
32
  // entries). A change to any of them can affect every file.
33
33
  // - Configs lists the project tsconfig followed by its `extends` ancestry.
34
34
  // - Candidates maps each importing file to the resolution probes that precede
35
- // its selected module target. They are a separate class from resolved
36
- // edges: a probe's file appearing or changing can change an unchanged
37
- // import's meaning.
38
- // - InputHashes and InputRealpaths pair graph members with the exact content
39
- // state and physical identity observed by the compiler filesystem.
35
+ // or otherwise participate in its selected module and type-reference
36
+ // results. They are a separate class from resolved edges: a predicate or
37
+ // package manifest changing can change an unchanged reference's meaning.
38
+ // - ResolutionInputs lists automatic type discovery and resolution inputs
39
+ // whose state can affect every source file, including type-root directory
40
+ // membership.
41
+ // - InputObservations preserves the independent filesystem predicates the
42
+ // compiler actually asked. InputHashes and InputRealpaths retain the legacy
43
+ // collapsed content/identity projection for older consumers.
40
44
  // - InputProofFailures gives a stable reason when a realized member lacks
41
- // that pair. Speculative candidates are omitted because the compiler was
42
- // never expected to read them.
45
+ // proof or a replayed resolver predicate changed.
43
46
  //
44
47
  // Keys and values use the same convention as the envelope's `typescript`
45
48
  // map: project-relative slash paths, falling back to slash-normalized
46
49
  // absolute paths outside the project root (see TransformOutputKey).
47
50
  type TransformGraph struct {
48
- Edges map[string][]string `json:"edges"`
49
- Globals []string `json:"globals"`
50
- Configs []string `json:"configs"`
51
- Candidates map[string][]string `json:"candidates,omitempty"`
52
- InputHashes map[string]*string `json:"inputHashes,omitempty"`
53
- InputRealpaths map[string]*string `json:"inputRealpaths,omitempty"`
54
- InputProofFailures map[string]string `json:"inputProofFailures,omitempty"`
51
+ Edges map[string][]string `json:"edges"`
52
+ Globals []string `json:"globals"`
53
+ Configs []string `json:"configs"`
54
+ Candidates map[string][]string `json:"candidates,omitempty"`
55
+ ResolutionInputs []string `json:"resolutionInputs,omitempty"`
56
+ InputObservations map[string]TransformInputObservation `json:"inputObservations,omitempty"`
57
+ InputHashes map[string]*string `json:"inputHashes,omitempty"`
58
+ InputRealpaths map[string]*string `json:"inputRealpaths,omitempty"`
59
+ InputProofFailures map[string]string `json:"inputProofFailures,omitempty"`
55
60
  }
56
61
 
57
62
  // NewTransformGraph computes the reference graph of a loaded program, keyed
@@ -63,11 +68,13 @@ func NewTransformGraph(prog *Program, cwd string) *TransformGraph {
63
68
  if prog == nil || prog.TSProgram == nil {
64
69
  return nil
65
70
  }
71
+ resolution := ObserveProgramResolutions(prog, cwd)
66
72
  graph := &TransformGraph{
67
- Edges: map[string][]string{},
68
- Globals: []string{},
69
- Configs: []string{},
70
- Candidates: SupersedingModuleCandidates(prog, cwd),
73
+ Edges: map[string][]string{},
74
+ Globals: []string{},
75
+ Configs: []string{},
76
+ Candidates: resolution.Candidates,
77
+ ResolutionInputs: resolution.Universal,
71
78
  }
72
79
  for _, file := range prog.TSProgram.SourceFiles() {
73
80
  fileName := file.FileName()
@@ -87,6 +94,10 @@ func NewTransformGraph(prog *Program, cwd string) *TransformGraph {
87
94
  }
88
95
  sort.Strings(graph.Globals)
89
96
  graph.Configs = configChain(prog, cwd)
97
+ resolution.ApplyUniversalResolutionFailure(graph.Edges)
98
+ if len(resolution.Failures) != 0 {
99
+ graph.InputProofFailures = resolution.Failures
100
+ }
90
101
  graph.attachInputProof(prog, cwd)
91
102
  return graph
92
103
  }
@@ -123,21 +134,39 @@ func (graph *TransformGraph) attachInputProof(prog *Program, cwd string) {
123
134
  inputs[candidate] = struct{}{}
124
135
  }
125
136
  }
137
+ for _, input := range graph.ResolutionInputs {
138
+ inputs[input] = struct{}{}
139
+ }
126
140
  hashes := map[string]*string{}
127
141
  realpaths := map[string]*string{}
128
- failures := map[string]string{}
142
+ observations := map[string]TransformInputObservation{}
143
+ failures := graph.InputProofFailures
144
+ if failures == nil {
145
+ failures = map[string]string{}
146
+ }
129
147
  for input := range inputs {
130
148
  file := filepath.FromSlash(input)
131
149
  if !filepath.IsAbs(file) {
132
150
  file = filepath.Join(cwd, file)
133
151
  }
134
- hash, realpath, failure := prog.inputObserver.proof(file)
135
- if failure != "" {
136
- // Speculative candidates are allowed to lack compiler reads. Reporting
137
- // only realized members keeps this diagnostic side channel bounded to
138
- // inputs whose missing proof can actually refuse generation reuse.
139
- if _, ok := realized[input]; ok {
140
- failures[input] = string(failure)
152
+ observation, predicateFailure := prog.inputObserver.predicateProof(file)
153
+ if predicateFailure == "" {
154
+ observations[input] = observation
155
+ }
156
+ var hash, realpath *string
157
+ legacyFailure := predicateFailure
158
+ if predicateFailure == "" {
159
+ hash, realpath, legacyFailure = prog.inputObserver.proof(file)
160
+ }
161
+ if legacyFailure != "" {
162
+ // A resolver input can have a complete predicate proof that the legacy
163
+ // path-kind projection cannot represent, such as a successful file check
164
+ // whose content was never requested. The rich proof remains sufficient
165
+ // for that input; only a predicate failure, or any realized-input
166
+ // failure, makes the generation inadmissible.
167
+ _, isRealized := realized[input]
168
+ if isRealized || (predicateFailure != "" && predicateFailure != inputProofUnobserved) {
169
+ failures[input] = string(legacyFailure)
141
170
  }
142
171
  continue
143
172
  }
@@ -148,6 +177,9 @@ func (graph *TransformGraph) attachInputProof(prog *Program, cwd string) {
148
177
  graph.InputHashes = hashes
149
178
  graph.InputRealpaths = realpaths
150
179
  }
180
+ if len(observations) != 0 {
181
+ graph.InputObservations = observations
182
+ }
151
183
  if len(failures) != 0 {
152
184
  graph.InputProofFailures = failures
153
185
  }
@@ -158,18 +190,18 @@ func (graph *TransformGraph) attachInputProof(prog *Program, cwd string) {
158
190
  func referenceTargets(prog *Program, cwd string, file *ast.SourceFile) []string {
159
191
  paths := shimcompiler.GetReferencedFilePaths(prog.TSProgram, file)
160
192
  targets := make([]string, 0, len(paths))
161
- for _, path := range paths {
162
- fileName := path
193
+ for _, referencedPath := range paths {
163
194
  // Referenced paths are case-canonicalized tspath.Path values; recover the
164
- // real spelling from the program so consumers can compare them against
165
- // filesystem paths byte-for-byte.
166
- if resolved := prog.TSProgram.GetSourceFileByPath(shimtspath.Path(path)); resolved != nil {
167
- fileName = resolved.FileName()
168
- }
169
- if fileName == file.FileName() || strings.HasPrefix(fileName, bundledScheme) {
195
+ // resident source and its real spelling from the Program. The incremental
196
+ // helper can retain a raw extensionless project-reference directive even
197
+ // when no corresponding source became resident. That spelling is a
198
+ // resolver candidate, not a realized graph edge, and has no compiler-time
199
+ // content proof.
200
+ resolved := prog.TSProgram.GetSourceFileByPath(shimtspath.Path(referencedPath))
201
+ if resolved == nil || resolved == file || strings.HasPrefix(resolved.FileName(), bundledScheme) {
170
202
  continue
171
203
  }
172
- targets = append(targets, TransformOutputKey(cwd, fileName))
204
+ targets = append(targets, TransformOutputKey(cwd, resolved.FileName()))
173
205
  }
174
206
  sort.Strings(targets)
175
207
  return targets