ttsc 0.19.3 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -34
- package/cmd/graphdump/main.go +2 -4
- package/cmd/ttsc/filter.go +12 -4
- package/cmd/ttsc/filter_resolves_flag_names_case_insensitively_test.go +53 -0
- package/cmd/ttsc/flags_gen.go +3 -3
- package/cmd/ttscgraph/dump.go +5 -7
- package/cmd/ttscgraph/run_dump_emits_only_checker_verified_member_relations_test.go +118 -0
- package/cmd/ttscgraph/serve.go +58 -308
- package/cmd/ttscgraph/serve_session_keeps_base_url_fallback_unchanged_test.go +49 -0
- package/cmd/ttscgraph/serve_session_keeps_esm_directory_candidate_unchanged_test.go +61 -0
- package/cmd/ttscgraph/serve_session_keeps_exports_blocked_file_candidates_unchanged_test.go +64 -0
- package/cmd/ttscgraph/serve_session_keeps_inactive_conditional_export_unchanged_test.go +66 -0
- package/cmd/ttscgraph/serve_session_keeps_lower_priority_module_candidate_unchanged_test.go +67 -0
- package/cmd/ttscgraph/serve_session_keeps_lower_priority_paths_pattern_unchanged_test.go +58 -0
- package/cmd/ttscgraph/serve_session_keeps_nonlongest_root_dirs_candidate_unchanged_test.go +54 -0
- package/cmd/ttscgraph/serve_session_reloads_commonjs_package_entry_for_esm_test.go +50 -0
- package/cmd/ttscgraph/serve_session_reloads_module_suffix_candidate_test.go +41 -0
- package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +216 -0
- package/cmd/ttscgraph/serve_session_retries_failed_dump_test.go +103 -0
- package/driver/graph.go +12 -6
- package/driver/lsp.go +30 -0
- package/driver/program.go +37 -1
- package/driver/resolution_candidates.go +819 -0
- package/driver/windowsjunction/windowsjunction.go +40 -0
- package/go.mod +2 -0
- package/internal/graph/build.go +45 -11
- package/internal/graph/dump.go +276 -100
- package/internal/graph/dump_checkout_paths_are_stable_test.go +210 -0
- package/internal/graph/dump_diagnostics_ride_the_generation_that_built_it_test.go +9 -3
- package/internal/graph/dump_paths.go +172 -0
- package/internal/graph/dump_paths_are_portable_and_injective_test.go +93 -0
- package/internal/graph/dump_uses_ecma_line_terminators_test.go +87 -0
- package/internal/graph/duplicate_variable_object_members_keep_first_declaration_test.go +61 -0
- package/internal/graph/edges.go +208 -0
- package/internal/graph/graph.go +143 -5
- package/internal/graph/line_model.go +122 -0
- package/internal/graph/member_relation_edges_follow_checker_validity_test.go +85 -0
- package/internal/graph/member_relation_edges_preserve_checker_boundaries_test.go +191 -0
- package/internal/graph/member_relation_edges_reject_checker_invalid_pairs_test.go +119 -0
- package/internal/graph/node_ids_preserve_hash_bearing_components_test.go +41 -0
- package/internal/graph/node_modifiers_emit_union_strings_test.go +4 -1
- package/internal/graph/object_literal_members_ride_on_the_snapshot_test.go +192 -0
- package/internal/graph/object_member_signatures_preserve_literal_whitespace_test.go +67 -0
- package/internal/graph/object_members.go +229 -0
- package/internal/graph/provenance.go +88 -30
- package/internal/graph/source_texts_cover_every_resident_program_source_test.go +73 -0
- package/internal/graphsymbols/position_helpers_follow_ecma_line_terminators_test.go +66 -0
- package/internal/graphsymbols/provider.go +26 -91
- package/internal/lspserver/lsp_clean_didopen_refreshes_compiler_state_test.go +144 -0
- package/internal/lspserver/lsp_completion.go +109 -0
- package/internal/lspserver/lsp_completion_gate_never_refuses_a_matching_line_test.go +50 -0
- package/internal/lspserver/lsp_completion_hints_longest_trigger_wins_test.go +149 -0
- package/internal/lspserver/lsp_completion_hints_nearest_trigger_wins_test.go +37 -0
- package/internal/lspserver/lsp_completion_merges_into_upstream_response_test.go +149 -0
- package/internal/lspserver/lsp_completion_nearest_hint_replaces_nearest_filter_test.go +43 -0
- package/internal/lspserver/lsp_completion_preserves_completion_list_extensions_test.go +99 -0
- package/internal/lspserver/lsp_completion_refuses_hints_inside_a_string_literal_test.go +69 -0
- package/internal/lspserver/lsp_completion_replaces_full_filter_in_rank_order_test.go +85 -0
- package/internal/lspserver/lsp_completion_request.go +455 -0
- package/internal/lspserver/lsp_completion_scope.go +530 -0
- package/internal/lspserver/lsp_completion_scope_bench_test.go +41 -0
- package/internal/lspserver/lsp_completion_scope_classifies_regex_statement_and_operator_boundaries_test.go +39 -0
- package/internal/lspserver/lsp_completion_scope_ignores_jsdoc_bytes_in_literals_test.go +108 -0
- package/internal/lspserver/lsp_completion_scope_refuses_regex_hints_test.go +52 -0
- package/internal/lspserver/lsp_completion_trigger_uses_last_rune_test.go +24 -0
- package/internal/lspserver/lsp_deleted_watched_file_withdraws_plugin_diagnostics_test.go +110 -0
- package/internal/lspserver/lsp_didsave_keeps_the_incremental_changed_uri_path_test.go +78 -0
- package/internal/lspserver/lsp_hints_absence_is_not_an_error_test.go +46 -0
- package/internal/lspserver/lsp_hints_refresh_coalesces_concurrent_requests_test.go +82 -0
- package/internal/lspserver/lsp_hints_refresh_ignores_a_superseded_generation_test.go +40 -0
- package/internal/lspserver/lsp_hints_refresh_keeps_each_producer_isolated_test.go +64 -0
- package/internal/lspserver/lsp_hints_refresh_replaces_the_session_snapshot_test.go +66 -0
- package/internal/lspserver/lsp_hints_refresh_scheduled_by_editor_events_test.go +90 -0
- package/internal/lspserver/lsp_hints_refresh_serves_completion_while_running_test.go +91 -0
- package/internal/lspserver/lsp_index_byte_from_bounds_its_search_start_test.go +38 -0
- package/internal/lspserver/lsp_initialize_constrains_position_encoding_to_utf16_test.go +205 -0
- package/internal/lspserver/lsp_late_completion_trigger_is_reported_once_test.go +105 -0
- package/internal/lspserver/lsp_native_completion_hints_decode_flat_lint_wire_test.go +66 -0
- package/internal/lspserver/lsp_native_completion_hints_grouped_compatibility_boundary_test.go +60 -0
- package/internal/lspserver/lsp_native_plugin_source.go +315 -2
- package/internal/lspserver/lsp_native_resident.go +304 -0
- package/internal/lspserver/lsp_plugin.go +85 -8
- package/internal/lspserver/lsp_plugin_completion_resolve_stays_local_test.go +82 -0
- package/internal/lspserver/lsp_position_conversions_span_astral_and_combining_runes_test.go +119 -0
- package/internal/lspserver/lsp_proxy.go +601 -9
- package/internal/lspserver/lsp_refresh.go +70 -0
- package/internal/lspserver/lsp_resident_falls_back_when_serve_unsupported_test.go +43 -0
- package/internal/lspserver/lsp_resident_maps_verb_args_test.go +34 -0
- package/internal/lspserver/lsp_server.go +13 -1
- package/internal/lspserver/lsp_watched_file_changes_reach_compiler_caches_test.go +172 -0
- package/lib/TtscCompiler.d.ts +3 -1
- package/lib/TtscCompiler.js +5 -1
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/TtscService.d.ts +13 -3
- package/lib/TtscService.js +6 -6
- package/lib/TtscService.js.map +1 -1
- package/lib/compiler/internal/project/readConfigJson.d.ts +30 -0
- package/lib/compiler/internal/project/readConfigJson.js +208 -0
- package/lib/compiler/internal/project/readConfigJson.js.map +1 -0
- package/lib/compiler/internal/project/readProjectConfig.js +53 -149
- package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
- package/lib/compiler/internal/residentTransformProcess.d.ts +15 -2
- package/lib/compiler/internal/residentTransformProcess.js +159 -39
- package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +6 -0
- package/lib/compiler/internal/runBuild.js +143 -44
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.d.ts +5 -1
- package/lib/compiler/internal/startResidentTransform.js +3 -1
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.js +12 -2
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/flags/parser.d.ts +19 -0
- package/lib/flags/parser.js +78 -36
- package/lib/flags/parser.js.map +1 -1
- package/lib/flags/schema.d.ts +49 -11
- package/lib/flags/schema.js +94 -22
- package/lib/flags/schema.js.map +1 -1
- package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +5 -0
- package/lib/internal/assertSafeExplicitCacheDirectory.js +26 -0
- package/lib/internal/assertSafeExplicitCacheDirectory.js.map +1 -0
- package/lib/launcher/internal/runTtsc.js +107 -69
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +28 -41
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +15 -0
- package/lib/launcher/internal/runtimeHooks.js +18 -1
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/launcher/internal/watchTopology.d.ts +43 -0
- package/lib/launcher/internal/watchTopology.js +360 -0
- package/lib/launcher/internal/watchTopology.js.map +1 -0
- package/lib/plugin/internal/buildSourcePlugin.d.ts +14 -3
- package/lib/plugin/internal/buildSourcePlugin.js +247 -61
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.js +63 -2
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/structures/ITtscCompilerTransformation.d.ts +16 -0
- package/lib/structures/internal/ITtscParsedProjectConfig.d.ts +2 -0
- package/package.json +8 -8
- package/shim/ast/shim.go +48 -3
- package/shim/astnav/go.mod +14 -0
- package/shim/astnav/go.sum +20 -0
- package/shim/astnav/shim.go +15 -0
- package/shim/checker/shim.go +100 -0
- package/shim/core/shim.go +47 -0
- package/shim/diagnosticwriter/lint.go +50 -0
- package/src/TtscCompiler.ts +5 -1
- package/src/TtscService.ts +37 -10
- package/src/compiler/internal/project/readConfigJson.ts +209 -0
- package/src/compiler/internal/project/readProjectConfig.ts +62 -158
- package/src/compiler/internal/residentTransformProcess.ts +197 -39
- package/src/compiler/internal/runBuild.ts +169 -47
- package/src/compiler/internal/startResidentTransform.ts +12 -1
- package/src/compiler/internal/transformProjectInMemory.ts +12 -1
- package/src/flags/parser.ts +113 -42
- package/src/flags/schema.ts +113 -31
- package/src/internal/assertSafeExplicitCacheDirectory.ts +29 -0
- package/src/launcher/internal/runTtsc.ts +117 -68
- package/src/launcher/internal/runTtsx.ts +29 -44
- package/src/launcher/internal/runtimeHooks.ts +30 -2
- package/src/launcher/internal/watchTopology.ts +466 -0
- package/src/plugin/internal/buildSourcePlugin.ts +326 -64
- package/src/plugin/internal/loadProjectPlugins.ts +80 -2
- package/src/structures/ITtscCompilerTransformation.ts +17 -0
- package/src/structures/internal/ITtscParsedProjectConfig.ts +2 -0
- package/tools/shim_audit/baseline.json +0 -3
- package/tools/shim_audit/main.go +42 -14
- package/tools/shim_audit/shim_dir_coverage_test.go +25 -0
- package/utility/flags_gen.go +3 -3
- package/utility/host.go +9 -3
package/internal/graph/edges.go
CHANGED
|
@@ -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
|
package/internal/graph/graph.go
CHANGED
|
@@ -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
|
|
91
|
-
End
|
|
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
|
+
}
|