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.
- package/README.md +7 -5
- package/cmd/graphdump/main.go +2 -4
- package/cmd/ttsc/filter.go +12 -4
- package/cmd/ttsc/filter_resolves_flag_names_case_insensitively_test.go +53 -0
- package/cmd/ttsc/flags_gen.go +3 -3
- package/cmd/ttscgraph/dump.go +5 -7
- package/cmd/ttscgraph/run_dump_emits_only_checker_verified_member_relations_test.go +118 -0
- package/cmd/ttscgraph/serve.go +58 -308
- package/cmd/ttscgraph/serve_session_keeps_base_url_fallback_unchanged_test.go +49 -0
- package/cmd/ttscgraph/serve_session_keeps_esm_directory_candidate_unchanged_test.go +61 -0
- package/cmd/ttscgraph/serve_session_keeps_exports_blocked_file_candidates_unchanged_test.go +64 -0
- package/cmd/ttscgraph/serve_session_keeps_inactive_conditional_export_unchanged_test.go +66 -0
- package/cmd/ttscgraph/serve_session_keeps_lower_priority_module_candidate_unchanged_test.go +67 -0
- package/cmd/ttscgraph/serve_session_keeps_lower_priority_paths_pattern_unchanged_test.go +58 -0
- package/cmd/ttscgraph/serve_session_keeps_nonlongest_root_dirs_candidate_unchanged_test.go +54 -0
- package/cmd/ttscgraph/serve_session_reloads_commonjs_package_entry_for_esm_test.go +50 -0
- package/cmd/ttscgraph/serve_session_reloads_module_suffix_candidate_test.go +41 -0
- package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +216 -0
- package/cmd/ttscgraph/serve_session_retries_failed_dump_test.go +103 -0
- package/driver/graph.go +12 -6
- package/driver/lsp.go +30 -0
- package/driver/program.go +37 -1
- package/driver/resolution_candidates.go +819 -0
- package/driver/windowsjunction/windowsjunction.go +40 -0
- package/go.mod +2 -0
- package/internal/graph/build.go +45 -11
- package/internal/graph/dump.go +312 -108
- package/internal/graph/dump_checkout_paths_are_stable_test.go +210 -0
- package/internal/graph/dump_diagnostics_ride_the_generation_that_built_it_test.go +9 -3
- package/internal/graph/dump_paths.go +172 -0
- package/internal/graph/dump_paths_are_portable_and_injective_test.go +93 -0
- package/internal/graph/dump_schema_version_matches_the_typescript_client_test.go +1 -1
- package/internal/graph/dump_uses_ecma_line_terminators_test.go +87 -0
- package/internal/graph/duplicate_variable_object_members_keep_first_declaration_test.go +61 -0
- package/internal/graph/edges.go +220 -5
- package/internal/graph/enum_members_carry_their_names_on_the_enum_node_test.go +131 -0
- package/internal/graph/graph.go +177 -5
- package/internal/graph/line_model.go +122 -0
- package/internal/graph/literals.go +175 -0
- package/internal/graph/literals_are_absent_when_the_type_admits_more_than_it_can_name_test.go +74 -0
- package/internal/graph/literals_cover_enum_members_the_graph_has_no_nodes_for_test.go +79 -0
- package/internal/graph/literals_do_not_depend_on_declaration_line_wrapping_test.go +84 -0
- package/internal/graph/literals_render_every_value_in_typescript_source_form_test.go +77 -0
- package/internal/graph/literals_resolve_through_alias_indirection_test.go +64 -0
- package/internal/graph/member_relation_edges_follow_checker_validity_test.go +85 -0
- package/internal/graph/member_relation_edges_preserve_checker_boundaries_test.go +191 -0
- package/internal/graph/member_relation_edges_reject_checker_invalid_pairs_test.go +119 -0
- package/internal/graph/node_ids_preserve_hash_bearing_components_test.go +41 -0
- package/internal/graph/node_modifiers_emit_union_strings_test.go +4 -1
- package/internal/graph/object_literal_members_ride_on_the_snapshot_test.go +192 -0
- package/internal/graph/object_member_signatures_preserve_literal_whitespace_test.go +67 -0
- package/internal/graph/object_members.go +229 -0
- package/internal/graph/provenance.go +88 -30
- package/internal/graph/source_texts_cover_every_resident_program_source_test.go +73 -0
- package/internal/graphsymbols/position_helpers_follow_ecma_line_terminators_test.go +66 -0
- package/internal/graphsymbols/provider.go +26 -91
- package/internal/lspserver/lsp_clean_didopen_refreshes_compiler_state_test.go +144 -0
- package/internal/lspserver/lsp_completion.go +109 -0
- package/internal/lspserver/lsp_completion_gate_never_refuses_a_matching_line_test.go +50 -0
- package/internal/lspserver/lsp_completion_hints_longest_trigger_wins_test.go +149 -0
- package/internal/lspserver/lsp_completion_hints_nearest_trigger_wins_test.go +37 -0
- package/internal/lspserver/lsp_completion_merges_into_upstream_response_test.go +149 -0
- package/internal/lspserver/lsp_completion_nearest_hint_replaces_nearest_filter_test.go +43 -0
- package/internal/lspserver/lsp_completion_preserves_completion_list_extensions_test.go +99 -0
- package/internal/lspserver/lsp_completion_refuses_hints_inside_a_string_literal_test.go +69 -0
- package/internal/lspserver/lsp_completion_replaces_full_filter_in_rank_order_test.go +85 -0
- package/internal/lspserver/lsp_completion_request.go +455 -0
- package/internal/lspserver/lsp_completion_scope.go +530 -0
- package/internal/lspserver/lsp_completion_scope_bench_test.go +41 -0
- package/internal/lspserver/lsp_completion_scope_classifies_regex_statement_and_operator_boundaries_test.go +39 -0
- package/internal/lspserver/lsp_completion_scope_ignores_jsdoc_bytes_in_literals_test.go +108 -0
- package/internal/lspserver/lsp_completion_scope_refuses_regex_hints_test.go +52 -0
- package/internal/lspserver/lsp_completion_trigger_uses_last_rune_test.go +24 -0
- package/internal/lspserver/lsp_deleted_watched_file_withdraws_plugin_diagnostics_test.go +110 -0
- package/internal/lspserver/lsp_didsave_keeps_the_incremental_changed_uri_path_test.go +78 -0
- package/internal/lspserver/lsp_hints_absence_is_not_an_error_test.go +46 -0
- package/internal/lspserver/lsp_hints_refresh_coalesces_concurrent_requests_test.go +82 -0
- package/internal/lspserver/lsp_hints_refresh_ignores_a_superseded_generation_test.go +40 -0
- package/internal/lspserver/lsp_hints_refresh_keeps_each_producer_isolated_test.go +64 -0
- package/internal/lspserver/lsp_hints_refresh_replaces_the_session_snapshot_test.go +66 -0
- package/internal/lspserver/lsp_hints_refresh_scheduled_by_editor_events_test.go +90 -0
- package/internal/lspserver/lsp_hints_refresh_serves_completion_while_running_test.go +91 -0
- package/internal/lspserver/lsp_index_byte_from_bounds_its_search_start_test.go +38 -0
- package/internal/lspserver/lsp_initialize_constrains_position_encoding_to_utf16_test.go +205 -0
- package/internal/lspserver/lsp_late_completion_trigger_is_reported_once_test.go +105 -0
- package/internal/lspserver/lsp_native_completion_hints_decode_flat_lint_wire_test.go +66 -0
- package/internal/lspserver/lsp_native_completion_hints_grouped_compatibility_boundary_test.go +60 -0
- package/internal/lspserver/lsp_native_plugin_source.go +315 -2
- package/internal/lspserver/lsp_native_resident.go +304 -0
- package/internal/lspserver/lsp_plugin.go +85 -8
- package/internal/lspserver/lsp_plugin_completion_resolve_stays_local_test.go +82 -0
- package/internal/lspserver/lsp_position_conversions_span_astral_and_combining_runes_test.go +119 -0
- package/internal/lspserver/lsp_proxy.go +601 -9
- package/internal/lspserver/lsp_refresh.go +70 -0
- package/internal/lspserver/lsp_resident_falls_back_when_serve_unsupported_test.go +43 -0
- package/internal/lspserver/lsp_resident_maps_verb_args_test.go +34 -0
- package/internal/lspserver/lsp_server.go +13 -1
- package/internal/lspserver/lsp_watched_file_changes_reach_compiler_caches_test.go +172 -0
- package/lib/TtscCompiler.d.ts +3 -1
- package/lib/TtscCompiler.js +5 -1
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/TtscService.d.ts +13 -3
- package/lib/TtscService.js +6 -6
- package/lib/TtscService.js.map +1 -1
- package/lib/compiler/internal/project/readConfigJson.d.ts +30 -0
- package/lib/compiler/internal/project/readConfigJson.js +208 -0
- package/lib/compiler/internal/project/readConfigJson.js.map +1 -0
- package/lib/compiler/internal/project/readProjectConfig.js +53 -149
- package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
- package/lib/compiler/internal/residentTransformProcess.d.ts +15 -2
- package/lib/compiler/internal/residentTransformProcess.js +159 -39
- package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +6 -0
- package/lib/compiler/internal/runBuild.js +143 -44
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.d.ts +5 -1
- package/lib/compiler/internal/startResidentTransform.js +3 -1
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.js +12 -2
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/flags/parser.d.ts +19 -0
- package/lib/flags/parser.js +78 -36
- package/lib/flags/parser.js.map +1 -1
- package/lib/flags/schema.d.ts +49 -11
- package/lib/flags/schema.js +94 -22
- package/lib/flags/schema.js.map +1 -1
- package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +5 -0
- package/lib/internal/assertSafeExplicitCacheDirectory.js +26 -0
- package/lib/internal/assertSafeExplicitCacheDirectory.js.map +1 -0
- package/lib/launcher/internal/runTtsc.js +107 -69
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +28 -41
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +15 -0
- package/lib/launcher/internal/runtimeHooks.js +18 -1
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/launcher/internal/watchTopology.d.ts +43 -0
- package/lib/launcher/internal/watchTopology.js +360 -0
- package/lib/launcher/internal/watchTopology.js.map +1 -0
- package/lib/plugin/internal/buildSourcePlugin.d.ts +14 -3
- package/lib/plugin/internal/buildSourcePlugin.js +247 -61
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.js +63 -2
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/structures/ITtscCompilerTransformation.d.ts +16 -0
- package/lib/structures/internal/ITtscParsedProjectConfig.d.ts +2 -0
- package/package.json +8 -8
- package/shim/ast/shim.go +48 -3
- package/shim/astnav/go.mod +14 -0
- package/shim/astnav/go.sum +20 -0
- package/shim/astnav/shim.go +15 -0
- package/shim/checker/shim.go +113 -0
- package/shim/core/shim.go +47 -0
- package/shim/diagnosticwriter/lint.go +50 -0
- package/src/TtscCompiler.ts +5 -1
- package/src/TtscService.ts +37 -10
- package/src/compiler/internal/project/readConfigJson.ts +209 -0
- package/src/compiler/internal/project/readProjectConfig.ts +62 -158
- package/src/compiler/internal/residentTransformProcess.ts +197 -39
- package/src/compiler/internal/runBuild.ts +169 -47
- package/src/compiler/internal/startResidentTransform.ts +12 -1
- package/src/compiler/internal/transformProjectInMemory.ts +12 -1
- package/src/flags/parser.ts +113 -42
- package/src/flags/schema.ts +113 -31
- package/src/internal/assertSafeExplicitCacheDirectory.ts +29 -0
- package/src/launcher/internal/runTtsc.ts +117 -68
- package/src/launcher/internal/runTtsx.ts +29 -44
- package/src/launcher/internal/runtimeHooks.ts +30 -2
- package/src/launcher/internal/watchTopology.ts +466 -0
- package/src/plugin/internal/buildSourcePlugin.ts +326 -64
- package/src/plugin/internal/loadProjectPlugins.ts +80 -2
- package/src/structures/ITtscCompilerTransformation.ts +17 -0
- package/src/structures/internal/ITtscParsedProjectConfig.ts +2 -0
- package/tools/shim_audit/baseline.json +0 -3
- package/tools/shim_audit/main.go +42 -14
- package/tools/shim_audit/shim_dir_coverage_test.go +25 -0
- package/utility/flags_gen.go +3 -3
- 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
|
+
}
|