ttsc 0.19.2 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +7 -5
  2. package/cmd/graphdump/main.go +2 -4
  3. package/cmd/ttsc/filter.go +12 -4
  4. package/cmd/ttsc/filter_resolves_flag_names_case_insensitively_test.go +53 -0
  5. package/cmd/ttsc/flags_gen.go +3 -3
  6. package/cmd/ttscgraph/dump.go +5 -7
  7. package/cmd/ttscgraph/run_dump_emits_only_checker_verified_member_relations_test.go +118 -0
  8. package/cmd/ttscgraph/serve.go +58 -308
  9. package/cmd/ttscgraph/serve_session_keeps_base_url_fallback_unchanged_test.go +49 -0
  10. package/cmd/ttscgraph/serve_session_keeps_esm_directory_candidate_unchanged_test.go +61 -0
  11. package/cmd/ttscgraph/serve_session_keeps_exports_blocked_file_candidates_unchanged_test.go +64 -0
  12. package/cmd/ttscgraph/serve_session_keeps_inactive_conditional_export_unchanged_test.go +66 -0
  13. package/cmd/ttscgraph/serve_session_keeps_lower_priority_module_candidate_unchanged_test.go +67 -0
  14. package/cmd/ttscgraph/serve_session_keeps_lower_priority_paths_pattern_unchanged_test.go +58 -0
  15. package/cmd/ttscgraph/serve_session_keeps_nonlongest_root_dirs_candidate_unchanged_test.go +54 -0
  16. package/cmd/ttscgraph/serve_session_reloads_commonjs_package_entry_for_esm_test.go +50 -0
  17. package/cmd/ttscgraph/serve_session_reloads_module_suffix_candidate_test.go +41 -0
  18. package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +216 -0
  19. package/cmd/ttscgraph/serve_session_retries_failed_dump_test.go +103 -0
  20. package/driver/graph.go +12 -6
  21. package/driver/lsp.go +30 -0
  22. package/driver/program.go +37 -1
  23. package/driver/resolution_candidates.go +819 -0
  24. package/driver/windowsjunction/windowsjunction.go +40 -0
  25. package/go.mod +2 -0
  26. package/internal/graph/build.go +45 -11
  27. package/internal/graph/dump.go +312 -108
  28. package/internal/graph/dump_checkout_paths_are_stable_test.go +210 -0
  29. package/internal/graph/dump_diagnostics_ride_the_generation_that_built_it_test.go +9 -3
  30. package/internal/graph/dump_paths.go +172 -0
  31. package/internal/graph/dump_paths_are_portable_and_injective_test.go +93 -0
  32. package/internal/graph/dump_schema_version_matches_the_typescript_client_test.go +1 -1
  33. package/internal/graph/dump_uses_ecma_line_terminators_test.go +87 -0
  34. package/internal/graph/duplicate_variable_object_members_keep_first_declaration_test.go +61 -0
  35. package/internal/graph/edges.go +220 -5
  36. package/internal/graph/enum_members_carry_their_names_on_the_enum_node_test.go +131 -0
  37. package/internal/graph/graph.go +177 -5
  38. package/internal/graph/line_model.go +122 -0
  39. package/internal/graph/literals.go +175 -0
  40. package/internal/graph/literals_are_absent_when_the_type_admits_more_than_it_can_name_test.go +74 -0
  41. package/internal/graph/literals_cover_enum_members_the_graph_has_no_nodes_for_test.go +79 -0
  42. package/internal/graph/literals_do_not_depend_on_declaration_line_wrapping_test.go +84 -0
  43. package/internal/graph/literals_render_every_value_in_typescript_source_form_test.go +77 -0
  44. package/internal/graph/literals_resolve_through_alias_indirection_test.go +64 -0
  45. package/internal/graph/member_relation_edges_follow_checker_validity_test.go +85 -0
  46. package/internal/graph/member_relation_edges_preserve_checker_boundaries_test.go +191 -0
  47. package/internal/graph/member_relation_edges_reject_checker_invalid_pairs_test.go +119 -0
  48. package/internal/graph/node_ids_preserve_hash_bearing_components_test.go +41 -0
  49. package/internal/graph/node_modifiers_emit_union_strings_test.go +4 -1
  50. package/internal/graph/object_literal_members_ride_on_the_snapshot_test.go +192 -0
  51. package/internal/graph/object_member_signatures_preserve_literal_whitespace_test.go +67 -0
  52. package/internal/graph/object_members.go +229 -0
  53. package/internal/graph/provenance.go +88 -30
  54. package/internal/graph/source_texts_cover_every_resident_program_source_test.go +73 -0
  55. package/internal/graphsymbols/position_helpers_follow_ecma_line_terminators_test.go +66 -0
  56. package/internal/graphsymbols/provider.go +26 -91
  57. package/internal/lspserver/lsp_clean_didopen_refreshes_compiler_state_test.go +144 -0
  58. package/internal/lspserver/lsp_completion.go +109 -0
  59. package/internal/lspserver/lsp_completion_gate_never_refuses_a_matching_line_test.go +50 -0
  60. package/internal/lspserver/lsp_completion_hints_longest_trigger_wins_test.go +149 -0
  61. package/internal/lspserver/lsp_completion_hints_nearest_trigger_wins_test.go +37 -0
  62. package/internal/lspserver/lsp_completion_merges_into_upstream_response_test.go +149 -0
  63. package/internal/lspserver/lsp_completion_nearest_hint_replaces_nearest_filter_test.go +43 -0
  64. package/internal/lspserver/lsp_completion_preserves_completion_list_extensions_test.go +99 -0
  65. package/internal/lspserver/lsp_completion_refuses_hints_inside_a_string_literal_test.go +69 -0
  66. package/internal/lspserver/lsp_completion_replaces_full_filter_in_rank_order_test.go +85 -0
  67. package/internal/lspserver/lsp_completion_request.go +455 -0
  68. package/internal/lspserver/lsp_completion_scope.go +530 -0
  69. package/internal/lspserver/lsp_completion_scope_bench_test.go +41 -0
  70. package/internal/lspserver/lsp_completion_scope_classifies_regex_statement_and_operator_boundaries_test.go +39 -0
  71. package/internal/lspserver/lsp_completion_scope_ignores_jsdoc_bytes_in_literals_test.go +108 -0
  72. package/internal/lspserver/lsp_completion_scope_refuses_regex_hints_test.go +52 -0
  73. package/internal/lspserver/lsp_completion_trigger_uses_last_rune_test.go +24 -0
  74. package/internal/lspserver/lsp_deleted_watched_file_withdraws_plugin_diagnostics_test.go +110 -0
  75. package/internal/lspserver/lsp_didsave_keeps_the_incremental_changed_uri_path_test.go +78 -0
  76. package/internal/lspserver/lsp_hints_absence_is_not_an_error_test.go +46 -0
  77. package/internal/lspserver/lsp_hints_refresh_coalesces_concurrent_requests_test.go +82 -0
  78. package/internal/lspserver/lsp_hints_refresh_ignores_a_superseded_generation_test.go +40 -0
  79. package/internal/lspserver/lsp_hints_refresh_keeps_each_producer_isolated_test.go +64 -0
  80. package/internal/lspserver/lsp_hints_refresh_replaces_the_session_snapshot_test.go +66 -0
  81. package/internal/lspserver/lsp_hints_refresh_scheduled_by_editor_events_test.go +90 -0
  82. package/internal/lspserver/lsp_hints_refresh_serves_completion_while_running_test.go +91 -0
  83. package/internal/lspserver/lsp_index_byte_from_bounds_its_search_start_test.go +38 -0
  84. package/internal/lspserver/lsp_initialize_constrains_position_encoding_to_utf16_test.go +205 -0
  85. package/internal/lspserver/lsp_late_completion_trigger_is_reported_once_test.go +105 -0
  86. package/internal/lspserver/lsp_native_completion_hints_decode_flat_lint_wire_test.go +66 -0
  87. package/internal/lspserver/lsp_native_completion_hints_grouped_compatibility_boundary_test.go +60 -0
  88. package/internal/lspserver/lsp_native_plugin_source.go +315 -2
  89. package/internal/lspserver/lsp_native_resident.go +304 -0
  90. package/internal/lspserver/lsp_plugin.go +85 -8
  91. package/internal/lspserver/lsp_plugin_completion_resolve_stays_local_test.go +82 -0
  92. package/internal/lspserver/lsp_position_conversions_span_astral_and_combining_runes_test.go +119 -0
  93. package/internal/lspserver/lsp_proxy.go +601 -9
  94. package/internal/lspserver/lsp_refresh.go +70 -0
  95. package/internal/lspserver/lsp_resident_falls_back_when_serve_unsupported_test.go +43 -0
  96. package/internal/lspserver/lsp_resident_maps_verb_args_test.go +34 -0
  97. package/internal/lspserver/lsp_server.go +13 -1
  98. package/internal/lspserver/lsp_watched_file_changes_reach_compiler_caches_test.go +172 -0
  99. package/lib/TtscCompiler.d.ts +3 -1
  100. package/lib/TtscCompiler.js +5 -1
  101. package/lib/TtscCompiler.js.map +1 -1
  102. package/lib/TtscService.d.ts +13 -3
  103. package/lib/TtscService.js +6 -6
  104. package/lib/TtscService.js.map +1 -1
  105. package/lib/compiler/internal/project/readConfigJson.d.ts +30 -0
  106. package/lib/compiler/internal/project/readConfigJson.js +208 -0
  107. package/lib/compiler/internal/project/readConfigJson.js.map +1 -0
  108. package/lib/compiler/internal/project/readProjectConfig.js +53 -149
  109. package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
  110. package/lib/compiler/internal/residentTransformProcess.d.ts +15 -2
  111. package/lib/compiler/internal/residentTransformProcess.js +159 -39
  112. package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
  113. package/lib/compiler/internal/runBuild.d.ts +6 -0
  114. package/lib/compiler/internal/runBuild.js +143 -44
  115. package/lib/compiler/internal/runBuild.js.map +1 -1
  116. package/lib/compiler/internal/startResidentTransform.d.ts +5 -1
  117. package/lib/compiler/internal/startResidentTransform.js +3 -1
  118. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  119. package/lib/compiler/internal/transformProjectInMemory.js +12 -2
  120. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  121. package/lib/flags/parser.d.ts +19 -0
  122. package/lib/flags/parser.js +78 -36
  123. package/lib/flags/parser.js.map +1 -1
  124. package/lib/flags/schema.d.ts +49 -11
  125. package/lib/flags/schema.js +94 -22
  126. package/lib/flags/schema.js.map +1 -1
  127. package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +5 -0
  128. package/lib/internal/assertSafeExplicitCacheDirectory.js +26 -0
  129. package/lib/internal/assertSafeExplicitCacheDirectory.js.map +1 -0
  130. package/lib/launcher/internal/runTtsc.js +107 -69
  131. package/lib/launcher/internal/runTtsc.js.map +1 -1
  132. package/lib/launcher/internal/runTtsx.js +28 -41
  133. package/lib/launcher/internal/runTtsx.js.map +1 -1
  134. package/lib/launcher/internal/runtimeHooks.d.ts +15 -0
  135. package/lib/launcher/internal/runtimeHooks.js +18 -1
  136. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  137. package/lib/launcher/internal/watchTopology.d.ts +43 -0
  138. package/lib/launcher/internal/watchTopology.js +360 -0
  139. package/lib/launcher/internal/watchTopology.js.map +1 -0
  140. package/lib/plugin/internal/buildSourcePlugin.d.ts +14 -3
  141. package/lib/plugin/internal/buildSourcePlugin.js +247 -61
  142. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  143. package/lib/plugin/internal/loadProjectPlugins.js +63 -2
  144. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  145. package/lib/structures/ITtscCompilerTransformation.d.ts +16 -0
  146. package/lib/structures/internal/ITtscParsedProjectConfig.d.ts +2 -0
  147. package/package.json +8 -8
  148. package/shim/ast/shim.go +48 -3
  149. package/shim/astnav/go.mod +14 -0
  150. package/shim/astnav/go.sum +20 -0
  151. package/shim/astnav/shim.go +15 -0
  152. package/shim/checker/shim.go +113 -0
  153. package/shim/core/shim.go +47 -0
  154. package/shim/diagnosticwriter/lint.go +50 -0
  155. package/src/TtscCompiler.ts +5 -1
  156. package/src/TtscService.ts +37 -10
  157. package/src/compiler/internal/project/readConfigJson.ts +209 -0
  158. package/src/compiler/internal/project/readProjectConfig.ts +62 -158
  159. package/src/compiler/internal/residentTransformProcess.ts +197 -39
  160. package/src/compiler/internal/runBuild.ts +169 -47
  161. package/src/compiler/internal/startResidentTransform.ts +12 -1
  162. package/src/compiler/internal/transformProjectInMemory.ts +12 -1
  163. package/src/flags/parser.ts +113 -42
  164. package/src/flags/schema.ts +113 -31
  165. package/src/internal/assertSafeExplicitCacheDirectory.ts +29 -0
  166. package/src/launcher/internal/runTtsc.ts +117 -68
  167. package/src/launcher/internal/runTtsx.ts +29 -44
  168. package/src/launcher/internal/runtimeHooks.ts +30 -2
  169. package/src/launcher/internal/watchTopology.ts +466 -0
  170. package/src/plugin/internal/buildSourcePlugin.ts +326 -64
  171. package/src/plugin/internal/loadProjectPlugins.ts +80 -2
  172. package/src/structures/ITtscCompilerTransformation.ts +17 -0
  173. package/src/structures/internal/ITtscParsedProjectConfig.ts +2 -0
  174. package/tools/shim_audit/baseline.json +0 -3
  175. package/tools/shim_audit/main.go +42 -14
  176. package/tools/shim_audit/shim_dir_coverage_test.go +25 -0
  177. package/utility/flags_gen.go +3 -3
  178. package/utility/host.go +9 -3
