ttsc 0.19.3 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) 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 +276 -100
  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_uses_ecma_line_terminators_test.go +87 -0
  33. package/internal/graph/duplicate_variable_object_members_keep_first_declaration_test.go +61 -0
  34. package/internal/graph/edges.go +208 -0
  35. package/internal/graph/graph.go +143 -5
  36. package/internal/graph/line_model.go +122 -0
  37. package/internal/graph/member_relation_edges_follow_checker_validity_test.go +85 -0
  38. package/internal/graph/member_relation_edges_preserve_checker_boundaries_test.go +191 -0
  39. package/internal/graph/member_relation_edges_reject_checker_invalid_pairs_test.go +119 -0
  40. package/internal/graph/node_ids_preserve_hash_bearing_components_test.go +41 -0
  41. package/internal/graph/node_modifiers_emit_union_strings_test.go +4 -1
  42. package/internal/graph/object_literal_members_ride_on_the_snapshot_test.go +192 -0
  43. package/internal/graph/object_member_signatures_preserve_literal_whitespace_test.go +67 -0
  44. package/internal/graph/object_members.go +229 -0
  45. package/internal/graph/provenance.go +88 -30
  46. package/internal/graph/source_texts_cover_every_resident_program_source_test.go +73 -0
  47. package/internal/graphsymbols/position_helpers_follow_ecma_line_terminators_test.go +66 -0
  48. package/internal/graphsymbols/provider.go +26 -91
  49. package/internal/lspserver/lsp_clean_didopen_refreshes_compiler_state_test.go +144 -0
  50. package/internal/lspserver/lsp_completion.go +109 -0
  51. package/internal/lspserver/lsp_completion_gate_never_refuses_a_matching_line_test.go +50 -0
  52. package/internal/lspserver/lsp_completion_hints_longest_trigger_wins_test.go +149 -0
  53. package/internal/lspserver/lsp_completion_hints_nearest_trigger_wins_test.go +37 -0
  54. package/internal/lspserver/lsp_completion_merges_into_upstream_response_test.go +149 -0
  55. package/internal/lspserver/lsp_completion_nearest_hint_replaces_nearest_filter_test.go +43 -0
  56. package/internal/lspserver/lsp_completion_preserves_completion_list_extensions_test.go +99 -0
  57. package/internal/lspserver/lsp_completion_refuses_hints_inside_a_string_literal_test.go +69 -0
  58. package/internal/lspserver/lsp_completion_replaces_full_filter_in_rank_order_test.go +85 -0
  59. package/internal/lspserver/lsp_completion_request.go +455 -0
  60. package/internal/lspserver/lsp_completion_scope.go +530 -0
  61. package/internal/lspserver/lsp_completion_scope_bench_test.go +41 -0
  62. package/internal/lspserver/lsp_completion_scope_classifies_regex_statement_and_operator_boundaries_test.go +39 -0
  63. package/internal/lspserver/lsp_completion_scope_ignores_jsdoc_bytes_in_literals_test.go +108 -0
  64. package/internal/lspserver/lsp_completion_scope_refuses_regex_hints_test.go +52 -0
  65. package/internal/lspserver/lsp_completion_trigger_uses_last_rune_test.go +24 -0
  66. package/internal/lspserver/lsp_deleted_watched_file_withdraws_plugin_diagnostics_test.go +110 -0
  67. package/internal/lspserver/lsp_didsave_keeps_the_incremental_changed_uri_path_test.go +78 -0
  68. package/internal/lspserver/lsp_hints_absence_is_not_an_error_test.go +46 -0
  69. package/internal/lspserver/lsp_hints_refresh_coalesces_concurrent_requests_test.go +82 -0
  70. package/internal/lspserver/lsp_hints_refresh_ignores_a_superseded_generation_test.go +40 -0
  71. package/internal/lspserver/lsp_hints_refresh_keeps_each_producer_isolated_test.go +64 -0
  72. package/internal/lspserver/lsp_hints_refresh_replaces_the_session_snapshot_test.go +66 -0
  73. package/internal/lspserver/lsp_hints_refresh_scheduled_by_editor_events_test.go +90 -0
  74. package/internal/lspserver/lsp_hints_refresh_serves_completion_while_running_test.go +91 -0
  75. package/internal/lspserver/lsp_index_byte_from_bounds_its_search_start_test.go +38 -0
  76. package/internal/lspserver/lsp_initialize_constrains_position_encoding_to_utf16_test.go +205 -0
  77. package/internal/lspserver/lsp_late_completion_trigger_is_reported_once_test.go +105 -0
  78. package/internal/lspserver/lsp_native_completion_hints_decode_flat_lint_wire_test.go +66 -0
  79. package/internal/lspserver/lsp_native_completion_hints_grouped_compatibility_boundary_test.go +60 -0
  80. package/internal/lspserver/lsp_native_plugin_source.go +315 -2
  81. package/internal/lspserver/lsp_native_resident.go +304 -0
  82. package/internal/lspserver/lsp_plugin.go +85 -8
  83. package/internal/lspserver/lsp_plugin_completion_resolve_stays_local_test.go +82 -0
  84. package/internal/lspserver/lsp_position_conversions_span_astral_and_combining_runes_test.go +119 -0
  85. package/internal/lspserver/lsp_proxy.go +601 -9
  86. package/internal/lspserver/lsp_refresh.go +70 -0
  87. package/internal/lspserver/lsp_resident_falls_back_when_serve_unsupported_test.go +43 -0
  88. package/internal/lspserver/lsp_resident_maps_verb_args_test.go +34 -0
  89. package/internal/lspserver/lsp_server.go +13 -1
  90. package/internal/lspserver/lsp_watched_file_changes_reach_compiler_caches_test.go +172 -0
  91. package/lib/TtscCompiler.d.ts +3 -1
  92. package/lib/TtscCompiler.js +5 -1
  93. package/lib/TtscCompiler.js.map +1 -1
  94. package/lib/TtscService.d.ts +13 -3
  95. package/lib/TtscService.js +6 -6
  96. package/lib/TtscService.js.map +1 -1
  97. package/lib/compiler/internal/project/readConfigJson.d.ts +30 -0
  98. package/lib/compiler/internal/project/readConfigJson.js +208 -0
  99. package/lib/compiler/internal/project/readConfigJson.js.map +1 -0
  100. package/lib/compiler/internal/project/readProjectConfig.js +53 -149
  101. package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
  102. package/lib/compiler/internal/residentTransformProcess.d.ts +15 -2
  103. package/lib/compiler/internal/residentTransformProcess.js +159 -39
  104. package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
  105. package/lib/compiler/internal/runBuild.d.ts +6 -0
  106. package/lib/compiler/internal/runBuild.js +143 -44
  107. package/lib/compiler/internal/runBuild.js.map +1 -1
  108. package/lib/compiler/internal/startResidentTransform.d.ts +5 -1
  109. package/lib/compiler/internal/startResidentTransform.js +3 -1
  110. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  111. package/lib/compiler/internal/transformProjectInMemory.js +12 -2
  112. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  113. package/lib/flags/parser.d.ts +19 -0
  114. package/lib/flags/parser.js +78 -36
  115. package/lib/flags/parser.js.map +1 -1
  116. package/lib/flags/schema.d.ts +49 -11
  117. package/lib/flags/schema.js +94 -22
  118. package/lib/flags/schema.js.map +1 -1
  119. package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +5 -0
  120. package/lib/internal/assertSafeExplicitCacheDirectory.js +26 -0
  121. package/lib/internal/assertSafeExplicitCacheDirectory.js.map +1 -0
  122. package/lib/launcher/internal/runTtsc.js +107 -69
  123. package/lib/launcher/internal/runTtsc.js.map +1 -1
  124. package/lib/launcher/internal/runTtsx.js +28 -41
  125. package/lib/launcher/internal/runTtsx.js.map +1 -1
  126. package/lib/launcher/internal/runtimeHooks.d.ts +15 -0
  127. package/lib/launcher/internal/runtimeHooks.js +18 -1
  128. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  129. package/lib/launcher/internal/watchTopology.d.ts +43 -0
  130. package/lib/launcher/internal/watchTopology.js +360 -0
  131. package/lib/launcher/internal/watchTopology.js.map +1 -0
  132. package/lib/plugin/internal/buildSourcePlugin.d.ts +14 -3
  133. package/lib/plugin/internal/buildSourcePlugin.js +247 -61
  134. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  135. package/lib/plugin/internal/loadProjectPlugins.js +63 -2
  136. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  137. package/lib/structures/ITtscCompilerTransformation.d.ts +16 -0
  138. package/lib/structures/internal/ITtscParsedProjectConfig.d.ts +2 -0
  139. package/package.json +8 -8
  140. package/shim/ast/shim.go +48 -3
  141. package/shim/astnav/go.mod +14 -0
  142. package/shim/astnav/go.sum +20 -0
  143. package/shim/astnav/shim.go +15 -0
  144. package/shim/checker/shim.go +100 -0
  145. package/shim/core/shim.go +47 -0
  146. package/shim/diagnosticwriter/lint.go +50 -0
  147. package/src/TtscCompiler.ts +5 -1
  148. package/src/TtscService.ts +37 -10
  149. package/src/compiler/internal/project/readConfigJson.ts +209 -0
  150. package/src/compiler/internal/project/readProjectConfig.ts +62 -158
  151. package/src/compiler/internal/residentTransformProcess.ts +197 -39
  152. package/src/compiler/internal/runBuild.ts +169 -47
  153. package/src/compiler/internal/startResidentTransform.ts +12 -1
  154. package/src/compiler/internal/transformProjectInMemory.ts +12 -1
  155. package/src/flags/parser.ts +113 -42
  156. package/src/flags/schema.ts +113 -31
  157. package/src/internal/assertSafeExplicitCacheDirectory.ts +29 -0
  158. package/src/launcher/internal/runTtsc.ts +117 -68
  159. package/src/launcher/internal/runTtsx.ts +29 -44
  160. package/src/launcher/internal/runtimeHooks.ts +30 -2
  161. package/src/launcher/internal/watchTopology.ts +466 -0
  162. package/src/plugin/internal/buildSourcePlugin.ts +326 -64
  163. package/src/plugin/internal/loadProjectPlugins.ts +80 -2
  164. package/src/structures/ITtscCompilerTransformation.ts +17 -0
  165. package/src/structures/internal/ITtscParsedProjectConfig.ts +2 -0
  166. package/tools/shim_audit/baseline.json +0 -3
  167. package/tools/shim_audit/main.go +42 -14
  168. package/tools/shim_audit/shim_dir_coverage_test.go +25 -0
  169. package/utility/flags_gen.go +3 -3
  170. package/utility/host.go +9 -3
