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.
- 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 +276 -100
- 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_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 +208 -0
- package/internal/graph/graph.go +143 -5
- package/internal/graph/line_model.go +122 -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 +100 -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,210 @@
|
|
|
1
|
+
package graph
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"strings"
|
|
7
|
+
"testing"
|
|
8
|
+
|
|
9
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
const portableDumpTSConfig = `{
|
|
13
|
+
"compilerOptions": {
|
|
14
|
+
"target": "ES2022",
|
|
15
|
+
"module": "commonjs",
|
|
16
|
+
"strict": true,
|
|
17
|
+
"noEmit": true
|
|
18
|
+
},
|
|
19
|
+
"files": ["src/main.ts", "../shared/value.ts", "../shared/types.d.ts"]
|
|
20
|
+
}
|
|
21
|
+
`
|
|
22
|
+
|
|
23
|
+
// TestDumpCheckoutPathsAreStable verifies the whole schema-v6 consequence
|
|
24
|
+
// surface, not only the path helper.
|
|
25
|
+
//
|
|
26
|
+
// 1. Build the same project plus sibling .ts/.d.ts under two checkout roots.
|
|
27
|
+
// 2. Remove only the producer-local Project locator and require byte identity.
|
|
28
|
+
// 3. Assert nodes, module names, endpoints, implementation evidence,
|
|
29
|
+
// diagnostics, manifests, and universe inputs all use one `../shared`
|
|
30
|
+
// coordinate, with the .ts internal and the .d.ts an external leaf.
|
|
31
|
+
func TestDumpCheckoutPathsAreStable(t *testing.T) {
|
|
32
|
+
firstCheckout := filepath.Join(t.TempDir(), "checkout-one")
|
|
33
|
+
secondCheckout := filepath.Join(t.TempDir(), "checkout-two")
|
|
34
|
+
first := portableFixtureDump(t, firstCheckout)
|
|
35
|
+
second := portableFixtureDump(t, secondCheckout)
|
|
36
|
+
|
|
37
|
+
first.Project = ""
|
|
38
|
+
second.Project = ""
|
|
39
|
+
firstJSON, err := json.Marshal(first)
|
|
40
|
+
if err != nil {
|
|
41
|
+
t.Fatal(err)
|
|
42
|
+
}
|
|
43
|
+
secondJSON, err := json.Marshal(second)
|
|
44
|
+
if err != nil {
|
|
45
|
+
t.Fatal(err)
|
|
46
|
+
}
|
|
47
|
+
if string(firstJSON) != string(secondJSON) {
|
|
48
|
+
t.Fatalf("checkout relocation changed the dump:\nfirst %s\nsecond %s", firstJSON, secondJSON)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
assertPortableFixturePaths(t, first)
|
|
52
|
+
if strings.Contains(string(firstJSON), filepath.ToSlash(firstCheckout)) ||
|
|
53
|
+
strings.Contains(string(secondJSON), filepath.ToSlash(secondCheckout)) {
|
|
54
|
+
t.Fatal("a producer-local checkout path escaped into schema-v6 identity")
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
func portableFixtureDump(t *testing.T, checkout string) Dump {
|
|
59
|
+
t.Helper()
|
|
60
|
+
project := filepath.Join(checkout, "app")
|
|
61
|
+
config := filepath.Join(project, "tsconfig.json")
|
|
62
|
+
mainFile := filepath.Join(project, "src", "main.ts")
|
|
63
|
+
siblingFile := filepath.Join(checkout, "shared", "value.ts")
|
|
64
|
+
declarationFile := filepath.Join(checkout, "shared", "types.d.ts")
|
|
65
|
+
|
|
66
|
+
writeFile(t, config, portableDumpTSConfig)
|
|
67
|
+
writeFile(t, mainFile, `import { sibling } from "../../shared/value";
|
|
68
|
+
import type { ExternalShape } from "../../shared/types";
|
|
69
|
+
export function main(input: ExternalShape): number {
|
|
70
|
+
return sibling(input.value);
|
|
71
|
+
}
|
|
72
|
+
`)
|
|
73
|
+
writeFile(t, siblingFile, `export function sibling(value: number): number {
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
export const broken: number = "nope";
|
|
77
|
+
`)
|
|
78
|
+
writeFile(t, declarationFile, `export interface ExternalShape {
|
|
79
|
+
value: number;
|
|
80
|
+
}
|
|
81
|
+
`)
|
|
82
|
+
|
|
83
|
+
prog, _, err := driver.LoadProgram(project, "tsconfig.json", driver.LoadProgramOptions{})
|
|
84
|
+
if err != nil {
|
|
85
|
+
t.Fatal(err)
|
|
86
|
+
}
|
|
87
|
+
defer func() { _ = prog.Close() }()
|
|
88
|
+
|
|
89
|
+
built := Build(prog)
|
|
90
|
+
// An implementation span is the one evidence path that cannot be recovered
|
|
91
|
+
// from its owner node. Point one real node at the sibling source so this
|
|
92
|
+
// end-to-end projection proves that optional path uses the same mapper too.
|
|
93
|
+
implementationFile := ""
|
|
94
|
+
for _, node := range built.Nodes {
|
|
95
|
+
if node.Kind == NodeFunction && node.Name == "sibling" {
|
|
96
|
+
implementationFile = node.File
|
|
97
|
+
break
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if implementationFile == "" {
|
|
101
|
+
t.Fatal("fixture graph omitted sibling function")
|
|
102
|
+
}
|
|
103
|
+
for _, node := range built.Nodes {
|
|
104
|
+
if node.Kind == NodeFunction && node.Name == "main" {
|
|
105
|
+
// Use the compiler's canonical spelling rather than filepath.Join's host
|
|
106
|
+
// spelling. On Windows SourceTexts is keyed by tsgo-normalized slashes;
|
|
107
|
+
// injecting the fixture's backslash path would test a missing source-map
|
|
108
|
+
// lookup instead of the wire mapper.
|
|
109
|
+
node.ImplementationFile = implementationFile
|
|
110
|
+
node.ImplementationPos = 0
|
|
111
|
+
node.ImplementationEnd = len("export function sibling")
|
|
112
|
+
break
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
texts := SourceTexts(prog)
|
|
117
|
+
dump, err := NewDump(
|
|
118
|
+
built,
|
|
119
|
+
project,
|
|
120
|
+
"tsconfig.json",
|
|
121
|
+
nil,
|
|
122
|
+
texts,
|
|
123
|
+
DumpOrigin{
|
|
124
|
+
Provenance: NewProvenance(
|
|
125
|
+
Producer{Tool: "portable-fixture", Version: "test", Typescript: TypescriptVersion()},
|
|
126
|
+
[]string{CapabilityUniverse, CapabilitySourceDigests, CapabilityDiagnostics},
|
|
127
|
+
[]FileDigest{{File: config, Digest: "config-digest"}},
|
|
128
|
+
[]RootFile{
|
|
129
|
+
{Config: config, File: mainFile},
|
|
130
|
+
{Config: config, File: siblingFile},
|
|
131
|
+
{Config: config, File: declarationFile},
|
|
132
|
+
},
|
|
133
|
+
texts,
|
|
134
|
+
nil,
|
|
135
|
+
),
|
|
136
|
+
Diagnostics: NewDiagnostics(prog),
|
|
137
|
+
},
|
|
138
|
+
)
|
|
139
|
+
if err != nil {
|
|
140
|
+
t.Fatal(err)
|
|
141
|
+
}
|
|
142
|
+
return dump
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
func assertPortableFixturePaths(t *testing.T, dump Dump) {
|
|
146
|
+
t.Helper()
|
|
147
|
+
if dump.Tsconfig != "tsconfig.json" {
|
|
148
|
+
t.Fatalf("tsconfig coordinate = %q", dump.Tsconfig)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
var internal, external, module, implemented bool
|
|
152
|
+
ids := map[string]bool{}
|
|
153
|
+
for _, node := range dump.Nodes {
|
|
154
|
+
ids[node.ID] = true
|
|
155
|
+
switch {
|
|
156
|
+
case node.Kind == string(NodeFunction) && node.Name == "sibling":
|
|
157
|
+
internal = node.File == "../shared/value.ts" && !node.External &&
|
|
158
|
+
strings.HasPrefix(node.ID, "../shared/value.ts#")
|
|
159
|
+
case node.Name == "ExternalShape":
|
|
160
|
+
external = node.File == "../shared/types.d.ts" && node.External &&
|
|
161
|
+
strings.HasPrefix(node.ID, "../shared/types.d.ts#")
|
|
162
|
+
case node.Kind == string(NodeModule) && node.File == "../shared/value.ts":
|
|
163
|
+
module = node.Name == "../shared/value.ts" &&
|
|
164
|
+
strings.Contains(node.ID, "#../shared/value.ts:module")
|
|
165
|
+
case node.Kind == string(NodeFunction) && node.Name == "main":
|
|
166
|
+
implemented = node.Implementation != nil && node.Implementation.File == "../shared/value.ts"
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if !internal || !external || !module || !implemented {
|
|
170
|
+
t.Fatalf("portable node surfaces missing: internal=%t external=%t module=%t implementation=%t", internal, external, module, implemented)
|
|
171
|
+
}
|
|
172
|
+
for _, edge := range dump.Edges {
|
|
173
|
+
if !ids[edge.From] || !ids[edge.To] {
|
|
174
|
+
t.Fatalf("mapped edge endpoint is not a mapped node: %+v", edge)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
diagnostic := false
|
|
179
|
+
for _, finding := range dump.Diagnostics {
|
|
180
|
+
if finding.Code == 2322 && finding.File == "../shared/value.ts" {
|
|
181
|
+
diagnostic = true
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if !diagnostic {
|
|
185
|
+
t.Fatalf("sibling diagnostic did not use the shared coordinate: %+v", dump.Diagnostics)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
source := false
|
|
189
|
+
for _, entry := range dump.Provenance.Sources {
|
|
190
|
+
if entry.File == "../shared/value.ts" {
|
|
191
|
+
source = true
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if !source || len(dump.Provenance.Universe.Configs) != 1 ||
|
|
195
|
+
dump.Provenance.Universe.Configs[0].File != "tsconfig.json" {
|
|
196
|
+
t.Fatalf("manifest/config coordinates drifted: %+v", dump.Provenance)
|
|
197
|
+
}
|
|
198
|
+
roots := map[string]bool{}
|
|
199
|
+
for _, root := range dump.Provenance.Universe.Roots {
|
|
200
|
+
if root.Config != "tsconfig.json" {
|
|
201
|
+
t.Fatalf("root config coordinate = %q", root.Config)
|
|
202
|
+
}
|
|
203
|
+
roots[root.File] = true
|
|
204
|
+
}
|
|
205
|
+
for _, want := range []string{"src/main.ts", "../shared/value.ts", "../shared/types.d.ts"} {
|
|
206
|
+
if !roots[want] {
|
|
207
|
+
t.Fatalf("universe roots omit %q: %+v", want, dump.Provenance.Universe.Roots)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -36,9 +36,12 @@ func TestDumpDiagnosticsRideTheGenerationThatBuiltIt(t *testing.T) {
|
|
|
36
36
|
}
|
|
37
37
|
defer func() { _ = prog.Close() }()
|
|
38
38
|
|
|
39
|
-
dump := NewDump(Build(prog), root, "tsconfig.json", nil, SourceTexts(prog), DumpOrigin{
|
|
40
|
-
Diagnostics: NewDiagnostics(prog
|
|
39
|
+
dump, err := NewDump(Build(prog), root, "tsconfig.json", nil, SourceTexts(prog), DumpOrigin{
|
|
40
|
+
Diagnostics: NewDiagnostics(prog),
|
|
41
41
|
})
|
|
42
|
+
if err != nil {
|
|
43
|
+
t.Fatal(err)
|
|
44
|
+
}
|
|
42
45
|
|
|
43
46
|
var match *Diagnostic
|
|
44
47
|
for i := range dump.Diagnostics {
|
|
@@ -69,7 +72,10 @@ func TestDumpDiagnosticsRideTheGenerationThatBuiltIt(t *testing.T) {
|
|
|
69
72
|
|
|
70
73
|
// The negative twin: a caller that does not collect diagnostics publishes an
|
|
71
74
|
// empty list, never a nil that would encode as JSON null.
|
|
72
|
-
quiet := NewDump(Build(prog), root, "tsconfig.json", nil, SourceTexts(prog), DumpOrigin{})
|
|
75
|
+
quiet, err := NewDump(Build(prog), root, "tsconfig.json", nil, SourceTexts(prog), DumpOrigin{})
|
|
76
|
+
if err != nil {
|
|
77
|
+
t.Fatal(err)
|
|
78
|
+
}
|
|
73
79
|
if quiet.Diagnostics == nil {
|
|
74
80
|
t.Fatal("an uncollected diagnostics list is nil, so it serializes as null rather than []")
|
|
75
81
|
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
package graph
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"strings"
|
|
6
|
+
|
|
7
|
+
shimtspath "github.com/microsoft/typescript-go/shim/tspath"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// dumpPathMapper owns the schema-v6 path vocabulary for one dump. Every
|
|
11
|
+
// identity-bearing path passes through this one cache, so the producer can
|
|
12
|
+
// reject both an unportable filesystem root and a non-injective projection
|
|
13
|
+
// before any JSON is written.
|
|
14
|
+
type dumpPathMapper struct {
|
|
15
|
+
project string
|
|
16
|
+
caseSensitive bool
|
|
17
|
+
|
|
18
|
+
physicalToWire map[string]string
|
|
19
|
+
wireToPhysical map[string]string
|
|
20
|
+
mappingErr error
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func newDumpPathMapper(project string) *dumpPathMapper {
|
|
24
|
+
normalized := shimtspath.NormalizePath(shimtspath.NormalizeSlashes(project))
|
|
25
|
+
mapper := &dumpPathMapper{
|
|
26
|
+
project: normalized,
|
|
27
|
+
caseSensitive: dumpPathRootIsCaseSensitive(normalized),
|
|
28
|
+
physicalToWire: map[string]string{},
|
|
29
|
+
wireToPhysical: map[string]string{},
|
|
30
|
+
}
|
|
31
|
+
if normalized == "" || shimtspath.GetRootLength(normalized) == 0 {
|
|
32
|
+
mapper.mappingErr = fmt.Errorf("ttscgraph: project root %q is not absolute", project)
|
|
33
|
+
}
|
|
34
|
+
return mapper
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// mapPath returns one portable, slash-normalized coordinate:
|
|
38
|
+
//
|
|
39
|
+
// - project files stay project-relative;
|
|
40
|
+
// - same-root siblings use `../` segments, preserving workspace structure;
|
|
41
|
+
// - package paths keep their full resolution context instead of collapsing
|
|
42
|
+
// to the last node_modules tail;
|
|
43
|
+
// - compiler virtual paths keep their bundled identity.
|
|
44
|
+
//
|
|
45
|
+
// A source on another drive or UNC share has no portable coordinate relative
|
|
46
|
+
// to the project. It records a precise error; NewDump returns that error before
|
|
47
|
+
// a caller can serialize the partial projection.
|
|
48
|
+
func (m *dumpPathMapper) mapPath(file string) string {
|
|
49
|
+
if file == "" {
|
|
50
|
+
return ""
|
|
51
|
+
}
|
|
52
|
+
normalized := shimtspath.NormalizeSlashes(file)
|
|
53
|
+
if strings.HasPrefix(normalized, "bundled:///") {
|
|
54
|
+
return m.claim(normalized, normalized)
|
|
55
|
+
}
|
|
56
|
+
normalized = shimtspath.NormalizePath(normalized)
|
|
57
|
+
if m.project == "" || shimtspath.GetRootLength(m.project) == 0 {
|
|
58
|
+
return normalized
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
physical := normalized
|
|
62
|
+
if shimtspath.GetRootLength(physical) == 0 {
|
|
63
|
+
physical = shimtspath.GetNormalizedAbsolutePath(physical, m.project)
|
|
64
|
+
}
|
|
65
|
+
if !dumpPathRootsEqual(m.project, physical, m.caseSensitive) {
|
|
66
|
+
m.fail(fmt.Errorf(
|
|
67
|
+
"ttscgraph: source path %q cannot be represented relative to project %q because they are on different filesystem roots",
|
|
68
|
+
normalized,
|
|
69
|
+
m.project,
|
|
70
|
+
))
|
|
71
|
+
return normalized
|
|
72
|
+
}
|
|
73
|
+
options := shimtspath.ComparePathsOptions{
|
|
74
|
+
CurrentDirectory: m.project,
|
|
75
|
+
UseCaseSensitiveFileNames: m.caseSensitive,
|
|
76
|
+
}
|
|
77
|
+
wire := shimtspath.GetRelativePathFromDirectory(m.project, physical, options)
|
|
78
|
+
if shimtspath.GetRootLength(wire) != 0 {
|
|
79
|
+
m.fail(fmt.Errorf(
|
|
80
|
+
"ttscgraph: source path %q cannot be represented relative to project %q because they are on different filesystem roots",
|
|
81
|
+
normalized,
|
|
82
|
+
m.project,
|
|
83
|
+
))
|
|
84
|
+
return normalized
|
|
85
|
+
}
|
|
86
|
+
return m.claim(physical, wire)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// claim records both directions of the projection. The reverse map is the
|
|
90
|
+
// injectivity gate: two distinct compiler sources may never acquire one wire
|
|
91
|
+
// identity, even if a future coordinate rule is added incorrectly.
|
|
92
|
+
func (m *dumpPathMapper) claim(physical, wire string) string {
|
|
93
|
+
key := physical
|
|
94
|
+
if !m.caseSensitive && !strings.HasPrefix(physical, "bundled:///") {
|
|
95
|
+
key = strings.ToLower(key)
|
|
96
|
+
}
|
|
97
|
+
if previous, ok := m.physicalToWire[key]; ok {
|
|
98
|
+
if previous != wire {
|
|
99
|
+
m.fail(fmt.Errorf(
|
|
100
|
+
"ttscgraph: source path %q mapped inconsistently to %q and %q",
|
|
101
|
+
physical,
|
|
102
|
+
previous,
|
|
103
|
+
wire,
|
|
104
|
+
))
|
|
105
|
+
}
|
|
106
|
+
return previous
|
|
107
|
+
}
|
|
108
|
+
if previous, ok := m.wireToPhysical[wire]; ok && previous != key {
|
|
109
|
+
m.fail(fmt.Errorf(
|
|
110
|
+
"ttscgraph: source paths %q and %q collide at wire identity %q",
|
|
111
|
+
previous,
|
|
112
|
+
physical,
|
|
113
|
+
wire,
|
|
114
|
+
))
|
|
115
|
+
return wire
|
|
116
|
+
}
|
|
117
|
+
m.physicalToWire[key] = wire
|
|
118
|
+
m.wireToPhysical[wire] = key
|
|
119
|
+
return wire
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
func (m *dumpPathMapper) fail(err error) {
|
|
123
|
+
if m.mappingErr == nil {
|
|
124
|
+
m.mappingErr = err
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
func (m *dumpPathMapper) err() error { return m.mappingErr }
|
|
129
|
+
|
|
130
|
+
// Windows drive and UNC roots use case-insensitive path comparison. POSIX
|
|
131
|
+
// roots remain case-sensitive. This decision follows the path's own grammar so
|
|
132
|
+
// synthetic Windows/UNC fixtures behave the same on every CI host.
|
|
133
|
+
func dumpPathRootIsCaseSensitive(path string) bool {
|
|
134
|
+
rootLength := shimtspath.GetRootLength(path)
|
|
135
|
+
if rootLength == 0 {
|
|
136
|
+
return true
|
|
137
|
+
}
|
|
138
|
+
root := path[:rootLength]
|
|
139
|
+
return !(strings.HasPrefix(root, "//") || (len(root) >= 2 && root[1] == ':'))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// dumpPathRootsEqual compares filesystem roots before asking tspath for a
|
|
143
|
+
// relative coordinate. tspath models a UNC root as `//server/`, which is useful
|
|
144
|
+
// for URL-like path operations but too broad for a filesystem identity: on
|
|
145
|
+
// Windows, `//server/share-a` and `//server/share-b` are different volumes and
|
|
146
|
+
// no `../share-b` coordinate can cross between them. Include the share component
|
|
147
|
+
// for that one grammar and keep tspath's roots for drive and POSIX paths.
|
|
148
|
+
func dumpPathRootsEqual(left, right string, caseSensitive bool) bool {
|
|
149
|
+
leftRoot := dumpFilesystemRoot(left)
|
|
150
|
+
rightRoot := dumpFilesystemRoot(right)
|
|
151
|
+
if caseSensitive {
|
|
152
|
+
return leftRoot == rightRoot
|
|
153
|
+
}
|
|
154
|
+
return strings.EqualFold(leftRoot, rightRoot)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
func dumpFilesystemRoot(path string) string {
|
|
158
|
+
normalized := shimtspath.NormalizeSlashes(path)
|
|
159
|
+
rootLength := shimtspath.GetRootLength(normalized)
|
|
160
|
+
if rootLength == 0 {
|
|
161
|
+
return ""
|
|
162
|
+
}
|
|
163
|
+
root := normalized[:rootLength]
|
|
164
|
+
if !strings.HasPrefix(root, "//") {
|
|
165
|
+
return root
|
|
166
|
+
}
|
|
167
|
+
remainder := normalized[rootLength:]
|
|
168
|
+
if slash := strings.IndexByte(remainder, '/'); slash >= 0 {
|
|
169
|
+
return root + remainder[:slash]
|
|
170
|
+
}
|
|
171
|
+
return root + remainder
|
|
172
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
package graph
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"strings"
|
|
5
|
+
"testing"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// TestDumpPathMapperUsesPortableCoordinates verifies the schema-v6 vocabulary
|
|
9
|
+
// directly, independent of the host OS running the test.
|
|
10
|
+
//
|
|
11
|
+
// 1. Map in-project and sibling paths for POSIX, drive, and UNC layouts.
|
|
12
|
+
// 2. Keep two pnpm version/peer contexts with the same package subpath apart.
|
|
13
|
+
// 3. Preserve compiler virtual identities exactly.
|
|
14
|
+
func TestDumpPathMapperUsesPortableCoordinates(t *testing.T) {
|
|
15
|
+
tests := []struct {
|
|
16
|
+
name string
|
|
17
|
+
project string
|
|
18
|
+
file string
|
|
19
|
+
want string
|
|
20
|
+
}{
|
|
21
|
+
{"posix-project", "/checkout/app", "/checkout/app/src/main.ts", "src/main.ts"},
|
|
22
|
+
{"posix-sibling", "/checkout/app", "/checkout/shared/src/value.ts", "../shared/src/value.ts"},
|
|
23
|
+
{"windows-project", `C:\checkout\app`, `C:\checkout\app\src\main.ts`, "src/main.ts"},
|
|
24
|
+
{"windows-sibling", `C:\checkout\app`, `C:\checkout\shared\src\value.ts`, "../shared/src/value.ts"},
|
|
25
|
+
{"unc-project", `\\server\share\checkout\app`, `\\server\share\checkout\app\src\main.ts`, "src/main.ts"},
|
|
26
|
+
{"unc-sibling", `\\server\share\checkout\app`, `\\server\share\checkout\shared\src\value.ts`, "../shared/src/value.ts"},
|
|
27
|
+
{
|
|
28
|
+
"pnpm-peer-a",
|
|
29
|
+
"/checkout/app",
|
|
30
|
+
"/checkout/app/node_modules/.pnpm/pkg@1.0.0_peer-a/node_modules/pkg/index.d.ts",
|
|
31
|
+
"node_modules/.pnpm/pkg@1.0.0_peer-a/node_modules/pkg/index.d.ts",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"pnpm-peer-b",
|
|
35
|
+
"/checkout/app",
|
|
36
|
+
"/checkout/app/node_modules/.pnpm/pkg@2.0.0_peer-b/node_modules/pkg/index.d.ts",
|
|
37
|
+
"node_modules/.pnpm/pkg@2.0.0_peer-b/node_modules/pkg/index.d.ts",
|
|
38
|
+
},
|
|
39
|
+
{"bundled", "/checkout/app", "bundled:///lib.es2024.d.ts", "bundled:///lib.es2024.d.ts"},
|
|
40
|
+
}
|
|
41
|
+
for _, test := range tests {
|
|
42
|
+
t.Run(test.name, func(t *testing.T) {
|
|
43
|
+
mapper := newDumpPathMapper(test.project)
|
|
44
|
+
if got := mapper.mapPath(test.file); got != test.want {
|
|
45
|
+
t.Fatalf("mapPath(%q) = %q, want %q", test.file, got, test.want)
|
|
46
|
+
}
|
|
47
|
+
if err := mapper.err(); err != nil {
|
|
48
|
+
t.Fatalf("mapPath(%q): %v", test.file, err)
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// TestDumpPathMapperRejectsUnportableRootsAndCollisions pins both fail-closed
|
|
55
|
+
// boundaries of the mapping contract.
|
|
56
|
+
//
|
|
57
|
+
// 1. Reject a different Windows drive and a different UNC share precisely.
|
|
58
|
+
// 2. Force two physical sources through one coordinate and require a collision.
|
|
59
|
+
// 3. Require NewDump to surface the root error before JSON serialization.
|
|
60
|
+
func TestDumpPathMapperRejectsUnportableRootsAndCollisions(t *testing.T) {
|
|
61
|
+
for _, test := range []struct {
|
|
62
|
+
name string
|
|
63
|
+
project string
|
|
64
|
+
file string
|
|
65
|
+
}{
|
|
66
|
+
{"windows-drive", "C:/checkout/app", "D:/shared/value.ts"},
|
|
67
|
+
{"unc-share", "//server/share-a/app", "//server/share-b/value.ts"},
|
|
68
|
+
} {
|
|
69
|
+
t.Run(test.name, func(t *testing.T) {
|
|
70
|
+
mapper := newDumpPathMapper(test.project)
|
|
71
|
+
mapper.mapPath(test.file)
|
|
72
|
+
if err := mapper.err(); err == nil || !strings.Contains(err.Error(), "different filesystem roots") {
|
|
73
|
+
t.Fatalf("cross-root error = %v, want a precise filesystem-root rejection", err)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
collision := newDumpPathMapper("/checkout/app")
|
|
79
|
+
collision.claim("/physical/one.ts", "shared.ts")
|
|
80
|
+
collision.claim("/physical/two.ts", "shared.ts")
|
|
81
|
+
if err := collision.err(); err == nil || !strings.Contains(err.Error(), "collide at wire identity") {
|
|
82
|
+
t.Fatalf("collision error = %v, want an injectivity rejection", err)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
file := "D:/shared/value.ts"
|
|
86
|
+
id := nodeID(file, "value", NodeVariable)
|
|
87
|
+
_, err := NewDump(&Graph{Nodes: map[string]*Node{
|
|
88
|
+
id: &Node{ID: id, Name: "value", Simple: "value", Kind: NodeVariable, File: file},
|
|
89
|
+
}}, "C:/checkout/app", "tsconfig.json", nil, nil, DumpOrigin{})
|
|
90
|
+
if err == nil || !strings.Contains(err.Error(), "different filesystem roots") {
|
|
91
|
+
t.Fatalf("NewDump cross-root error = %v, want rejection before serialization", err)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
package graph
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// TestDumpUsesECMALineTerminators verifies graph dump evidence and compact
|
|
10
|
+
// source display use the compiler's complete ECMAScript line model.
|
|
11
|
+
//
|
|
12
|
+
// The graph used to split only on LF, so a CR, LS, or PS file placed every
|
|
13
|
+
// declaration on line one and let a leading // comment consume the rest of the
|
|
14
|
+
// file. Each fixture drives a real compiler program and checks the independent
|
|
15
|
+
// dump coordinates, comment compaction, and regular-expression boundary.
|
|
16
|
+
//
|
|
17
|
+
// 1. Compile the same two-function project with each ECMAScript terminator.
|
|
18
|
+
// 2. Marshal its graph and find both declarations and the call edge.
|
|
19
|
+
// 3. Assert their evidence and source helpers agree on the three logical lines.
|
|
20
|
+
func TestDumpUsesECMALineTerminators(t *testing.T) {
|
|
21
|
+
cases := []struct {
|
|
22
|
+
name string
|
|
23
|
+
terminator string
|
|
24
|
+
}{
|
|
25
|
+
{name: "LF", terminator: "\n"},
|
|
26
|
+
{name: "CRLF", terminator: "\r\n"},
|
|
27
|
+
{name: "CR", terminator: "\r"},
|
|
28
|
+
{name: "LS", terminator: "\u2028"},
|
|
29
|
+
{name: "PS", terminator: "\u2029"},
|
|
30
|
+
}
|
|
31
|
+
for _, tc := range cases {
|
|
32
|
+
t.Run(tc.name, func(t *testing.T) {
|
|
33
|
+
source := "// lead comment" + tc.terminator +
|
|
34
|
+
"export function alpha(): number { return beta(); }" + tc.terminator +
|
|
35
|
+
"export function beta(): number { return 1; }"
|
|
36
|
+
root := t.TempDir()
|
|
37
|
+
writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
|
|
38
|
+
writeFile(t, filepath.Join(root, "src", "main.ts"), source)
|
|
39
|
+
|
|
40
|
+
var dump Dump
|
|
41
|
+
if err := json.Unmarshal(dumpBytes(t, root), &dump); err != nil {
|
|
42
|
+
t.Fatalf("dump is not valid JSON: %v", err)
|
|
43
|
+
}
|
|
44
|
+
nodes := map[string]*DumpNode{}
|
|
45
|
+
for i := range dump.Nodes {
|
|
46
|
+
node := &dump.Nodes[i]
|
|
47
|
+
if node.Kind == "function" {
|
|
48
|
+
nodes[node.Name] = node
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
for _, name := range []string{"alpha", "beta"} {
|
|
52
|
+
node := nodes[name]
|
|
53
|
+
if node == nil || node.Evidence == nil {
|
|
54
|
+
t.Fatalf("missing %s evidence: %+v", name, dump.Nodes)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if got := nodes["alpha"].Evidence; got.StartLine != 2 || got.StartCol != 1 {
|
|
58
|
+
t.Fatalf("alpha evidence = line %d col %d, want line 2 col 1", got.StartLine, got.StartCol)
|
|
59
|
+
}
|
|
60
|
+
if got := nodes["beta"].Evidence; got.StartLine != 3 || got.StartCol != 1 {
|
|
61
|
+
t.Fatalf("beta evidence = line %d col %d, want line 3 col 1", got.StartLine, got.StartCol)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
var call *DumpEdge
|
|
65
|
+
for i := range dump.Edges {
|
|
66
|
+
edge := &dump.Edges[i]
|
|
67
|
+
if edge.Kind == "calls" && edge.From == "src/main.ts#alpha:function" {
|
|
68
|
+
call = edge
|
|
69
|
+
break
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if call == nil || call.Evidence == nil || call.Evidence.StartLine != 2 {
|
|
73
|
+
t.Fatalf("alpha call evidence = %+v, want line 2", call)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if lines := newLineStarts(source); len(lines) != 3 {
|
|
77
|
+
t.Fatalf("line starts = %v, want three logical lines", lines)
|
|
78
|
+
}
|
|
79
|
+
if got := compactObjectMemberSignature("// lead" + tc.terminator + "value: 1"); got != "// lead"+tc.terminator+"value: 1" {
|
|
80
|
+
t.Fatalf("compact signature = %q, want line comment to end before value", got)
|
|
81
|
+
}
|
|
82
|
+
if got := regularExpressionEnd("/line"+tc.terminator+"next/", 0); got != 0 {
|
|
83
|
+
t.Fatalf("regular-expression end = %d, want reject a cross-line literal", got)
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package graph
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"path/filepath"
|
|
5
|
+
"testing"
|
|
6
|
+
|
|
7
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// TestDuplicateVariableObjectMembersKeepFirstDeclaration verifies that a legal
|
|
11
|
+
// repeated var declaration cannot attach its initializer to the first node.
|
|
12
|
+
//
|
|
13
|
+
// Variable symbols merge across declarations, while graph node identity keeps
|
|
14
|
+
// the first declaration. Collecting object members after every add used to
|
|
15
|
+
// combine the first declaration's span with the second declaration's members,
|
|
16
|
+
// producing a node that never existed in the compiler snapshot.
|
|
17
|
+
//
|
|
18
|
+
// 1. Compile two var declarations with the same symbol and different objects.
|
|
19
|
+
// 2. Assert the graph keeps the first declaration and its direct member.
|
|
20
|
+
// 3. Dump the graph and assert the same member remains on the wire.
|
|
21
|
+
func TestDuplicateVariableObjectMembersKeepFirstDeclaration(t *testing.T) {
|
|
22
|
+
root := t.TempDir()
|
|
23
|
+
writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
|
|
24
|
+
writeFile(t, filepath.Join(root, "src", "main.ts"), `export var duplicate = { first: 1 };
|
|
25
|
+
export var duplicate = { second: 2 };
|
|
26
|
+
`)
|
|
27
|
+
|
|
28
|
+
prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
|
|
29
|
+
if err != nil {
|
|
30
|
+
t.Fatal(err)
|
|
31
|
+
}
|
|
32
|
+
if len(diags) != 0 {
|
|
33
|
+
t.Fatalf("unexpected diagnostics: %v", diags)
|
|
34
|
+
}
|
|
35
|
+
defer func() { _ = prog.Close() }()
|
|
36
|
+
|
|
37
|
+
graph := Build(prog)
|
|
38
|
+
path := sourceFile(t, prog, "main.ts").FileName()
|
|
39
|
+
node := graph.Nodes[nodeID(path, "duplicate", NodeVariable)]
|
|
40
|
+
if node == nil {
|
|
41
|
+
t.Fatalf("missing duplicate variable; nodes: %v", nodeIDSet(graph))
|
|
42
|
+
}
|
|
43
|
+
if len(node.ObjectMembers) != 1 || node.ObjectMembers[0].Name != "first" {
|
|
44
|
+
t.Fatalf("first declaration members were overwritten: %+v", node.ObjectMembers)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
dump, err := NewDump(graph, root, "tsconfig.json", nil, SourceTexts(prog), DumpOrigin{})
|
|
48
|
+
if err != nil {
|
|
49
|
+
t.Fatal(err)
|
|
50
|
+
}
|
|
51
|
+
for _, dumped := range dump.Nodes {
|
|
52
|
+
if dumped.ID != "src/main.ts#duplicate:variable" {
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
if len(dumped.ObjectMembers) != 1 || dumped.ObjectMembers[0].Name != "first" || dumped.ObjectMembers[0].Signature != "first: 1" {
|
|
56
|
+
t.Fatalf("dump mixed declarations: %+v", dumped.ObjectMembers)
|
|
57
|
+
}
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
t.Fatalf("dump omitted duplicate variable: %+v", dump.Nodes)
|
|
61
|
+
}
|