ttsc 0.19.2 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +7 -5
  2. package/cmd/graphdump/main.go +2 -4
  3. package/cmd/ttsc/filter.go +12 -4
  4. package/cmd/ttsc/filter_resolves_flag_names_case_insensitively_test.go +53 -0
  5. package/cmd/ttsc/flags_gen.go +3 -3
  6. package/cmd/ttscgraph/dump.go +5 -7
  7. package/cmd/ttscgraph/run_dump_emits_only_checker_verified_member_relations_test.go +118 -0
  8. package/cmd/ttscgraph/serve.go +58 -308
  9. package/cmd/ttscgraph/serve_session_keeps_base_url_fallback_unchanged_test.go +49 -0
  10. package/cmd/ttscgraph/serve_session_keeps_esm_directory_candidate_unchanged_test.go +61 -0
  11. package/cmd/ttscgraph/serve_session_keeps_exports_blocked_file_candidates_unchanged_test.go +64 -0
  12. package/cmd/ttscgraph/serve_session_keeps_inactive_conditional_export_unchanged_test.go +66 -0
  13. package/cmd/ttscgraph/serve_session_keeps_lower_priority_module_candidate_unchanged_test.go +67 -0
  14. package/cmd/ttscgraph/serve_session_keeps_lower_priority_paths_pattern_unchanged_test.go +58 -0
  15. package/cmd/ttscgraph/serve_session_keeps_nonlongest_root_dirs_candidate_unchanged_test.go +54 -0
  16. package/cmd/ttscgraph/serve_session_reloads_commonjs_package_entry_for_esm_test.go +50 -0
  17. package/cmd/ttscgraph/serve_session_reloads_module_suffix_candidate_test.go +41 -0
  18. package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +216 -0
  19. package/cmd/ttscgraph/serve_session_retries_failed_dump_test.go +103 -0
  20. package/driver/graph.go +12 -6
  21. package/driver/lsp.go +30 -0
  22. package/driver/program.go +37 -1
  23. package/driver/resolution_candidates.go +819 -0
  24. package/driver/windowsjunction/windowsjunction.go +40 -0
  25. package/go.mod +2 -0
  26. package/internal/graph/build.go +45 -11
  27. package/internal/graph/dump.go +312 -108
  28. package/internal/graph/dump_checkout_paths_are_stable_test.go +210 -0
  29. package/internal/graph/dump_diagnostics_ride_the_generation_that_built_it_test.go +9 -3
  30. package/internal/graph/dump_paths.go +172 -0
  31. package/internal/graph/dump_paths_are_portable_and_injective_test.go +93 -0
  32. package/internal/graph/dump_schema_version_matches_the_typescript_client_test.go +1 -1
  33. package/internal/graph/dump_uses_ecma_line_terminators_test.go +87 -0
  34. package/internal/graph/duplicate_variable_object_members_keep_first_declaration_test.go +61 -0
  35. package/internal/graph/edges.go +220 -5
  36. package/internal/graph/enum_members_carry_their_names_on_the_enum_node_test.go +131 -0
  37. package/internal/graph/graph.go +177 -5
  38. package/internal/graph/line_model.go +122 -0
  39. package/internal/graph/literals.go +175 -0
  40. package/internal/graph/literals_are_absent_when_the_type_admits_more_than_it_can_name_test.go +74 -0
  41. package/internal/graph/literals_cover_enum_members_the_graph_has_no_nodes_for_test.go +79 -0
  42. package/internal/graph/literals_do_not_depend_on_declaration_line_wrapping_test.go +84 -0
  43. package/internal/graph/literals_render_every_value_in_typescript_source_form_test.go +77 -0
  44. package/internal/graph/literals_resolve_through_alias_indirection_test.go +64 -0
  45. package/internal/graph/member_relation_edges_follow_checker_validity_test.go +85 -0
  46. package/internal/graph/member_relation_edges_preserve_checker_boundaries_test.go +191 -0
  47. package/internal/graph/member_relation_edges_reject_checker_invalid_pairs_test.go +119 -0
  48. package/internal/graph/node_ids_preserve_hash_bearing_components_test.go +41 -0
  49. package/internal/graph/node_modifiers_emit_union_strings_test.go +4 -1
  50. package/internal/graph/object_literal_members_ride_on_the_snapshot_test.go +192 -0
  51. package/internal/graph/object_member_signatures_preserve_literal_whitespace_test.go +67 -0
  52. package/internal/graph/object_members.go +229 -0
  53. package/internal/graph/provenance.go +88 -30
  54. package/internal/graph/source_texts_cover_every_resident_program_source_test.go +73 -0
  55. package/internal/graphsymbols/position_helpers_follow_ecma_line_terminators_test.go +66 -0
  56. package/internal/graphsymbols/provider.go +26 -91
  57. package/internal/lspserver/lsp_clean_didopen_refreshes_compiler_state_test.go +144 -0
  58. package/internal/lspserver/lsp_completion.go +109 -0
  59. package/internal/lspserver/lsp_completion_gate_never_refuses_a_matching_line_test.go +50 -0
  60. package/internal/lspserver/lsp_completion_hints_longest_trigger_wins_test.go +149 -0
  61. package/internal/lspserver/lsp_completion_hints_nearest_trigger_wins_test.go +37 -0
  62. package/internal/lspserver/lsp_completion_merges_into_upstream_response_test.go +149 -0
  63. package/internal/lspserver/lsp_completion_nearest_hint_replaces_nearest_filter_test.go +43 -0
  64. package/internal/lspserver/lsp_completion_preserves_completion_list_extensions_test.go +99 -0
  65. package/internal/lspserver/lsp_completion_refuses_hints_inside_a_string_literal_test.go +69 -0
  66. package/internal/lspserver/lsp_completion_replaces_full_filter_in_rank_order_test.go +85 -0
  67. package/internal/lspserver/lsp_completion_request.go +455 -0
  68. package/internal/lspserver/lsp_completion_scope.go +530 -0
  69. package/internal/lspserver/lsp_completion_scope_bench_test.go +41 -0
  70. package/internal/lspserver/lsp_completion_scope_classifies_regex_statement_and_operator_boundaries_test.go +39 -0
  71. package/internal/lspserver/lsp_completion_scope_ignores_jsdoc_bytes_in_literals_test.go +108 -0
  72. package/internal/lspserver/lsp_completion_scope_refuses_regex_hints_test.go +52 -0
  73. package/internal/lspserver/lsp_completion_trigger_uses_last_rune_test.go +24 -0
  74. package/internal/lspserver/lsp_deleted_watched_file_withdraws_plugin_diagnostics_test.go +110 -0
  75. package/internal/lspserver/lsp_didsave_keeps_the_incremental_changed_uri_path_test.go +78 -0
  76. package/internal/lspserver/lsp_hints_absence_is_not_an_error_test.go +46 -0
  77. package/internal/lspserver/lsp_hints_refresh_coalesces_concurrent_requests_test.go +82 -0
  78. package/internal/lspserver/lsp_hints_refresh_ignores_a_superseded_generation_test.go +40 -0
  79. package/internal/lspserver/lsp_hints_refresh_keeps_each_producer_isolated_test.go +64 -0
  80. package/internal/lspserver/lsp_hints_refresh_replaces_the_session_snapshot_test.go +66 -0
  81. package/internal/lspserver/lsp_hints_refresh_scheduled_by_editor_events_test.go +90 -0
  82. package/internal/lspserver/lsp_hints_refresh_serves_completion_while_running_test.go +91 -0
  83. package/internal/lspserver/lsp_index_byte_from_bounds_its_search_start_test.go +38 -0
  84. package/internal/lspserver/lsp_initialize_constrains_position_encoding_to_utf16_test.go +205 -0
  85. package/internal/lspserver/lsp_late_completion_trigger_is_reported_once_test.go +105 -0
  86. package/internal/lspserver/lsp_native_completion_hints_decode_flat_lint_wire_test.go +66 -0
  87. package/internal/lspserver/lsp_native_completion_hints_grouped_compatibility_boundary_test.go +60 -0
  88. package/internal/lspserver/lsp_native_plugin_source.go +315 -2
  89. package/internal/lspserver/lsp_native_resident.go +304 -0
  90. package/internal/lspserver/lsp_plugin.go +85 -8
  91. package/internal/lspserver/lsp_plugin_completion_resolve_stays_local_test.go +82 -0
  92. package/internal/lspserver/lsp_position_conversions_span_astral_and_combining_runes_test.go +119 -0
  93. package/internal/lspserver/lsp_proxy.go +601 -9
  94. package/internal/lspserver/lsp_refresh.go +70 -0
  95. package/internal/lspserver/lsp_resident_falls_back_when_serve_unsupported_test.go +43 -0
  96. package/internal/lspserver/lsp_resident_maps_verb_args_test.go +34 -0
  97. package/internal/lspserver/lsp_server.go +13 -1
  98. package/internal/lspserver/lsp_watched_file_changes_reach_compiler_caches_test.go +172 -0
  99. package/lib/TtscCompiler.d.ts +3 -1
  100. package/lib/TtscCompiler.js +5 -1
  101. package/lib/TtscCompiler.js.map +1 -1
  102. package/lib/TtscService.d.ts +13 -3
  103. package/lib/TtscService.js +6 -6
  104. package/lib/TtscService.js.map +1 -1
  105. package/lib/compiler/internal/project/readConfigJson.d.ts +30 -0
  106. package/lib/compiler/internal/project/readConfigJson.js +208 -0
  107. package/lib/compiler/internal/project/readConfigJson.js.map +1 -0
  108. package/lib/compiler/internal/project/readProjectConfig.js +53 -149
  109. package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
  110. package/lib/compiler/internal/residentTransformProcess.d.ts +15 -2
  111. package/lib/compiler/internal/residentTransformProcess.js +159 -39
  112. package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
  113. package/lib/compiler/internal/runBuild.d.ts +6 -0
  114. package/lib/compiler/internal/runBuild.js +143 -44
  115. package/lib/compiler/internal/runBuild.js.map +1 -1
  116. package/lib/compiler/internal/startResidentTransform.d.ts +5 -1
  117. package/lib/compiler/internal/startResidentTransform.js +3 -1
  118. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  119. package/lib/compiler/internal/transformProjectInMemory.js +12 -2
  120. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  121. package/lib/flags/parser.d.ts +19 -0
  122. package/lib/flags/parser.js +78 -36
  123. package/lib/flags/parser.js.map +1 -1
  124. package/lib/flags/schema.d.ts +49 -11
  125. package/lib/flags/schema.js +94 -22
  126. package/lib/flags/schema.js.map +1 -1
  127. package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +5 -0
  128. package/lib/internal/assertSafeExplicitCacheDirectory.js +26 -0
  129. package/lib/internal/assertSafeExplicitCacheDirectory.js.map +1 -0
  130. package/lib/launcher/internal/runTtsc.js +107 -69
  131. package/lib/launcher/internal/runTtsc.js.map +1 -1
  132. package/lib/launcher/internal/runTtsx.js +28 -41
  133. package/lib/launcher/internal/runTtsx.js.map +1 -1
  134. package/lib/launcher/internal/runtimeHooks.d.ts +15 -0
  135. package/lib/launcher/internal/runtimeHooks.js +18 -1
  136. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  137. package/lib/launcher/internal/watchTopology.d.ts +43 -0
  138. package/lib/launcher/internal/watchTopology.js +360 -0
  139. package/lib/launcher/internal/watchTopology.js.map +1 -0
  140. package/lib/plugin/internal/buildSourcePlugin.d.ts +14 -3
  141. package/lib/plugin/internal/buildSourcePlugin.js +247 -61
  142. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  143. package/lib/plugin/internal/loadProjectPlugins.js +63 -2
  144. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  145. package/lib/structures/ITtscCompilerTransformation.d.ts +16 -0
  146. package/lib/structures/internal/ITtscParsedProjectConfig.d.ts +2 -0
  147. package/package.json +8 -8
  148. package/shim/ast/shim.go +48 -3
  149. package/shim/astnav/go.mod +14 -0
  150. package/shim/astnav/go.sum +20 -0
  151. package/shim/astnav/shim.go +15 -0
  152. package/shim/checker/shim.go +113 -0
  153. package/shim/core/shim.go +47 -0
  154. package/shim/diagnosticwriter/lint.go +50 -0
  155. package/src/TtscCompiler.ts +5 -1
  156. package/src/TtscService.ts +37 -10
  157. package/src/compiler/internal/project/readConfigJson.ts +209 -0
  158. package/src/compiler/internal/project/readProjectConfig.ts +62 -158
  159. package/src/compiler/internal/residentTransformProcess.ts +197 -39
  160. package/src/compiler/internal/runBuild.ts +169 -47
  161. package/src/compiler/internal/startResidentTransform.ts +12 -1
  162. package/src/compiler/internal/transformProjectInMemory.ts +12 -1
  163. package/src/flags/parser.ts +113 -42
  164. package/src/flags/schema.ts +113 -31
  165. package/src/internal/assertSafeExplicitCacheDirectory.ts +29 -0
  166. package/src/launcher/internal/runTtsc.ts +117 -68
  167. package/src/launcher/internal/runTtsx.ts +29 -44
  168. package/src/launcher/internal/runtimeHooks.ts +30 -2
  169. package/src/launcher/internal/watchTopology.ts +466 -0
  170. package/src/plugin/internal/buildSourcePlugin.ts +326 -64
  171. package/src/plugin/internal/loadProjectPlugins.ts +80 -2
  172. package/src/structures/ITtscCompilerTransformation.ts +17 -0
  173. package/src/structures/internal/ITtscParsedProjectConfig.ts +2 -0
  174. package/tools/shim_audit/baseline.json +0 -3
  175. package/tools/shim_audit/main.go +42 -14
  176. package/tools/shim_audit/shim_dir_coverage_test.go +25 -0
  177. package/utility/flags_gen.go +3 -3
  178. package/utility/host.go +9 -3
