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
@@ -0,0 +1,93 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "strings"
6
+ "testing"
7
+
8
+ "github.com/samchon/ttsc/packages/ttsc/driver"
9
+ )
10
+
11
+ // unusedLocalsFixtureTSConfig is the shared fixture config with the one flag
12
+ // this test is about. `noUnusedLocals` is what makes the compiler state, in a
13
+ // diagnostic, whether it counted a documentation link as a use.
14
+ const unusedLocalsFixtureTSConfig = `{
15
+ "compilerOptions": {
16
+ "target": "ES2022",
17
+ "module": "commonjs",
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "rootDir": "src",
21
+ "outDir": "dist"
22
+ },
23
+ "files": ["src/main.ts"]
24
+ }
25
+ `
26
+
27
+ // TestDocRefsAreTheUseTheCheckerAlreadyCounts verifies the premise this edge
28
+ // rests on: the compiler resolves a documentation link and counts it as a use,
29
+ // and recording the edge does not change what it reports.
30
+ //
31
+ // The whole argument for the edge is that it is a compiler fact rather than a
32
+ // text match, and the compiler says so itself — an import supporting only a link
33
+ // survives `noUnusedLocals` while the same import without one does not. That
34
+ // asymmetry is the evidence the issue was opened on, and it lived only in its
35
+ // prose. Pinning it here matters twice: it fails if a TypeScript upgrade ever
36
+ // stops counting links as uses, which would make the edge a text match dressed
37
+ // as a checker fact, and it fails if this pass ever changes what the compiler
38
+ // reports, which it must never do.
39
+ //
40
+ // 1. Build one file importing two types, using one only through a link and the
41
+ // other not at all.
42
+ // 2. Assert the compiler reports the unlinked import unused and the linked one
43
+ // not.
44
+ // 3. Assert the link produced its edge in the same build.
45
+ func TestDocRefsAreTheUseTheCheckerAlreadyCounts(t *testing.T) {
46
+ root := t.TempDir()
47
+ writeFile(t, filepath.Join(root, "tsconfig.json"), unusedLocalsFixtureTSConfig)
48
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `interface ILinked {
49
+ a: number;
50
+ }
51
+
52
+ interface IUnlinked {
53
+ b: number;
54
+ }
55
+
56
+ /** Names {@link ILinked} and nothing else. */
57
+ export function subject(): void {}
58
+ `)
59
+
60
+ prog, _, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
61
+ if err != nil {
62
+ t.Fatal(err)
63
+ }
64
+ defer func() { _ = prog.Close() }()
65
+
66
+ linked, unlinked := false, false
67
+ for _, diagnostic := range prog.Diagnostics() {
68
+ message := diagnostic.String()
69
+ if !strings.Contains(message, "never used") &&
70
+ !strings.Contains(message, "declared but") {
71
+ continue
72
+ }
73
+ if strings.Contains(message, "ILinked") {
74
+ linked = true
75
+ }
76
+ if strings.Contains(message, "IUnlinked") {
77
+ unlinked = true
78
+ }
79
+ }
80
+ if !unlinked {
81
+ t.Fatal("the unlinked declaration was not reported unused, so this fixture " +
82
+ "cannot demonstrate the asymmetry the edge rests on")
83
+ }
84
+ if linked {
85
+ t.Fatal("the linked declaration was reported unused, so the compiler no " +
86
+ "longer counts a documentation link as a use and this edge is a text " +
87
+ "match rather than a checker fact")
88
+ }
89
+
90
+ // The same build produced the edge, so the fact the compiler counted and the
91
+ // fact the graph records are one fact.
92
+ assertDocRef(t, Build(prog), "#subject:function", "#ILinked:interface")
93
+ }
@@ -0,0 +1,335 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "strings"
6
+ "testing"
7
+
8
+ "github.com/samchon/ttsc/packages/ttsc/driver"
9
+ )
10
+
11
+ // TestDocRefsResolveDocumentationLinks verifies that an inline link in a
12
+ // declaration's documentation becomes a checker-resolved edge, and that the tag
13
+ // it sits under changes nothing.
14
+ //
15
+ // The checker already resolves such a name and counts it as a use — the
16
+ // companion negative case is a project where `noUnusedLocals` keeps an import
17
+ // that only a link supports — so this was the one class of resolved reference
18
+ // the graph held no edge for, and the citation-only `import type` a citation
19
+ // convention recommends is exactly the form nothing else in the module records.
20
+ //
21
+ // 1. Build a fixture linking one type from a tag, one from ordinary prose, one
22
+ // through `{@linkcode}`, one qualified, and one that resolves to nothing.
23
+ // 2. Assert each resolvable link produced exactly one doc_ref edge to the
24
+ // declaration the checker resolved.
25
+ // 3. Assert the unresolvable link, the self-link, and the untagged declaration
26
+ // produced none.
27
+ // 4. Declare the same name in two more modules that flank it alphabetically,
28
+ // import all three, and assert every link resolves to the one this module
29
+ // imports the name from — the claim the rest of the file now rests on,
30
+ // since three declarations of `ISale` exist here.
31
+ func TestDocRefsResolveDocumentationLinks(t *testing.T) {
32
+ root := t.TempDir()
33
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
34
+ // Two rivals, one sorting before `sale` and one after, each imported so the
35
+ // program holds it. One rival is not enough: a text matcher has to break the
36
+ // tie somehow, and whichever end it picks — lowest id, highest id, first or
37
+ // last in program order — a single rival leaves half of those choices landing
38
+ // on the right answer by luck. Flanking the real module closes every one.
39
+ writeFile(t, filepath.Join(root, "src", "archive.ts"), `export interface ISale {
40
+ archived: boolean;
41
+ }
42
+
43
+ export interface IArchive {
44
+ flag: boolean;
45
+ }
46
+ `)
47
+ writeFile(t, filepath.Join(root, "src", "vendor.ts"), `export interface ISale {
48
+ vendored: boolean;
49
+ }
50
+
51
+ export interface IVendor {
52
+ flag: boolean;
53
+ }
54
+ `)
55
+ writeFile(t, filepath.Join(root, "src", "sale.ts"), `export interface ISale {
56
+ price: number;
57
+ }
58
+
59
+ export namespace Shopping {
60
+ export interface ICoupon {
61
+ code: string;
62
+ }
63
+ }
64
+ `)
65
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `// The real module is imported between its two rivals, so no end of the import
66
+ // order is the right answer either.
67
+ import type { IArchive } from "./archive";
68
+ import type { ISale, Shopping } from "./sale";
69
+ import type { IVendor } from "./vendor";
70
+
71
+ /** @evidence {@link ISale} Cited from a tag. */
72
+ export function fromTag(): void {}
73
+
74
+ /** Ordinary prose naming {@link ISale} with no tag at all. */
75
+ export function fromProse(): void {}
76
+
77
+ /** @evidence {@linkcode ISale} Code form. */
78
+ export function fromCode(): void {}
79
+
80
+ /** @evidence {@linkplain ISale} Plain form. */
81
+ export function fromPlain(): void {}
82
+
83
+ /** Two links, {@link ISale} and {@link Shopping.ICoupon}, plus {@link ISale} twice. */
84
+ export function severalLinks(): void {}
85
+
86
+ /** @evidence {@link Shopping.ICoupon} Qualified. */
87
+ export function fromQualified(): void {}
88
+
89
+ /** @evidence {@link NoSuchSymbol} Resolves to nothing. */
90
+ export function fromUnresolved(): void {}
91
+
92
+ /** @see {@link ISale} A tag TypeScript does know. */
93
+ export function fromSee(): void {}
94
+
95
+ /**
96
+ * @param value The parameter, whose tag carries no link.
97
+ * @returns Nothing.
98
+ */
99
+ export function withKnownTags(value: string): void {}
100
+
101
+ /** Names {@link selfLinked} itself. */
102
+ export function selfLinked(): void {}
103
+
104
+ /** Carries documentation but no link. */
105
+ export function noLink(): void {}
106
+
107
+ // A link in a line comment: {@link ISale} is not documentation.
108
+ export function lineCommentLink(): void {}
109
+
110
+ export namespace Reached {
111
+ /** A namespace member naming {@link ISale}. */
112
+ export function member(): void {}
113
+ }
114
+
115
+ export function hostsClosure(): void {
116
+ /** A closure naming {@link ISale}. */
117
+ function inner(): void {}
118
+ inner();
119
+ }
120
+
121
+ /** Names {@link ISale}, of which this file imports exactly one. */
122
+ export function ambiguousName(): void {}
123
+
124
+ /** Names {@link IArchive}, which only the first rival declares. */
125
+ export function usesArchive(): void {}
126
+
127
+ /** Names {@link IVendor}, which only the second rival declares. */
128
+ export function usesVendor(): void {}
129
+
130
+ /** Names a library type: {@link Promise}. */
131
+ export function linksExternal(): void {}
132
+
133
+ /** A class documented with {@link ISale} on the class itself. */
134
+ export class Documented {
135
+ /** @evidence {@link Shopping.ICoupon} On the member, not the class. */
136
+ public readonly coupon: string = "";
137
+
138
+ /** Method documentation naming {@link ISale}. */
139
+ public run(): void {}
140
+ }
141
+
142
+ /** An interface documented with {@link ISale}. */
143
+ export interface IDocumented {
144
+ value: number;
145
+ }
146
+
147
+ /** A namespace documented with {@link ISale}. */
148
+ export namespace Documented2 {
149
+ export const value = 1;
150
+ }
151
+ `)
152
+
153
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
154
+ if err != nil {
155
+ t.Fatal(err)
156
+ }
157
+ if len(diags) != 0 {
158
+ t.Fatalf("unexpected diagnostics: %v", diags)
159
+ }
160
+ defer func() { _ = prog.Close() }()
161
+
162
+ g := Build(prog)
163
+
164
+ assertDocRef(t, g, "#fromTag:function", "sale.ts#ISale:interface")
165
+ // The tag decides nothing: a link in ordinary prose is the same relation.
166
+ assertDocRef(t, g, "#fromProse:function", "sale.ts#ISale:interface")
167
+ assertDocRef(t, g, "#fromCode:function", "sale.ts#ISale:interface")
168
+ assertDocRef(t, g, "#fromPlain:function", "sale.ts#ISale:interface")
169
+ // Distinct targets each get an edge; a target named twice collapses under the
170
+ // uniqueness rule every edge kind shares, keeping the first span.
171
+ assertDocRef(t, g, "#severalLinks:function", "sale.ts#ISale:interface")
172
+ assertDocRef(t, g, "#severalLinks:function", "#Shopping.ICoupon:interface")
173
+ assertDocRef(t, g, "#fromQualified:function", "#Shopping.ICoupon:interface")
174
+ // A link under a tag TypeScript recognizes is the same relation. Reading a
175
+ // tag's comment through its per-kind struct crashed on the first `@param`
176
+ // instead, so this case and the one below are one fix and one regression.
177
+ assertDocRef(t, g, "#fromSee:function", "sale.ts#ISale:interface")
178
+
179
+ // A name the checker cannot resolve is not a relation, and must not
180
+ // fabricate a node to point at.
181
+ assertNoDocRef(t, g, "#fromUnresolved:function")
182
+ // A declaration naming itself is not an edge, the same rule typeRefEdge keeps.
183
+ assertNoDocRef(t, g, "#selfLinked:function")
184
+ assertNoDocRef(t, g, "#noLink:function")
185
+ assertNoDocRef(t, g, "#withKnownTags:function")
186
+ // A `//` comment is not documentation, so the parser attaches it to nothing
187
+ // and no link can be read out of it.
188
+ assertNoDocRef(t, g, "#lineCommentLink:function")
189
+
190
+ // Both nested forms are nodes the build pass records, so both resolve their
191
+ // own links — the edge pass reads the same host set the node pass filled.
192
+ assertDocRef(t, g, "#Reached.member:function", "sale.ts#ISale:interface")
193
+ assertDocRef(t, g, "#hostsClosure.inner:function", "sale.ts#ISale:interface")
194
+
195
+ // A class, an interface, and a namespace carry documentation of their own,
196
+ // and the container walk the edge pass would naturally reuse never visits any
197
+ // of them as a node: it descends straight into their members. Each of these
198
+ // resolved to nothing while the tag beside it was indexed, so the two halves
199
+ // a reader composes disagreed exactly where a type is documented.
200
+ assertDocRef(t, g, "#Documented:class", "sale.ts#ISale:interface")
201
+ assertDocRef(t, g, "#IDocumented:interface", "sale.ts#ISale:interface")
202
+ // A namespace is a grouping container the graph models as no node at all, so
203
+ // its own documentation has nothing to hang on — and the tag half agrees,
204
+ // because `putDeclaredNode` is never called for one either. The two halves
205
+ // being absent together is the invariant; one of them answering alone is the
206
+ // defect this pairing exists to catch.
207
+ assertNoNamespaceNode(t, g, "#Documented2:")
208
+ assertNoDocRefTo(t, g, "#Documented2.value:variable", "sale.ts#ISale:interface")
209
+
210
+ // A member's link belongs to the member. The same walk hands a property's
211
+ // subtree to its class as well, which is right for dependency edges and wrong
212
+ // here: the class's own documentation names nothing.
213
+ assertDocRef(t, g, "#Documented.coupon:variable", "#Shopping.ICoupon:interface")
214
+ assertNoDocRefTo(t, g, "#Documented:class", "#Shopping.ICoupon:interface")
215
+ assertDocRef(t, g, "#Documented.run:method", "sale.ts#ISale:interface")
216
+
217
+ // Two modules declare `ISale`, and only the checker knows which one a link
218
+ // means: it resolves the name through this module's imports. A text match on
219
+ // the written token could not tell them apart, and would have to guess.
220
+ assertNodeExists(t, g, "archive.ts#ISale:interface")
221
+ assertNodeExists(t, g, "vendor.ts#ISale:interface")
222
+ // Each rival is reached for a name only it declares, which is what keeps its
223
+ // import from being the kind a stricter project would remove.
224
+ assertDocRef(t, g, "#usesArchive:function", "archive.ts#IArchive:interface")
225
+ assertDocRef(t, g, "#usesVendor:function", "vendor.ts#IVendor:interface")
226
+ assertDocRef(t, g, "#ambiguousName:function", "sale.ts#ISale:interface")
227
+ assertNoDocRefTo(t, g, "#ambiguousName:function", "archive.ts#ISale:interface")
228
+ assertNoDocRefTo(t, g, "#ambiguousName:function", "vendor.ts#ISale:interface")
229
+
230
+ // A link to a library type follows the external-boundary policy every other
231
+ // edge kind keeps: the target is a named endpoint, not walked into.
232
+ assertExternalDocRef(t, g, "#linksExternal:function", "Promise")
233
+ }
234
+
235
+ // assertExternalDocRef fails unless exactly one doc_ref edge leaves the node and
236
+ // lands on an external declaration of this name.
237
+ func assertExternalDocRef(t *testing.T, g *Graph, fromSuffix, name string) {
238
+ t.Helper()
239
+ found := 0
240
+ for _, edge := range g.Edges {
241
+ if edge.Kind != EdgeDocRef || !suffixMatch(edge.From, fromSuffix) {
242
+ continue
243
+ }
244
+ target := g.Nodes[edge.To]
245
+ if target == nil {
246
+ t.Fatalf("%s cited %s, which is no node", fromSuffix, edge.To)
247
+ }
248
+ if target.Simple != name {
249
+ continue
250
+ }
251
+ found++
252
+ if !target.External {
253
+ t.Fatalf("%s is a library declaration and must be an external boundary leaf", edge.To)
254
+ }
255
+ }
256
+ if found != 1 {
257
+ t.Fatalf("%s -> external %s edges = %d, want 1", fromSuffix, name, found)
258
+ }
259
+ }
260
+
261
+ // assertNoNamespaceNode fails when the graph holds a node for this namespace,
262
+ // or any doc_ref edge leaving one.
263
+ //
264
+ // A suffix match cannot express this: every node id ends in its kind, so
265
+ // "#Documented2:" is a suffix of nothing and an assertion written that way
266
+ // passes against an implementation that records the namespace and cites from
267
+ // it — the exact defect the pairing exists to catch.
268
+ func assertNoNamespaceNode(t *testing.T, g *Graph, infix string) {
269
+ t.Helper()
270
+ for id := range g.Nodes {
271
+ if strings.Contains(id, infix) {
272
+ t.Fatalf("graph recorded %s; a namespace is modelled as no node, so its documentation has no host", id)
273
+ }
274
+ }
275
+ for _, edge := range g.Edges {
276
+ if edge.Kind == EdgeDocRef && strings.Contains(edge.From, infix) {
277
+ t.Fatalf("a namespace node cited %s", edge.To)
278
+ }
279
+ }
280
+ }
281
+
282
+ // assertNodeExists fails unless the graph holds a node whose id ends this way.
283
+ // It guards the same-name case from passing because the rival never existed.
284
+ func assertNodeExists(t *testing.T, g *Graph, suffix string) {
285
+ t.Helper()
286
+ for id := range g.Nodes {
287
+ if suffixMatch(id, suffix) {
288
+ return
289
+ }
290
+ }
291
+ t.Fatalf("the graph holds no %s, so a resolution asserted against it proves nothing", suffix)
292
+ }
293
+
294
+ // assertNoDocRefTo fails when a doc_ref edge runs between these two nodes.
295
+ func assertNoDocRefTo(t *testing.T, g *Graph, fromSuffix, toSuffix string) {
296
+ t.Helper()
297
+ for _, edge := range g.Edges {
298
+ if edge.Kind == EdgeDocRef &&
299
+ suffixMatch(edge.From, fromSuffix) &&
300
+ suffixMatch(edge.To, toSuffix) {
301
+ t.Fatalf("%s must not name %s", fromSuffix, toSuffix)
302
+ }
303
+ }
304
+ }
305
+
306
+ // assertDocRef fails unless exactly one doc_ref edge runs between the two nodes
307
+ // whose ids end with these suffixes, carrying a span.
308
+ func assertDocRef(t *testing.T, g *Graph, fromSuffix, toSuffix string) {
309
+ t.Helper()
310
+ found := 0
311
+ for _, edge := range g.Edges {
312
+ if edge.Kind != EdgeDocRef {
313
+ continue
314
+ }
315
+ if suffixMatch(edge.From, fromSuffix) && suffixMatch(edge.To, toSuffix) {
316
+ found++
317
+ if edge.Pos < 0 || edge.End <= edge.Pos {
318
+ t.Fatalf("%s -> %s carries no span (%d..%d)", fromSuffix, toSuffix, edge.Pos, edge.End)
319
+ }
320
+ }
321
+ }
322
+ if found != 1 {
323
+ t.Fatalf("%s -> %s doc_ref edges = %d, want 1", fromSuffix, toSuffix, found)
324
+ }
325
+ }
326
+
327
+ // assertNoDocRef fails when any doc_ref edge leaves the named node.
328
+ func assertNoDocRef(t *testing.T, g *Graph, fromSuffix string) {
329
+ t.Helper()
330
+ for _, edge := range g.Edges {
331
+ if edge.Kind == EdgeDocRef && suffixMatch(edge.From, fromSuffix) {
332
+ t.Fatalf("%s produced an unexpected doc_ref edge to %s", fromSuffix, edge.To)
333
+ }
334
+ }
335
+ }