ttsc 0.26.2 → 0.28.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 +9 -0
  2. package/cmd/graphbench/main.go +26 -16
  3. package/cmd/graphdump/main.go +37 -12
  4. package/cmd/graphdump/run_declares_the_capability_its_output_carries_test.go +137 -0
  5. package/cmd/graphdump/run_reports_an_unknown_flag_without_exiting_the_process_test.go +56 -0
  6. package/cmd/graphdump/run_reports_an_unloadable_project_without_printing_a_dump_test.go +46 -0
  7. package/cmd/ttsc/api_transform.go +28 -3
  8. package/cmd/ttscgraph/auxiliary_identity_state_tracks_retargets_not_source_contents_test.go +85 -0
  9. package/cmd/ttscgraph/disk_realpath_other.go +16 -0
  10. package/cmd/ttscgraph/disk_realpath_windows.go +56 -0
  11. package/cmd/ttscgraph/dump.go +43 -11
  12. package/cmd/ttscgraph/run_dump_claims_artifacts_only_when_it_asked_test.go +135 -0
  13. package/cmd/ttscgraph/run_dump_declares_the_capability_its_output_carries_test.go +96 -0
  14. package/cmd/ttscgraph/serve.go +357 -60
  15. package/cmd/ttscgraph/serve_adopts_a_republished_artifact_set_test.go +348 -0
  16. package/cmd/ttscgraph/serve_projects_an_artifact_no_source_owns_test.go +90 -0
  17. package/cmd/ttscgraph/serve_shards.go +31 -9
  18. package/driver/config_discovery.go +121 -0
  19. package/driver/emit_plugin.go +77 -11
  20. package/driver/graph.go +64 -11
  21. package/driver/input_observation.go +273 -0
  22. package/driver/input_observation_test.go +149 -0
  23. package/driver/plugins.go +506 -15
  24. package/driver/program.go +54 -1
  25. package/driver/resolution_candidates.go +13 -5
  26. package/driver/rewrite.go +45 -2
  27. package/driver/transform_dependencies.go +283 -0
  28. package/driver/transform_dependencies_declares_every_file_without_a_contributor_test.go +28 -0
  29. package/driver/transform_dependencies_drops_a_self_reported_dependency_test.go +21 -0
  30. package/driver/transform_dependencies_keys_a_reported_path_like_the_envelope_test.go +46 -0
  31. package/driver/transform_dependencies_requires_every_contributor_to_declare_test.go +38 -0
  32. package/driver/transform_dependencies_stays_silent_for_an_undeclared_contributor_test.go +23 -0
  33. package/driver/transform_dependencies_withdraws_completeness_after_an_unusable_dependency_test.go +37 -0
  34. package/internal/graph/artifacts.go +230 -0
  35. package/internal/graph/artifacts_index_what_a_plugin_published_test.go +154 -0
  36. package/internal/graph/artifacts_survive_a_partial_rebuild_test.go +89 -0
  37. package/internal/graph/build.go +35 -0
  38. package/internal/graph/build_releases_the_scratch_it_no_longer_reads_test.go +118 -0
  39. package/internal/graph/doc_refs_are_the_use_the_checker_already_counts_test.go +93 -0
  40. package/internal/graph/doc_refs_resolve_documentation_links_test.go +335 -0
  41. package/internal/graph/doc_tags.go +416 -0
  42. package/internal/graph/doc_tags_are_claimed_before_they_are_read_test.go +131 -0
  43. package/internal/graph/doc_tags_capture_every_unrecognized_tag_test.go +165 -0
  44. package/internal/graph/doc_tags_read_only_what_the_parser_attached_test.go +97 -0
  45. package/internal/graph/doc_tags_stop_at_the_external_boundary_test.go +59 -0
  46. package/internal/graph/doc_tags_survive_line_endings_and_repeated_blocks_test.go +146 -0
  47. package/internal/graph/dump.go +60 -13
  48. package/internal/graph/dump_checkout_paths_are_stable_test.go +2 -2
  49. package/internal/graph/dump_paths.go +2 -2
  50. package/internal/graph/dump_paths_are_portable_and_injective_test.go +1 -1
  51. package/internal/graph/edge_kinds_names_every_declared_kind_test.go +49 -0
  52. package/internal/graph/edges.go +1 -0
  53. package/internal/graph/graph.go +79 -4
  54. package/internal/graph/provenance.go +29 -2
  55. package/lib/TtscCompiler.d.ts +12 -11
  56. package/lib/TtscCompiler.js +44 -29
  57. package/lib/TtscCompiler.js.map +1 -1
  58. package/lib/compiler/internal/captureProcessOutput.js +2 -2
  59. package/lib/compiler/internal/captureProcessOutput.js.map +1 -1
  60. package/lib/compiler/internal/compileProjectInMemory.js +4 -3
  61. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  62. package/lib/compiler/internal/project/createNativeProjectContextArgs.d.ts +9 -0
  63. package/lib/compiler/internal/project/createNativeProjectContextArgs.js +16 -4
  64. package/lib/compiler/internal/project/createNativeProjectContextArgs.js.map +1 -1
  65. package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +2 -0
  66. package/lib/compiler/internal/resolveEmittedJavaScript.js +3 -0
  67. package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
  68. package/lib/compiler/internal/runBuild.d.ts +1 -1
  69. package/lib/compiler/internal/runBuild.js +80 -34
  70. package/lib/compiler/internal/runBuild.js.map +1 -1
  71. package/lib/compiler/internal/runSingleFileEmit.js +2 -2
  72. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  73. package/lib/compiler/internal/sharedHostHelpers.d.ts +28 -0
  74. package/lib/compiler/internal/sharedHostHelpers.js +39 -0
  75. package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
  76. package/lib/compiler/internal/startResidentTransform.js +12 -4
  77. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  78. package/lib/compiler/internal/transformProjectInMemory.d.ts +3 -0
  79. package/lib/compiler/internal/transformProjectInMemory.js +239 -35
  80. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  81. package/lib/flags/schema.js +12 -5
  82. package/lib/flags/schema.js.map +1 -1
  83. package/lib/index.d.ts +1 -0
  84. package/lib/index.js +1 -0
  85. package/lib/index.js.map +1 -1
  86. package/lib/internal/createCanonicalTempDirectory.d.ts +17 -0
  87. package/lib/internal/createCanonicalTempDirectory.js +42 -0
  88. package/lib/internal/createCanonicalTempDirectory.js.map +1 -0
  89. package/lib/internal/projectInputPathIdentity.d.ts +3 -0
  90. package/lib/internal/projectInputPathIdentity.js +11 -5
  91. package/lib/internal/projectInputPathIdentity.js.map +1 -1
  92. package/lib/internal/resolveNodeBinary.d.ts +14 -0
  93. package/lib/internal/resolveNodeBinary.js +150 -0
  94. package/lib/internal/resolveNodeBinary.js.map +1 -0
  95. package/lib/internal/resolveSafeCacheCleanupTargets.d.ts +15 -0
  96. package/lib/internal/resolveSafeCacheCleanupTargets.js +61 -0
  97. package/lib/internal/resolveSafeCacheCleanupTargets.js.map +1 -0
  98. package/lib/launcher/internal/prepareExecution.d.ts +3 -0
  99. package/lib/launcher/internal/prepareExecution.js +42 -14
  100. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  101. package/lib/launcher/internal/runTtsc.js +25 -13
  102. package/lib/launcher/internal/runTtsc.js.map +1 -1
  103. package/lib/launcher/internal/runTtscserver.js +18 -3
  104. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  105. package/lib/launcher/internal/runTtsx.js +2 -0
  106. package/lib/launcher/internal/runTtsx.js.map +1 -1
  107. package/lib/launcher/internal/runtimeHooks.d.ts +47 -3
  108. package/lib/launcher/internal/runtimeHooks.js +746 -64
  109. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  110. package/lib/plugin/internal/buildSourcePlugin.d.ts +62 -8
  111. package/lib/plugin/internal/buildSourcePlugin.js +754 -72
  112. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  113. package/lib/plugin/internal/capabilityResolutionCache.d.ts +76 -0
  114. package/lib/plugin/internal/capabilityResolutionCache.js +224 -0
  115. package/lib/plugin/internal/capabilityResolutionCache.js.map +1 -0
  116. package/lib/plugin/internal/loadProjectPlugins.d.ts +17 -5
  117. package/lib/plugin/internal/loadProjectPlugins.js +1339 -77
  118. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  119. package/lib/plugin/resolveCapabilityPlugins.d.ts +55 -0
  120. package/lib/plugin/resolveCapabilityPlugins.js +118 -0
  121. package/lib/plugin/resolveCapabilityPlugins.js.map +1 -0
  122. package/lib/register.d.ts +1 -0
  123. package/lib/register.js +46 -0
  124. package/lib/register.js.map +1 -0
  125. package/lib/structures/ITtscCompilerContext.d.ts +6 -3
  126. package/lib/structures/ITtscCompilerTransformation.d.ts +38 -0
  127. package/lib/structures/ITtscPlugin.d.ts +33 -0
  128. package/package.json +16 -9
  129. package/shim/ast/parent.go +16 -3
  130. package/shim/checker/enums_gen.go +11 -0
  131. package/shim/checker/go.mod +9 -0
  132. package/shim/checker/go.sum +22 -0
  133. package/shim/checker/shim.go +78 -0
  134. package/shim/compiler/emithook.go +10 -0
  135. package/shim/compiler/incremental.go +45 -8
  136. package/src/TtscCompiler.ts +55 -33
  137. package/src/compiler/internal/captureProcessOutput.ts +3 -2
  138. package/src/compiler/internal/compileProjectInMemory.ts +4 -3
  139. package/src/compiler/internal/project/createNativeProjectContextArgs.ts +19 -4
  140. package/src/compiler/internal/resolveEmittedJavaScript.ts +6 -0
  141. package/src/compiler/internal/runBuild.ts +102 -39
  142. package/src/compiler/internal/runSingleFileEmit.ts +2 -2
  143. package/src/compiler/internal/sharedHostHelpers.ts +44 -0
  144. package/src/compiler/internal/startResidentTransform.ts +12 -3
  145. package/src/compiler/internal/transformProjectInMemory.ts +410 -39
  146. package/src/flags/schema.ts +12 -5
  147. package/src/index.ts +1 -0
  148. package/src/internal/createCanonicalTempDirectory.ts +53 -0
  149. package/src/internal/projectInputPathIdentity.ts +17 -4
  150. package/src/internal/resolveNodeBinary.ts +173 -0
  151. package/src/internal/resolveSafeCacheCleanupTargets.ts +85 -0
  152. package/src/launcher/internal/prepareExecution.ts +50 -14
  153. package/src/launcher/internal/runTtsc.ts +24 -13
  154. package/src/launcher/internal/runTtscserver.ts +18 -3
  155. package/src/launcher/internal/runTtsx.ts +2 -0
  156. package/src/launcher/internal/runtimeHooks.ts +934 -68
  157. package/src/plugin/internal/buildSourcePlugin.ts +984 -79
  158. package/src/plugin/internal/capabilityResolutionCache.ts +305 -0
  159. package/src/plugin/internal/loadProjectPlugins.ts +1718 -104
  160. package/src/plugin/resolveCapabilityPlugins.ts +171 -0
  161. package/src/register.ts +47 -0
  162. package/src/structures/ITtscCompilerContext.ts +6 -3
  163. package/src/structures/ITtscCompilerTransformation.ts +46 -0
  164. package/src/structures/ITtscPlugin.ts +36 -0
  165. package/tools/shim_audit/baseline.json +0 -2
  166. package/utility/host.go +18 -3
  167. package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +0 -5
  168. package/lib/internal/assertSafeExplicitCacheDirectory.js +0 -26
  169. package/lib/internal/assertSafeExplicitCacheDirectory.js.map +0 -1
  170. package/src/internal/assertSafeExplicitCacheDirectory.ts +0 -29