@@ -0,0 +1,67 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestObjectMemberSignaturesPreserveLiteralWhitespace verifies that compact
11
+ // display text never rewrites whitespace owned by a lexical value.
12
+ //
13
+ // strings.Fields treated quoted and template contents as trivia. It changed
14
+ // string and regular-expression values, and its first-line cut left a multiline
15
+ // template without a closing backtick. The outline may compact surrounding
16
+ // syntax only when the literal itself remains byte-identical and complete.
17
+ //
18
+ // 1. Compile object members with spaced strings, a regexp, and a template.
19
+ // 2. Dump the graph from the Program-owned source snapshot.
20
+ // 3. Assert literal whitespace and the complete template delimiter survive.
21
+ func TestObjectMemberSignaturesPreserveLiteralWhitespace(t *testing.T) {
22
+ root := t.TempDir()
23
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
24
+ source := `export const shape = {
25
+ double: "a b",
26
+ single: 'c d',
27
+ regexp: /e f/,
28
+ template: ` + "`left\n right`" + `,
29
+ };
30
+ `
31
+ writeFile(t, filepath.Join(root, "src", "main.ts"), source)
32
+
33
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
34
+ if err != nil {
35
+ t.Fatal(err)
36
+ }
37
+ if len(diags) != 0 {
38
+ t.Fatalf("unexpected diagnostics: %v", diags)
39
+ }
40
+ defer func() { _ = prog.Close() }()
41
+
42
+ graph := Build(prog)
43
+ dump, err := NewDump(graph, root, "tsconfig.json", nil, SourceTexts(prog), DumpOrigin{})
44
+ if err != nil {
45
+ t.Fatal(err)
46
+ }
47
+ signatures := map[string]string{}
48
+ for _, node := range dump.Nodes {
49
+ if node.ID != "src/main.ts#shape:variable" {
50
+ continue
51
+ }
52
+ for _, member := range node.ObjectMembers {
53
+ signatures[member.Name] = member.Signature
54
+ }
55
+ }
56
+ expected := map[string]string{
57
+ "double": `double: "a b"`,
58
+ "single": `single: 'c d'`,
59
+ "regexp": `regexp: /e f/`,
60
+ "template": "template: `left\n right`",
61
+ }
62
+ for name, want := range expected {
63
+ if got := signatures[name]; got != want {
64
+ t.Fatalf("signature for %q = %q, want %q", name, got, want)
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,229 @@
1
+ package graph
2
+
3
+ import shimast "github.com/microsoft/typescript-go/shim/ast"
4
+
5
+ // collectObjectMembers records the direct, statically named members of the
6
+ // object literal bound by declaration on the variable node Build already owns.
7
+ // The AST is the structure oracle: no source text, brace counter, or regex is
8
+ // involved in deciding which properties belong to the outer object.
9
+ func (g *Graph) collectObjectMembers(path string, declaration *shimast.Node) {
10
+ if declaration == nil || declaration.Symbol() == nil {
11
+ return
12
+ }
13
+ variable := declaration.AsVariableDeclaration()
14
+ if variable == nil {
15
+ return
16
+ }
17
+ object := objectLiteralInitializer(variable.Initializer)
18
+ if object == nil || object.Properties == nil {
19
+ return
20
+ }
21
+ node := g.Nodes[nodeID(path, qualifiedName(declaration.Symbol()), NodeVariable)]
22
+ if node == nil || node.Pos != declaration.Pos() || node.End != declaration.End() {
23
+ return
24
+ }
25
+ members := make([]ObjectMember, 0, len(object.Properties.Nodes))
26
+ for _, property := range object.Properties.Nodes {
27
+ name, kind, ok := objectMemberIdentity(property)
28
+ if !ok {
29
+ continue
30
+ }
31
+ signatureEnd, signatureBoundary, signatureTokenLen := objectMemberSignatureSpan(property)
32
+ members = append(members, ObjectMember{
33
+ Name: name,
34
+ Kind: kind,
35
+ Pos: property.Pos(),
36
+ End: property.End(),
37
+ SignatureEnd: signatureEnd,
38
+ SignatureBoundary: signatureBoundary,
39
+ SignatureTokenLen: signatureTokenLen,
40
+ })
41
+ }
42
+ if len(members) > 0 {
43
+ node.ObjectMembers = members
44
+ }
45
+ }
46
+
47
+ // objectMemberSignatureSpan finds the earliest AST boundary after which source
48
+ // belongs to a value body rather than the member outline. It keeps compact
49
+ // scalar initializers such as `real: 1`, but stops before function bodies,
50
+ // arrow expressions, nested object members, and array contents can be copied
51
+ // onto the wire.
52
+ func objectMemberSignatureSpan(property *shimast.Node) (end int, boundary bool, tokenLen int) {
53
+ end = property.End()
54
+ candidate, found := sourceBodyBoundary(property)
55
+ if !found || candidate.end <= property.Pos() || candidate.end > end {
56
+ return end, false, 0
57
+ }
58
+ return candidate.end, true, candidate.tokenLen
59
+ }
60
+
61
+ type signatureBoundary struct {
62
+ end int
63
+ tokenLen int
64
+ }
65
+
66
+ func sourceBodyBoundary(node *shimast.Node) (signatureBoundary, bool) {
67
+ if node == nil {
68
+ return signatureBoundary{}, false
69
+ }
70
+ switch node.Kind {
71
+ case shimast.KindArrowFunction:
72
+ arrow := node.AsArrowFunction()
73
+ if arrow != nil && arrow.EqualsGreaterThanToken != nil {
74
+ return signatureBoundary{end: arrow.EqualsGreaterThanToken.End()}, true
75
+ }
76
+ case shimast.KindBlock, shimast.KindObjectLiteralExpression, shimast.KindArrayLiteralExpression:
77
+ return signatureBoundary{end: node.Pos(), tokenLen: 1}, true
78
+ case shimast.KindClassExpression:
79
+ return signatureBoundary{end: node.Pos(), tokenLen: len("class")}, true
80
+ }
81
+
82
+ var boundary signatureBoundary
83
+ found := false
84
+ node.ForEachChild(func(child *shimast.Node) bool {
85
+ candidate, ok := sourceBodyBoundary(child)
86
+ if ok {
87
+ boundary = candidate
88
+ found = true
89
+ // ForEachChild visits source order, so the first body boundary is the
90
+ // earliest and later children cannot improve the outline cut.
91
+ return true
92
+ }
93
+ return false
94
+ })
95
+ return boundary, found
96
+ }
97
+
98
+ // objectLiteralInitializer unwraps the transparent expression wrappers a
99
+ // declaration commonly places around an object literal. None changes which
100
+ // direct properties the literal declares.
101
+ func objectLiteralInitializer(initializer *shimast.Node) *shimast.ObjectLiteralExpression {
102
+ for initializer != nil {
103
+ switch initializer.Kind {
104
+ case shimast.KindObjectLiteralExpression:
105
+ return initializer.AsObjectLiteralExpression()
106
+ case shimast.KindAsExpression:
107
+ expression := initializer.AsAsExpression()
108
+ if expression == nil {
109
+ return nil
110
+ }
111
+ initializer = expression.Expression
112
+ case shimast.KindSatisfiesExpression:
113
+ expression := initializer.AsSatisfiesExpression()
114
+ if expression == nil {
115
+ return nil
116
+ }
117
+ initializer = expression.Expression
118
+ case shimast.KindParenthesizedExpression:
119
+ expression := initializer.AsParenthesizedExpression()
120
+ if expression == nil {
121
+ return nil
122
+ }
123
+ initializer = expression.Expression
124
+ case shimast.KindTypeAssertionExpression:
125
+ expression := initializer.AsTypeAssertion()
126
+ if expression == nil {
127
+ return nil
128
+ }
129
+ initializer = expression.Expression
130
+ case shimast.KindNonNullExpression:
131
+ expression := initializer.AsNonNullExpression()
132
+ if expression == nil {
133
+ return nil
134
+ }
135
+ initializer = expression.Expression
136
+ default:
137
+ return nil
138
+ }
139
+ }
140
+ return nil
141
+ }
142
+
143
+ // objectMemberIdentity returns the source-visible name and details kind of one
144
+ // direct object-literal property. The boolean distinguishes an unsupported
145
+ // spread or dynamic computed key from the valid static property name `""`.
146
+ func objectMemberIdentity(property *shimast.Node) (string, NodeKind, bool) {
147
+ if property == nil {
148
+ return "", "", false
149
+ }
150
+ kind := NodeVariable
151
+ switch property.Kind {
152
+ case shimast.KindPropertyAssignment, shimast.KindShorthandPropertyAssignment:
153
+ case shimast.KindMethodDeclaration, shimast.KindGetAccessor, shimast.KindSetAccessor:
154
+ kind = NodeMethod
155
+ default:
156
+ return "", "", false
157
+ }
158
+ name, ok := staticObjectMemberName(property.Name())
159
+ return name, kind, ok
160
+ }
161
+
162
+ // staticObjectMemberName resolves the declaration forms whose property name is
163
+ // fixed by syntax. A computed literal is fixed; a computed expression is not.
164
+ func staticObjectMemberName(name *shimast.Node) (string, bool) {
165
+ if name == nil {
166
+ return "", false
167
+ }
168
+ switch name.Kind {
169
+ case shimast.KindIdentifier:
170
+ if identifier := name.AsIdentifier(); identifier != nil {
171
+ return identifier.Text, true
172
+ }
173
+ case shimast.KindStringLiteral:
174
+ if literal := name.AsStringLiteral(); literal != nil {
175
+ return literal.Text, true
176
+ }
177
+ case shimast.KindNoSubstitutionTemplateLiteral:
178
+ if literal := name.AsNoSubstitutionTemplateLiteral(); literal != nil {
179
+ return literal.Text, true
180
+ }
181
+ case shimast.KindNumericLiteral:
182
+ if literal := name.AsNumericLiteral(); literal != nil {
183
+ return literal.Text, true
184
+ }
185
+ case shimast.KindBigIntLiteral:
186
+ if literal := name.AsBigIntLiteral(); literal != nil {
187
+ return literal.Text, true
188
+ }
189
+ case shimast.KindComputedPropertyName:
190
+ computed := name.AsComputedPropertyName()
191
+ if computed == nil {
192
+ return "", false
193
+ }
194
+ return staticComputedObjectMemberName(computed.Expression)
195
+ }
196
+ return "", false
197
+ }
198
+
199
+ func staticComputedObjectMemberName(expression *shimast.Node) (string, bool) {
200
+ for expression != nil && expression.Kind == shimast.KindParenthesizedExpression {
201
+ parenthesized := expression.AsParenthesizedExpression()
202
+ if parenthesized == nil {
203
+ return "", false
204
+ }
205
+ expression = parenthesized.Expression
206
+ }
207
+ if expression == nil {
208
+ return "", false
209
+ }
210
+ switch expression.Kind {
211
+ case shimast.KindStringLiteral:
212
+ if literal := expression.AsStringLiteral(); literal != nil {
213
+ return literal.Text, true
214
+ }
215
+ case shimast.KindNoSubstitutionTemplateLiteral:
216
+ if literal := expression.AsNoSubstitutionTemplateLiteral(); literal != nil {
217
+ return literal.Text, true
218
+ }
219
+ case shimast.KindNumericLiteral:
220
+ if literal := expression.AsNumericLiteral(); literal != nil {
221
+ return literal.Text, true
222
+ }
223
+ case shimast.KindBigIntLiteral:
224
+ if literal := expression.AsBigIntLiteral(); literal != nil {
225
+ return literal.Text, true
226
+ }
227
+ }
228
+ return "", false
229
+ }
@@ -14,7 +14,7 @@ import (
14
14
  // field is added, removed, or given a new meaning, independently of the serve
15
15
  // envelope's protocol version: a one-shot `ttscgraph dump` written to a file has
16
16
  // a schema but never rode the protocol.
17
- const DumpSchemaVersion = 1
17
+ const DumpSchemaVersion = 6
18
18
 
19
19
  // The capabilities a snapshot can declare. Each names one class of evidence a
20
20
  // consumer may rely on when, and only when, the snapshot lists it.
@@ -119,16 +119,16 @@ type Universe struct {
119
119
  // references mean two configs can name the same file, and they are not the same
120
120
  // input, so the pair is the unit rather than the bare path.
121
121
  type RootFile struct {
122
- // Config is the tsconfig that named this root, project-relative.
122
+ // Config is the tsconfig that named this root, in the dump's path vocabulary.
123
123
  Config string `json:"config"`
124
124
 
125
- // File is the root file, project-relative.
125
+ // File is the root file, in the dump's path vocabulary.
126
126
  File string `json:"file"`
127
127
  }
128
128
 
129
129
  // FileDigest pairs a file with the SHA-256 of its bytes, hex-encoded.
130
130
  type FileDigest struct {
131
- // File is project-relative.
131
+ // File uses the dump's path vocabulary.
132
132
  File string `json:"file"`
133
133
 
134
134
  // Digest is the hex-encoded SHA-256 of the file's on-disk bytes.
@@ -159,7 +159,7 @@ type FileDigest struct {
159
159
  // Both are digests. Neither is text: the wire never carries a file's bytes, and
160
160
  // the field names say digest so that stays true by reading.
161
161
  type SourceDigest struct {
162
- // File is project-relative.
162
+ // File uses the dump's path vocabulary.
163
163
  File string `json:"file"`
164
164
 
165
165
  // Checker is the hex-encoded SHA-256 of the text the checker resolved
@@ -180,7 +180,7 @@ type SourceDigest struct {
180
180
  // Diagnostic is one compiler diagnostic riding the snapshot that produced the
181
181
  // facts. Positions are 1-based, matching what tsgo reports.
182
182
  type Diagnostic struct {
183
- // File is project-relative.
183
+ // File uses the dump's path vocabulary.
184
184
  File string `json:"file"`
185
185
 
186
186
  // Line is the 1-based line.
@@ -207,13 +207,14 @@ func Digest(sum [sha256.Size]byte) string { return hex.EncodeToString(sum[:]) }
207
207
  // implements.
208
208
  func TypescriptVersion() string { return shimcore.Version() }
209
209
 
210
- // NewProvenance assembles the evidence for a snapshot. project relativizes every
211
- // path; texts maps a source file's absolute path to the text the checker read
212
- // (as SourceTexts returns it); disk maps a source file's absolute path to the
213
- // hex digest of its on-disk bytes, and a path absent from it is reported with an
214
- // empty Disk. configs and roots come from the same capture that produced texts.
210
+ // NewProvenance assembles the evidence for a snapshot while retaining the
211
+ // compiler's physical paths. NewDump projects those paths together with every
212
+ // node, edge, span, and diagnostic through its one cached schema-v6 mapper.
213
+ // texts maps a source file's path to the text the checker read (as SourceTexts
214
+ // returns it); disk maps that path to the hex digest of its on-disk bytes, and
215
+ // a path absent from it is reported with an empty Disk. configs and roots come
216
+ // from the same capture that produced texts.
215
217
  func NewProvenance(
216
- project string,
217
218
  producer Producer,
218
219
  capabilities []string,
219
220
  configs []FileDigest,
@@ -221,32 +222,31 @@ func NewProvenance(
221
222
  texts map[string]string,
222
223
  disk map[string]string,
223
224
  ) Provenance {
224
- ctx := newDumpContext(project, nil)
225
225
  sources := make([]SourceDigest, 0, len(texts))
226
226
  for path, text := range texts {
227
227
  sources = append(sources, SourceDigest{
228
- File: ctx.rel(path),
228
+ File: path,
229
229
  Checker: Digest(sha256.Sum256([]byte(text))),
230
230
  Disk: disk[path],
231
231
  })
232
232
  }
233
233
  sort.Slice(sources, func(i, j int) bool { return sources[i].File < sources[j].File })
234
234
 
235
- relConfigs := make([]FileDigest, 0, len(configs))
235
+ capturedConfigs := make([]FileDigest, 0, len(configs))
236
236
  for _, config := range configs {
237
- relConfigs = append(relConfigs, FileDigest{File: ctx.rel(config.File), Digest: config.Digest})
237
+ capturedConfigs = append(capturedConfigs, FileDigest{File: config.File, Digest: config.Digest})
238
238
  }
239
- sort.Slice(relConfigs, func(i, j int) bool { return relConfigs[i].File < relConfigs[j].File })
239
+ sort.Slice(capturedConfigs, func(i, j int) bool { return capturedConfigs[i].File < capturedConfigs[j].File })
240
240
 
241
- relRoots := make([]RootFile, 0, len(roots))
241
+ capturedRoots := make([]RootFile, 0, len(roots))
242
242
  for _, root := range roots {
243
- relRoots = append(relRoots, RootFile{Config: ctx.rel(root.Config), File: ctx.rel(root.File)})
243
+ capturedRoots = append(capturedRoots, RootFile{Config: root.Config, File: root.File})
244
244
  }
245
- sort.Slice(relRoots, func(i, j int) bool {
246
- if relRoots[i].Config != relRoots[j].Config {
247
- return relRoots[i].Config < relRoots[j].Config
245
+ sort.Slice(capturedRoots, func(i, j int) bool {
246
+ if capturedRoots[i].Config != capturedRoots[j].Config {
247
+ return capturedRoots[i].Config < capturedRoots[j].Config
248
248
  }
249
- return relRoots[i].File < relRoots[j].File
249
+ return capturedRoots[i].File < capturedRoots[j].File
250
250
  })
251
251
 
252
252
  // Copy before sorting: the caller's slice is a shared package-level constant,
@@ -259,26 +259,25 @@ func NewProvenance(
259
259
  SchemaVersion: DumpSchemaVersion,
260
260
  Capabilities: declared,
261
261
  Producer: producer,
262
- Universe: Universe{Configs: relConfigs, Roots: relRoots},
262
+ Universe: Universe{Configs: capturedConfigs, Roots: capturedRoots},
263
263
  Sources: sources,
264
264
  }
265
265
  }
266
266
 
267
- // NewDiagnostics projects the resident program's compiler diagnostics onto the
268
- // wire shape, relativized against project and ordered by file, line, column,
269
- // then code so a snapshot is byte-stable.
267
+ // NewDiagnostics projects the resident program's compiler diagnostics while
268
+ // retaining physical file identities. NewDump maps and re-sorts them with the
269
+ // rest of the snapshot so every path uses one vocabulary.
270
270
  //
271
271
  // This is one Program.Diagnostics() call over the already-warm checker rather
272
272
  // than a second compile, but it is not free: it forces the semantic check of
273
273
  // every file the graph would otherwise only have bound. Callers that do not
274
274
  // publish diagnostics should not call it.
275
- func NewDiagnostics(prog *driver.Program, project string) []Diagnostic {
276
- ctx := newDumpContext(project, nil)
275
+ func NewDiagnostics(prog *driver.Program) []Diagnostic {
277
276
  raw := prog.Diagnostics()
278
277
  out := make([]Diagnostic, 0, len(raw))
279
278
  for _, diagnostic := range raw {
280
279
  out = append(out, Diagnostic{
281
- File: ctx.rel(diagnostic.File),
280
+ File: diagnostic.File,
282
281
  Line: diagnostic.Line,
283
282
  Column: diagnostic.Column,
284
283
  Code: int(diagnostic.Code),
@@ -301,6 +300,65 @@ func NewDiagnostics(prog *driver.Program, project string) []Diagnostic {
301
300
  return out
302
301
  }
303
302
 
303
+ // provenance maps every path-bearing provenance field through the dump's one
304
+ // mapper and sorts on the resulting wire identity.
305
+ func (c *dumpContext) provenance(value Provenance) Provenance {
306
+ sources := make([]SourceDigest, 0, len(value.Sources))
307
+ for _, source := range value.Sources {
308
+ sources = append(sources, SourceDigest{
309
+ File: c.rel(source.File),
310
+ Checker: source.Checker,
311
+ Disk: source.Disk,
312
+ })
313
+ }
314
+ sort.Slice(sources, func(i, j int) bool { return sources[i].File < sources[j].File })
315
+
316
+ configs := make([]FileDigest, 0, len(value.Universe.Configs))
317
+ for _, config := range value.Universe.Configs {
318
+ configs = append(configs, FileDigest{File: c.rel(config.File), Digest: config.Digest})
319
+ }
320
+ sort.Slice(configs, func(i, j int) bool { return configs[i].File < configs[j].File })
321
+
322
+ roots := make([]RootFile, 0, len(value.Universe.Roots))
323
+ for _, root := range value.Universe.Roots {
324
+ roots = append(roots, RootFile{Config: c.rel(root.Config), File: c.rel(root.File)})
325
+ }
326
+ sort.Slice(roots, func(i, j int) bool {
327
+ if roots[i].Config != roots[j].Config {
328
+ return roots[i].Config < roots[j].Config
329
+ }
330
+ return roots[i].File < roots[j].File
331
+ })
332
+
333
+ value.Capabilities = append([]string{}, value.Capabilities...)
334
+ value.Sources = sources
335
+ value.Universe = Universe{Configs: configs, Roots: roots}
336
+ return value
337
+ }
338
+
339
+ // diagnostics maps and orders compiler findings after they join the dump, not
340
+ // in their constructor, so they cannot drift onto a second path vocabulary.
341
+ func (c *dumpContext) diagnostics(values []Diagnostic) []Diagnostic {
342
+ out := make([]Diagnostic, 0, len(values))
343
+ for _, diagnostic := range values {
344
+ diagnostic.File = c.rel(diagnostic.File)
345
+ out = append(out, diagnostic)
346
+ }
347
+ sort.Slice(out, func(i, j int) bool {
348
+ if out[i].File != out[j].File {
349
+ return out[i].File < out[j].File
350
+ }
351
+ if out[i].Line != out[j].Line {
352
+ return out[i].Line < out[j].Line
353
+ }
354
+ if out[i].Column != out[j].Column {
355
+ return out[i].Column < out[j].Column
356
+ }
357
+ return out[i].Code < out[j].Code
358
+ })
359
+ return out
360
+ }
361
+
304
362
  // diagnosticCategory names a severity the way tsc labels it. The driver
305
363
  // distinguishes exactly two, and SeverityError is the zero value, so anything
306
364
  // that is not an explicit warning is an error.
@@ -0,0 +1,73 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "strings"
6
+ "testing"
7
+
8
+ shimtspath "github.com/microsoft/typescript-go/shim/tspath"
9
+
10
+ "github.com/samchon/ttsc/packages/ttsc/driver"
11
+ )
12
+
13
+ // TestSourceTextsCoverEveryResidentProgramSource verifies that snapshot
14
+ // evidence includes every class of source the checker loaded.
15
+ //
16
+ // External graph leaves still carry checker-derived facts and spans. Omitting
17
+ // their resident text from the source manifest leaves those facts without the
18
+ // byte identity that the sourceDigests capability promises. Virtual bundled
19
+ // libraries need the same checker digest even though they have no disk digest.
20
+ //
21
+ // 1. Compile a project that calls a function declared by an outside `.d.ts`.
22
+ // 2. Build the graph and capture the resident program's source texts.
23
+ // 3. Assert both the outside declaration and a virtual bundled library are in
24
+ // the checker-text manifest.
25
+ func TestSourceTextsCoverEveryResidentProgramSource(t *testing.T) {
26
+ workspace := t.TempDir()
27
+ root := filepath.Join(workspace, "app")
28
+ declaration := filepath.Join(workspace, "dependency", "index.d.ts")
29
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
30
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `import { external } from "../../dependency";
31
+
32
+ export const value = external();
33
+ `)
34
+ writeFile(t, declaration, `export declare function external(): number;
35
+ `)
36
+ declaration = shimtspath.ResolvePath(declaration)
37
+
38
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
39
+ if err != nil {
40
+ t.Fatal(err)
41
+ }
42
+ if len(diags) != 0 {
43
+ t.Fatalf("unexpected diagnostics: %v", diags)
44
+ }
45
+ defer func() { _ = prog.Close() }()
46
+
47
+ built := Build(prog)
48
+ hasExternalFact := false
49
+ for _, node := range built.Nodes {
50
+ if node.External && node.File == declaration {
51
+ hasExternalFact = true
52
+ break
53
+ }
54
+ }
55
+ if !hasExternalFact {
56
+ t.Fatalf("graph has no external fact from %s", declaration)
57
+ }
58
+
59
+ texts := SourceTexts(prog)
60
+ if got, ok := texts[declaration]; !ok || got != "export declare function external(): number;\n" {
61
+ t.Fatalf("declaration source text = %q, present %v", got, ok)
62
+ }
63
+ hasBundledSource := false
64
+ for path := range texts {
65
+ if strings.HasPrefix(path, "bundled:///") {
66
+ hasBundledSource = true
67
+ break
68
+ }
69
+ }
70
+ if !hasBundledSource {
71
+ t.Fatal("source manifest has no virtual bundled library")
72
+ }
73
+ }
@@ -0,0 +1,66 @@
1
+ package graphsymbols
2
+
3
+ import (
4
+ "testing"
5
+ "unicode/utf8"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/internal/graph"
8
+ "github.com/samchon/ttsc/packages/ttsc/internal/lspserver"
9
+ )
10
+
11
+ // TestPositionHelpersFollowECMALineTerminators verifies graph-backed LSP
12
+ // positions use the compiler's LF, CRLF, CR, LS, and PS line boundaries.
13
+ //
14
+ // The old offset-to-position helper counted only LF while its inverse counted
15
+ // CR too, so an editor cursor could map to a graph offset that returned on a
16
+ // different line. The test walks every valid UTF-8 cursor boundary on each
17
+ // logical line, including a surrogate-pair column, and requires a round trip.
18
+ //
19
+ // 1. Build three lines with each ECMAScript terminator.
20
+ // 2. Enumerate every valid cursor boundary before each terminator.
21
+ // 3. Convert each offset to UTF-16 LSP position and back to the same offset.
22
+ func TestPositionHelpersFollowECMALineTerminators(t *testing.T) {
23
+ cases := []struct {
24
+ name string
25
+ terminator string
26
+ }{
27
+ {name: "LF", terminator: "\n"},
28
+ {name: "CRLF", terminator: "\r\n"},
29
+ {name: "CR", terminator: "\r"},
30
+ {name: "LS", terminator: "\u2028"},
31
+ {name: "PS", terminator: "\u2029"},
32
+ }
33
+ for _, tc := range cases {
34
+ t.Run(tc.name, func(t *testing.T) {
35
+ text := "alpha😀" + tc.terminator + "beta" + tc.terminator + "gamma"
36
+ starts := graph.ECMALineStarts(text)
37
+ if len(starts) != 3 {
38
+ t.Fatalf("line starts = %v, want three lines", starts)
39
+ }
40
+ for line, start := range starts {
41
+ end := graph.LineEnd(text, starts, line)
42
+ for offset := start; ; {
43
+ position := offsetToPosition(text, offset)
44
+ if position.Line != line {
45
+ t.Fatalf("offset %d position line = %d, want %d", offset, position.Line, line)
46
+ }
47
+ restored, ok := lspPositionToOffset(text, position)
48
+ if !ok || restored != offset {
49
+ t.Fatalf("offset %d -> %+v -> (%d, %t), want itself", offset, position, restored, ok)
50
+ }
51
+ if offset == end {
52
+ break
53
+ }
54
+ _, size := utf8.DecodeRuneInString(text[offset:])
55
+ if size == 0 {
56
+ t.Fatal("unexpected zero-width UTF-8 rune")
57
+ }
58
+ offset += size
59
+ }
60
+ }
61
+ if _, ok := lspPositionToOffset(text, lspserver.LSPPosition{Line: len(starts), Character: 0}); ok {
62
+ t.Fatal("position on a nonexistent source line must not resolve")
63
+ }
64
+ })
65
+ }
66
+ }