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,122 @@
|
|
|
1
|
+
package graph
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"unicode/utf8"
|
|
5
|
+
|
|
6
|
+
shimcore "github.com/microsoft/typescript-go/shim/core"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// ECMALineStarts returns the byte offset of each logical source line. The
|
|
10
|
+
// compiler is the authority for which bytes end a line, so graph evidence and
|
|
11
|
+
// graph-backed LSP locations cannot drift from its LF, CRLF, CR, LS, and PS
|
|
12
|
+
// coordinates.
|
|
13
|
+
func ECMALineStarts(text string) []int {
|
|
14
|
+
compilerStarts := shimcore.ComputeECMALineStarts(text)
|
|
15
|
+
starts := make([]int, len(compilerStarts))
|
|
16
|
+
for i, start := range compilerStarts {
|
|
17
|
+
starts[i] = int(start)
|
|
18
|
+
}
|
|
19
|
+
return starts
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// LineEnd returns the byte offset immediately before line's terminator, or the
|
|
23
|
+
// end of text for its final logical line. starts must come from ECMALineStarts
|
|
24
|
+
// for the same text.
|
|
25
|
+
func LineEnd(text string, starts []int, line int) int {
|
|
26
|
+
if line < 0 || line >= len(starts) {
|
|
27
|
+
return len(text)
|
|
28
|
+
}
|
|
29
|
+
if line+1 == len(starts) {
|
|
30
|
+
return len(text)
|
|
31
|
+
}
|
|
32
|
+
for i := starts[line]; i < starts[line+1]; {
|
|
33
|
+
if lineTerminatorWidth(text, i) > 0 {
|
|
34
|
+
return i
|
|
35
|
+
}
|
|
36
|
+
_, size := utf8.DecodeRuneInString(text[i:])
|
|
37
|
+
if size == 0 {
|
|
38
|
+
return len(text)
|
|
39
|
+
}
|
|
40
|
+
i += size
|
|
41
|
+
}
|
|
42
|
+
return starts[line+1]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// FirstCodeOffset advances over leading whitespace and comments so a graph
|
|
46
|
+
// span begins at its declaration rather than its leading trivia.
|
|
47
|
+
func FirstCodeOffset(text string, pos int) int {
|
|
48
|
+
if pos < 0 {
|
|
49
|
+
return 0
|
|
50
|
+
}
|
|
51
|
+
for pos < len(text) {
|
|
52
|
+
width := sourceWhitespaceWidth(text, pos)
|
|
53
|
+
switch {
|
|
54
|
+
case width > 0:
|
|
55
|
+
pos += width
|
|
56
|
+
case text[pos] == '/' && pos+1 < len(text) && text[pos+1] == '/':
|
|
57
|
+
pos = LineCommentEnd(text, pos)
|
|
58
|
+
case text[pos] == '/' && pos+1 < len(text) && text[pos+1] == '*':
|
|
59
|
+
pos += 2
|
|
60
|
+
for pos+1 < len(text) && !(text[pos] == '*' && text[pos+1] == '/') {
|
|
61
|
+
_, size := utf8.DecodeRuneInString(text[pos:])
|
|
62
|
+
if size == 0 {
|
|
63
|
+
return len(text)
|
|
64
|
+
}
|
|
65
|
+
pos += size
|
|
66
|
+
}
|
|
67
|
+
if pos+1 < len(text) {
|
|
68
|
+
pos += 2
|
|
69
|
+
} else {
|
|
70
|
+
return len(text)
|
|
71
|
+
}
|
|
72
|
+
default:
|
|
73
|
+
return pos
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return len(text)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// LineCommentEnd returns the byte offset immediately after a // comment and
|
|
80
|
+
// its line terminator, or len(text) when the comment reaches EOF.
|
|
81
|
+
func LineCommentEnd(text string, start int) int {
|
|
82
|
+
for i := start + 2; i < len(text); {
|
|
83
|
+
if width := lineTerminatorWidth(text, i); width > 0 {
|
|
84
|
+
return i + width
|
|
85
|
+
}
|
|
86
|
+
_, size := utf8.DecodeRuneInString(text[i:])
|
|
87
|
+
if size == 0 {
|
|
88
|
+
return len(text)
|
|
89
|
+
}
|
|
90
|
+
i += size
|
|
91
|
+
}
|
|
92
|
+
return len(text)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func sourceWhitespaceWidth(text string, pos int) int {
|
|
96
|
+
switch text[pos] {
|
|
97
|
+
case ' ', '\t', '\r', '\n', '\v', '\f':
|
|
98
|
+
return 1
|
|
99
|
+
}
|
|
100
|
+
r, size := utf8.DecodeRuneInString(text[pos:])
|
|
101
|
+
if r == '\u2028' || r == '\u2029' {
|
|
102
|
+
return size
|
|
103
|
+
}
|
|
104
|
+
return 0
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
func lineTerminatorWidth(text string, pos int) int {
|
|
108
|
+
switch text[pos] {
|
|
109
|
+
case '\r':
|
|
110
|
+
if pos+1 < len(text) && text[pos+1] == '\n' {
|
|
111
|
+
return 2
|
|
112
|
+
}
|
|
113
|
+
return 1
|
|
114
|
+
case '\n':
|
|
115
|
+
return 1
|
|
116
|
+
}
|
|
117
|
+
r, size := utf8.DecodeRuneInString(text[pos:])
|
|
118
|
+
if r == '\u2028' || r == '\u2029' {
|
|
119
|
+
return size
|
|
120
|
+
}
|
|
121
|
+
return 0
|
|
122
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
package graph
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
5
|
+
shimchecker "github.com/microsoft/typescript-go/shim/checker"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// collectLiterals records the value set of every type alias and enum in file
|
|
9
|
+
// whose declared type resolves to literals, so a consumer asking what values a
|
|
10
|
+
// type admits is answered from the checker rather than from the declaration's
|
|
11
|
+
// source text.
|
|
12
|
+
//
|
|
13
|
+
// It runs on the checker for the reason markExports does: the syntactic reading
|
|
14
|
+
// is not the real answer. A union's members are only spelled out at the
|
|
15
|
+
// declaration when nothing indirects — `type Indirect = Kind | 'f'` names one
|
|
16
|
+
// literal and reaches five more through an alias, and only the checker has
|
|
17
|
+
// resolved that. It has also already flattened nested unions, dropped
|
|
18
|
+
// duplicates, and reduced subtypes, so the constituents it hands back are the
|
|
19
|
+
// type's members exactly once each.
|
|
20
|
+
//
|
|
21
|
+
// Enums come here too, and they need it more. An enum's members are not nodes
|
|
22
|
+
// of their own — the build pass records member nodes for classes and interfaces
|
|
23
|
+
// only — so its node is the only place anything about them can ride. It carries
|
|
24
|
+
// two facts, and they are not the same fact: the value set the enum admits,
|
|
25
|
+
// deduped because a type is a set, and the members it declares, which is the
|
|
26
|
+
// list a caller writes from and which nothing folds.
|
|
27
|
+
func (g *Graph) collectLiterals(checker *shimchecker.Checker, file *shimast.SourceFile) {
|
|
28
|
+
if file.Statements == nil {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
g.collectLiteralsIn(checker, file.FileName(), file.Statements.Nodes)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// collectLiteralsIn walks a statement list — a file's top level, or a namespace
|
|
35
|
+
// body it recurses into — and records the value set of each type alias and enum
|
|
36
|
+
// it holds. It mirrors collectStatements' descent so a `namespace X { type K =
|
|
37
|
+
// … }` is resolved on the node the build pass keyed by its qualified name.
|
|
38
|
+
func (g *Graph) collectLiteralsIn(checker *shimchecker.Checker, path string, statements []*shimast.Node) {
|
|
39
|
+
for _, statement := range statements {
|
|
40
|
+
switch statement.Kind {
|
|
41
|
+
case shimast.KindTypeAliasDeclaration:
|
|
42
|
+
g.putLiterals(checker, path, statement, NodeTypeAlias)
|
|
43
|
+
case shimast.KindEnumDeclaration:
|
|
44
|
+
g.putLiterals(checker, path, statement, NodeEnum)
|
|
45
|
+
case shimast.KindModuleDeclaration:
|
|
46
|
+
g.collectLiteralsIn(checker, path, moduleStatements(statement))
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// putLiterals resolves the declared type of statement's symbol and records its
|
|
52
|
+
// value set on the node the build pass recorded for it. A declaration the graph
|
|
53
|
+
// does not hold a node for, or whose type has no complete literal answer, is
|
|
54
|
+
// left alone rather than given a partial one.
|
|
55
|
+
func (g *Graph) putLiterals(checker *shimchecker.Checker, path string, statement *shimast.Node, kind NodeKind) {
|
|
56
|
+
symbol := statement.Symbol()
|
|
57
|
+
if symbol == nil || symbol.Name == "" {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
node, ok := g.Nodes[nodeID(path, qualifiedName(symbol), kind)]
|
|
61
|
+
if !ok {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
declared := shimchecker.Checker_getDeclaredTypeOfSymbol(checker, symbol)
|
|
65
|
+
if declared == nil {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
if values, ok := literalValues(declared); ok {
|
|
69
|
+
node.Literals = values
|
|
70
|
+
}
|
|
71
|
+
if kind == NodeEnum {
|
|
72
|
+
node.EnumMembers = enumMembers(checker, statement)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// enumMembers pairs each member an enum declares with the value it carries.
|
|
77
|
+
//
|
|
78
|
+
// The names are the half a caller writes. `literals` answers which values the
|
|
79
|
+
// enum admits, the question a serializer asks, but the code says `Colors.Red`
|
|
80
|
+
// and never `"red"` — so an enum whose node the graph already held still sent a
|
|
81
|
+
// caller to the file to learn what to type (#738).
|
|
82
|
+
//
|
|
83
|
+
// The list comes from the declaration, not from the declared type's
|
|
84
|
+
// constituents, and that is not a detail. A type is a set, so the checker folds
|
|
85
|
+
// two members carrying one value into one constituent: `enum Dup { A = 'x', B =
|
|
86
|
+
// 'x' }` resolves to a single type, and reading names off it reports `A` and
|
|
87
|
+
// loses `B` — the same silent under-report as #732, out of the same instinct to
|
|
88
|
+
// take a declaration fact from a type. The value set really is one value, which
|
|
89
|
+
// is why `literals` is right to say `"x"` once; the member list is two members,
|
|
90
|
+
// and only the declaration says so.
|
|
91
|
+
//
|
|
92
|
+
// Each member's value still comes from the checker, per member, so an implicit
|
|
93
|
+
// `First` reports 0 with nothing reading the source. A member the checker
|
|
94
|
+
// cannot fold to a constant keeps its name and drops its value, because the
|
|
95
|
+
// name is the part this is for.
|
|
96
|
+
func enumMembers(checker *shimchecker.Checker, statement *shimast.Node) []EnumMember {
|
|
97
|
+
declaration := statement.AsEnumDeclaration()
|
|
98
|
+
if declaration == nil || declaration.Members == nil {
|
|
99
|
+
return nil
|
|
100
|
+
}
|
|
101
|
+
out := make([]EnumMember, 0, len(declaration.Members.Nodes))
|
|
102
|
+
for _, node := range declaration.Members.Nodes {
|
|
103
|
+
symbol := node.Symbol()
|
|
104
|
+
if symbol == nil || symbol.Name == "" {
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
member := EnumMember{Name: symbol.Name}
|
|
108
|
+
if t := shimchecker.Checker_getTypeOfSymbol(checker, symbol); t != nil {
|
|
109
|
+
if value, ok := literalValue(t); ok {
|
|
110
|
+
member.Value = value
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
out = append(out, member)
|
|
114
|
+
}
|
|
115
|
+
if len(out) == 0 {
|
|
116
|
+
return nil
|
|
117
|
+
}
|
|
118
|
+
return out
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// literalValues renders every constituent of t in TypeScript source form,
|
|
122
|
+
// reporting false unless all of them are enumerable.
|
|
123
|
+
//
|
|
124
|
+
// All-or-nothing is the contract. `type T = Kind | number` admits five literals
|
|
125
|
+
// and every other number besides; answering with the five would describe a type
|
|
126
|
+
// that does not exist, and the caller cannot tell that answer from a complete
|
|
127
|
+
// one. So a type whose members cannot all be named yields no list, and its
|
|
128
|
+
// signature carries the shape instead.
|
|
129
|
+
func literalValues(t *shimchecker.Type) ([]string, bool) {
|
|
130
|
+
// Types() panics on anything that is not a union, an intersection, or a
|
|
131
|
+
// template literal, so the flag test is what makes the union branch safe. A
|
|
132
|
+
// single-literal alias (`type One = 'a'`) is not a union and is its own only
|
|
133
|
+
// constituent.
|
|
134
|
+
constituents := []*shimchecker.Type{t}
|
|
135
|
+
if t.Flags()&shimchecker.TypeFlagsUnion != 0 {
|
|
136
|
+
constituents = t.Types()
|
|
137
|
+
}
|
|
138
|
+
values := make([]string, 0, len(constituents))
|
|
139
|
+
for _, constituent := range constituents {
|
|
140
|
+
value, ok := literalValue(constituent)
|
|
141
|
+
if !ok {
|
|
142
|
+
return nil, false
|
|
143
|
+
}
|
|
144
|
+
values = append(values, value)
|
|
145
|
+
}
|
|
146
|
+
if len(values) == 0 {
|
|
147
|
+
return nil, false
|
|
148
|
+
}
|
|
149
|
+
return values, true
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// literalValue renders one constituent in TypeScript source form, reporting
|
|
153
|
+
// false when it names no single value a caller could write.
|
|
154
|
+
func literalValue(t *shimchecker.Type) (string, bool) {
|
|
155
|
+
flags := t.Flags()
|
|
156
|
+
switch {
|
|
157
|
+
case flags&shimchecker.TypeFlagsNull != 0:
|
|
158
|
+
return "null", true
|
|
159
|
+
case flags&shimchecker.TypeFlagsUndefined != 0:
|
|
160
|
+
return "undefined", true
|
|
161
|
+
case flags&shimchecker.TypeFlagsLiteral != 0:
|
|
162
|
+
// TypeFlagsLiteral covers an enum member too: EnumLiteral is always paired
|
|
163
|
+
// with StringLiteral or NumberLiteral on a member, and with Union on the
|
|
164
|
+
// enum type itself, which the union branch above has already opened.
|
|
165
|
+
value := t.AsLiteralType().Value()
|
|
166
|
+
if value == nil {
|
|
167
|
+
// A computed enum member the checker could not fold to a constant. It has
|
|
168
|
+
// a value at runtime that nothing here can name, so the enum has no
|
|
169
|
+
// complete answer.
|
|
170
|
+
return "", false
|
|
171
|
+
}
|
|
172
|
+
return shimchecker.ValueToString(value), true
|
|
173
|
+
}
|
|
174
|
+
return "", false
|
|
175
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
package graph
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"path/filepath"
|
|
5
|
+
"testing"
|
|
6
|
+
|
|
7
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// TestLiteralsAreAbsentWhenTheTypeAdmitsMoreThanItCanName verifies the negative
|
|
11
|
+
// twin of every literal case: a type whose members cannot all be named reports
|
|
12
|
+
// none of them rather than the subset that can.
|
|
13
|
+
//
|
|
14
|
+
// This is what lets a present `literals` mean the whole type. `Widened` admits
|
|
15
|
+
// four literals and every other string besides, and a caller cannot tell a
|
|
16
|
+
// four-value answer for it from a four-value answer for a genuine four-member
|
|
17
|
+
// union — so it must get no answer, and read the signature instead. Each fixture
|
|
18
|
+
// here is one way a constituent stops being nameable: a primitive next to the
|
|
19
|
+
// literals, an unresolved type parameter, a plain primitive alias, and a
|
|
20
|
+
// computed enum member whose value the checker cannot fold to a constant.
|
|
21
|
+
//
|
|
22
|
+
// 1. Compile a fixture holding each non-enumerable shape.
|
|
23
|
+
// 2. Build the graph.
|
|
24
|
+
// 3. Assert none of them recorded a value set.
|
|
25
|
+
func TestLiteralsAreAbsentWhenTheTypeAdmitsMoreThanItCanName(t *testing.T) {
|
|
26
|
+
root := t.TempDir()
|
|
27
|
+
writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
|
|
28
|
+
writeFile(t, filepath.Join(root, "src", "main.ts"), `declare const compute: () => number;
|
|
29
|
+
|
|
30
|
+
export type Narrow = 'a' | 'b' | 'c' | 'd';
|
|
31
|
+
export type Widened = Narrow | string;
|
|
32
|
+
export type Generic<T> = T | 'a';
|
|
33
|
+
export type Primitive = string;
|
|
34
|
+
|
|
35
|
+
export enum Computed {
|
|
36
|
+
A = 'a',
|
|
37
|
+
B = compute(),
|
|
38
|
+
}
|
|
39
|
+
`)
|
|
40
|
+
|
|
41
|
+
prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
|
|
42
|
+
if err != nil {
|
|
43
|
+
t.Fatal(err)
|
|
44
|
+
}
|
|
45
|
+
if len(diags) != 0 {
|
|
46
|
+
t.Fatalf("unexpected diagnostics: %v", diags)
|
|
47
|
+
}
|
|
48
|
+
defer func() { _ = prog.Close() }()
|
|
49
|
+
|
|
50
|
+
graph := Build(prog)
|
|
51
|
+
path := sourceFile(t, prog, "main.ts").FileName()
|
|
52
|
+
|
|
53
|
+
// The control: the shapes below differ from this one by exactly the property
|
|
54
|
+
// that makes them unnameable, so an empty result there is the rule acting and
|
|
55
|
+
// not the pass failing to run on this fixture at all.
|
|
56
|
+
if got := literalsOf(t, graph, nodeID(path, "Narrow", NodeTypeAlias)); len(got) != 4 {
|
|
57
|
+
t.Fatalf("control union did not report its four members: got %v", got)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for _, testCase := range []struct {
|
|
61
|
+
name string
|
|
62
|
+
kind NodeKind
|
|
63
|
+
why string
|
|
64
|
+
}{
|
|
65
|
+
{"Widened", NodeTypeAlias, "a union with `string` in it admits values no list can name"},
|
|
66
|
+
{"Generic", NodeTypeAlias, "an unresolved type parameter names no value"},
|
|
67
|
+
{"Primitive", NodeTypeAlias, "a primitive is not a value set"},
|
|
68
|
+
{"Computed", NodeEnum, "a computed enum member has a value nothing here can name"},
|
|
69
|
+
} {
|
|
70
|
+
if got := literalsOf(t, graph, nodeID(path, testCase.name, testCase.kind)); len(got) != 0 {
|
|
71
|
+
t.Fatalf("%s reported a partial value set %v: %s", testCase.name, got, testCase.why)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
package graph
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"path/filepath"
|
|
5
|
+
"slices"
|
|
6
|
+
"testing"
|
|
7
|
+
|
|
8
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestLiteralsCoverEnumMembersTheGraphHasNoNodesFor verifies that an enum
|
|
12
|
+
// reports its member values, string-valued, numeric, and implicitly numbered
|
|
13
|
+
// alike, however the declaration is laid out.
|
|
14
|
+
//
|
|
15
|
+
// An enum needed this more than a union did (#732). Its members are not nodes —
|
|
16
|
+
// the build pass records member nodes for classes and interfaces only — so
|
|
17
|
+
// nothing else in a details result carries them, and the old source-text scrape
|
|
18
|
+
// stopped at the line holding `{`, which is the first line of every enum written
|
|
19
|
+
// the ordinary way. A multi-line enum therefore reported no members at all,
|
|
20
|
+
// through `literals` or otherwise, and only a single-line one answered. The
|
|
21
|
+
// implicit fixture pins that the values come from the checker rather than the
|
|
22
|
+
// text: nothing in `Implicit`'s source spells out 0 and 1.
|
|
23
|
+
//
|
|
24
|
+
// 1. Compile a fixture with a multi-line string enum, a single-line one, and an
|
|
25
|
+
// enum whose members are implicitly numbered.
|
|
26
|
+
// 2. Build the graph.
|
|
27
|
+
// 3. Assert each reports its resolved member values, and that the enum still
|
|
28
|
+
// has no member nodes so `literals` is genuinely the only carrier.
|
|
29
|
+
func TestLiteralsCoverEnumMembersTheGraphHasNoNodesFor(t *testing.T) {
|
|
30
|
+
root := t.TempDir()
|
|
31
|
+
writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
|
|
32
|
+
writeFile(t, filepath.Join(root, "src", "main.ts"), `export enum Wrapped {
|
|
33
|
+
A = 'a',
|
|
34
|
+
B = 'b',
|
|
35
|
+
C = 'c',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export enum Flat { A = 'a', B = 'b', C = 'c' }
|
|
39
|
+
|
|
40
|
+
export enum Implicit {
|
|
41
|
+
First,
|
|
42
|
+
Second,
|
|
43
|
+
}
|
|
44
|
+
`)
|
|
45
|
+
|
|
46
|
+
prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
|
|
47
|
+
if err != nil {
|
|
48
|
+
t.Fatal(err)
|
|
49
|
+
}
|
|
50
|
+
if len(diags) != 0 {
|
|
51
|
+
t.Fatalf("unexpected diagnostics: %v", diags)
|
|
52
|
+
}
|
|
53
|
+
defer func() { _ = prog.Close() }()
|
|
54
|
+
|
|
55
|
+
graph := Build(prog)
|
|
56
|
+
path := sourceFile(t, prog, "main.ts").FileName()
|
|
57
|
+
|
|
58
|
+
// The declaration whose members the old scrape could never reach.
|
|
59
|
+
wrapped := literalsOf(t, graph, nodeID(path, "Wrapped", NodeEnum))
|
|
60
|
+
if want := []string{`"a"`, `"b"`, `"c"`}; !slices.Equal(wrapped, want) {
|
|
61
|
+
t.Fatalf("multi-line enum under-reported its members: got %v, want %v", wrapped, want)
|
|
62
|
+
}
|
|
63
|
+
// Layout is not the fact; the single-line twin must agree exactly.
|
|
64
|
+
flat := literalsOf(t, graph, nodeID(path, "Flat", NodeEnum))
|
|
65
|
+
if !slices.Equal(flat, wrapped) {
|
|
66
|
+
t.Fatalf("enum layout changed the value set: wrapped %v, flat %v", wrapped, flat)
|
|
67
|
+
}
|
|
68
|
+
// Implicitly numbered members: the values exist only in the checker, never in
|
|
69
|
+
// the source text, so a scrape of any kind cannot produce this list.
|
|
70
|
+
implicit := literalsOf(t, graph, nodeID(path, "Implicit", NodeEnum))
|
|
71
|
+
if want := []string{"0", "1"}; !slices.Equal(implicit, want) {
|
|
72
|
+
t.Fatalf("implicitly numbered enum did not report its resolved values: got %v, want %v", implicit, want)
|
|
73
|
+
}
|
|
74
|
+
// The premise: no member node exists to carry these instead, so dropping
|
|
75
|
+
// `literals` for an enum would take its members out of the graph entirely.
|
|
76
|
+
if node, ok := graph.Nodes[nodeID(path, "Wrapped.A", NodeMethod)]; ok {
|
|
77
|
+
t.Fatalf("enum member unexpectedly modeled as a node (%v); literals may no longer be the only carrier", node.ID)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
package graph
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"path/filepath"
|
|
5
|
+
"slices"
|
|
6
|
+
"testing"
|
|
7
|
+
|
|
8
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestLiteralsDoNotDependOnDeclarationLineWrapping verifies that collectLiterals
|
|
12
|
+
// answers with the union's members whatever the declaration's layout: the same
|
|
13
|
+
// eight-member type written one member per line and written on one line both
|
|
14
|
+
// report all eight.
|
|
15
|
+
//
|
|
16
|
+
// This is the #732 regression. The value set used to be scraped out of the
|
|
17
|
+
// declaration's first four lines of source text and cut to six, so completeness
|
|
18
|
+
// was a function of line wrapping and of member count rather than of the type:
|
|
19
|
+
// the wrapped form reported the three members that fit in the snippet, and the
|
|
20
|
+
// flat form reported six of eight. Both cuts were silent. Eight members is the
|
|
21
|
+
// boundary that pins both at once — it is past the old six-member cap, so a
|
|
22
|
+
// reintroduced cap fails here on the flat twin instead of hiding behind a small
|
|
23
|
+
// fixture.
|
|
24
|
+
//
|
|
25
|
+
// 1. Compile a fixture with a wrapped union and a flat union of the same eight
|
|
26
|
+
// string literals.
|
|
27
|
+
// 2. Build the graph.
|
|
28
|
+
// 3. Assert both nodes carry all eight values, in source form, and that the two
|
|
29
|
+
// lists are identical.
|
|
30
|
+
func TestLiteralsDoNotDependOnDeclarationLineWrapping(t *testing.T) {
|
|
31
|
+
root := t.TempDir()
|
|
32
|
+
writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
|
|
33
|
+
writeFile(t, filepath.Join(root, "src", "main.ts"), `export type Wrapped =
|
|
34
|
+
| 'a' // a trailing comment the old snippet scrape also had to survive
|
|
35
|
+
| 'b'
|
|
36
|
+
| 'c'
|
|
37
|
+
| 'd'
|
|
38
|
+
| 'e'
|
|
39
|
+
| 'f'
|
|
40
|
+
| 'g'
|
|
41
|
+
| 'h';
|
|
42
|
+
|
|
43
|
+
export type Flat = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h';
|
|
44
|
+
`)
|
|
45
|
+
|
|
46
|
+
prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
|
|
47
|
+
if err != nil {
|
|
48
|
+
t.Fatal(err)
|
|
49
|
+
}
|
|
50
|
+
if len(diags) != 0 {
|
|
51
|
+
t.Fatalf("unexpected diagnostics: %v", diags)
|
|
52
|
+
}
|
|
53
|
+
defer func() { _ = prog.Close() }()
|
|
54
|
+
|
|
55
|
+
graph := Build(prog)
|
|
56
|
+
path := sourceFile(t, prog, "main.ts").FileName()
|
|
57
|
+
|
|
58
|
+
want := []string{`"a"`, `"b"`, `"c"`, `"d"`, `"e"`, `"f"`, `"g"`, `"h"`}
|
|
59
|
+
wrapped := literalsOf(t, graph, nodeID(path, "Wrapped", NodeTypeAlias))
|
|
60
|
+
flat := literalsOf(t, graph, nodeID(path, "Flat", NodeTypeAlias))
|
|
61
|
+
|
|
62
|
+
if !slices.Equal(wrapped, want) {
|
|
63
|
+
t.Fatalf("wrapped union under-reported its members: got %v, want %v", wrapped, want)
|
|
64
|
+
}
|
|
65
|
+
if !slices.Equal(flat, want) {
|
|
66
|
+
t.Fatalf("flat union under-reported its members: got %v, want %v", flat, want)
|
|
67
|
+
}
|
|
68
|
+
// The point of the fix stated directly: layout is not allowed to change the
|
|
69
|
+
// answer for one and the same type.
|
|
70
|
+
if !slices.Equal(wrapped, flat) {
|
|
71
|
+
t.Fatalf("line wrapping changed the value set: wrapped %v, flat %v", wrapped, flat)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// literalsOf returns the recorded value set of the node id names, failing the
|
|
76
|
+
// test when the graph holds no such node.
|
|
77
|
+
func literalsOf(t *testing.T, graph *Graph, id string) []string {
|
|
78
|
+
t.Helper()
|
|
79
|
+
node, ok := graph.Nodes[id]
|
|
80
|
+
if !ok {
|
|
81
|
+
t.Fatalf("graph has no node %q; nodes: %v", id, nodeIDSet(graph))
|
|
82
|
+
}
|
|
83
|
+
return node.Literals
|
|
84
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
package graph
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"path/filepath"
|
|
5
|
+
"slices"
|
|
6
|
+
"testing"
|
|
7
|
+
|
|
8
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestLiteralsRenderEveryValueInTypescriptSourceForm verifies that each kind of
|
|
12
|
+
// literal a union can hold reaches the wire as the way it is written, and that
|
|
13
|
+
// the rendering is the checker's own rather than this package's.
|
|
14
|
+
//
|
|
15
|
+
// Source form is what makes the list readable as the type: it is the only
|
|
16
|
+
// rendering that tells `1` from `"1"` and `true` from `"true"`, which bare
|
|
17
|
+
// contents cannot, and the old string-only scrape could not report a numeric
|
|
18
|
+
// union at all. The escaping fixture is why the checker's `ValueToString` is
|
|
19
|
+
// used instead of a local formatter — a quote inside a value has to come back
|
|
20
|
+
// escaped the way TypeScript escapes it, and Go's own quoting is not that.
|
|
21
|
+
//
|
|
22
|
+
// 1. Compile a fixture with a string, numeric, boolean, bigint, nullable, and
|
|
23
|
+
// escaped-string union.
|
|
24
|
+
// 2. Build the graph.
|
|
25
|
+
// 3. Assert each value set renders in TypeScript source form.
|
|
26
|
+
func TestLiteralsRenderEveryValueInTypescriptSourceForm(t *testing.T) {
|
|
27
|
+
root := t.TempDir()
|
|
28
|
+
writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
|
|
29
|
+
writeFile(t, filepath.Join(root, "src", "main.ts"), `export type Strings = 'a' | 'b';
|
|
30
|
+
export type Numbers = 1 | 2 | 3;
|
|
31
|
+
export type Bools = true | false;
|
|
32
|
+
export type Bigints = 1n | 2n;
|
|
33
|
+
export type Nullable = 'a' | null;
|
|
34
|
+
export type Optional = 'a' | undefined;
|
|
35
|
+
export type Escaped = 'he said "hi"' | "it's" | 'tab\there';
|
|
36
|
+
export type Mixed = 'a' | 1 | true;
|
|
37
|
+
`)
|
|
38
|
+
|
|
39
|
+
prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
|
|
40
|
+
if err != nil {
|
|
41
|
+
t.Fatal(err)
|
|
42
|
+
}
|
|
43
|
+
if len(diags) != 0 {
|
|
44
|
+
t.Fatalf("unexpected diagnostics: %v", diags)
|
|
45
|
+
}
|
|
46
|
+
defer func() { _ = prog.Close() }()
|
|
47
|
+
|
|
48
|
+
graph := Build(prog)
|
|
49
|
+
path := sourceFile(t, prog, "main.ts").FileName()
|
|
50
|
+
|
|
51
|
+
for _, testCase := range []struct {
|
|
52
|
+
name string
|
|
53
|
+
want []string
|
|
54
|
+
}{
|
|
55
|
+
// A string keeps its quotes, so the list reads as the type does.
|
|
56
|
+
{"Strings", []string{`"a"`, `"b"`}},
|
|
57
|
+
// Numbers were absent entirely under the string-only scrape.
|
|
58
|
+
{"Numbers", []string{"1", "2", "3"}},
|
|
59
|
+
{"Bools", []string{"false", "true"}},
|
|
60
|
+
{"Bigints", []string{"1n", "2n"}},
|
|
61
|
+
// A unit type that is not a literal still names one value a caller writes,
|
|
62
|
+
// so the set stays complete rather than dropping to nothing.
|
|
63
|
+
{"Nullable", []string{"null", `"a"`}},
|
|
64
|
+
{"Optional", []string{"undefined", `"a"`}},
|
|
65
|
+
// The checker's escaping, not Go's: an inner double quote is backslashed,
|
|
66
|
+
// an apostrophe is not re-quoted, and a tab stays an escape rather than a
|
|
67
|
+
// raw control character.
|
|
68
|
+
{"Escaped", []string{`"he said \"hi\""`, `"it's"`, `"tab\there"`}},
|
|
69
|
+
// Kinds mix in one union, and source form is what keeps them apart.
|
|
70
|
+
{"Mixed", []string{`"a"`, "1", "true"}},
|
|
71
|
+
} {
|
|
72
|
+
got := literalsOf(t, graph, nodeID(path, testCase.name, NodeTypeAlias))
|
|
73
|
+
if !slices.Equal(got, testCase.want) {
|
|
74
|
+
t.Fatalf("%s rendered %v, want %v", testCase.name, got, testCase.want)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package graph
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"path/filepath"
|
|
5
|
+
"slices"
|
|
6
|
+
"testing"
|
|
7
|
+
|
|
8
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestLiteralsResolveThroughAliasIndirection verifies that a union assembled out
|
|
12
|
+
// of other unions reports every member it admits, including the ones no token of
|
|
13
|
+
// its own declaration names.
|
|
14
|
+
//
|
|
15
|
+
// This is the half of #732 that was not truncation but a wrong answer of the
|
|
16
|
+
// right shape. Reading `type Wide = Narrow | 'd'` off the source text finds one
|
|
17
|
+
// quoted token and reports `'d'`, a complete-looking three-member type reduced
|
|
18
|
+
// to one, with nothing marking the loss. Only the checker has followed Narrow.
|
|
19
|
+
// The nested case (`Wider = Wide | 'e'`) pins that the resolution is not one hop
|
|
20
|
+
// deep, and the checker also flattens and dedups, so `Duplicated` must report
|
|
21
|
+
// `'a'` once rather than twice.
|
|
22
|
+
//
|
|
23
|
+
// 1. Compile a fixture whose aliases build on each other, one of them
|
|
24
|
+
// re-listing a member an aliased union already has.
|
|
25
|
+
// 2. Build the graph.
|
|
26
|
+
// 3. Assert each alias reports the full set it admits, deduplicated.
|
|
27
|
+
func TestLiteralsResolveThroughAliasIndirection(t *testing.T) {
|
|
28
|
+
root := t.TempDir()
|
|
29
|
+
writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
|
|
30
|
+
writeFile(t, filepath.Join(root, "src", "main.ts"), `export type Narrow = 'a' | 'b' | 'c';
|
|
31
|
+
export type Wide = Narrow | 'd';
|
|
32
|
+
export type Wider = Wide | 'e';
|
|
33
|
+
export type Duplicated = Narrow | 'a';
|
|
34
|
+
`)
|
|
35
|
+
|
|
36
|
+
prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
|
|
37
|
+
if err != nil {
|
|
38
|
+
t.Fatal(err)
|
|
39
|
+
}
|
|
40
|
+
if len(diags) != 0 {
|
|
41
|
+
t.Fatalf("unexpected diagnostics: %v", diags)
|
|
42
|
+
}
|
|
43
|
+
defer func() { _ = prog.Close() }()
|
|
44
|
+
|
|
45
|
+
graph := Build(prog)
|
|
46
|
+
path := sourceFile(t, prog, "main.ts").FileName()
|
|
47
|
+
|
|
48
|
+
// One hop: the three members reaching Wide through Narrow are Wide's too.
|
|
49
|
+
wide := literalsOf(t, graph, nodeID(path, "Wide", NodeTypeAlias))
|
|
50
|
+
if want := []string{`"a"`, `"b"`, `"c"`, `"d"`}; !slices.Equal(wide, want) {
|
|
51
|
+
t.Fatalf("alias indirection lost members: got %v, want %v", wide, want)
|
|
52
|
+
}
|
|
53
|
+
// Two hops: resolution is the type's, so depth costs nothing.
|
|
54
|
+
wider := literalsOf(t, graph, nodeID(path, "Wider", NodeTypeAlias))
|
|
55
|
+
if want := []string{`"a"`, `"b"`, `"c"`, `"d"`, `"e"`}; !slices.Equal(wider, want) {
|
|
56
|
+
t.Fatalf("nested alias indirection lost members: got %v, want %v", wider, want)
|
|
57
|
+
}
|
|
58
|
+
// A member named twice is one member; the checker has already deduped, so the
|
|
59
|
+
// list must not report `"a"` once per mention.
|
|
60
|
+
duplicated := literalsOf(t, graph, nodeID(path, "Duplicated", NodeTypeAlias))
|
|
61
|
+
if want := []string{`"a"`, `"b"`, `"c"`}; !slices.Equal(duplicated, want) {
|
|
62
|
+
t.Fatalf("a member named twice was reported twice: got %v, want %v", duplicated, want)
|
|
63
|
+
}
|
|
64
|
+
}
|