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
@@ -7,11 +7,17 @@ import (
7
7
  "github.com/samchon/ttsc/packages/ttsc/driver"
8
8
  )
9
9
 
10
- // addEdges resolves the relationships between the declaration nodes Build
11
- // recorded. It walks each source file again and, for every class or interface,
12
- // resolves its heritage bases through the checker (unwrapping barrel re-exports
13
- // to the real declaration) and links the declaration to that base, materializing
14
- // an external boundary-leaf node when the base lives in node_modules or a `.d.ts`.
10
+ // addEdges is the checker pass over the declaration nodes Build recorded. It
11
+ // walks each source file again and, for every class or interface, resolves its
12
+ // heritage bases through the checker (unwrapping barrel re-exports to the real
13
+ // declaration) and links the declaration to that base, materializing an external
14
+ // boundary-leaf node when the base lives in node_modules or a `.d.ts`.
15
+ //
16
+ // Not everything it resolves is an edge. A fact that belongs to one declaration
17
+ // rather than to a pair of them rides on its own node: a decorator on the target
18
+ // it annotates, a literal value set on the type that admits it. Both need the
19
+ // same checker and the same walk, so they are resolved here and written to the
20
+ // node the build pass keyed.
15
21
  func (g *Graph) addEdges(prog *driver.Program) {
16
22
  checker := prog.Checker
17
23
  for _, file := range prog.SourceFiles() {
@@ -19,6 +25,7 @@ func (g *Graph) addEdges(prog *driver.Program) {
19
25
  g.collectHeritage(checker, file)
20
26
  g.collectCalls(checker, file)
21
27
  g.collectTypeRefs(checker, file)
28
+ g.collectLiterals(checker, file)
22
29
  if file.Statements != nil {
23
30
  g.collectDecorators(file.FileName(), file.Statements.Nodes)
24
31
  }
@@ -115,8 +122,216 @@ func (g *Graph) heritageEdges(checker *shimchecker.Checker, path string, node *s
115
122
  continue
116
123
  }
117
124
  g.addEdgeAt(from, to, EdgeHeritage, origin, base.Expression.Pos(), base.Expression.End())
125
+ g.memberRelationEdges(checker, node, kind, typeNode, target, origin)
126
+ }
127
+ }
128
+ }
129
+
130
+ // memberRelationEdges records the directly declared member pairs that a
131
+ // heritage clause makes checker-valid. The TypeScript loader used to infer
132
+ // these from equal names after the dump was built. That made an authoritative
133
+ // edge even when the checker rejected the container relation, and a method /
134
+ // property kind guard could not repair it: same-kind signatures can be
135
+ // incompatible while a method can validly satisfy a function-valued property.
136
+ //
137
+ // Each pair is checked independently. A whole-container assignability gate
138
+ // would let one broken sibling erase a valid relation in the same partially
139
+ // edited class. The pair query retains the checker's instantiated property
140
+ // symbols, so optionality, overloads, and private/protected declaration origins
141
+ // still use upstream propertyRelatedTo semantics.
142
+ func (g *Graph) memberRelationEdges(
143
+ checker *shimchecker.Checker,
144
+ derivedDeclaration *shimast.Node,
145
+ derivedKind NodeKind,
146
+ baseTypeNode *shimast.Node,
147
+ baseTarget *Target,
148
+ heritageOrigin string,
149
+ ) {
150
+ if checker == nil || derivedDeclaration == nil || baseTypeNode == nil || baseTarget == nil || baseTarget.External {
151
+ return
152
+ }
153
+ derivedSymbol := derivedDeclaration.Symbol()
154
+ if derivedSymbol == nil || baseTarget.Symbol == nil {
155
+ return
156
+ }
157
+ derivedType := checker.GetDeclaredTypeOfSymbol(derivedSymbol)
158
+ baseType := checker.GetTypeFromTypeNode(baseTypeNode)
159
+ if derivedType == nil || baseType == nil {
160
+ return
161
+ }
162
+
163
+ derivedMembers := declaredTypeMembers(derivedSymbol)
164
+ baseMembers := declaredTypeMembers(baseTarget.Symbol)
165
+
166
+ relation := "overrides"
167
+ if heritageOrigin == "implements" {
168
+ relation = "implements"
169
+ }
170
+ for _, derivedMember := range derivedMembers {
171
+ derivedMemberSymbol := derivedMember.Symbol()
172
+ if derivedMemberSymbol == nil || derivedMemberSymbol.Name == "" ||
173
+ shimast.GetCombinedModifierFlags(derivedMember)&shimast.ModifierFlagsStatic != 0 ||
174
+ derivedMember.Kind == shimast.KindConstructor {
175
+ continue
176
+ }
177
+
178
+ derivedProperty := checker.GetPropertyOfType(derivedType, derivedMemberSymbol.Name)
179
+ baseProperty := checker.GetPropertyOfType(baseType, derivedMemberSymbol.Name)
180
+ if derivedProperty == nil || baseProperty == nil ||
181
+ !propertyRootsAt(checker, derivedProperty, derivedMemberSymbol) {
182
+ continue
183
+ }
184
+ baseMember := directMemberForProperty(checker, baseProperty, baseMembers)
185
+ if baseMember == nil {
186
+ // The heritage clause names one type; the member may live further up.
187
+ // `interface Child extends Root {}` with `class W implements Child`
188
+ // resolves `W.execute` against `Root.execute`, and the program compiles
189
+ // with no diagnostic — but the immediate base declares nothing, so
190
+ // requiring a directly declared member dropped a relation the checker
191
+ // had already established. Execution tracing follows these edges to
192
+ // reach an implementation, so the missing edge ends the trace at the
193
+ // abstract declaration.
194
+ baseMember = inheritedMemberForProperty(checker, baseProperty)
195
+ }
196
+ if baseMember == nil ||
197
+ !shimchecker.Checker_isPropertyAssignableTo(checker, derivedProperty, baseProperty) {
198
+ continue
199
+ }
200
+ if relation == "overrides" && derivedKind == NodeClass &&
201
+ !shimchecker.Checker_isValidClassMemberOverridePair(checker, derivedProperty, baseProperty) {
202
+ continue
203
+ }
204
+
205
+ from := graphMemberNodeID(g, derivedMember)
206
+ to := graphMemberNodeID(g, baseMember)
207
+ if from == "" || to == "" {
208
+ continue
209
+ }
210
+ evidence := derivedMember.Name()
211
+ if evidence == nil {
212
+ evidence = derivedMember
213
+ }
214
+ g.addEdgeAt(from, to, EdgeMemberRelation, relation, evidence.Pos(), evidence.End())
215
+ }
216
+ }
217
+
218
+ // declaredTypeMembers returns the members written on every class/interface
219
+ // declaration merged into symbol. The checker sees that merged type as one
220
+ // surface, so choosing one representative declaration would omit valid pairs
221
+ // contributed by another declaration even though both graph nodes already
222
+ // exist.
223
+ func declaredTypeMembers(symbol *shimast.Symbol) []*shimast.Node {
224
+ if symbol == nil {
225
+ return nil
226
+ }
227
+ var members []*shimast.Node
228
+ for _, declaration := range symbol.Declarations {
229
+ members = append(members, classMembers(declaration)...)
230
+ }
231
+ return members
232
+ }
233
+
234
+ // inheritedMemberForProperty resolves the declaration a base type inherits
235
+ // rather than declares, by following the property to the symbol it roots at.
236
+ //
237
+ // It is the fallback for what directMemberForProperty below cannot see. A
238
+ // heritage clause names one type and the member may be declared further up:
239
+ // `interface Child extends Root {}` with `class W implements Child` roots
240
+ // `W.execute` at `Root.execute`, which `Child`'s own member list does not hold.
241
+ // Static members and constructors are skipped for the same reason the direct
242
+ // scan skips them — neither participates in a member relation.
243
+ func inheritedMemberForProperty(
244
+ checker *shimchecker.Checker,
245
+ property *shimast.Symbol,
246
+ ) *shimast.Node {
247
+ if checker == nil || property == nil {
248
+ return nil
249
+ }
250
+ for _, root := range checker.GetRootSymbols(property) {
251
+ if root == nil {
252
+ continue
253
+ }
254
+ for _, declaration := range root.Declarations {
255
+ if declaration == nil ||
256
+ shimast.GetCombinedModifierFlags(declaration)&shimast.ModifierFlagsStatic != 0 ||
257
+ declaration.Kind == shimast.KindConstructor {
258
+ continue
259
+ }
260
+ return declaration
261
+ }
262
+ }
263
+ return nil
264
+ }
265
+
266
+ // directMemberForProperty maps an instantiated or transient checker property
267
+ // back to the declaration the named base type itself owns. It answers nothing
268
+ // for a property the base only inherits, which is what
269
+ // inheritedMemberForProperty above then resolves.
270
+ func directMemberForProperty(
271
+ checker *shimchecker.Checker,
272
+ property *shimast.Symbol,
273
+ members []*shimast.Node,
274
+ ) *shimast.Node {
275
+ for _, member := range members {
276
+ symbol := member.Symbol()
277
+ if symbol == nil || symbol.Name == "" ||
278
+ shimast.GetCombinedModifierFlags(member)&shimast.ModifierFlagsStatic != 0 ||
279
+ member.Kind == shimast.KindConstructor {
280
+ continue
281
+ }
282
+ if propertyRootsAt(checker, property, symbol) {
283
+ return member
284
+ }
285
+ }
286
+ return nil
287
+ }
288
+
289
+ func propertyRootsAt(checker *shimchecker.Checker, property, declaration *shimast.Symbol) bool {
290
+ if checker == nil || property == nil || declaration == nil {
291
+ return false
292
+ }
293
+ for _, root := range checker.GetRootSymbols(property) {
294
+ if root == declaration {
295
+ return true
118
296
  }
119
297
  }
298
+ return false
299
+ }
300
+
301
+ // graphMemberNodeID returns the existing graph node for a class or interface
302
+ // member. It never materializes a new endpoint: member relations stay within
303
+ // the same workspace-owned member surface Build already records.
304
+ func graphMemberNodeID(g *Graph, member *shimast.Node) string {
305
+ if g == nil || member == nil || member.Symbol() == nil {
306
+ return ""
307
+ }
308
+ kind := NodeKind("")
309
+ switch {
310
+ case isMethodMember(member.Kind):
311
+ kind = NodeMethod
312
+ case isPropertyMember(member.Kind):
313
+ kind = NodeVariable
314
+ default:
315
+ return ""
316
+ }
317
+ file := shimast.GetSourceFileOfNode(member)
318
+ name := methodName(member.Symbol())
319
+ if file == nil || name == "" {
320
+ return ""
321
+ }
322
+ id := nodeID(file.FileName(), name, kind)
323
+ stored, ok := g.Nodes[id]
324
+ if !ok {
325
+ return ""
326
+ }
327
+ for _, declaration := range member.Symbol().Declarations {
328
+ declarationFile := shimast.GetSourceFileOfNode(declaration)
329
+ if declarationFile != nil && declarationFile.FileName() == stored.File &&
330
+ declaration.Pos() == stored.Pos && declaration.End() == stored.End {
331
+ return id
332
+ }
333
+ }
334
+ return ""
120
335
  }