@@ -23,6 +23,11 @@ func runDump(args []string) int {
23
23
  cwdFlag := fs.String("cwd", "", "project root (defaults to process cwd)")
24
24
  tsconfigFlag := fs.String("tsconfig", "tsconfig.json", "project tsconfig path")
25
25
  prettyFlag := fs.Bool("pretty", false, "indent the JSON output")
26
+ artifactsFlag := fs.String(
27
+ "artifacts",
28
+ "",
29
+ "path to the artifacts a plugin published (JSON); absent or missing means none",
30
+ )
26
31
  if err := fs.Parse(args); err != nil {
27
32
  return 2
28
33
  }
@@ -37,7 +42,7 @@ func runDump(args []string) int {
37
42
  cwd = resolved
38
43
  }
39
44
  // Resolve the project root the same way LoadProgram does (absolute, then
40
- // tsgo-normalized) so the schema-v6 mapper receives the same canonical root
45
+ // tsgo-normalized) so the dump path mapper receives the same canonical root
41
46
  // grammar and drive-letter case as the compiler's source paths.
42
47
  if abs, err := filepath.Abs(cwd); err == nil {
43
48
  cwd = abs
@@ -57,9 +62,19 @@ func runDump(args []string) int {
57
62
  defer func() { _ = prog.Close() }()
58
63
 
59
64
  g := graph.Build(prog)
65
+ // The artifacts arrive from a plugin that parsed documents this Program never
66
+ // read, so they are a second producer's facts and the origin says so. Applying
67
+ // them before the dump is what turns a citation of a document section from a
68
+ // token into a relation.
69
+ artifacts, err := graph.LoadArtifacts(strings.TrimSpace(*artifactsFlag))
70
+ if err != nil {
71
+ fmt.Fprintf(stderr, "ttscgraph: could not read the published artifacts: %v\n", err)
72
+ return 1
73
+ }
74
+ graph.ApplyArtifacts(g, artifacts)
60
75
  ignored := graph.GitIgnoredFiles(cwd, g)
61
76
  texts := graph.SourceTexts(prog)
62
- origin, err := dumpOrigin(prog, texts)
77
+ origin, err := dumpOrigin(prog, texts, strings.TrimSpace(*artifactsFlag) != "")
63
78
  if err != nil {
64
79
  fmt.Fprintf(stderr, "ttscgraph: %v\n", err)
65
80
  return 1
@@ -89,7 +104,16 @@ func runDump(args []string) int {
89
104
  // saw the project, so it carries the same provenance a served snapshot does:
90
105
  // without it the file is a pile of facts with no way to tell which program, or
91
106
  // which day, they describe.
92
- func dumpOrigin(prog *driver.Program, texts map[string]string) (graph.DumpOrigin, error) {
107
+ // dumpOrigin builds the provenance for one dump. askedForArtifacts is what
108
+ // turns the artifact capability from a guess into a claim: a producer that was
109
+ // never pointed at a publisher did not look, and saying it did would make a
110
+ // project with no artifacts indistinguishable from one whose artifacts were
111
+ // never requested.
112
+ func dumpOrigin(
113
+ prog *driver.Program,
114
+ texts map[string]string,
115
+ askedForArtifacts bool,
116
+ ) (graph.DumpOrigin, error) {
93
117
  configs, err := parsedConfigs(prog)
94
118
  if err != nil {
95
119
  return graph.DumpOrigin{}, err
@@ -102,15 +126,23 @@ func dumpOrigin(prog *driver.Program, texts map[string]string) (graph.DumpOrigin
102
126
  if err != nil {
103
127
  return graph.DumpOrigin{}, err
104
128
  }
129
+ capabilities := fullSnapshotCapabilities
130
+ if askedForArtifacts {
131
+ capabilities = append(append([]string{}, capabilities...), graph.CapabilityArtifactNodes)
132
+ }
133
+ provenance := graph.NewProvenance(
134
+ serveProducer(),
135
+ capabilities,
136
+ fileDigests(configHashes),
137
+ rootFileEntries(projectRootFilesFromConfigs(configs, false)),
138
+ texts,
139
+ diskDigests,
140
+ )
141
+ if askedForArtifacts {
142
+ provenance.ArtifactProducer = &graph.Producer{Tool: "@ttsc/lint graph-nodes"}
143
+ }
105
144
  return graph.DumpOrigin{
106
- Provenance: graph.NewProvenance(
107
- serveProducer(),
108
- fullSnapshotCapabilities,
109
- fileDigests(configHashes),
110
- rootFileEntries(projectRootFilesFromConfigs(configs, false)),
111
- texts,
112
- diskDigests,
113
- ),
145
+ Provenance: provenance,
114
146
  Diagnostics: graph.NewDiagnostics(prog),
115
147
  }, nil
116
148
  }
@@ -0,0 +1,135 @@
1
+ package main
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "os"
7
+ "path/filepath"
8
+ "slices"
9
+ "strings"
10
+ "testing"
11
+
12
+ "github.com/samchon/ttsc/packages/ttsc/internal/graph"
13
+ )
14
+
15
+ // TestRunDumpClaimsArtifactsOnlyWhenItAsked verifies that the artifact
16
+ // capability is a statement about what this producer did, not about what it
17
+ // found.
18
+ //
19
+ // The two states a consumer must distinguish are "this project publishes no
20
+ // artifacts" and "this producer was never pointed at a publisher". Both are the
21
+ // same absent nodes, so only the claim separates them — and a producer that
22
+ // claimed unconditionally would make a project with no lint install look like a
23
+ // project whose documents cite nothing. The second producer is named for the
24
+ // same reason: these facts did not come from this Program, and the dump's
25
+ // one-generation contract stays honest by saying so rather than by hiding it.
26
+ //
27
+ // 1. Dump a project with no `--artifacts`, and assert neither the claim nor the
28
+ // second producer appears, and that the citation stays a token.
29
+ // 2. Dump the same project with a published set, and assert both appear and the
30
+ // citation resolved to a node carrying its heading and its line.
31
+ func TestRunDumpClaimsArtifactsOnlyWhenItAsked(t *testing.T) {
32
+ root := t.TempDir()
33
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
34
+ "compilerOptions": { "target": "ES2022", "module": "commonjs", "strict": true },
35
+ "files": ["src/main.ts"]
36
+ }
37
+ `)
38
+ writeGraphFile(t, filepath.Join(root, "src", "main.ts"), `/** @evidence docs/sale.md#pricing States the rule. */
39
+ export function priced(): void {}
40
+ `)
41
+
42
+ plain := decodeArtifactDump(t, runDumpForArtifacts(t, root, ""))
43
+ if slices.Contains(plain.Provenance.Capabilities, graph.CapabilityArtifactNodes) {
44
+ t.Fatalf("a dump that was never pointed at a publisher claimed %q", graph.CapabilityArtifactNodes)
45
+ }
46
+ if plain.Provenance.ArtifactProducer != nil {
47
+ t.Fatal("a dump with no artifacts named a second producer")
48
+ }
49
+ for _, node := range plain.Nodes {
50
+ if node.ID == "docs/sale.md#pricing" {
51
+ t.Fatal("an unrequested dump fabricated an artifact node")
52
+ }
53
+ }
54
+
55
+ published := filepath.Join(root, "artifacts.json")
56
+ if err := os.WriteFile(published, []byte(`[
57
+ {"address":"docs/sale.md","kind":"markdown_document","readable":"Sale","file":"docs/sale.md","line":1},
58
+ {"address":"docs/sale.md#pricing","kind":"markdown_section","readable":"Pricing","parent":"docs/sale.md","file":"docs/sale.md","line":7}
59
+ ]`), 0o644); err != nil {
60
+ t.Fatal(err)
61
+ }
62
+
63
+ indexed := decodeArtifactDump(t, runDumpForArtifacts(t, root, published))
64
+ if !slices.Contains(indexed.Provenance.Capabilities, graph.CapabilityArtifactNodes) {
65
+ t.Fatalf("a dump that asked declares %v, missing %q",
66
+ indexed.Provenance.Capabilities, graph.CapabilityArtifactNodes)
67
+ }
68
+ if indexed.Provenance.ArtifactProducer == nil {
69
+ t.Fatal("the artifact nodes came from another Program and the dump did not say so")
70
+ }
71
+
72
+ var section *graph.DumpNode
73
+ for index := range indexed.Nodes {
74
+ if indexed.Nodes[index].ID == "docs/sale.md#pricing" {
75
+ section = &indexed.Nodes[index]
76
+ }
77
+ }
78
+ if section == nil {
79
+ t.Fatal("the cited section is not a node")
80
+ }
81
+ if section.Name != "Pricing" {
82
+ t.Fatalf("the section is named %q, not the heading text an index exists to hold", section.Name)
83
+ }
84
+ if section.Parent != "docs/sale.md" {
85
+ t.Fatalf("the section's parent is %q, want its document", section.Parent)
86
+ }
87
+ if section.Evidence == nil || section.Evidence.StartLine != 7 {
88
+ t.Fatalf("the section carries no span at its heading: %+v", section.Evidence)
89
+ }
90
+ cited := false
91
+ for _, edge := range indexed.Edges {
92
+ if edge.Kind == "doc_ref" && edge.To == "docs/sale.md#pricing" {
93
+ cited = true
94
+ }
95
+ }
96
+ if !cited {
97
+ t.Fatal("the citation stayed a token; it has to resolve to the node now that one exists")
98
+ }
99
+ }
100
+
101
+ // runDumpForArtifacts runs the dump subcommand and returns its stdout.
102
+ func runDumpForArtifacts(t *testing.T, root, artifacts string) string {
103
+ t.Helper()
104
+ args := []string{"dump", "--cwd", root, "--tsconfig", "tsconfig.json"}
105
+ if artifacts != "" {
106
+ args = append(args, "--artifacts", artifacts)
107
+ }
108
+ var out, errOut bytes.Buffer
109
+ restoreStdout, restoreStderr := stdout, stderr
110
+ stdout, stderr = &out, &errOut
111
+ defer func() { stdout, stderr = restoreStdout, restoreStderr }()
112
+
113
+ if code := run(args); code != 0 {
114
+ t.Fatalf("dump exited %d: %s", code, errOut.String())
115
+ }
116
+ return out.String()
117
+ }
118
+
119
+ type artifactDump struct {
120
+ Provenance struct {
121
+ Capabilities []string `json:"capabilities"`
122
+ ArtifactProducer *graph.Producer `json:"artifactProducer"`
123
+ } `json:"provenance"`
124
+ Nodes []graph.DumpNode `json:"nodes"`
125
+ Edges []graph.DumpEdge `json:"edges"`
126
+ }
127
+
128
+ func decodeArtifactDump(t *testing.T, raw string) artifactDump {
129
+ t.Helper()
130
+ var parsed artifactDump
131
+ if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &parsed); err != nil {
132
+ t.Fatalf("dump is not JSON: %v", err)
133
+ }
134
+ return parsed
135
+ }
@@ -0,0 +1,96 @@
1
+ package main
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "path/filepath"
7
+ "slices"
8
+ "strings"
9
+ "testing"
10
+
11
+ "github.com/samchon/ttsc/packages/ttsc/internal/graph"
12
+ )
13
+
14
+ // TestRunDumpDeclaresTheCapabilityItsOutputCarries verifies that the shipped
15
+ // `dump` command's own output declares the documentation-tag capability, and
16
+ // that a project carrying no tag emits the field nowhere.
17
+ //
18
+ // The claim is the contract, and it can only be tested where it is made. A test
19
+ // that hands a capability list to the marshaller and reads it back proves the
20
+ // slice was copied; it leaves the production list free to lose the member with
21
+ // nothing failing. That matters because the two states a consumer must
22
+ // distinguish — "this declaration cites nothing" and "this producer never
23
+ // looked" — are the same absent field, so a dump that carries tags and forgets
24
+ // to say so is read as a repository where nothing cites anything.
25
+ //
26
+ // 1. Run the `dump` subcommand over a project with a tag, capturing stdout.
27
+ // 2. Assert the capability is declared and the tag is present in the output.
28
+ // 3. Run it over a project with no tag and assert the field appears nowhere,
29
+ // while the claim still does.
30
+ func TestRunDumpDeclaresTheCapabilityItsOutputCarries(t *testing.T) {
31
+ tagged := runDumpForCapabilityProbe(t, `/** @evidence docs/a.md#x Cited. */
32
+ export function subject(): void {}
33
+ `)
34
+ untagged := runDumpForCapabilityProbe(t, `/** Ordinary documentation. */
35
+ export function subject(): void {}
36
+ `)
37
+
38
+ for name, raw := range map[string]string{"tagged": tagged, "untagged": untagged} {
39
+ var parsed struct {
40
+ Provenance struct {
41
+ Capabilities []string `json:"capabilities"`
42
+ } `json:"provenance"`
43
+ }
44
+ if err := json.Unmarshal([]byte(raw), &parsed); err != nil {
45
+ t.Fatalf("%s dump is not JSON: %v", name, err)
46
+ }
47
+ if !slices.Contains(parsed.Provenance.Capabilities, graph.CapabilityDocTags) {
48
+ t.Fatalf("%s dump declares %v, missing %q; a consumer would then read a "+
49
+ "tagged declaration as citing nothing",
50
+ name, parsed.Provenance.Capabilities, graph.CapabilityDocTags)
51
+ }
52
+ }
53
+
54
+ if !strings.Contains(tagged, `"docs/a.md#x Cited."`) {
55
+ t.Fatalf("the tagged dump carried no tag text")
56
+ }
57
+ // Read the raw document rather than a decoded slice: an absent field and an
58
+ // empty one decode identically, and absent is what the wire contract says.
59
+ // The key is matched with its colon, because the capability shares the name
60
+ // and rides the same document as a bare string.
61
+ if strings.Contains(untagged, `"docTags":`) {
62
+ t.Fatalf("the untagged dump carried a docTags field; it must be absent")
63
+ }
64
+ if !strings.Contains(tagged, `"docTags":`) {
65
+ t.Fatalf("the tagged dump carried no docTags field, so the check above proves nothing")
66
+ }
67
+ }
68
+
69
+ // runDumpForCapabilityProbe runs the dump subcommand over a one-file project and
70
+ // returns its stdout.
71
+ func runDumpForCapabilityProbe(t *testing.T, source string) string {
72
+ t.Helper()
73
+ root := t.TempDir()
74
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
75
+ "compilerOptions": {
76
+ "target": "ES2022",
77
+ "module": "commonjs",
78
+ "strict": true,
79
+ "rootDir": "src",
80
+ "outDir": "dist"
81
+ },
82
+ "files": ["src/main.ts"]
83
+ }
84
+ `)
85
+ writeGraphFile(t, filepath.Join(root, "src", "main.ts"), source)
86
+
87
+ var out, errOut bytes.Buffer
88
+ restoreStdout, restoreStderr := stdout, stderr
89
+ stdout, stderr = &out, &errOut
90
+ defer func() { stdout, stderr = restoreStdout, restoreStderr }()
91
+
92
+ if code := run([]string{"dump", "--cwd", root, "--tsconfig", "tsconfig.json"}); code != 0 {
93
+ t.Fatalf("dump exited %d: %s", code, errOut.String())
94
+ }
95
+ return out.String()
96
+ }