ttsc 0.19.3 → 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 (170) 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 +276 -100
  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_uses_ecma_line_terminators_test.go +87 -0
  33. package/internal/graph/duplicate_variable_object_members_keep_first_declaration_test.go +61 -0
  34. package/internal/graph/edges.go +208 -0
  35. package/internal/graph/graph.go +143 -5
  36. package/internal/graph/line_model.go +122 -0
  37. package/internal/graph/member_relation_edges_follow_checker_validity_test.go +85 -0
  38. package/internal/graph/member_relation_edges_preserve_checker_boundaries_test.go +191 -0
  39. package/internal/graph/member_relation_edges_reject_checker_invalid_pairs_test.go +119 -0
  40. package/internal/graph/node_ids_preserve_hash_bearing_components_test.go +41 -0
  41. package/internal/graph/node_modifiers_emit_union_strings_test.go +4 -1
  42. package/internal/graph/object_literal_members_ride_on_the_snapshot_test.go +192 -0
  43. package/internal/graph/object_member_signatures_preserve_literal_whitespace_test.go +67 -0
  44. package/internal/graph/object_members.go +229 -0
  45. package/internal/graph/provenance.go +88 -30
  46. package/internal/graph/source_texts_cover_every_resident_program_source_test.go +73 -0
  47. package/internal/graphsymbols/position_helpers_follow_ecma_line_terminators_test.go +66 -0
  48. package/internal/graphsymbols/provider.go +26 -91
  49. package/internal/lspserver/lsp_clean_didopen_refreshes_compiler_state_test.go +144 -0
  50. package/internal/lspserver/lsp_completion.go +109 -0
  51. package/internal/lspserver/lsp_completion_gate_never_refuses_a_matching_line_test.go +50 -0
  52. package/internal/lspserver/lsp_completion_hints_longest_trigger_wins_test.go +149 -0
  53. package/internal/lspserver/lsp_completion_hints_nearest_trigger_wins_test.go +37 -0
  54. package/internal/lspserver/lsp_completion_merges_into_upstream_response_test.go +149 -0
  55. package/internal/lspserver/lsp_completion_nearest_hint_replaces_nearest_filter_test.go +43 -0
  56. package/internal/lspserver/lsp_completion_preserves_completion_list_extensions_test.go +99 -0
  57. package/internal/lspserver/lsp_completion_refuses_hints_inside_a_string_literal_test.go +69 -0
  58. package/internal/lspserver/lsp_completion_replaces_full_filter_in_rank_order_test.go +85 -0
  59. package/internal/lspserver/lsp_completion_request.go +455 -0
  60. package/internal/lspserver/lsp_completion_scope.go +530 -0
  61. package/internal/lspserver/lsp_completion_scope_bench_test.go +41 -0
  62. package/internal/lspserver/lsp_completion_scope_classifies_regex_statement_and_operator_boundaries_test.go +39 -0
  63. package/internal/lspserver/lsp_completion_scope_ignores_jsdoc_bytes_in_literals_test.go +108 -0
  64. package/internal/lspserver/lsp_completion_scope_refuses_regex_hints_test.go +52 -0
  65. package/internal/lspserver/lsp_completion_trigger_uses_last_rune_test.go +24 -0
  66. package/internal/lspserver/lsp_deleted_watched_file_withdraws_plugin_diagnostics_test.go +110 -0
  67. package/internal/lspserver/lsp_didsave_keeps_the_incremental_changed_uri_path_test.go +78 -0
  68. package/internal/lspserver/lsp_hints_absence_is_not_an_error_test.go +46 -0
  69. package/internal/lspserver/lsp_hints_refresh_coalesces_concurrent_requests_test.go +82 -0
  70. package/internal/lspserver/lsp_hints_refresh_ignores_a_superseded_generation_test.go +40 -0
  71. package/internal/lspserver/lsp_hints_refresh_keeps_each_producer_isolated_test.go +64 -0
  72. package/internal/lspserver/lsp_hints_refresh_replaces_the_session_snapshot_test.go +66 -0
  73. package/internal/lspserver/lsp_hints_refresh_scheduled_by_editor_events_test.go +90 -0
  74. package/internal/lspserver/lsp_hints_refresh_serves_completion_while_running_test.go +91 -0
  75. package/internal/lspserver/lsp_index_byte_from_bounds_its_search_start_test.go +38 -0
  76. package/internal/lspserver/lsp_initialize_constrains_position_encoding_to_utf16_test.go +205 -0
  77. package/internal/lspserver/lsp_late_completion_trigger_is_reported_once_test.go +105 -0
  78. package/internal/lspserver/lsp_native_completion_hints_decode_flat_lint_wire_test.go +66 -0
  79. package/internal/lspserver/lsp_native_completion_hints_grouped_compatibility_boundary_test.go +60 -0
  80. package/internal/lspserver/lsp_native_plugin_source.go +315 -2
  81. package/internal/lspserver/lsp_native_resident.go +304 -0
  82. package/internal/lspserver/lsp_plugin.go +85 -8
  83. package/internal/lspserver/lsp_plugin_completion_resolve_stays_local_test.go +82 -0
  84. package/internal/lspserver/lsp_position_conversions_span_astral_and_combining_runes_test.go +119 -0
  85. package/internal/lspserver/lsp_proxy.go +601 -9
  86. package/internal/lspserver/lsp_refresh.go +70 -0
  87. package/internal/lspserver/lsp_resident_falls_back_when_serve_unsupported_test.go +43 -0
  88. package/internal/lspserver/lsp_resident_maps_verb_args_test.go +34 -0
  89. package/internal/lspserver/lsp_server.go +13 -1
  90. package/internal/lspserver/lsp_watched_file_changes_reach_compiler_caches_test.go +172 -0
  91. package/lib/TtscCompiler.d.ts +3 -1
  92. package/lib/TtscCompiler.js +5 -1
  93. package/lib/TtscCompiler.js.map +1 -1
  94. package/lib/TtscService.d.ts +13 -3
  95. package/lib/TtscService.js +6 -6
  96. package/lib/TtscService.js.map +1 -1
  97. package/lib/compiler/internal/project/readConfigJson.d.ts +30 -0
  98. package/lib/compiler/internal/project/readConfigJson.js +208 -0
  99. package/lib/compiler/internal/project/readConfigJson.js.map +1 -0
  100. package/lib/compiler/internal/project/readProjectConfig.js +53 -149
  101. package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
  102. package/lib/compiler/internal/residentTransformProcess.d.ts +15 -2
  103. package/lib/compiler/internal/residentTransformProcess.js +159 -39
  104. package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
  105. package/lib/compiler/internal/runBuild.d.ts +6 -0
  106. package/lib/compiler/internal/runBuild.js +143 -44
  107. package/lib/compiler/internal/runBuild.js.map +1 -1
  108. package/lib/compiler/internal/startResidentTransform.d.ts +5 -1
  109. package/lib/compiler/internal/startResidentTransform.js +3 -1
  110. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  111. package/lib/compiler/internal/transformProjectInMemory.js +12 -2
  112. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  113. package/lib/flags/parser.d.ts +19 -0
  114. package/lib/flags/parser.js +78 -36
  115. package/lib/flags/parser.js.map +1 -1
  116. package/lib/flags/schema.d.ts +49 -11
  117. package/lib/flags/schema.js +94 -22
  118. package/lib/flags/schema.js.map +1 -1
  119. package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +5 -0
  120. package/lib/internal/assertSafeExplicitCacheDirectory.js +26 -0
  121. package/lib/internal/assertSafeExplicitCacheDirectory.js.map +1 -0
  122. package/lib/launcher/internal/runTtsc.js +107 -69
  123. package/lib/launcher/internal/runTtsc.js.map +1 -1
  124. package/lib/launcher/internal/runTtsx.js +28 -41
  125. package/lib/launcher/internal/runTtsx.js.map +1 -1
  126. package/lib/launcher/internal/runtimeHooks.d.ts +15 -0
  127. package/lib/launcher/internal/runtimeHooks.js +18 -1
  128. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  129. package/lib/launcher/internal/watchTopology.d.ts +43 -0
  130. package/lib/launcher/internal/watchTopology.js +360 -0
  131. package/lib/launcher/internal/watchTopology.js.map +1 -0
  132. package/lib/plugin/internal/buildSourcePlugin.d.ts +14 -3
  133. package/lib/plugin/internal/buildSourcePlugin.js +247 -61
  134. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  135. package/lib/plugin/internal/loadProjectPlugins.js +63 -2
  136. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  137. package/lib/structures/ITtscCompilerTransformation.d.ts +16 -0
  138. package/lib/structures/internal/ITtscParsedProjectConfig.d.ts +2 -0
  139. package/package.json +8 -8
  140. package/shim/ast/shim.go +48 -3
  141. package/shim/astnav/go.mod +14 -0
  142. package/shim/astnav/go.sum +20 -0
  143. package/shim/astnav/shim.go +15 -0
  144. package/shim/checker/shim.go +100 -0
  145. package/shim/core/shim.go +47 -0
  146. package/shim/diagnosticwriter/lint.go +50 -0
  147. package/src/TtscCompiler.ts +5 -1
  148. package/src/TtscService.ts +37 -10
  149. package/src/compiler/internal/project/readConfigJson.ts +209 -0
  150. package/src/compiler/internal/project/readProjectConfig.ts +62 -158
  151. package/src/compiler/internal/residentTransformProcess.ts +197 -39
  152. package/src/compiler/internal/runBuild.ts +169 -47
  153. package/src/compiler/internal/startResidentTransform.ts +12 -1
  154. package/src/compiler/internal/transformProjectInMemory.ts +12 -1
  155. package/src/flags/parser.ts +113 -42
  156. package/src/flags/schema.ts +113 -31
  157. package/src/internal/assertSafeExplicitCacheDirectory.ts +29 -0
  158. package/src/launcher/internal/runTtsc.ts +117 -68
  159. package/src/launcher/internal/runTtsx.ts +29 -44
  160. package/src/launcher/internal/runtimeHooks.ts +30 -2
  161. package/src/launcher/internal/watchTopology.ts +466 -0
  162. package/src/plugin/internal/buildSourcePlugin.ts +326 -64
  163. package/src/plugin/internal/loadProjectPlugins.ts +80 -2
  164. package/src/structures/ITtscCompilerTransformation.ts +17 -0
  165. package/src/structures/internal/ITtscParsedProjectConfig.ts +2 -0
  166. package/tools/shim_audit/baseline.json +0 -3
  167. package/tools/shim_audit/main.go +42 -14
  168. package/tools/shim_audit/shim_dir_coverage_test.go +25 -0
  169. package/utility/flags_gen.go +3 -3
  170. package/utility/host.go +9 -3
