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
package/README.md CHANGED
@@ -75,15 +75,17 @@ let total = count;
75
75
 
76
76
  ```text
77
77
  $ npx ttsc --noEmit
78
+ src/index.ts:1:1 - error TS11966: [no-var] Unexpected var, use let or const instead.
79
+
80
+ 1 var count = 3;
81
+ ~~~
82
+
78
83
  src/index.ts:2:5 - error TS17397: [prefer-const] Use const instead of let.
79
84
 
80
85
  2 let total = count;
81
- ~~~~~~~~~~~~~
86
+ ~~~~~
82
87
 
83
- src/index.ts:1:1 - error TS11966: [no-var] Unexpected var, use let or const instead.
84
-
85
- 1 var count = 3;
86
- ~~~~~~~~~~~~~~
88
+ Found 2 errors in the same file, starting at: src/index.ts:1
87
89
  ```
88
90
 
89
91
  Clean the project in place:
@@ -28,9 +28,8 @@ func run() int {
28
28
  flag.Parse()
29
29
 
30
30
  // Resolve the project root the same way LoadProgram does (absolute, then
31
- // tsgo-normalized) so it shares the node file paths' drive-letter case on
32
- // Windows; otherwise the dump's prefix-based relativization would miss and
33
- // leave every path absolute.
31
+ // tsgo-normalized) so the schema-v6 mapper receives the same canonical root
32
+ // grammar and drive-letter case as the compiler's source paths.
34
33
  root := *cwd
35
34
  if abs, err := filepath.Abs(root); err == nil {
36
35
  root = abs
@@ -60,7 +59,6 @@ func run() int {
60
59
  // `ttscgraph dump` is the one that proves the whole contract.
61
60
  data, err := graph.MarshalDump(g, root, *tsconfig, ignored, texts, graph.DumpOrigin{
62
61
  Provenance: graph.NewProvenance(
63
- root,
64
62
  // No version: this tool is built from the tree on demand and never
65
63
  // stamped, and an invented one would be worse than an absent one.
66
64
  graph.Producer{Tool: "graphdump", Typescript: graph.TypescriptVersion()},
@@ -59,12 +59,20 @@ func filterHostArgs(args []string) []string {
59
59
  return filtered
60
60
  }
61
61
 
62
- // splitFlagName strips the leading `--` from a flag argument and reports
63
- // whether the argument carries an inline value (`--foo=value`).
62
+ // splitFlagName strips the leading `--` from a flag argument, lower-cases the
63
+ // result, and reports whether the argument carries an inline value
64
+ // (`--foo=value`).
65
+ //
66
+ // Lower-casing is the same normalization `normalizeFlagToken` in
67
+ // `packages/ttsc/src/flags/schema.ts` applies, and the generated
68
+ // `HostFlagAllowList` keys are produced by it. TypeScript's option parser
69
+ // matches option names case-insensitively, so keying this lookup on the exact
70
+ // spelling would make the Go layer stop recognising a flag the launcher and the
71
+ // compiler both resolve.
64
72
  func splitFlagName(arg string) (string, bool) {
65
73
  name := strings.TrimPrefix(arg, "--")
66
74
  if i := strings.IndexByte(name, '='); i != -1 {
67
- return name[:i], true
75
+ return strings.ToLower(name[:i]), true
68
76
  }
69
- return name, false
77
+ return strings.ToLower(name), false
70
78
  }
@@ -0,0 +1,53 @@
1
+ package main
2
+
3
+ import (
4
+ "reflect"
5
+ "testing"
6
+ )
7
+
8
+ // TestFilterHostArgsResolvesFlagNamesCaseInsensitively verifies the host
9
+ // allow-list lookup applies the same flag-name normalization the schema uses
10
+ // when it generates that allow-list.
11
+ //
12
+ // TypeScript's option parser matches option names case-insensitively, and
13
+ // `normalizeFlagToken` in `packages/ttsc/src/flags/schema.ts` is the one
14
+ // normalization every layer keys off — including `buildGoAllowList`, whose
15
+ // output is `flags_gen.go`. Keying this lookup on the exact spelling while the
16
+ // generated keys are normalized would recreate the split-brain one layer
17
+ // further down: a flag the launcher and the compiler both resolve would be
18
+ // dropped here, silently taking the following token with it.
19
+ //
20
+ // 1. A case-variant known flag survives with its value.
21
+ // 2. A case-variant boolean known flag survives and does not eat the next token.
22
+ // 3. An unknown flag is still dropped in every casing, with its value.
23
+ func TestFilterHostArgsResolvesFlagNamesCaseInsensitively(t *testing.T) {
24
+ cases := []struct {
25
+ name string
26
+ in []string
27
+ want []string
28
+ }{
29
+ {
30
+ name: "keeps a case-variant value flag with its value",
31
+ in: []string{"--TSCONFIG", "tsconfig.json", "--CWD=."},
32
+ want: []string{"--TSCONFIG", "tsconfig.json", "--CWD=."},
33
+ },
34
+ {
35
+ name: "keeps a case-variant boolean flag without eating the next token",
36
+ in: []string{"--NOEMIT", "--tsconfig", "tsconfig.json"},
37
+ want: []string{"--NOEMIT", "--tsconfig", "tsconfig.json"},
38
+ },
39
+ {
40
+ name: "still drops an unknown flag in any casing",
41
+ in: []string{"--tsconfig=tsconfig.json", "--sTrIcT", "true"},
42
+ want: []string{"--tsconfig=tsconfig.json"},
43
+ },
44
+ }
45
+ for _, tc := range cases {
46
+ t.Run(tc.name, func(t *testing.T) {
47
+ got := filterHostArgs(tc.in)
48
+ if !reflect.DeepEqual(got, tc.want) {
49
+ t.Fatalf("filterHostArgs(%v):\n want %v\n got %v", tc.in, tc.want, got)
50
+ }
51
+ })
52
+ }
53
+ }
@@ -17,13 +17,13 @@ var HostFlagAllowList = map[string]bool{
17
17
  "cwd": true,
18
18
  "emit": false,
19
19
  "manifest": true,
20
- "noEmit": false,
21
- "outDir": true,
20
+ "noemit": false,
21
+ "outdir": true,
22
22
  "p": true,
23
23
  "plugins-json": true,
24
24
  "project": true,
25
25
  "quiet": false,
26
- "singleThreaded": false,
26
+ "singlethreaded": false,
27
27
  "tsconfig": true,
28
28
  "tsgo-args": true,
29
29
  "verbose": false,
@@ -37,9 +37,8 @@ func runDump(args []string) int {
37
37
  cwd = resolved
38
38
  }
39
39
  // Resolve the project root the same way LoadProgram does (absolute, then
40
- // tsgo-normalized) so it shares the node file paths' drive-letter case on
41
- // Windows; otherwise the dump's prefix-based relativization would miss and
42
- // leave every path absolute.
40
+ // tsgo-normalized) so the schema-v6 mapper receives the same canonical root
41
+ // grammar and drive-letter case as the compiler's source paths.
43
42
  if abs, err := filepath.Abs(cwd); err == nil {
44
43
  cwd = abs
45
44
  }
@@ -60,7 +59,7 @@ func runDump(args []string) int {
60
59
  g := graph.Build(prog)
61
60
  ignored := graph.GitIgnoredFiles(cwd, g)
62
61
  texts := graph.SourceTexts(prog)
63
- origin, err := dumpOrigin(prog, cwd, texts)
62
+ origin, err := dumpOrigin(prog, texts)
64
63
  if err != nil {
65
64
  fmt.Fprintf(stderr, "ttscgraph: %v\n", err)
66
65
  return 1
@@ -90,7 +89,7 @@ func runDump(args []string) int {
90
89
  // saw the project, so it carries the same provenance a served snapshot does:
91
90
  // without it the file is a pile of facts with no way to tell which program, or
92
91
  // which day, they describe.
93
- func dumpOrigin(prog *driver.Program, cwd string, texts map[string]string) (graph.DumpOrigin, error) {
92
+ func dumpOrigin(prog *driver.Program, texts map[string]string) (graph.DumpOrigin, error) {
94
93
  configs, err := parsedConfigs(prog)
95
94
  if err != nil {
96
95
  return graph.DumpOrigin{}, err
@@ -105,7 +104,6 @@ func dumpOrigin(prog *driver.Program, cwd string, texts map[string]string) (grap
105
104
  }
106
105
  return graph.DumpOrigin{
107
106
  Provenance: graph.NewProvenance(
108
- cwd,
109
107
  serveProducer(),
110
108
  fullSnapshotCapabilities,
111
109
  fileDigests(configHashes),
@@ -113,6 +111,6 @@ func dumpOrigin(prog *driver.Program, cwd string, texts map[string]string) (grap
113
111
  texts,
114
112
  diskDigests,
115
113
  ),
116
- Diagnostics: graph.NewDiagnostics(prog, cwd),
114
+ Diagnostics: graph.NewDiagnostics(prog),
117
115
  }, nil
118
116
  }
@@ -0,0 +1,118 @@
1
+ package main
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "path/filepath"
7
+ "testing"
8
+
9
+ "github.com/samchon/ttsc/packages/ttsc/internal/graph"
10
+ )
11
+
12
+ // TestRunDumpEmitsOnlyCheckerVerifiedMemberRelations verifies the shipped dump
13
+ // command serializes native member facts and does not revive a rejected pair.
14
+ //
15
+ // The graph reader no longer synthesizes member edges from equal names, so the
16
+ // command boundary must prove all three layers together: compiler diagnostics,
17
+ // native pair validation, and the public implements/overrides wire vocabulary.
18
+ //
19
+ // 1. Dump a program with one valid and one TS2416-invalid implementation.
20
+ // 2. Locate the container and member nodes in the serialized document.
21
+ // 3. Require the valid member edge and its evidence, reject the invalid edge,
22
+ // and ensure the internal native edge kind never reaches JSON.
23
+ func TestRunDumpEmitsOnlyCheckerVerifiedMemberRelations(t *testing.T) {
24
+ root := t.TempDir()
25
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
26
+ "compilerOptions": {
27
+ "target": "ES2022",
28
+ "module": "commonjs",
29
+ "strict": true,
30
+ "rootDir": "src",
31
+ "outDir": "dist"
32
+ },
33
+ "files": ["src/main.ts"]
34
+ }
35
+ `)
36
+ writeGraphFile(t, filepath.Join(root, "src", "main.ts"), `export interface Contract {
37
+ execute(input: number): void;
38
+ }
39
+ export class Good implements Contract {
40
+ execute(input: number): void {}
41
+ }
42
+ export class Bad implements Contract {
43
+ execute(input: string): void {}
44
+ }
45
+ `)
46
+
47
+ oldStdout, oldStderr := stdout, stderr
48
+ defer func() { stdout, stderr = oldStdout, oldStderr }()
49
+ var out, errBuf bytes.Buffer
50
+ stdout, stderr = &out, &errBuf
51
+
52
+ if code := run([]string{"dump", "--cwd", root, "--tsconfig", "tsconfig.json"}); code != 0 {
53
+ t.Fatalf("run dump exit = %d, want 0; stderr:\n%s", code, errBuf.String())
54
+ }
55
+ if bytes.Contains(out.Bytes(), []byte("member-relation")) {
56
+ t.Fatalf("internal edge kind leaked into the public dump:\n%s", out.String())
57
+ }
58
+
59
+ var dump graph.Dump
60
+ if err := json.Unmarshal(out.Bytes(), &dump); err != nil {
61
+ t.Fatalf("dump output is not valid JSON: %v\n%s", err, out.String())
62
+ }
63
+ seen2416 := false
64
+ for _, diagnostic := range dump.Diagnostics {
65
+ seen2416 = seen2416 || diagnostic.Code == 2416
66
+ }
67
+ if !seen2416 {
68
+ t.Fatalf("fixture must retain TS2416 in the same snapshot: %v", dump.Diagnostics)
69
+ }
70
+
71
+ node := func(name, kind string) *graph.DumpNode {
72
+ t.Helper()
73
+ for i := range dump.Nodes {
74
+ candidate := &dump.Nodes[i]
75
+ declaredName := candidate.QualifiedName
76
+ if declaredName == "" {
77
+ declaredName = candidate.Name
78
+ }
79
+ if declaredName == name && candidate.Kind == kind {
80
+ return candidate
81
+ }
82
+ }
83
+ t.Fatalf("missing %s %s; nodes: %v", kind, name, dump.Nodes)
84
+ return nil
85
+ }
86
+ contract := node("Contract", "interface")
87
+ good := node("Good", "class")
88
+ contractExecute := node("Contract.execute", "method")
89
+ goodExecute := node("Good.execute", "method")
90
+ badExecute := node("Bad.execute", "method")
91
+
92
+ countEdge := func(from, to, kind string) int {
93
+ count := 0
94
+ for _, edge := range dump.Edges {
95
+ if edge.From == from && edge.To == to && edge.Kind == kind {
96
+ count++
97
+ }
98
+ }
99
+ return count
100
+ }
101
+ if count := countEdge(good.ID, contract.ID, "implements"); count != 1 {
102
+ t.Fatalf("container implementation edge count = %d, want 1; edges: %v", count, dump.Edges)
103
+ }
104
+ if count := countEdge(goodExecute.ID, contractExecute.ID, "implements"); count != 1 {
105
+ t.Fatalf("valid member implementation edge count = %d, want 1; edges: %v", count, dump.Edges)
106
+ }
107
+ if count := countEdge(badExecute.ID, contractExecute.ID, "implements"); count != 0 {
108
+ t.Fatalf("checker-invalid member implementation edge count = %d, want 0; edges: %v", count, dump.Edges)
109
+ }
110
+ for _, edge := range dump.Edges {
111
+ if edge.From == goodExecute.ID && edge.To == contractExecute.ID && edge.Kind == "implements" {
112
+ if edge.Evidence == nil || goodExecute.Evidence == nil ||
113
+ edge.Evidence.StartLine != goodExecute.Evidence.StartLine {
114
+ t.Fatalf("member edge evidence does not identify Good.execute: edge=%v node=%v", edge.Evidence, goodExecute.Evidence)
115
+ }
116
+ }
117
+ }
118
+ }