@@ -14,7 +14,6 @@ import (
14
14
  "sort"
15
15
  "strings"
16
16
 
17
- shimast "github.com/microsoft/typescript-go/shim/ast"
18
17
  shimtsoptions "github.com/microsoft/typescript-go/shim/tsoptions"
19
18
  shimtspath "github.com/microsoft/typescript-go/shim/tspath"
20
19
 
@@ -128,6 +127,10 @@ type graphSession struct {
128
127
  configDigests []graph.FileDigest
129
128
  roots []graph.RootFile
130
129
  initialized bool
130
+ // pendingDumpMode remembers a generation whose state was captured but whose
131
+ // dump failed. Until an input change lets the session rebuild, an unchanged
132
+ // request must retry that dump instead of falsely confirming the older graph.
133
+ pendingDumpMode string
131
134
  }
132
135
 
133
136
  func newGraphSession(cwd, tsconfig string) (*graphSession, error) {
@@ -147,8 +150,17 @@ func (s *graphSession) Close() error {
147
150
 
148
151
  func (s *graphSession) Snapshot() (*graph.Dump, string, bool, error) {
149
152
  if !s.initialized {
150
- dump := s.buildDump()
153
+ // The captured compiler state is initialized even when its first dump is
154
+ // not publishable. Mark the attempt before building so a later request can
155
+ // observe a config/source edit that repairs the path error instead of
156
+ // retrying the stale Program forever.
151
157
  s.initialized = true
158
+ dump, err := s.buildDump()
159
+ if err != nil {
160
+ s.pendingDumpMode = serveModeInitial
161
+ return nil, "", false, err
162
+ }
163
+ s.pendingDumpMode = ""
152
164
  return &dump, serveModeInitial, true, nil
153
165
  }
154
166
 
@@ -160,16 +172,14 @@ func (s *graphSession) Snapshot() (*graph.Dump, string, bool, error) {
160
172
  if err := s.reload(); err != nil {
161
173
  return nil, "", false, err
162
174
  }
163
- dump := s.buildDump()
164
- return &dump, serveModeReload, true, nil
175
+ return s.changedSnapshot(serveModeReload)
165
176
  }
166
177
 
167
178
  if diskStatesChanged(s.auxStates) {
168
179
  if err := s.reload(); err != nil {
169
180
  return nil, "", false, err
170
181
  }
171
- dump := s.buildDump()
172
- return &dump, serveModeReload, true, nil
182
+ return s.changedSnapshot(serveModeReload)
173
183
  }
174
184
 
175
185
  roots, err := projectRootFiles(s.compiler.Program(), true)
@@ -180,8 +190,7 @@ func (s *graphSession) Snapshot() (*graph.Dump, string, bool, error) {
180
190
  if err := s.reload(); err != nil {
181
191
  return nil, "", false, err
182
192
  }
183
- dump := s.buildDump()
184
- return &dump, serveModeReload, true, nil
193
+ return s.changedSnapshot(serveModeReload)
185
194
  }
186
195
 
187
196
  changed, deleted, err := changedSources(s.sourceHashes)
@@ -192,18 +201,19 @@ func (s *graphSession) Snapshot() (*graph.Dump, string, bool, error) {
192
201
  if err := s.reload(); err != nil {
193
202
  return nil, "", false, err
194
203
  }
195
- dump := s.buildDump()
196
- return &dump, serveModeReload, true, nil
204
+ return s.changedSnapshot(serveModeReload)
197
205
  }
198
206
  if len(changed) == 0 {
207
+ if s.pendingDumpMode != "" {
208
+ return s.changedSnapshot(s.pendingDumpMode)
209
+ }
199
210
  return nil, serveModeUnchanged, false, nil
200
211
  }
201
212
  if s.compiler.Program().HasLinkedProgramPlugins() {
202
213
  if err := s.reload(); err != nil {
203
214
  return nil, "", false, err
204
215
  }
205
- dump := s.buildDump()
206
- return &dump, serveModeReload, true, nil
216
+ return s.changedSnapshot(serveModeReload)
207
217
  }
208
218
 
209
219
  mode := serveModeIncremental
@@ -222,14 +232,22 @@ func (s *graphSession) Snapshot() (*graph.Dump, string, bool, error) {
222
232
  if err := s.reload(); err != nil {
223
233
  return nil, "", false, err
224
234
  }
225
- dump := s.buildDump()
226
- return &dump, serveModeReload, true, nil
235
+ return s.changedSnapshot(serveModeReload)
227
236
  }
228
237
  }
229
238
  if err := s.captureState(); err != nil {
230
239
  return nil, "", false, err
231
240
  }
232
- dump := s.buildDump()
241
+ return s.changedSnapshot(mode)
242
+ }
243
+
244
+ func (s *graphSession) changedSnapshot(mode string) (*graph.Dump, string, bool, error) {
245
+ dump, err := s.buildDump()
246
+ if err != nil {
247
+ s.pendingDumpMode = mode
248
+ return nil, "", false, err
249
+ }
250
+ s.pendingDumpMode = ""
233
251
  return &dump, mode, true, nil
234
252
  }
235
253
 
@@ -323,7 +341,7 @@ func missingRootInputs(configs []*shimtsoptions.ParsedCommandLine, sourceHashes
323
341
  return missing
324
342
  }
325
343
 
326
- func (s *graphSession) buildDump() graph.Dump {
344
+ func (s *graphSession) buildDump() (graph.Dump, error) {
327
345
  program := s.compiler.Program()
328
346
  built := graph.Build(program)
329
347
  // One texts map feeds both the spans and the manifest digests, so the bytes a
@@ -337,7 +355,6 @@ func (s *graphSession) buildDump() graph.Dump {
337
355
  texts,
338
356
  graph.DumpOrigin{
339
357
  Provenance: graph.NewProvenance(
340
- s.cwd,
341
358
  serveProducer(),
342
359
  fullSnapshotCapabilities,
343
360
  s.configDigests,
@@ -345,7 +362,7 @@ func (s *graphSession) buildDump() graph.Dump {
345
362
  texts,
346
363
  s.diskDigests,
347
364
  ),
348
- Diagnostics: graph.NewDiagnostics(program, s.cwd),
365
+ Diagnostics: graph.NewDiagnostics(program),
349
366
  },
350
367
  )
351
368
  }
@@ -553,11 +570,6 @@ type diskState struct {
553
570
  Exists bool
554
571
  }
555
572
 
556
- type packageTarget struct {
557
- path string
558
- wildcard string
559
- }
560
-
561
573
  // captureDiskStates records the freshness state of speculative resolution
562
574
  // candidates. Most candidates do not exist, and a module specifier can name a
563
575
  // path the host OS cannot even parse (`./style.css?inline`, a `data:` URL on
@@ -614,17 +626,35 @@ func auxiliaryInputs(program *driver.Program, configs []*shimtsoptions.ParsedCom
614
626
  directory := filepath.Dir(file)
615
627
  inputs = appendAncestorInputs(inputs, directory, cwd)
616
628
  for _, reference := range source.ReferencedFiles {
617
- inputs = append(inputs, fileCandidates(filepath.Join(directory, filepath.FromSlash(reference.FileName)))...)
629
+ inputs = append(inputs, driver.FileCandidates(filepath.Join(directory, filepath.FromSlash(reference.FileName)))...)
618
630
  }
619
631
  for _, reference := range source.TypeReferenceDirectives {
620
- inputs = append(inputs, typeReferenceCandidates(configs, directory, cwd, reference.FileName)...)
632
+ inputs = append(inputs, driver.TypeReferenceCandidates(configs, directory, cwd, reference.FileName)...)
621
633
  }
622
- for _, specifier := range sourceModuleSpecifiers(source) {
634
+ for _, specifier := range driver.SourceModuleSpecifiers(source) {
635
+ context := driver.ModuleResolutionContext{
636
+ Mode: program.TSProgram.GetModeForUsageLocation(source, specifier),
637
+ }
638
+ for _, parsed := range configs {
639
+ if parsed != nil && parsed.ParsedConfig != nil && parsed.ParsedConfig.CompilerOptions != nil {
640
+ context.Options = parsed.ParsedConfig.CompilerOptions
641
+ break
642
+ }
643
+ }
623
644
  resolved := program.TSProgram.GetResolvedModuleFromModuleSpecifier(source, specifier)
624
645
  if resolved != nil && resolved.IsResolved() {
646
+ inputs = append(inputs, driver.ModuleResolutionPredecessors(
647
+ configs,
648
+ directory,
649
+ cwd,
650
+ specifier.Text(),
651
+ resolved.ResolvedFileName,
652
+ program.FS.UseCaseSensitiveFileNames(),
653
+ context,
654
+ )...)
625
655
  continue
626
656
  }
627
- inputs = append(inputs, unresolvedModuleCandidates(configs, directory, cwd, specifier.Text())...)
657
+ inputs = append(inputs, driver.ModuleResolutionCandidates(configs, directory, cwd, specifier.Text(), context)...)
628
658
  }
629
659
  }
630
660
  // Config `types` entries request type packages without any source syntax, so
@@ -635,82 +665,12 @@ func auxiliaryInputs(program *driver.Program, configs []*shimtsoptions.ParsedCom
635
665
  continue
636
666
  }
637
667
  for _, name := range parsed.ParsedConfig.CompilerOptions.Types {
638
- inputs = append(inputs, typeReferenceCandidates(configs, parsed.GetCurrentDirectory(), cwd, name)...)
668
+ inputs = append(inputs, driver.TypeReferenceCandidates(configs, parsed.GetCurrentDirectory(), cwd, name)...)
639
669
  }
640
670
  }
641
671
  return compactSortedStrings(inputs)
642
672
  }
643
673
 
644
- func typeReferenceCandidates(configs []*shimtsoptions.ParsedCommandLine, directory, cwd, name string) []string {
645
- candidates := []string{}
646
- for _, parsed := range configs {
647
- if parsed == nil || parsed.ParsedConfig == nil || parsed.ParsedConfig.CompilerOptions == nil {
648
- continue
649
- }
650
- for _, root := range parsed.ParsedConfig.CompilerOptions.TypeRoots {
651
- candidates = append(candidates, fileCandidates(filepath.Join(root, filepath.FromSlash(name)))...)
652
- }
653
- }
654
- for current := filepath.Clean(directory); ; current = filepath.Dir(current) {
655
- candidates = append(candidates, fileCandidates(filepath.Join(current, "node_modules", "@types", filepath.FromSlash(name)))...)
656
- if current == filepath.Clean(cwd) || filepath.Dir(current) == current {
657
- return candidates
658
- }
659
- }
660
- }
661
-
662
- func sourceModuleSpecifiers(source *shimast.SourceFile) []*shimast.Node {
663
- if source == nil {
664
- return nil
665
- }
666
- specifiers := []*shimast.Node{}
667
- var walk func(*shimast.Node) bool
668
- walk = func(node *shimast.Node) bool {
669
- if node == nil {
670
- return false
671
- }
672
- var specifier *shimast.Node
673
- switch node.Kind {
674
- case shimast.KindImportDeclaration, shimast.KindJSImportDeclaration:
675
- specifier = node.AsImportDeclaration().ModuleSpecifier
676
- case shimast.KindExportDeclaration:
677
- specifier = node.AsExportDeclaration().ModuleSpecifier
678
- case shimast.KindImportEqualsDeclaration:
679
- reference := node.AsImportEqualsDeclaration().ModuleReference
680
- if reference != nil && reference.Kind == shimast.KindExternalModuleReference {
681
- specifier = reference.AsExternalModuleReference().Expression
682
- }
683
- case shimast.KindImportType:
684
- argument := node.AsImportTypeNode().Argument
685
- if argument != nil && argument.Kind == shimast.KindLiteralType {
686
- specifier = argument.AsLiteralTypeNode().Literal
687
- }
688
- case shimast.KindCallExpression:
689
- call := node.AsCallExpression()
690
- if isModuleSpecifierCall(call) && call.Arguments != nil && len(call.Arguments.Nodes) > 0 {
691
- specifier = call.Arguments.Nodes[0]
692
- }
693
- }
694
- if specifier != nil && (specifier.Kind == shimast.KindStringLiteral || specifier.Kind == shimast.KindNoSubstitutionTemplateLiteral) {
695
- specifiers = append(specifiers, specifier)
696
- }
697
- node.ForEachChild(walk)
698
- return false
699
- }
700
- walk(source.AsNode())
701
- return specifiers
702
- }
703
-
704
- func isModuleSpecifierCall(call *shimast.CallExpression) bool {
705
- if call == nil || call.Expression == nil {
706
- return false
707
- }
708
- if call.Expression.Kind == shimast.KindImportKeyword {
709
- return true
710
- }
711
- return call.Expression.Kind == shimast.KindIdentifier && call.Expression.Text() == "require"
712
- }
713
-
714
674
  func appendAncestorInputs(inputs []string, directory, stop string) []string {
715
675
  stop = filepath.Clean(stop)
716
676
  for current := filepath.Clean(directory); ; current = filepath.Dir(current) {
@@ -721,216 +681,6 @@ func appendAncestorInputs(inputs []string, directory, stop string) []string {
721
681
  }
722
682
  }
723
683
 
724
- func unresolvedModuleCandidates(configs []*shimtsoptions.ParsedCommandLine, directory, cwd, specifier string) []string {
725
- if specifier == "" {
726
- return nil
727
- }
728
- if strings.HasPrefix(specifier, ".") {
729
- candidates := fileCandidates(filepath.Clean(filepath.Join(directory, filepath.FromSlash(specifier))))
730
- return append(candidates, rootDirsCandidates(configs, directory, specifier)...)
731
- }
732
- candidates := compilerOptionCandidates(configs, specifier)
733
- if strings.HasPrefix(specifier, "#") {
734
- for current := filepath.Clean(directory); ; current = filepath.Dir(current) {
735
- candidates = append(candidates, packageManifestCandidates(current, specifier)...)
736
- if current == filepath.Clean(cwd) || filepath.Dir(current) == current {
737
- return candidates
738
- }
739
- }
740
- }
741
- for current := filepath.Clean(directory); ; current = filepath.Dir(current) {
742
- base := filepath.Join(current, "node_modules", filepath.FromSlash(specifier))
743
- root := packageRoot(base, specifier)
744
- candidates = append(candidates, fileCandidates(base)...)
745
- candidates = append(candidates, filepath.Join(root, "package.json"))
746
- candidates = append(candidates, packageManifestCandidates(root, packageSubpath(specifier))...)
747
- if current == filepath.Clean(cwd) || filepath.Dir(current) == current {
748
- break
749
- }
750
- }
751
- return candidates
752
- }
753
-
754
- func packageManifestCandidates(root, wildcard string) []string {
755
- content, err := os.ReadFile(filepath.Join(root, "package.json"))
756
- if err != nil {
757
- return nil
758
- }
759
- var manifest struct {
760
- Main string `json:"main"`
761
- Module string `json:"module"`
762
- Types string `json:"types"`
763
- Typings string `json:"typings"`
764
- Exports any `json:"exports"`
765
- Imports any `json:"imports"`
766
- TypesVersions any `json:"typesVersions"`
767
- }
768
- if json.Unmarshal(content, &manifest) != nil {
769
- return nil
770
- }
771
- defaultWildcard := strings.TrimPrefix(strings.TrimPrefix(wildcard, "./"), "#")
772
- targets := []packageTarget{
773
- {path: manifest.Main, wildcard: defaultWildcard},
774
- {path: manifest.Module, wildcard: defaultWildcard},
775
- {path: manifest.Types, wildcard: defaultWildcard},
776
- {path: manifest.Typings, wildcard: defaultWildcard},
777
- }
778
- exportRequest := "."
779
- if wildcard != "" && !strings.HasPrefix(wildcard, "#") {
780
- exportRequest = "./" + strings.TrimPrefix(wildcard, "./")
781
- }
782
- collectPackageMappingTargets(manifest.Exports, exportRequest, defaultWildcard, &targets)
783
- collectPackageMappingTargets(manifest.Imports, wildcard, defaultWildcard, &targets)
784
- collectPackageTargets(manifest.TypesVersions, defaultWildcard, &targets)
785
- candidates := []string{}
786
- for _, target := range targets {
787
- if target.path == "" || filepath.IsAbs(target.path) || strings.Contains(target.path, "://") {
788
- continue
789
- }
790
- path := strings.Replace(target.path, "*", target.wildcard, 1)
791
- candidates = append(candidates, fileCandidates(filepath.Join(root, filepath.FromSlash(path)))...)
792
- }
793
- return candidates
794
- }
795
-
796
- func collectPackageMappingTargets(value any, request, wildcard string, targets *[]packageTarget) {
797
- if object, ok := value.(map[string]any); ok {
798
- mapping := false
799
- for key := range object {
800
- if strings.HasPrefix(key, ".") || strings.HasPrefix(key, "#") {
801
- mapping = true
802
- break
803
- }
804
- }
805
- if mapping {
806
- for pattern, child := range object {
807
- matched, ok := matchPathPattern(pattern, request)
808
- if ok {
809
- collectPackageTargets(child, matched, targets)
810
- }
811
- }
812
- return
813
- }
814
- }
815
- collectPackageTargets(value, wildcard, targets)
816
- }
817
-
818
- func collectPackageTargets(value any, wildcard string, targets *[]packageTarget) {
819
- switch value := value.(type) {
820
- case string:
821
- *targets = append(*targets, packageTarget{path: value, wildcard: wildcard})
822
- case []any:
823
- for _, child := range value {
824
- collectPackageTargets(child, wildcard, targets)
825
- }
826
- case map[string]any:
827
- for _, child := range value {
828
- collectPackageTargets(child, wildcard, targets)
829
- }
830
- }
831
- }
832
-
833
- func packageSubpath(specifier string) string {
834
- parts := strings.Split(filepath.ToSlash(specifier), "/")
835
- count := 1
836
- if strings.HasPrefix(specifier, "@") && len(parts) > 1 {
837
- count = 2
838
- }
839
- if len(parts) <= count {
840
- return ""
841
- }
842
- return strings.Join(parts[count:], "/")
843
- }
844
-
845
- func compilerOptionCandidates(configs []*shimtsoptions.ParsedCommandLine, specifier string) []string {
846
- candidates := []string{}
847
- for _, parsed := range configs {
848
- if parsed == nil || parsed.ParsedConfig == nil || parsed.ParsedConfig.CompilerOptions == nil {
849
- continue
850
- }
851
- options := parsed.ParsedConfig.CompilerOptions
852
- if options.BaseUrl != "" {
853
- candidates = append(candidates, fileCandidates(filepath.Join(options.BaseUrl, filepath.FromSlash(specifier)))...)
854
- }
855
- if options.Paths == nil {
856
- continue
857
- }
858
- base := options.GetPathsBasePath(parsed.GetCurrentDirectory())
859
- for pattern, targets := range options.Paths.Entries() {
860
- matched, ok := matchPathPattern(pattern, specifier)
861
- if !ok {
862
- continue
863
- }
864
- for _, target := range targets {
865
- target = strings.Replace(target, "*", matched, 1)
866
- candidates = append(candidates, fileCandidates(filepath.Join(base, filepath.FromSlash(target)))...)
867
- }
868
- }
869
- }
870
- return candidates
871
- }
872
-
873
- func rootDirsCandidates(configs []*shimtsoptions.ParsedCommandLine, directory, specifier string) []string {
874
- candidates := []string{}
875
- for _, parsed := range configs {
876
- if parsed == nil || parsed.ParsedConfig == nil || parsed.ParsedConfig.CompilerOptions == nil {
877
- continue
878
- }
879
- roots := parsed.ParsedConfig.CompilerOptions.RootDirs
880
- for _, sourceRoot := range roots {
881
- relative, err := filepath.Rel(sourceRoot, directory)
882
- if err != nil || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
883
- continue
884
- }
885
- suffix := filepath.Join(relative, filepath.FromSlash(specifier))
886
- for _, targetRoot := range roots {
887
- candidates = append(candidates, fileCandidates(filepath.Join(targetRoot, suffix))...)
888
- }
889
- }
890
- }
891
- return candidates
892
- }
893
-
894
- func matchPathPattern(pattern, specifier string) (string, bool) {
895
- star := strings.IndexByte(pattern, '*')
896
- if star < 0 {
897
- return "", pattern == specifier
898
- }
899
- prefix := pattern[:star]
900
- suffix := pattern[star+1:]
901
- if len(specifier) < len(prefix)+len(suffix) || !strings.HasPrefix(specifier, prefix) || !strings.HasSuffix(specifier, suffix) {
902
- return "", false
903
- }
904
- return specifier[len(prefix) : len(specifier)-len(suffix)], true
905
- }
906
-
907
- func packageRoot(base, specifier string) string {
908
- parts := strings.Split(filepath.ToSlash(specifier), "/")
909
- count := 1
910
- if strings.HasPrefix(specifier, "@") && len(parts) > 1 {
911
- count = 2
912
- }
913
- suffixCount := len(parts) - count
914
- root := base
915
- for range suffixCount {
916
- root = filepath.Dir(root)
917
- }
918
- return root
919
- }
920
-
921
- func fileCandidates(base string) []string {
922
- candidates := []string{base}
923
- extension := strings.ToLower(filepath.Ext(base))
924
- if extension == ".js" || extension == ".jsx" || extension == ".mjs" || extension == ".cjs" {
925
- base = strings.TrimSuffix(base, filepath.Ext(base))
926
- }
927
- for _, suffix := range []string{".ts", ".tsx", ".mts", ".cts", ".d.ts", ".d.mts", ".d.cts", ".js", ".jsx", ".mjs", ".cjs", ".json"} {
928
- candidates = append(candidates, base+suffix, filepath.Join(base, "index"+suffix))
929
- }
930
- candidates = append(candidates, filepath.Join(base, "package.json"))
931
- return candidates
932
- }
933
-
934
684
  func compactSortedStrings(input []string) []string {
935
685
  out := make([]string, 0, len(input))
936
686
  for _, value := range input {
@@ -0,0 +1,49 @@
1
+ package main
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+ )
7
+
8
+ // TestServeSessionKeepsBaseURLFallbackUnchanged verifies an unmatched
9
+ // baseUrl-style file does not invalidate a paths resolution.
10
+ //
11
+ // The pinned TypeScript-Go resolver applies a matching paths substitution and
12
+ // does not fall back to baseUrl afterward. Recording a baseUrl probe would make
13
+ // an irrelevant file creation rebuild a resident session without changing the
14
+ // selected module.
15
+ //
16
+ // 1. Load a paths import whose configured fallback target exists.
17
+ // 2. Create only the corresponding baseUrl-style file.
18
+ // 3. Assert the session remains unchanged because the paths target still wins.
19
+ func TestServeSessionKeepsBaseURLFallbackUnchanged(t *testing.T) {
20
+ root := t.TempDir()
21
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
22
+ "compilerOptions": {
23
+ "baseUrl": ".",
24
+ "module": "commonjs",
25
+ "paths": { "@generated/*": ["fallback/*"] },
26
+ "target": "ES2022"
27
+ },
28
+ "files": ["src/main.ts"]
29
+ }`)
30
+ writeGraphFile(t, filepath.Join(root, "src", "main.ts"), "import { winner } from '@generated/value';\nexport function main(): void { winner(); }\n")
31
+ writeGraphFile(t, filepath.Join(root, "fallback", "value.ts"), "export function winner(): void {}\n")
32
+
33
+ session, err := newGraphSession(root, "tsconfig.json")
34
+ if err != nil {
35
+ t.Fatal(err)
36
+ }
37
+ defer session.Close()
38
+ if _, _, _, err := session.Snapshot(); err != nil {
39
+ t.Fatal(err)
40
+ }
41
+ writeGraphFile(t, filepath.Join(root, "@generated", "value.ts"), "export function winner(): void {}\n")
42
+ dump, mode, changed, err := session.Snapshot()
43
+ if err != nil {
44
+ t.Fatal(err)
45
+ }
46
+ if dump != nil || mode != serveModeUnchanged || changed {
47
+ t.Fatalf("baseUrl fallback = dump:%v mode:%q changed:%v", dump != nil, mode, changed)
48
+ }
49
+ }
@@ -0,0 +1,61 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "testing"
7
+ )
8
+
9
+ // TestServeSessionKeepsESMDirectoryCandidateUnchanged verifies ESM relative
10
+ // resolution does not inherit CommonJS directory probes.
11
+ //
12
+ // TypeScript-Go may substitute value.ts for the explicit value.js import, but
13
+ // it never performs the CommonJS value/package.json or value/index.* lookup in
14
+ // ESM mode. Those directory members must therefore stay out of a resident
15
+ // session's freshness inputs.
16
+ func TestServeSessionKeepsESMDirectoryCandidateUnchanged(t *testing.T) {
17
+ root := t.TempDir()
18
+ writeGraphFile(t, filepath.Join(root, "package.json"), `{"type":"module"}`)
19
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
20
+ "compilerOptions": {
21
+ "allowJs": true,
22
+ "module": "nodenext",
23
+ "moduleResolution": "nodenext",
24
+ "target": "ES2022"
25
+ },
26
+ "files": ["src/main.ts"]
27
+ }`)
28
+ writeGraphFile(t, filepath.Join(root, "src", "main.ts"), "import { winner } from './value.js';\nexport function main(): void { winner(); }\n")
29
+ writeGraphFile(t, filepath.Join(root, "src", "value.js"), "export function winner() {}\n")
30
+
31
+ session, err := newGraphSession(root, "tsconfig.json")
32
+ if err != nil {
33
+ t.Fatal(err)
34
+ }
35
+ defer session.Close()
36
+ if _, _, _, err := session.Snapshot(); err != nil {
37
+ t.Fatal(err)
38
+ }
39
+
40
+ if err := os.MkdirAll(filepath.Join(root, "src", "value"), 0o755); err != nil {
41
+ t.Fatal(err)
42
+ }
43
+ writeGraphFile(t, filepath.Join(root, "src", "value", "package.json"), `{"type":"module"}`)
44
+ writeGraphFile(t, filepath.Join(root, "src", "value", "index.ts"), "export function winner(): void {}\n")
45
+ dump, mode, changed, err := session.Snapshot()
46
+ if err != nil {
47
+ t.Fatal(err)
48
+ }
49
+ if dump != nil || mode != serveModeUnchanged || changed {
50
+ t.Fatalf("ESM directory candidate = dump:%v mode:%q changed:%v", dump != nil, mode, changed)
51
+ }
52
+
53
+ writeGraphFile(t, filepath.Join(root, "src", "value.ts"), "export function winner(): void {}\nexport function typescriptWinner(): void {}\n")
54
+ dump, mode, changed, err = session.Snapshot()
55
+ if err != nil {
56
+ t.Fatal(err)
57
+ }
58
+ if dump == nil || mode != serveModeReload || !changed || !hasDumpNode(*dump, "typescriptWinner") {
59
+ t.Fatalf("ESM higher-priority file = dump:%v mode:%q changed:%v", dump != nil, mode, changed)
60
+ }
61
+ }
@@ -0,0 +1,64 @@
1
+ package main
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+ )
7
+
8
+ // TestServeSessionKeepsExportsBlockedFileCandidatesUnchanged verifies a
9
+ // package export map prevents unlisted bare-file candidates from invalidating a
10
+ // resident session.
11
+ //
12
+ // An export map controls subpath resolution before TypeScript considers an
13
+ // on-disk package file. Recording that blocked file as a predecessor would make
14
+ // unrelated package contents churn the snapshot despite the selected export
15
+ // remaining exactly the same.
16
+ //
17
+ // 1. Load a package subpath resolved through its export-map fallback target.
18
+ // 2. Create an unlisted file at the bare subpath without changing package.json.
19
+ // 3. Assert the session is unchanged, then create the export-map target that
20
+ // genuinely precedes the fallback and assert it reloads.
21
+ func TestServeSessionKeepsExportsBlockedFileCandidatesUnchanged(t *testing.T) {
22
+ root := t.TempDir()
23
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
24
+ "compilerOptions": {
25
+ "allowJs": true,
26
+ "module": "nodenext",
27
+ "moduleResolution": "nodenext",
28
+ "target": "ES2022"
29
+ },
30
+ "files": ["src/main.ts"]
31
+ }`)
32
+ writeGraphFile(t, filepath.Join(root, "src", "main.ts"), "import { winner } from 'fixture-package/feature';\nexport function main(): void { winner(); }\n")
33
+ writeGraphFile(t, filepath.Join(root, "node_modules", "fixture-package", "package.json"), `{
34
+ "name": "fixture-package",
35
+ "exports": { "./feature": ["./dist/first.js", "./dist/fallback.js"] }
36
+ }`)
37
+ writeGraphFile(t, filepath.Join(root, "node_modules", "fixture-package", "dist", "fallback.js"), "export function winner() {}\n")
38
+
39
+ session, err := newGraphSession(root, "tsconfig.json")
40
+ if err != nil {
41
+ t.Fatal(err)
42
+ }
43
+ defer session.Close()
44
+ if _, _, _, err := session.Snapshot(); err != nil {
45
+ t.Fatal(err)
46
+ }
47
+ writeGraphFile(t, filepath.Join(root, "node_modules", "fixture-package", "feature.ts"), "export function winner(): void {}\n")
48
+ dump, mode, changed, err := session.Snapshot()
49
+ if err != nil {
50
+ t.Fatal(err)
51
+ }
52
+ if dump != nil || mode != serveModeUnchanged || changed {
53
+ t.Fatalf("exports-blocked feature.ts = dump:%v mode:%q changed:%v", dump != nil, mode, changed)
54
+ }
55
+
56
+ writeGraphFile(t, filepath.Join(root, "node_modules", "fixture-package", "dist", "first.js"), "export function winner() {}\nexport function exportsWinner() {}\n")
57
+ dump, mode, changed, err = session.Snapshot()
58
+ if err != nil {
59
+ t.Fatal(err)
60
+ }
61
+ if dump == nil || mode != serveModeReload || !changed {
62
+ t.Fatalf("export-map first target = dump:%v mode:%q changed:%v", dump != nil, mode, changed)
63
+ }
64
+ }