@@ -0,0 +1,819 @@
1
+ package driver
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "os"
7
+ "path/filepath"
8
+ "strings"
9
+
10
+ "github.com/microsoft/typescript-go/shim/ast"
11
+ shimcore "github.com/microsoft/typescript-go/shim/core"
12
+ "github.com/microsoft/typescript-go/shim/tsoptions"
13
+ )
14
+
15
+ // ModuleResolutionContext carries the exact TypeScript-Go configuration for a
16
+ // module-specifier lookup. Candidate discovery must share its mode and package
17
+ // resolution switches, otherwise a path the compiler cannot select becomes an
18
+ // accidental freshness input.
19
+ type ModuleResolutionContext struct {
20
+ Options *shimcore.CompilerOptions
21
+ Mode shimcore.ResolutionMode
22
+ }
23
+
24
+ // SupersedingModuleCandidates returns, per source-file envelope key, the
25
+ // resolution candidates strictly ahead of each module target the loaded
26
+ // program selected. A missing member can therefore become a freshness input
27
+ // without making the whole resolution search (including lower-priority paths)
28
+ // an invalidation input.
29
+ //
30
+ // Candidate enumeration is host-owned. It mirrors the resident graph session's
31
+ // speculation for relative modules, paths, rootDirs, package imports/exports,
32
+ // and node_modules ancestry, while the compiler itself remains the authority
33
+ // that says which target won. The returned values use TransformOutputKey so
34
+ // they travel in the same envelope vocabulary as graph edges.
35
+ func SupersedingModuleCandidates(prog *Program, cwd string) map[string][]string {
36
+ if prog == nil || prog.TSProgram == nil || prog.ParsedConfig == nil {
37
+ return nil
38
+ }
39
+ configs := []*tsoptions.ParsedCommandLine{prog.ParsedConfig}
40
+ output := map[string][]string{}
41
+ caseSensitive := true
42
+ if prog.FS != nil {
43
+ caseSensitive = prog.FS.UseCaseSensitiveFileNames()
44
+ }
45
+ for _, source := range prog.TSProgram.SourceFiles() {
46
+ if source == nil || strings.HasPrefix(source.FileName(), bundledScheme) {
47
+ continue
48
+ }
49
+ key := TransformOutputKey(cwd, source.FileName())
50
+ directory := filepath.Dir(source.FileName())
51
+ for _, specifier := range SourceModuleSpecifiers(source) {
52
+ context := moduleResolutionContextFor(configs, prog.TSProgram.GetModeForUsageLocation(source, specifier))
53
+ resolved := prog.TSProgram.GetResolvedModuleFromModuleSpecifier(source, specifier)
54
+ if resolved == nil || !resolved.IsResolved() {
55
+ continue
56
+ }
57
+ before := ModuleResolutionPredecessors(
58
+ configs,
59
+ directory,
60
+ cwd,
61
+ specifier.Text(),
62
+ resolved.ResolvedFileName,
63
+ caseSensitive,
64
+ context,
65
+ )
66
+ for _, candidate := range before {
67
+ output[key] = append(output[key], TransformOutputKey(cwd, candidate))
68
+ }
69
+ }
70
+ }
71
+ for source, candidates := range output {
72
+ output[source] = compactStringsInOrder(candidates)
73
+ }
74
+ return output
75
+ }
76
+
77
+ // ModuleResolutionPredecessors returns the paths in the host-owned candidate
78
+ // search that precede resolvedFileName. An unresolved specifier keeps the full
79
+ // candidate list through ModuleResolutionCandidates; a resolved specifier must
80
+ // not track a lower-priority path because creating it cannot alter resolution.
81
+ func ModuleResolutionPredecessors(
82
+ configs []*tsoptions.ParsedCommandLine,
83
+ directory, cwd, specifier, resolvedFileName string,
84
+ caseSensitive bool,
85
+ context ModuleResolutionContext,
86
+ ) []string {
87
+ candidates := ModuleResolutionCandidates(configs, directory, cwd, specifier, context)
88
+ for index, candidate := range candidates {
89
+ if sameCandidatePath(candidate, resolvedFileName, caseSensitive) {
90
+ return compactStringsInOrder(candidates[:index])
91
+ }
92
+ }
93
+ // The winner is not spelled the way this search spells it. The compiler
94
+ // resolves a `node_modules` package through the real path, so its selected
95
+ // target can arrive symlink-resolved — and on Windows expanded out of an 8.3
96
+ // short name such as `RUNNER~1` — while every candidate carries the importing
97
+ // file's spelling. Ask the filesystem which candidate is that file instead of
98
+ // discarding the whole search.
99
+ //
100
+ // This second pass runs only when the first found nothing, so a program whose
101
+ // spellings already agree pays nothing for it.
102
+ for index, candidate := range candidates {
103
+ if sameExistingPath(candidate, resolvedFileName) {
104
+ return compactStringsInOrder(candidates[:index])
105
+ }
106
+ }
107
+ // Never widen freshness to candidates whose precedence relative to the
108
+ // compiler's selected target we could not prove. A winner that is genuinely
109
+ // outside this search remains covered by its realized graph edge.
110
+ return nil
111
+ }
112
+
113
+ func moduleResolutionContextFor(configs []*tsoptions.ParsedCommandLine, mode shimcore.ResolutionMode) ModuleResolutionContext {
114
+ context := ModuleResolutionContext{Mode: mode}
115
+ for _, parsed := range configs {
116
+ if parsed != nil && parsed.ParsedConfig != nil && parsed.ParsedConfig.CompilerOptions != nil {
117
+ context.Options = parsed.ParsedConfig.CompilerOptions
118
+ break
119
+ }
120
+ }
121
+ return context
122
+ }
123
+
124
+ func (context ModuleResolutionContext) isESM() bool {
125
+ return context.Mode == shimcore.ResolutionModeESM
126
+ }
127
+
128
+ func (context ModuleResolutionContext) moduleResolutionKind() shimcore.ModuleResolutionKind {
129
+ if context.Options == nil {
130
+ return shimcore.ModuleResolutionKindBundler
131
+ }
132
+ return context.Options.GetModuleResolutionKind()
133
+ }
134
+
135
+ func (context ModuleResolutionContext) usesPackageExports() bool {
136
+ switch context.moduleResolutionKind() {
137
+ case shimcore.ModuleResolutionKindNode16, shimcore.ModuleResolutionKindNodeNext:
138
+ return true
139
+ case shimcore.ModuleResolutionKindBundler:
140
+ return context.Options == nil || context.Options.GetResolvePackageJsonExports()
141
+ default:
142
+ return false
143
+ }
144
+ }
145
+
146
+ func (context ModuleResolutionContext) usesPackageImports() bool {
147
+ switch context.moduleResolutionKind() {
148
+ case shimcore.ModuleResolutionKindNode16, shimcore.ModuleResolutionKindNodeNext:
149
+ return true
150
+ case shimcore.ModuleResolutionKindBundler:
151
+ return context.Options == nil || context.Options.GetResolvePackageJsonImports()
152
+ default:
153
+ return false
154
+ }
155
+ }
156
+
157
+ func (context ModuleResolutionContext) packageConditions() map[string]struct{} {
158
+ if context.Options == nil {
159
+ return map[string]struct{}{"require": {}, "types": {}}
160
+ }
161
+ mode := context.Mode
162
+ if mode == shimcore.ResolutionModeNone && context.moduleResolutionKind() == shimcore.ModuleResolutionKindBundler {
163
+ mode = shimcore.ResolutionModeESM
164
+ }
165
+ conditions := map[string]struct{}{}
166
+ if mode == shimcore.ResolutionModeESM {
167
+ conditions["import"] = struct{}{}
168
+ } else {
169
+ conditions["require"] = struct{}{}
170
+ }
171
+ if context.Options.NoDtsResolution != shimcore.TSTrue {
172
+ conditions["types"] = struct{}{}
173
+ }
174
+ if context.moduleResolutionKind() != shimcore.ModuleResolutionKindBundler {
175
+ conditions["node"] = struct{}{}
176
+ }
177
+ for _, condition := range context.Options.CustomConditions {
178
+ conditions[condition] = struct{}{}
179
+ }
180
+ return conditions
181
+ }
182
+
183
+ func sameCandidatePath(left, right string, caseSensitive bool) bool {
184
+ left = filepath.Clean(left)
185
+ right = filepath.Clean(right)
186
+ if caseSensitive {
187
+ return left == right
188
+ }
189
+ return strings.EqualFold(left, right)
190
+ }
191
+
192
+ // sameExistingPath reports whether two spellings name the same existing
193
+ // filesystem object. A path that does not exist answers false, so a
194
+ // speculative candidate costs one failing `os.Stat` and never reaches the
195
+ // second call.
196
+ func sameExistingPath(left, right string) bool {
197
+ leftInfo, err := os.Stat(left)
198
+ if err != nil {
199
+ return false
200
+ }
201
+ rightInfo, err := os.Stat(right)
202
+ if err != nil {
203
+ return false
204
+ }
205
+ return os.SameFile(leftInfo, rightInfo)
206
+ }
207
+
208
+ // reachedWalkStop reports whether an upward `node_modules` walk has arrived at
209
+ // the directory it must not pass. The walk starts from the importing file's
210
+ // directory as the compiler spells it and stops at the project root as the
211
+ // caller spells it, and the two need not agree character for character on a
212
+ // case-insensitive or short-name-bearing filesystem. A missed stop walks every
213
+ // remaining ancestor up to the volume root and watches probes the compiler
214
+ // would never consult.
215
+ //
216
+ // Two spellings of one directory still share a final component up to case
217
+ // unless that component is itself shortened, so the mismatching levels — every
218
+ // level below the stop — are rejected without touching the filesystem. A stop
219
+ // missed by the remaining heuristic costs breadth, never a wrong answer: the
220
+ // extra ancestors are appended after the correct ones, so a predecessor list
221
+ // cut at the winner is unaffected.
222
+ func reachedWalkStop(current, stop string) bool {
223
+ stop = filepath.Clean(stop)
224
+ if current == stop {
225
+ return true
226
+ }
227
+ if !strings.EqualFold(filepath.Base(current), filepath.Base(stop)) {
228
+ return false
229
+ }
230
+ return sameExistingPath(current, stop)
231
+ }
232
+
233
+ // FileCandidates lists the ordered file and directory probes for base. The
234
+ // suffix family follows the module specifier's explicit extension, matching the
235
+ // resolver's extension branch instead of treating every source kind as a
236
+ // possible predecessor.
237
+ func FileCandidates(base string) []string {
238
+ base, suffixes := fileCandidateBaseAndSuffixes(base)
239
+ candidates := []string{}
240
+ for _, suffix := range suffixes {
241
+ candidates = append(candidates, base+suffix)
242
+ }
243
+ candidates = append(candidates, filepath.Join(base, "package.json"))
244
+ for _, suffix := range suffixes {
245
+ candidates = append(candidates, filepath.Join(base, "index"+suffix))
246
+ }
247
+ return candidates
248
+ }
249
+
250
+ func moduleFileCandidates(base string, context ModuleResolutionContext, includeDirectory bool) []string {
251
+ explicitExtension := filepath.Ext(base)
252
+ base, suffixes := fileCandidateBaseAndSuffixes(base)
253
+ if context.Options != nil && context.Options.NoDtsResolution == shimcore.TSTrue {
254
+ suffixes = withoutDeclarationSuffixes(suffixes)
255
+ }
256
+ if context.isESM() && explicitExtension == "" {
257
+ return nil
258
+ }
259
+ candidates := []string{}
260
+ for _, suffix := range suffixes {
261
+ candidates = append(candidates, moduleSuffixCandidates(base+suffix, context)...)
262
+ }
263
+ if !includeDirectory || context.isESM() {
264
+ return candidates
265
+ }
266
+ candidates = append(candidates, filepath.Join(base, "package.json"))
267
+ for _, suffix := range suffixes {
268
+ candidates = append(candidates, moduleSuffixCandidates(filepath.Join(base, "index"+suffix), context)...)
269
+ }
270
+ return candidates
271
+ }
272
+
273
+ func withoutDeclarationSuffixes(suffixes []string) []string {
274
+ output := make([]string, 0, len(suffixes))
275
+ for _, suffix := range suffixes {
276
+ if !strings.HasPrefix(suffix, ".d.") {
277
+ output = append(output, suffix)
278
+ }
279
+ }
280
+ return output
281
+ }
282
+
283
+ func moduleSuffixCandidates(path string, context ModuleResolutionContext) []string {
284
+ if context.Options == nil || len(context.Options.ModuleSuffixes) == 0 {
285
+ return []string{path}
286
+ }
287
+ extension := filepath.Ext(path)
288
+ base := strings.TrimSuffix(path, extension)
289
+ candidates := make([]string, 0, len(context.Options.ModuleSuffixes))
290
+ for _, suffix := range context.Options.ModuleSuffixes {
291
+ candidates = append(candidates, base+suffix+extension)
292
+ }
293
+ return candidates
294
+ }
295
+
296
+ func fileCandidateBaseAndSuffixes(base string) (string, []string) {
297
+ lower := strings.ToLower(base)
298
+ switch {
299
+ case strings.HasSuffix(lower, ".d.mts"):
300
+ return base[:len(base)-len(".d.mts")], []string{".mts", ".d.mts", ".mjs"}
301
+ case strings.HasSuffix(lower, ".mjs"), strings.HasSuffix(lower, ".mts"):
302
+ return base[:len(base)-len(filepath.Ext(base))], []string{".mts", ".d.mts", ".mjs"}
303
+ case strings.HasSuffix(lower, ".d.cts"):
304
+ return base[:len(base)-len(".d.cts")], []string{".cts", ".d.cts", ".cjs"}
305
+ case strings.HasSuffix(lower, ".cjs"), strings.HasSuffix(lower, ".cts"):
306
+ return base[:len(base)-len(filepath.Ext(base))], []string{".cts", ".d.cts", ".cjs"}
307
+ case strings.HasSuffix(lower, ".tsx"), strings.HasSuffix(lower, ".jsx"):
308
+ return base[:len(base)-len(filepath.Ext(base))], []string{".tsx", ".ts", ".d.ts", ".jsx", ".js"}
309
+ case strings.HasSuffix(lower, ".d.ts"):
310
+ return base[:len(base)-len(".d.ts")], []string{".ts", ".tsx", ".d.ts", ".js", ".jsx"}
311
+ case strings.HasSuffix(lower, ".ts"), strings.HasSuffix(lower, ".js"):
312
+ return base[:len(base)-len(filepath.Ext(base))], []string{".ts", ".tsx", ".d.ts", ".js", ".jsx"}
313
+ default:
314
+ return base, []string{".ts", ".tsx", ".d.ts", ".js", ".jsx"}
315
+ }
316
+ }
317
+
318
+ // TypeReferenceCandidates lists the probes used for a triple-slash type
319
+ // directive or compilerOptions.types entry.
320
+ func TypeReferenceCandidates(configs []*tsoptions.ParsedCommandLine, directory, cwd, name string) []string {
321
+ candidates := []string{}
322
+ for _, parsed := range configs {
323
+ if parsed == nil || parsed.ParsedConfig == nil || parsed.ParsedConfig.CompilerOptions == nil {
324
+ continue
325
+ }
326
+ for _, root := range parsed.ParsedConfig.CompilerOptions.TypeRoots {
327
+ candidates = append(candidates, FileCandidates(filepath.Join(root, filepath.FromSlash(name)))...)
328
+ }
329
+ }
330
+ for current := filepath.Clean(directory); ; current = filepath.Dir(current) {
331
+ candidates = append(candidates, FileCandidates(filepath.Join(current, "node_modules", "@types", filepath.FromSlash(name)))...)
332
+ if reachedWalkStop(current, cwd) || filepath.Dir(current) == current {
333
+ return candidates
334
+ }
335
+ }
336
+ }
337
+
338
+ // ModuleResolutionCandidates lists the bounded speculative probes for one
339
+ // module specifier in compiler precedence order. The list is used unchanged for
340
+ // unresolved specifiers and is cut at the compiler-selected target for resolved
341
+ // specifiers by ModuleResolutionPredecessors.
342
+ func ModuleResolutionCandidates(configs []*tsoptions.ParsedCommandLine, directory, cwd, specifier string, context ModuleResolutionContext) []string {
343
+ if specifier == "" {
344
+ return nil
345
+ }
346
+ if strings.HasPrefix(specifier, ".") {
347
+ candidates := moduleFileCandidates(filepath.Clean(filepath.Join(directory, filepath.FromSlash(specifier))), context, true)
348
+ return append(candidates, rootDirsCandidates(configs, directory, specifier, context)...)
349
+ }
350
+ candidates := compilerOptionCandidates(configs, specifier, context)
351
+ if strings.HasPrefix(specifier, "#") {
352
+ if !context.usesPackageImports() {
353
+ return candidates
354
+ }
355
+ for current := filepath.Clean(directory); ; current = filepath.Dir(current) {
356
+ fromManifest, _ := packageManifestCandidates(current, specifier, context)
357
+ candidates = append(candidates, fromManifest...)
358
+ if reachedWalkStop(current, cwd) || filepath.Dir(current) == current {
359
+ return candidates
360
+ }
361
+ }
362
+ }
363
+ for current := filepath.Clean(directory); ; current = filepath.Dir(current) {
364
+ base := filepath.Join(current, "node_modules", filepath.FromSlash(specifier))
365
+ root := packageRoot(base, specifier)
366
+ fromManifest, hasExports := packageManifestCandidates(root, packageSubpath(specifier), context)
367
+ // Package exports block bare file and folder lookups. Recording them would
368
+ // make a file that TypeScript will never select invalidate a resident
369
+ // snapshot, so only the export-map paths participate in this branch.
370
+ if !hasExports {
371
+ candidates = append(candidates, moduleFileCandidates(base, context, true)...)
372
+ }
373
+ candidates = append(candidates, filepath.Join(root, "package.json"))
374
+ candidates = append(candidates, fromManifest...)
375
+ if reachedWalkStop(current, cwd) || filepath.Dir(current) == current {
376
+ break
377
+ }
378
+ }
379
+ return candidates
380
+ }
381
+
382
+ // SourceModuleSpecifiers returns all static and dynamic module-specifier
383
+ // literals a source file carries, including imports, exports, import-equals,
384
+ // import types, require calls, and dynamic imports.
385
+ func SourceModuleSpecifiers(source *ast.SourceFile) []*ast.Node {
386
+ if source == nil {
387
+ return nil
388
+ }
389
+ specifiers := []*ast.Node{}
390
+ var walk func(*ast.Node) bool
391
+ walk = func(node *ast.Node) bool {
392
+ if node == nil {
393
+ return false
394
+ }
395
+ var specifier *ast.Node
396
+ switch node.Kind {
397
+ case ast.KindImportDeclaration, ast.KindJSImportDeclaration:
398
+ specifier = node.AsImportDeclaration().ModuleSpecifier
399
+ case ast.KindExportDeclaration:
400
+ specifier = node.AsExportDeclaration().ModuleSpecifier
401
+ case ast.KindImportEqualsDeclaration:
402
+ reference := node.AsImportEqualsDeclaration().ModuleReference
403
+ if reference != nil && reference.Kind == ast.KindExternalModuleReference {
404
+ specifier = reference.AsExternalModuleReference().Expression
405
+ }
406
+ case ast.KindImportType:
407
+ argument := node.AsImportTypeNode().Argument
408
+ if argument != nil && argument.Kind == ast.KindLiteralType {
409
+ specifier = argument.AsLiteralTypeNode().Literal
410
+ }
411
+ case ast.KindCallExpression:
412
+ call := node.AsCallExpression()
413
+ if isModuleSpecifierCall(call) && call.Arguments != nil && len(call.Arguments.Nodes) > 0 {
414
+ specifier = call.Arguments.Nodes[0]
415
+ }
416
+ }
417
+ if specifier != nil && (specifier.Kind == ast.KindStringLiteral || specifier.Kind == ast.KindNoSubstitutionTemplateLiteral) {
418
+ specifiers = append(specifiers, specifier)
419
+ }
420
+ node.ForEachChild(walk)
421
+ return false
422
+ }
423
+ walk(source.AsNode())
424
+ return specifiers
425
+ }
426
+
427
+ func isModuleSpecifierCall(call *ast.CallExpression) bool {
428
+ if call == nil || call.Expression == nil {
429
+ return false
430
+ }
431
+ if call.Expression.Kind == ast.KindImportKeyword {
432
+ return true
433
+ }
434
+ return call.Expression.Kind == ast.KindIdentifier && call.Expression.Text() == "require"
435
+ }
436
+
437
+ type packageTarget struct {
438
+ path string
439
+ wildcard string
440
+ packageEntry bool
441
+ usesCommonJS bool
442
+ }
443
+
444
+ // packageValue preserves a package.json object's declaration order. That order
445
+ // is resolution semantics for conditional exports/imports, so decoding into a
446
+ // Go map would make the speculative prefix nondeterministic.
447
+ type packageValue struct {
448
+ array []packageValue
449
+ object []packageProperty
450
+ text *string
451
+ }
452
+
453
+ type packageProperty struct {
454
+ key string
455
+ value packageValue
456
+ }
457
+
458
+ func packageManifestCandidates(root, wildcard string, context ModuleResolutionContext) ([]string, bool) {
459
+ content, err := os.ReadFile(filepath.Join(root, "package.json"))
460
+ if err != nil {
461
+ return nil, false
462
+ }
463
+ var manifest struct {
464
+ Main string `json:"main"`
465
+ Module string `json:"module"`
466
+ Types string `json:"types"`
467
+ Typings string `json:"typings"`
468
+ Exports json.RawMessage `json:"exports"`
469
+ Imports json.RawMessage `json:"imports"`
470
+ Type string `json:"type"`
471
+ TypesVersions json.RawMessage `json:"typesVersions"`
472
+ }
473
+ if json.Unmarshal(content, &manifest) != nil {
474
+ return nil, false
475
+ }
476
+ defaultWildcard := strings.TrimPrefix(strings.TrimPrefix(wildcard, "./"), "#")
477
+ targets := []packageTarget{}
478
+ if strings.HasPrefix(wildcard, "#") {
479
+ if context.usesPackageImports() {
480
+ if value, ok := decodePackageValue(manifest.Imports); ok {
481
+ collectPackageMappingTargets(value, wildcard, defaultWildcard, context.packageConditions(), &targets)
482
+ }
483
+ }
484
+ return packageTargetCandidates(root, targets, context), false
485
+ }
486
+ exportRequest := "."
487
+ if wildcard != "" && !strings.HasPrefix(wildcard, "#") {
488
+ exportRequest = "./" + strings.TrimPrefix(wildcard, "./")
489
+ }
490
+ exports, hasExports := decodePackageValue(manifest.Exports)
491
+ if hasExports && context.usesPackageExports() {
492
+ collectPackageMappingTargets(exports, exportRequest, defaultWildcard, context.packageConditions(), &targets)
493
+ } else {
494
+ hasExports = false
495
+ if value, ok := decodePackageValue(manifest.TypesVersions); ok {
496
+ collectPackageTargets(value, defaultWildcard, nil, &targets)
497
+ }
498
+ // `typings`, `types`, and `main` are directory-entrypoint fields. A
499
+ // subpath resolution never falls back to them, and `module` is not a
500
+ // TypeScript-Go resolution field at all.
501
+ if wildcard == "" {
502
+ usesCommonJS := manifest.Type != "module"
503
+ if context.Options == nil || context.Options.NoDtsResolution != shimcore.TSTrue {
504
+ targets = append(targets,
505
+ packageTarget{path: manifest.Typings, wildcard: defaultWildcard, packageEntry: true, usesCommonJS: usesCommonJS},
506
+ packageTarget{path: manifest.Types, wildcard: defaultWildcard, packageEntry: true, usesCommonJS: usesCommonJS},
507
+ )
508
+ }
509
+ targets = append(targets, packageTarget{path: manifest.Main, wildcard: defaultWildcard, packageEntry: true, usesCommonJS: usesCommonJS})
510
+ }
511
+ }
512
+ return packageTargetCandidates(root, targets, context), hasExports
513
+ }
514
+
515
+ func packageTargetCandidates(root string, targets []packageTarget, context ModuleResolutionContext) []string {
516
+ candidates := []string{}
517
+ for _, target := range targets {
518
+ if target.path == "" || filepath.IsAbs(target.path) || strings.Contains(target.path, "://") {
519
+ continue
520
+ }
521
+ targetPath := strings.Replace(target.path, "*", target.wildcard, 1)
522
+ targetContext := context
523
+ if target.packageEntry && target.usesCommonJS {
524
+ targetContext.Mode = shimcore.ResolutionModeCommonJS
525
+ }
526
+ candidates = append(candidates, moduleFileCandidates(filepath.Join(root, filepath.FromSlash(targetPath)), targetContext, target.packageEntry)...)
527
+ }
528
+ return candidates
529
+ }
530
+
531
+ func collectPackageMappingTargets(value packageValue, request, wildcard string, conditions map[string]struct{}, targets *[]packageTarget) {
532
+ if value.object != nil {
533
+ mapping := false
534
+ for _, property := range value.object {
535
+ if strings.HasPrefix(property.key, ".") || strings.HasPrefix(property.key, "#") {
536
+ mapping = true
537
+ break
538
+ }
539
+ }
540
+ if mapping {
541
+ property, matched, ok := matchingPackageProperty(value.object, request)
542
+ if ok {
543
+ collectPackageTargets(property.value, matched, conditions, targets)
544
+ }
545
+ return
546
+ }
547
+ }
548
+ collectPackageTargets(value, wildcard, conditions, targets)
549
+ }
550
+
551
+ func collectPackageTargets(value packageValue, wildcard string, conditions map[string]struct{}, targets *[]packageTarget) {
552
+ if value.text != nil {
553
+ *targets = append(*targets, packageTarget{path: *value.text, wildcard: wildcard})
554
+ return
555
+ }
556
+ for _, child := range value.array {
557
+ collectPackageTargets(child, wildcard, conditions, targets)
558
+ }
559
+ for _, property := range value.object {
560
+ if conditions == nil {
561
+ collectPackageTargets(property.value, wildcard, nil, targets)
562
+ continue
563
+ }
564
+ if property.key == "default" {
565
+ collectPackageTargets(property.value, wildcard, conditions, targets)
566
+ continue
567
+ }
568
+ if _, active := conditions[property.key]; active {
569
+ collectPackageTargets(property.value, wildcard, conditions, targets)
570
+ }
571
+ }
572
+ }
573
+
574
+ func decodePackageValue(raw json.RawMessage) (packageValue, bool) {
575
+ trimmed := bytes.TrimSpace(raw)
576
+ if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
577
+ return packageValue{}, false
578
+ }
579
+ switch trimmed[0] {
580
+ case '"':
581
+ var text string
582
+ if json.Unmarshal(trimmed, &text) != nil {
583
+ return packageValue{}, false
584
+ }
585
+ return packageValue{text: &text}, true
586
+ case '[':
587
+ var rawChildren []json.RawMessage
588
+ if json.Unmarshal(trimmed, &rawChildren) != nil {
589
+ return packageValue{}, false
590
+ }
591
+ value := packageValue{array: make([]packageValue, 0, len(rawChildren))}
592
+ for _, child := range rawChildren {
593
+ parsed, ok := decodePackageValue(child)
594
+ if ok {
595
+ value.array = append(value.array, parsed)
596
+ }
597
+ }
598
+ return value, true
599
+ case '{':
600
+ decoder := json.NewDecoder(bytes.NewReader(trimmed))
601
+ if _, err := decoder.Token(); err != nil {
602
+ return packageValue{}, false
603
+ }
604
+ value := packageValue{}
605
+ for decoder.More() {
606
+ token, err := decoder.Token()
607
+ key, keyOK := token.(string)
608
+ if err != nil || !keyOK {
609
+ return packageValue{}, false
610
+ }
611
+ var child json.RawMessage
612
+ if decoder.Decode(&child) != nil {
613
+ return packageValue{}, false
614
+ }
615
+ parsed, ok := decodePackageValue(child)
616
+ if ok {
617
+ value.object = append(value.object, packageProperty{key: key, value: parsed})
618
+ }
619
+ }
620
+ if _, err := decoder.Token(); err != nil {
621
+ return packageValue{}, false
622
+ }
623
+ return value, true
624
+ default:
625
+ return packageValue{}, false
626
+ }
627
+ }
628
+
629
+ func packageSubpath(specifier string) string {
630
+ parts := strings.Split(filepath.ToSlash(specifier), "/")
631
+ count := 1
632
+ if strings.HasPrefix(specifier, "@") && len(parts) > 1 {
633
+ count = 2
634
+ }
635
+ if len(parts) <= count {
636
+ return ""
637
+ }
638
+ return strings.Join(parts[count:], "/")
639
+ }
640
+
641
+ func compilerOptionCandidates(configs []*tsoptions.ParsedCommandLine, specifier string, context ModuleResolutionContext) []string {
642
+ candidates := []string{}
643
+ for _, parsed := range configs {
644
+ if parsed == nil || parsed.ParsedConfig == nil || parsed.ParsedConfig.CompilerOptions == nil {
645
+ continue
646
+ }
647
+ options := parsed.ParsedConfig.CompilerOptions
648
+ if options.Paths == nil {
649
+ continue
650
+ }
651
+ base := options.GetPathsBasePath(parsed.GetCurrentDirectory())
652
+ pattern := ""
653
+ matched := ""
654
+ targets := []string(nil)
655
+ for candidatePattern, candidateTargets := range options.Paths.Entries() {
656
+ if candidatePattern == specifier {
657
+ pattern = candidatePattern
658
+ matched = ""
659
+ targets = candidateTargets
660
+ break
661
+ }
662
+ candidateMatched, ok := matchPathPattern(candidatePattern, specifier)
663
+ if !ok || (pattern != "" && comparePatternKeys(candidatePattern, pattern) >= 0) {
664
+ continue
665
+ }
666
+ pattern = candidatePattern
667
+ matched = candidateMatched
668
+ targets = candidateTargets
669
+ }
670
+ if pattern == "" {
671
+ continue
672
+ }
673
+ for _, target := range targets {
674
+ target = strings.Replace(target, "*", matched, 1)
675
+ candidates = append(candidates, moduleFileCandidates(filepath.Join(base, filepath.FromSlash(target)), context, true)...)
676
+ }
677
+ }
678
+ return candidates
679
+ }
680
+
681
+ func rootDirsCandidates(configs []*tsoptions.ParsedCommandLine, directory, specifier string, context ModuleResolutionContext) []string {
682
+ candidates := []string{}
683
+ for _, parsed := range configs {
684
+ if parsed == nil || parsed.ParsedConfig == nil || parsed.ParsedConfig.CompilerOptions == nil {
685
+ continue
686
+ }
687
+ roots := parsed.ParsedConfig.CompilerOptions.RootDirs
688
+ candidate := filepath.Clean(filepath.Join(directory, filepath.FromSlash(specifier)))
689
+ matchedRoot := ""
690
+ suffix := ""
691
+ for _, root := range roots {
692
+ relative, err := filepath.Rel(root, candidate)
693
+ if err != nil || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
694
+ continue
695
+ }
696
+ if len(root) > len(matchedRoot) {
697
+ matchedRoot = root
698
+ suffix = relative
699
+ }
700
+ }
701
+ if matchedRoot == "" {
702
+ continue
703
+ }
704
+ for _, targetRoot := range roots {
705
+ if filepath.Clean(targetRoot) == filepath.Clean(matchedRoot) {
706
+ continue
707
+ }
708
+ candidates = append(candidates, moduleFileCandidates(filepath.Join(targetRoot, suffix), context, true)...)
709
+ }
710
+ }
711
+ return candidates
712
+ }
713
+
714
+ func matchPathPattern(pattern, specifier string) (string, bool) {
715
+ star := strings.IndexByte(pattern, '*')
716
+ if star < 0 {
717
+ return "", pattern == specifier
718
+ }
719
+ prefix := pattern[:star]
720
+ suffix := pattern[star+1:]
721
+ if len(specifier) < len(prefix)+len(suffix) || !strings.HasPrefix(specifier, prefix) || !strings.HasSuffix(specifier, suffix) {
722
+ return "", false
723
+ }
724
+ return specifier[len(prefix) : len(specifier)-len(suffix)], true
725
+ }
726
+
727
+ func matchingPackageProperty(properties []packageProperty, request string) (packageProperty, string, bool) {
728
+ for _, property := range properties {
729
+ if property.key == request {
730
+ return property, "", true
731
+ }
732
+ }
733
+ var selected *packageProperty
734
+ selectedText := ""
735
+ for index := range properties {
736
+ matched, ok := matchPackagePathPattern(properties[index].key, request)
737
+ if !ok || (selected != nil && comparePatternKeys(properties[index].key, selected.key) >= 0) {
738
+ continue
739
+ }
740
+ selected = &properties[index]
741
+ selectedText = matched
742
+ }
743
+ if selected == nil {
744
+ return packageProperty{}, "", false
745
+ }
746
+ return *selected, selectedText, true
747
+ }
748
+
749
+ func matchPackagePathPattern(pattern, request string) (string, bool) {
750
+ if matched, ok := matchPathPattern(pattern, request); ok {
751
+ return matched, true
752
+ }
753
+ if strings.HasSuffix(pattern, "/") && strings.HasPrefix(request, pattern) {
754
+ return request[len(pattern):], true
755
+ }
756
+ return "", false
757
+ }
758
+
759
+ // comparePatternKeys is the TypeScript-Go resolver's pattern ordering: the
760
+ // longest prefix wins, then a pattern with the longest trailer. It lets the
761
+ // host retain candidates from exactly the one mapping the compiler selected.
762
+ func comparePatternKeys(left, right string) int {
763
+ leftStar := strings.IndexByte(left, '*')
764
+ rightStar := strings.IndexByte(right, '*')
765
+ leftBaseLength := len(left)
766
+ if leftStar >= 0 {
767
+ leftBaseLength = leftStar + 1
768
+ }
769
+ rightBaseLength := len(right)
770
+ if rightStar >= 0 {
771
+ rightBaseLength = rightStar + 1
772
+ }
773
+ switch {
774
+ case leftBaseLength > rightBaseLength:
775
+ return -1
776
+ case leftBaseLength < rightBaseLength:
777
+ return 1
778
+ case leftStar < 0:
779
+ return 1
780
+ case rightStar < 0:
781
+ return -1
782
+ case len(left) > len(right):
783
+ return -1
784
+ case len(left) < len(right):
785
+ return 1
786
+ default:
787
+ return 0
788
+ }
789
+ }
790
+
791
+ func packageRoot(base, specifier string) string {
792
+ parts := strings.Split(filepath.ToSlash(specifier), "/")
793
+ count := 1
794
+ if strings.HasPrefix(specifier, "@") && len(parts) > 1 {
795
+ count = 2
796
+ }
797
+ suffixCount := len(parts) - count
798
+ root := base
799
+ for range suffixCount {
800
+ root = filepath.Dir(root)
801
+ }
802
+ return root
803
+ }
804
+
805
+ func compactStringsInOrder(input []string) []string {
806
+ output := make([]string, 0, len(input))
807
+ seen := map[string]struct{}{}
808
+ for _, value := range input {
809
+ if strings.TrimSpace(value) == "" {
810
+ continue
811
+ }
812
+ if _, exists := seen[value]; exists {
813
+ continue
814
+ }
815
+ seen[value] = struct{}{}
816
+ output = append(output, value)
817
+ }
818
+ return output
819
+ }