121
336
 
122
337
  // collectCalls records a value-call edge from each declaration to every function,
@@ -0,0 +1,131 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestEnumMembersCarryTheirNamesOnTheEnumNode verifies that an enum records the
11
+ // name and value of each member on its own node, and that nothing else does.
12
+ //
13
+ // The enum's node was always in the graph and had nothing in it (#738). Its
14
+ // signature stops at the `{`, its members are not nodes, so the outline a class
15
+ // gets is empty for an enum — and #732 gave it only values. The code writes
16
+ // `Colors.Red`, never `"red"`, so a caller that had already named the enum
17
+ // still opened the file for the one fact it came for.
18
+ //
19
+ // The names ride on the enum rather than on member nodes on purpose. A member
20
+ // node would be indexing what `grep -rn "Colors.Red"` answers exactly, and it
21
+ // would put leaves into tour flows to do it. Filling in a node the graph holds
22
+ // is indexing; minting nodes to carry detail is not.
23
+ //
24
+ // 1. Compile a fixture with a string enum, an implicitly numbered one, and a
25
+ // class beside them.
26
+ // 2. Build the graph.
27
+ // 3. Assert each enum node carries its members name-and-value, that no member
28
+ // became a node, and that a class node carries none of this.
29
+ func TestEnumMembersCarryTheirNamesOnTheEnumNode(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 Colors {
33
+ Red = 'red',
34
+ Green = 'green',
35
+ }
36
+
37
+ export enum Implicit {
38
+ First,
39
+ Second,
40
+ }
41
+
42
+ // Two members, one value: a type folds these together, a declaration does not.
43
+ export enum Dup {
44
+ A = 'x',
45
+ B = 'x',
46
+ C = 'y',
47
+ }
48
+
49
+ export class Cls {
50
+ public value = 1;
51
+ }
52
+ `)
53
+
54
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
55
+ if err != nil {
56
+ t.Fatal(err)
57
+ }
58
+ if len(diags) != 0 {
59
+ t.Fatalf("unexpected diagnostics: %v", diags)
60
+ }
61
+ defer func() { _ = prog.Close() }()
62
+
63
+ graph := Build(prog)
64
+ path := sourceFile(t, prog, "main.ts").FileName()
65
+
66
+ colors, ok := graph.Nodes[nodeID(path, "Colors", NodeEnum)]
67
+ if !ok {
68
+ t.Fatalf("missing enum node; nodes: %v", nodeIDSet(graph))
69
+ }
70
+ want := []EnumMember{{Name: "Red", Value: `"red"`}, {Name: "Green", Value: `"green"`}}
71
+ if len(colors.EnumMembers) != len(want) {
72
+ t.Fatalf("Colors reported %v, want %v", colors.EnumMembers, want)
73
+ }
74
+ for i, member := range want {
75
+ if colors.EnumMembers[i] != member {
76
+ t.Fatalf("Colors member %d is %v, want %v", i, colors.EnumMembers[i], member)
77
+ }
78
+ }
79
+
80
+ // The values exist only in the checker here, so a scrape of the source could
81
+ // not produce this pairing.
82
+ implicit := graph.Nodes[nodeID(path, "Implicit", NodeEnum)]
83
+ if implicit == nil ||
84
+ len(implicit.EnumMembers) != 2 ||
85
+ implicit.EnumMembers[0] != (EnumMember{Name: "First", Value: "0"}) ||
86
+ implicit.EnumMembers[1] != (EnumMember{Name: "Second", Value: "1"}) {
87
+ t.Fatalf("implicitly numbered enum did not pair its members: %v", implicit.EnumMembers)
88
+ }
89
+
90
+ // Two members carrying one value. The declared type folds them into a single
91
+ // constituent — a type is a set — so reading the list off the type reports A
92
+ // and C and drops B, silently, which is #732's defect from #732's instinct:
93
+ // taking a declaration fact from a type. The list is the declaration's.
94
+ dup := graph.Nodes[nodeID(path, "Dup", NodeEnum)]
95
+ if dup == nil {
96
+ t.Fatalf("missing Dup; nodes: %v", nodeIDSet(graph))
97
+ }
98
+ if names := memberNames(dup.EnumMembers); len(names) != 3 ||
99
+ names[0] != "A" || names[1] != "B" || names[2] != "C" {
100
+ t.Fatalf("a member sharing another's value was dropped: %v", names)
101
+ }
102
+ // And the value set is right to say `"x"` once: two members, two names, but
103
+ // the values they admit really are two.
104
+ if len(dup.Literals) != 2 {
105
+ t.Fatalf("the value set should hold each distinct value once: %v", dup.Literals)
106
+ }
107
+
108
+ // The line this fix holds: the members are facts on the enum, not nodes of
109
+ // their own. A node per member would index what grep already answers.
110
+ for id := range graph.Nodes {
111
+ if id == nodeID(path, "Colors.Red", NodeVariable) ||
112
+ id == nodeID(path, "Red", NodeVariable) {
113
+ t.Fatalf("an enum member became a node (%s); it is a fact on the enum", id)
114
+ }
115
+ }
116
+
117
+ // The negative twin: this rides on enums only. A class's fields are member
118
+ // nodes and its outline comes from those.
119
+ if cls := graph.Nodes[nodeID(path, "Cls", NodeClass)]; cls == nil ||
120
+ len(cls.EnumMembers) != 0 {
121
+ t.Fatalf("a class node carried enum members: %v", cls)
122
+ }
123
+ }
124
+
125
+ func memberNames(members []EnumMember) []string {
126
+ out := make([]string, 0, len(members))
127
+ for _, member := range members {
128
+ out = append(out, member.Name)
129
+ }
130
+ return out
131
+ }
@@ -1,6 +1,8 @@
1
1
  package graph
2
2
 
3
3
  import (
4
+ "strings"
5
+
4
6
  shimast "github.com/microsoft/typescript-go/shim/ast"
5
7
  )
6
8
 
@@ -58,16 +60,82 @@ type Node struct {
58
60
  // the declaration's combined modifier flags during the build pass and emitted
59
61
  // for projections that filter on visibility and shape.
60
62
  Modifiers []string
63
+ // Literals is the complete value set of a type alias or enum whose declared
64
+ // type the checker resolved to literals, each rendered in TypeScript source
65
+ // form ("a", 1, true, null). It is set only when every constituent is
66
+ // enumerable, so a present list is the whole type and never a sample of it;
67
+ // a union that mixes in `string`, a type parameter, or a computed enum member
68
+ // has no complete answer and gets none.
69
+ //
70
+ // It is a checker fact because nothing else is sound. The value set was read
71
+ // off the declaration's source text for a while, which made the answer a
72
+ // function of line wrapping rather than of the type: a union written one
73
+ // member per line reported the members that fit in the snippet, an enum
74
+ // written across lines reported nothing at all, and `type I = Kind | 'f'`
75
+ // reported `'f'` while the members reaching it through `Kind` vanished (#732).
76
+ // The checker has already resolved every one of them, indirection included.
77
+ Literals []string
78
+ // EnumMembers is what an enum declares, in checker order: the name a caller
79
+ // writes and the value it carries. Empty for every other kind.
80
+ //
81
+ // The enum's node was always here and had nothing in it. `literals` says what
82
+ // values the enum admits, which answers a serializer; the code says
83
+ // `Colors.Red`, so a caller that had already named the enum still opened the
84
+ // file to learn what to type (#738). The members are not nodes of their own —
85
+ // `Colors.Red` is a literal string a grep finds exactly, and minting a node
86
+ // per member would grow the graph and put leaves into tour flows to index
87
+ // what grep already does. This fills in the node that exists instead.
88
+ EnumMembers []EnumMember
89
+ // ObjectMembers is the direct, statically named outline of an object literal
90
+ // assigned to this variable. It is captured from the compiler AST, in source
91
+ // order, so comments and lexical trivia cannot change member identity. The
92
+ // positions point into the same Program-owned source snapshot NewDump uses to
93
+ // render the compact signature and line carried on the wire.
94
+ ObjectMembers []ObjectMember
61
95
  // Pos and End bound the declaration in its source file (byte offsets). They
62
96
  // are for display, never identity, so an edit that shifts them does not re-key
63
97
  // the node.
64
- Pos int
65
- End int
98
+ Pos int
99
+ End int
100
+ // SignatureEnd bounds the declaration head — everything up to where the body
101
+ // opens. A consumer that guessed the boundary by scanning physical lines both
102
+ // leaked implementation text when a declaration shared its line with its body
103
+ // and stopped early when the head itself contained a brace, because a line is
104
+ // not a declaration boundary and a brace is not always a body. The compiler
105
+ // knows where the body starts, so it says so here. Zero when the declaration
106
+ // has no body to bound, in which case the whole declaration is the head.
107
+ SignatureEnd int
66
108
  ImplementationFile string
67
109
  ImplementationPos int
68
110
  ImplementationEnd int
69
111
  }
70
112
 
113
+ // EnumMember is one member of an enum: the name a caller writes and the value
114
+ // it carries, in TypeScript source form. Value is empty when the checker could
115
+ // not fold the member's initializer to a constant.
116
+ type EnumMember struct {
117
+ Name string
118
+ Value string
119
+ }
120
+
121
+ // ObjectMember is one direct, statically named property, method, or accessor of
122
+ // an object-literal variable. A spread has no declaration name of its own and a
123
+ // dynamic computed key cannot be named soundly, so neither fabricates a member.
124
+ type ObjectMember struct {
125
+ Name string
126
+ Kind NodeKind
127
+ Pos int
128
+ End int
129
+ // SignatureEnd is the AST boundary before a nested value body. When the
130
+ // boundary lands at an opening token's full start, SignatureTokenLen says how
131
+ // many source bytes belong to the outline (`{`, `[`, or `class`). A true
132
+ // SignatureBoundary permits whitespace-normalizing a multiline declaration
133
+ // through that safe endpoint; otherwise the outline stops at its first line.
134
+ SignatureEnd int
135
+ SignatureBoundary bool
136
+ SignatureTokenLen int
137
+ }
138
+
71
139
  // EdgeKind classifies a relationship between two nodes.
72
140
  type EdgeKind string
73
141
 
@@ -75,6 +143,13 @@ const (
75
143
  // EdgeHeritage is an `extends` / `implements` relationship from a class or
76
144
  // interface to a base it derives from.
77
145
  EdgeHeritage EdgeKind = "heritage"
146
+ // EdgeMemberRelation is a checker-verified relationship from a directly
147
+ // declared member to the directly declared base member it implements or
148
+ // overrides. Origin is "implements" or "overrides" and becomes the wire
149
+ // kind. Keeping this separate from EdgeHeritage prevents a member fact from
150
+ // being confused with the syntactic container clause that led the checker to
151
+ // compare the two types.
152
+ EdgeMemberRelation EdgeKind = "member-relation"
78
153
  // EdgeValueCall is a runtime use from one declaration of the function, method,
79
154
  // or constructor it invokes: a call, a `new T()`, a `<Component/>` JSX use, or
80
155
  // a tagged-template tag. Uses of a dependency's method are not modeled (the
@@ -108,7 +183,8 @@ type Edge struct {
108
183
  // (calls / instantiates / renders, extends / implements) without the
109
184
  // MCP-facing model losing the distinction. It is "" for kinds that need no
110
185
  // split (type-ref, value-access). For EdgeValueCall it is "call", "new",
111
- // "jsx", or "tagged"; for EdgeHeritage it is "extends" or "implements".
186
+ // "jsx", or "tagged"; for EdgeHeritage it is "extends" or "implements";
187
+ // for EdgeMemberRelation it is "implements" or "overrides".
112
188
  Origin string
113
189
  Pos int
114
190
  End int
@@ -148,7 +224,103 @@ type edgeKey struct {
148
224
  }
149
225
 
150
226
  // nodeID builds the position-invariant identity for a symbol named name,
151
- // declared as kind in the source file at path.
227
+ // declared as kind in the source file at path. The visible grammar remains
228
+ // path#name:kind, but path and name quote a literal backslash and hash so a
229
+ // consumer never has to guess which hash separates the two components.
152
230
  func nodeID(path string, name string, kind NodeKind) string {
153
- return path + "#" + name + ":" + string(kind)
231
+ return escapeNodeIDPart(path) + "#" + escapeNodeIDPart(name) + ":" + string(kind)
232
+ }
233
+
234
+ // nodeIDParts are the structured facts carried by a symbol id. File nodes are
235
+ // raw paths rather than symbol ids, so parseNodeID deliberately rejects them.
236
+ type nodeIDParts struct {
237
+ path string
238
+ name string
239
+ kind NodeKind
240
+ }
241
+
242
+ // parseNodeID recovers the structured path, name, and kind from an id emitted
243
+ // by nodeID. It also accepts older ids whose ordinary components were not
244
+ // escaped, so a newer reader can still consume a pre-codec dump.
245
+ func parseNodeID(id string) (nodeIDParts, bool) {
246
+ hash := nodeIDHash(id)
247
+ if hash < 0 {
248
+ return nodeIDParts{}, false
249
+ }
250
+ tail := id[hash+1:]
251
+ colon := strings.LastIndex(tail, ":")
252
+ if colon <= 0 || colon == len(tail)-1 {
253
+ return nodeIDParts{}, false
254
+ }
255
+ return nodeIDParts{
256
+ path: unescapeNodeIDPart(id[:hash]),
257
+ name: unescapeNodeIDPart(tail[:colon]),
258
+ kind: NodeKind(tail[colon+1:]),
259
+ }, true
260
+ }
261
+
262
+ // nodeFile recovers the raw source path embedded in a symbol id. An id without
263
+ // a symbol component is a file id, and therefore has no node-file component.
264
+ func nodeFile(id string) string {
265
+ parts, ok := parseNodeID(id)
266
+ if !ok {
267
+ return ""
268
+ }
269
+ return parts.path
270
+ }
271
+
272
+ // NodeFile is the graph-symbol provider's shared view of the node-id grammar.
273
+ // Keeping the parser here prevents its LSP path comparison from drifting from
274
+ // the dump producer's edge-evidence lookup.
275
+ func NodeFile(id string) string {
276
+ return nodeFile(id)
277
+ }
278
+
279
+ func escapeNodeIDPart(value string) string {
280
+ value = strings.ReplaceAll(value, "\\", "\\\\")
281
+ return strings.ReplaceAll(value, "#", "\\#")
282
+ }
283
+
284
+ func unescapeNodeIDPart(value string) string {
285
+ var out strings.Builder
286
+ out.Grow(len(value))
287
+ for i := 0; i < len(value); i++ {
288
+ if value[i] == '\\' && i+1 < len(value) {
289
+ next := value[i+1]
290
+ if next == '#' || (next == '\\' && !legacyUNCStart(value, i)) {
291
+ i++
292
+ }
293
+ }
294
+ out.WriteByte(value[i])
295
+ }
296
+ return out.String()
297
+ }
298
+
299
+ // legacyUNCStart distinguishes an older raw UNC path (\\server) from a new
300
+ // codec spelling (\\\\server). The former predates backslash escaping and must
301
+ // remain readable by a current consumer.
302
+ func legacyUNCStart(value string, index int) bool {
303
+ return index == 0 && len(value) > 2 && value[2] != '\\' && value[2] != '#'
304
+ }
305
+
306
+ func nodeIDHash(id string) int {
307
+ for i := 0; i < len(id); i++ {
308
+ if id[i] != '#' {
309
+ continue
310
+ }
311
+ if i == 0 || id[i-1] != '\\' || escapedBackslash(id, i-1) {
312
+ return i
313
+ }
314
+ }
315
+ return -1
316
+ }
317
+
318
+ // escapedBackslash reports whether slash is itself escaped by the run before
319
+ // it. Only an odd run quotes the following hash.
320
+ func escapedBackslash(id string, slash int) bool {
321
+ count := 0
322
+ for i := slash; i >= 0 && id[i] == '\\'; i-- {
323
+ count++
324
+ }
325
+ return count%2 == 0
154
326
  }