@@ -122,10 +122,218 @@ func (g *Graph) heritageEdges(checker *shimchecker.Checker, path string, node *s
122
122
  continue
123
123
  }
124
124
  g.addEdgeAt(from, to, EdgeHeritage, origin, base.Expression.Pos(), base.Expression.End())
125
+ g.memberRelationEdges(checker, node, kind, typeNode, target, origin)
125
126
  }
126
127
  }
127
128
  }
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
296
+ }
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 ""
335
+ }
336
+
129
337
  // collectCalls records a value-call edge from each declaration to every function,
130
338
  // method, or constructor it invokes. The reference walk is attributed to the
131
339
  // nearest enclosing graph node: a top-level function, a class/interface method, a
@@ -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
 
@@ -84,11 +86,25 @@ type Node struct {
84
86
  // per member would grow the graph and put leaves into tour flows to index
85
87
  // what grep already does. This fills in the node that exists instead.
86
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
87
95
  // Pos and End bound the declaration in its source file (byte offsets). They
88
96
  // are for display, never identity, so an edit that shifts them does not re-key
89
97
  // the node.
90
- Pos int
91
- 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
92
108
  ImplementationFile string
93
109
  ImplementationPos int
94
110
  ImplementationEnd int
@@ -102,6 +118,24 @@ type EnumMember struct {
102
118
  Value string
103
119
  }
104
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
+
105
139
  // EdgeKind classifies a relationship between two nodes.
106
140
  type EdgeKind string
107
141
 
@@ -109,6 +143,13 @@ const (
109
143
  // EdgeHeritage is an `extends` / `implements` relationship from a class or
110
144
  // interface to a base it derives from.
111
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"
112
153
  // EdgeValueCall is a runtime use from one declaration of the function, method,
113
154
  // or constructor it invokes: a call, a `new T()`, a `<Component/>` JSX use, or
114
155
  // a tagged-template tag. Uses of a dependency's method are not modeled (the
@@ -142,7 +183,8 @@ type Edge struct {
142
183
  // (calls / instantiates / renders, extends / implements) without the
143
184
  // MCP-facing model losing the distinction. It is "" for kinds that need no
144
185
  // split (type-ref, value-access). For EdgeValueCall it is "call", "new",
145
- // "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".
146
188
  Origin string
147
189
  Pos int
148
190
  End int
@@ -182,7 +224,103 @@ type edgeKey struct {
182
224
  }
183
225
 
184
226
  // nodeID builds the position-invariant identity for a symbol named name,
185
- // 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.
186
230
  func nodeID(path string, name string, kind NodeKind) string {
187
- 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
188
326
  }
@@ -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,85 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestMemberRelationEdgesFollowCheckerValidity verifies that native graph
11
+ // member relationships preserve the structurally valid shapes the TypeScript
12
+ // checker accepts, including shapes whose syntax kinds differ.
13
+ //
14
+ // A method/property equality gate would drop two valid implementations here: a
15
+ // getter satisfying a readonly property and a method satisfying a
16
+ // function-valued property. The graph must follow checker assignability rather
17
+ // than replace one name heuristic with a kind heuristic.
18
+ //
19
+ // 1. Compile a class implementing an interface through ordinary methods plus
20
+ // the two valid cross-kind shapes, and a class overriding an abstract base.
21
+ // 2. Build the native graph.
22
+ // 3. Assert every directly declared checker-valid pair has the correct member
23
+ // relation while constructors never become override edges.
24
+ func TestMemberRelationEdgesFollowCheckerValidity(t *testing.T) {
25
+ root := t.TempDir()
26
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
27
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export interface Contract {
28
+ run(input: string): string;
29
+ readonly name: string;
30
+ callback: () => void;
31
+ }
32
+ export class Implementation implements Contract {
33
+ run(input: string): string { return input; }
34
+ get name(): string { return "implementation"; }
35
+ callback(): void {}
36
+ }
37
+ export abstract class Base {
38
+ constructor(readonly seed: string) {}
39
+ abstract act(input: string): string;
40
+ property: () => void = () => {};
41
+ }
42
+ export class Derived extends Base {
43
+ constructor() { super("seed"); }
44
+ act(input: string): string { return input; }
45
+ property: () => void = () => {};
46
+ }
47
+ `)
48
+
49
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
50
+ if err != nil {
51
+ t.Fatal(err)
52
+ }
53
+ if len(diags) != 0 {
54
+ t.Fatalf("unexpected load diagnostics: %v", diags)
55
+ }
56
+ defer func() { _ = prog.Close() }()
57
+ if diagnostics := prog.Diagnostics(); len(diagnostics) != 0 {
58
+ t.Fatalf("fixture must be checker-valid: %v", diagnostics)
59
+ }
60
+
61
+ built := Build(prog)
62
+ path := sourceFile(t, prog, "main.ts").FileName()
63
+ assertions := []struct {
64
+ from string
65
+ to string
66
+ origin string
67
+ }{
68
+ {nodeID(path, "Implementation.run", NodeMethod), nodeID(path, "Contract.run", NodeMethod), "implements"},
69
+ {nodeID(path, "Implementation.name", NodeMethod), nodeID(path, "Contract.name", NodeVariable), "implements"},
70
+ {nodeID(path, "Implementation.callback", NodeMethod), nodeID(path, "Contract.callback", NodeVariable), "implements"},
71
+ {nodeID(path, "Derived.act", NodeMethod), nodeID(path, "Base.act", NodeMethod), "overrides"},
72
+ {nodeID(path, "Derived.property", NodeVariable), nodeID(path, "Base.property", NodeVariable), "overrides"},
73
+ }
74
+ for _, assertion := range assertions {
75
+ if got := edgeOrigin(built, assertion.from, assertion.to, EdgeMemberRelation); got != assertion.origin {
76
+ t.Fatalf("%s -> %s: want %q member relation, got %q; edges: %v", assertion.from, assertion.to, assertion.origin, got, built.Edges)
77
+ }
78
+ }
79
+
80
+ derivedConstructor := nodeID(path, "Derived.__constructor", NodeMethod)
81
+ baseConstructor := nodeID(path, "Base.__constructor", NodeMethod)
82
+ if hasEdge(built, derivedConstructor, baseConstructor, EdgeMemberRelation) {
83
+ t.Fatalf("constructors are not member overrides; edges: %v", built.Edges)
84
+ }
85
+ }