@ttsc/graph 0.27.0 → 0.28.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.
Files changed (82) hide show
  1. package/README.md +3 -1
  2. package/lib/index.d.ts +1 -1
  3. package/lib/index.js +15 -3
  4. package/lib/index.js.map +1 -1
  5. package/lib/model/TtscGraphMemory.d.ts +26 -0
  6. package/lib/model/TtscGraphMemory.js +121 -2
  7. package/lib/model/TtscGraphMemory.js.map +1 -1
  8. package/lib/model/TtscGraphSession.d.ts +23 -0
  9. package/lib/model/TtscGraphSession.js +248 -96
  10. package/lib/model/TtscGraphSession.js.map +1 -1
  11. package/lib/model/TtscGraphShardStore.js +6 -1
  12. package/lib/model/TtscGraphShardStore.js.map +1 -1
  13. package/lib/model/TtscLintDaemon.d.ts +54 -0
  14. package/lib/model/TtscLintDaemon.js +166 -0
  15. package/lib/model/TtscLintDaemon.js.map +1 -0
  16. package/lib/model/loadGraph.d.ts +8 -1
  17. package/lib/model/loadGraph.js +144 -72
  18. package/lib/model/loadGraph.js.map +1 -1
  19. package/lib/model/publishedArtifacts.d.ts +142 -0
  20. package/lib/model/publishedArtifacts.js +403 -0
  21. package/lib/model/publishedArtifacts.js.map +1 -0
  22. package/lib/reduce.js +20 -7
  23. package/lib/reduce.js.map +1 -1
  24. package/lib/server/createServer.js +38 -2
  25. package/lib/server/createServer.js.map +1 -1
  26. package/lib/server/runDetails.d.ts +15 -0
  27. package/lib/server/runDetails.js +48 -0
  28. package/lib/server/runDetails.js.map +1 -1
  29. package/lib/server/runEntrypoints.js +7 -1
  30. package/lib/server/runEntrypoints.js.map +1 -1
  31. package/lib/server/runLookup.js +129 -11
  32. package/lib/server/runLookup.js.map +1 -1
  33. package/lib/structures/ITtscGraphApplication.d.ts +3 -1
  34. package/lib/structures/ITtscGraphDetails.d.ts +12 -0
  35. package/lib/structures/ITtscGraphDocTag.d.ts +44 -0
  36. package/lib/structures/ITtscGraphDocTag.js +3 -0
  37. package/lib/structures/ITtscGraphDocTag.js.map +1 -0
  38. package/lib/structures/ITtscGraphDump.d.ts +23 -4
  39. package/lib/structures/ITtscGraphLookup.d.ts +29 -0
  40. package/lib/structures/ITtscGraphNode.d.ts +25 -0
  41. package/lib/structures/TtscGraphArtifactNodeKind.d.ts +23 -0
  42. package/lib/structures/TtscGraphArtifactNodeKind.js +37 -0
  43. package/lib/structures/TtscGraphArtifactNodeKind.js.map +1 -0
  44. package/lib/structures/TtscGraphDumpEdgeKind.d.ts +1 -1
  45. package/lib/structures/TtscGraphDumpNodeKind.d.ts +10 -2
  46. package/lib/structures/TtscGraphEdgeKind.d.ts +8 -1
  47. package/lib/structures/TtscGraphNodeKind.d.ts +1 -1
  48. package/lib/structures/index.d.ts +2 -0
  49. package/lib/structures/index.js +2 -0
  50. package/lib/structures/index.js.map +1 -1
  51. package/lib/view.js +17 -1
  52. package/lib/view.js.map +1 -1
  53. package/lib/viewer/index.html +5 -13
  54. package/lib/viewer/viewer.js +190 -190
  55. package/package.json +5 -5
  56. package/src/index.ts +23 -6
  57. package/src/model/TtscGraphMemory.ts +118 -2
  58. package/src/model/TtscGraphSession.ts +112 -2
  59. package/src/model/TtscGraphShardStore.ts +6 -1
  60. package/src/model/TtscLintDaemon.ts +179 -0
  61. package/src/model/loadGraph.ts +29 -5
  62. package/src/model/publishedArtifacts.ts +548 -0
  63. package/src/reduce.ts +20 -7
  64. package/src/server/runDetails.ts +52 -1
  65. package/src/server/runEntrypoints.ts +7 -1
  66. package/src/server/runLookup.ts +139 -14
  67. package/src/structures/ITtscGraphApplication.ts +3 -1
  68. package/src/structures/ITtscGraphDetails.ts +13 -0
  69. package/src/structures/ITtscGraphDocTag.ts +45 -0
  70. package/src/structures/ITtscGraphDump.ts +25 -4
  71. package/src/structures/ITtscGraphLookup.ts +31 -0
  72. package/src/structures/ITtscGraphNode.ts +27 -0
  73. package/src/structures/TtscGraphArtifactNodeKind.ts +35 -0
  74. package/src/structures/TtscGraphDumpEdgeKind.ts +1 -0
  75. package/src/structures/TtscGraphDumpNodeKind.ts +16 -2
  76. package/src/structures/TtscGraphEdgeKind.ts +8 -0
  77. package/src/structures/TtscGraphNodeKind.ts +7 -1
  78. package/src/structures/index.ts +2 -0
  79. package/src/view.ts +17 -1
  80. package/src/viewer/index.html +5 -13
  81. package/src/viewer/legend.ts +107 -0
  82. package/src/viewer/main.ts +19 -21
@@ -1,13 +1,20 @@
1
- import { TtscGraphMemory } from "../model/TtscGraphMemory";
1
+ import { TtscGraphMemory, documentationTarget } from "../model/TtscGraphMemory";
2
2
  import { ITtscGraphLookup } from "../structures/ITtscGraphLookup";
3
3
  import { ITtscGraphNode } from "../structures/ITtscGraphNode";
4
+ import { isArtifactNodeKind } from "../structures/TtscGraphArtifactNodeKind";
4
5
  import { exportFanIn } from "./exportSurface";
5
6
  import { isExternalNode, isSupportPath } from "./pathPolicy";
6
7
  import { IRunnerOutput, resultNext } from "./resultNext";
7
- import { decoratorsOf, signatureOf } from "./runDetails";
8
+ import { decoratorsOf, docTagsOf, signatureOf } from "./runDetails";
8
9
 
9
10
  // One file should not crowd out the rest of the ranking, so cap hits per file.
10
11
  const PER_FILE = 3;
12
+ // The score a citation carrier reports. Citation hits are placed ahead of the
13
+ // ranked list rather than sorted into it, so this value decides no order: it
14
+ // exists because every hit owes a score, and it is high to say what kind of
15
+ // answer this is — an exact match on an address the caller and the author spell
16
+ // identically, not a better guess at a name.
17
+ const CITATION_SCORE = 1000;
11
18
  const DEFAULT_LIMIT = 5;
12
19
  const MAX_LIMIT = 6;
13
20
 
@@ -29,7 +36,12 @@ export function runLookup(
29
36
  const wantsInternal = wantsInternalSymbol(queryLc, codeTerms);
30
37
  const wantsSupport = wantsSupportSymbol(queryLc);
31
38
  const includeExternal = props.includeExternal === true;
32
- if (terms.length === 0)
39
+ // The reverse citation question is answered before the name tokenizer gets a
40
+ // veto. `subwords` splits on ASCII alphanumerics, so a target written in
41
+ // another script, or one made only of punctuation, produces no term and used
42
+ // to return "no searchable terms" while the index held that exact address.
43
+ const cited = citationHits(graph, props.query, includeExternal);
44
+ if (terms.length === 0 && cited.length === 0)
33
45
  return {
34
46
  result: {
35
47
  type: "lookup",
@@ -42,7 +54,12 @@ export function runLookup(
42
54
  };
43
55
 
44
56
  const scored: ITtscGraphLookup.IHit[] = [];
45
- for (const node of graph.nodes) {
57
+ // With no term to match, nothing is a name match. The whole-query bonus below
58
+ // is awarded when every term landed, and no terms trivially satisfies that —
59
+ // so a query the tokenizer cannot read (an address in another script) would
60
+ // otherwise return the citation hit followed by arbitrary central symbols,
61
+ // presented as answers to a name it never matched.
62
+ for (const node of terms.length === 0 ? [] : graph.nodes) {
46
63
  if (node.kind === "file") continue;
47
64
  if (!includeExternal && isExternalNode(node)) continue;
48
65
  const score = scoreNode(
@@ -69,18 +86,46 @@ export function runLookup(
69
86
  scored.push(hit);
70
87
  }
71
88
 
72
- scored.sort((a, b) => b.score - a.score);
89
+ // A query naming a documentation target is the reverse citation question, and
90
+ // it is the one an index has to answer: the declarations implementing one
91
+ // specification are scattered across every file that implements it, so
92
+ // anything less than an index means searching the repository.
93
+ //
94
+ // These are prepended rather than scored in. A tag target is an exact token
95
+ // the caller typed — `docs/pricing.md#sale`, `POST:/orders` — so a carrier is
96
+ // not a better fuzzy match than the name hits, it is a different and certain
97
+ // kind of answer, and name scoring cannot rank the two against each other.
98
+ const citedIds = new Set(cited.map((hit) => hit.id));
99
+ const ranked = scored.filter((hit) => !citedIds.has(hit.id));
100
+ ranked.sort((a, b) => b.score - a.score);
101
+ ranked.unshift(...cited);
73
102
 
74
103
  // Diversity: keep at most PER_FILE hits per file while filling up to the limit.
104
+ //
105
+ // A citation carrier is exempt. The cap exists so one file's roster cannot
106
+ // crowd out a name ranking, and a citation is not ranked against anything: it
107
+ // is an exact match on an address, and a module implementing one specification
108
+ // across four functions is the ordinary shape of the answer rather than a file
109
+ // dominating a shortlist. Capping those returned three of five carriers and
110
+ // called the result complete.
75
111
  const limit = bound(props.limit, DEFAULT_LIMIT, 1, MAX_LIMIT);
76
112
  const perFile = new Map<string, number>();
77
113
  const hits: ITtscGraphLookup.IHit[] = [];
78
- for (const hit of scored) {
79
- const used = perFile.get(hit.file) ?? 0;
80
- if (used >= PER_FILE) continue;
81
- perFile.set(hit.file, used + 1);
114
+ let truncated = false;
115
+ for (const hit of ranked) {
116
+ if (hits.length >= limit) {
117
+ truncated = true;
118
+ break;
119
+ }
120
+ if (!citedIds.has(hit.id)) {
121
+ const used = perFile.get(hit.file) ?? 0;
122
+ if (used >= PER_FILE) {
123
+ truncated = true;
124
+ continue;
125
+ }
126
+ perFile.set(hit.file, used + 1);
127
+ }
82
128
  hits.push(hit);
83
- if (hits.length >= limit) break;
84
129
  }
85
130
 
86
131
  // Attach each kept hit's signature only for the shortlist, so the model can
@@ -95,6 +140,7 @@ export function runLookup(
95
140
  result: {
96
141
  type: "lookup",
97
142
  hits,
143
+ ...(truncated ? { truncated: true } : {}),
98
144
  },
99
145
  next:
100
146
  hits.length === 0
@@ -102,10 +148,16 @@ export function runLookup(
102
148
  "outside",
103
149
  "No symbol matched, so the graph did not resolve this name.",
104
150
  )
105
- : resultNext(
106
- "answer",
107
- "The ranked hits and their signatures resolve the name.",
108
- ),
151
+ : truncated
152
+ ? resultNext(
153
+ "answer",
154
+ "The hits resolve the name, and `truncated` says more matched " +
155
+ "than the limit returned: raise `limit` for the rest.",
156
+ )
157
+ : resultNext(
158
+ "answer",
159
+ "The ranked hits and their signatures resolve the name.",
160
+ ),
109
161
  };
110
162
  }
111
163
 
@@ -293,3 +345,76 @@ function subwords(text: string): string[] {
293
345
  .filter((w) => w.length > 0)
294
346
  .map((w) => w.toLowerCase());
295
347
  }
348
+
349
+ /**
350
+ * The declarations whose documentation opens a tag with the address this query
351
+ * opens with, in file order.
352
+ *
353
+ * A caller writing `docs/pricing.md#sale` or `POST:/orders` is asking the
354
+ * reverse citation question: not what a symbol is, but which code answers to
355
+ * this specification. Nothing else in the graph can answer it, because the
356
+ * other end of the relation is a document section rather than a declaration.
357
+ *
358
+ * The match is exact, and only a token shaped like an address is one at all —
359
+ * `documentationTarget` decides that for the query and for every indexed tag,
360
+ * so the two sides can never disagree about what counts. Fuzzy matching is what
361
+ * the name scoring above does, and it is wrong here: an address is spelled
362
+ * identically by the author and the caller, and a near-miss would put a
363
+ * different specification's implementers in the answer under the same confident
364
+ * heading.
365
+ *
366
+ * Each returned hit carries the tags that matched, so the result says why the
367
+ * declaration is in it.
368
+ */
369
+ function citationHits(
370
+ graph: TtscGraphMemory,
371
+ query: string,
372
+ includeExternal: boolean,
373
+ ): ITtscGraphLookup.IHit[] {
374
+ const target = documentationTarget(query);
375
+ if (target === undefined) return [];
376
+ const hits: ITtscGraphLookup.IHit[] = [];
377
+ // The artifact itself leads, when one was published under this address. A
378
+ // caller asking about `docs/sale.md#pricing` wants what that anchor names —
379
+ // the heading's own text and where it starts — before it wants the list of
380
+ // declarations that cite it, and answering only the second half is the gap an
381
+ // index of tokens had. It is not name-ranked: the address matched exactly.
382
+ const artifact = graph.node(target);
383
+ if (artifact !== undefined && isArtifactNodeKind(artifact.kind))
384
+ hits.push({
385
+ id: artifact.id,
386
+ name: artifact.name,
387
+ kind: artifact.kind,
388
+ file: artifact.file,
389
+ line: artifact.evidence?.startLine,
390
+ score: CITATION_SCORE,
391
+ });
392
+ for (const node of graph.citing(target)) {
393
+ if (node.kind === "file") continue;
394
+ if (!includeExternal && isExternalNode(node)) continue;
395
+ // Matched on the node's own tags and elided afterwards. Filtering the
396
+ // elided text instead dropped the explanation from a hit whose address was
397
+ // longer than the cap: the node was found by the address and then returned
398
+ // without the tag that said why.
399
+ const matched = docTagsOf(
400
+ node,
401
+ (tag) => documentationTarget(tag.text) === target,
402
+ );
403
+ const hit: ITtscGraphLookup.IHit = {
404
+ id: node.id,
405
+ name: node.qualifiedName ?? node.name,
406
+ kind: node.kind,
407
+ file: node.file,
408
+ line: node.evidence?.startLine,
409
+ // An exact target match is a certain answer rather than a ranked guess.
410
+ // The score exists so the field is never absent; it does not compete with
411
+ // the name scores, because these hits are not ordered against them.
412
+ score: CITATION_SCORE,
413
+ };
414
+ const decorators = decoratorsOf(node);
415
+ if (decorators !== undefined) hit.decorators = decorators;
416
+ if (matched !== undefined && matched.length > 0) hit.docTags = matched;
417
+ hits.push(hit);
418
+ }
419
+ return hits;
420
+ }
@@ -34,7 +34,9 @@ import { ITtscGraphTrace } from "./ITtscGraphTrace";
34
34
  * the whole answer; do not split it. Name the machinery you expect it to be
35
35
  * made of in its `reinterpretations`, or send none.
36
36
  * - `entrypoints`: find where execution starts when entry points are unknown.
37
- * - `lookup`: locate a named symbol.
37
+ * - `lookup`: locate a named symbol, or — with a documentation target as the
38
+ * query (`docs/pricing.md#sale`, `POST:/orders`) — the declarations whose
39
+ * documentation cites it.
38
40
  * - `trace`: follow calls or data flow forward or backward from a symbol, or —
39
41
  * with `to` — the path between two symbols when both ends are known, which is
40
42
  * the one call that answers "how does A reach B".
@@ -1,4 +1,5 @@
1
1
  import { ITtscGraphDecorator } from "./ITtscGraphDecorator";
2
+ import { ITtscGraphDocTag } from "./ITtscGraphDocTag";
2
3
  import { ITtscGraphEvidence } from "./ITtscGraphEvidence";
3
4
 
4
5
  /**
@@ -135,6 +136,18 @@ export namespace ITtscGraphDetails {
135
136
  /** Decorators written on this declaration, when any. */
136
137
  decorators?: ITtscGraphDecorator[];
137
138
 
139
+ /**
140
+ * Documentation tags naming what outside the type system this declaration
141
+ * answers to: a specification section, an API operation, a reference
142
+ * document (`@evidence docs/pricing.md#sale`, `@reference …`).
143
+ *
144
+ * Part of what the declaration is, so it is returned whole like its members
145
+ * and its values — long text is elided, and nothing else is cut. The tag is
146
+ * reported as written and never interpreted: the graph does not say the
147
+ * text resolves to anything or that the claim is true.
148
+ */
149
+ docTags?: ITtscGraphDocTag[];
150
+
138
151
  /** Assigned implementation span, when source comes from one. */
139
152
  implementation?: ITtscGraphEvidence;
140
153
 
@@ -0,0 +1,45 @@
1
+ /**
2
+ * One documentation tag TypeScript itself does not recognize, written on a
3
+ * declaration and carried on its {@link ITtscGraphNode}'s `docTags`.
4
+ *
5
+ * A convention attaches a declaration to something outside the type system — a
6
+ * specification section, an API operation, a reference document — and writes
7
+ * that attachment as a tag, because the thing on the other end is not a
8
+ * TypeScript declaration and so cannot be an edge. `@evidence
9
+ * docs/pricing.md#sale`, `@reference https://…`, and a project's own `@spec`
10
+ * are one fact in three spellings.
11
+ *
12
+ * Reported faithfully, never interpreted. The graph does not say a tag's text
13
+ * resolves to anything, is covered, or is true — a target naming nothing is
14
+ * carried exactly as written, the way {@link ITtscGraphDecorator} carries
15
+ * `@Controller` without knowing what a controller is.
16
+ *
17
+ * Which tags arrive is the compiler's boundary, not a list kept here: these are
18
+ * the tags the parser had no meaning for. A known tag — `@param`, `@returns`,
19
+ * `@deprecated` — has its own shape and its own meaning and is not one of
20
+ * these.
21
+ *
22
+ * The parser also decides where a tag begins, and that decision is reported
23
+ * rather than second-guessed. TypeScript opens a tag at an `@` wherever it sits
24
+ * inside a documentation block, so a tag name written mid-sentence arrives here
25
+ * as a tag, while a `//` comment is not documentation and contributes nothing at
26
+ * all. A convention that wants the stricter rule enforces it in its own linter,
27
+ * where the author gets a diagnostic; the graph reports what the compiler
28
+ * parsed.
29
+ */
30
+ export interface ITtscGraphDocTag {
31
+ /** The tag name without its `@`: `evidence`, `evidenceExclude`, `reference`. */
32
+ name: string;
33
+
34
+ /**
35
+ * Everything written after the tag name, joined into one line.
36
+ *
37
+ * An inline link keeps the braced form it was written in (`{@link ISale}`),
38
+ * so a consumer matching a citation target sees what the author typed. The
39
+ * same link is separately a resolved edge on the node, which is what says
40
+ * *which* `ISale` — a name can be declared twice and only the checker knows.
41
+ *
42
+ * Absent when the tag carries no text.
43
+ */
44
+ text?: string;
45
+ }
@@ -16,7 +16,7 @@ import { TtscGraphDumpNodeKind } from "./TtscGraphDumpNodeKind";
16
16
  * the same dump.
17
17
  *
18
18
  * `project` is the producer-local absolute locator. Every identity-bearing path
19
- * uses one schema-v6 coordinate relative to it: project files are ordinary
19
+ * uses one portable coordinate relative to it: project files are ordinary
20
20
  * relative paths; same-filesystem siblings use `../` segments; package files
21
21
  * keep their full resolution context (including version/peer-store segments);
22
22
  * and a virtual compiler source stays `bundled:///…`. Raw absolute identities
@@ -76,8 +76,8 @@ export namespace ITtscGraphDump {
76
76
  * guessing from a field's emptiness, because an empty list and an
77
77
  * uncollected one look identical on the wire.
78
78
  *
79
- * The known members are `universe`, `sourceDigests`, `diskDigests`, and
80
- * `diagnostics`. The type stays `string[]` rather than a union of those on
79
+ * The known members are `universe`, `sourceDigests`, `diskDigests`,
80
+ * `diagnostics`, and `docTags`. The type stays `string[]` rather than a union of those on
81
81
  * purpose: a union would make `typia.assert` reject a newer producer for
82
82
  * naming a capability this client has not heard of, turning "proves more
83
83
  * than you know about" into a hard failure. An unknown capability is
@@ -88,6 +88,17 @@ export namespace ITtscGraphDump {
88
88
  /** What built the snapshot. */
89
89
  producer: IProducer;
90
90
 
91
+ /**
92
+ * The second producer behind the artifact nodes, absent when the dump
93
+ * carries none.
94
+ *
95
+ * Every other fact in this dump came from one Program. These did not: a
96
+ * plugin parsed documents that Program never read, in a process of its own.
97
+ * Saying so is what keeps the one-generation contract honest instead of
98
+ * letting an overlay ride the same claim as the compiler's facts.
99
+ */
100
+ artifactProducer?: IProducer;
101
+
91
102
  /** The inputs that decide which files are in the program at all. */
92
103
  universe: IUniverse;
93
104
 
@@ -233,9 +244,19 @@ export namespace ITtscGraphDump {
233
244
  ITtscGraphNode,
234
245
  "evidence" | "implementation" | "kind"
235
246
  > {
236
- /** Declaration kind written by the native producer. */
247
+ /** Node kind written by the native producer. */
237
248
  kind: TtscGraphDumpNodeKind;
238
249
 
250
+ /**
251
+ * The artifact containing this one, by id.
252
+ *
253
+ * Present only on an artifact node: a declaration's containment is
254
+ * synthesized by the memory layer, and two producers of one relation would
255
+ * put two answers in the graph. Absent at the top of a chain, and absent
256
+ * rather than invented when the parent was not published.
257
+ */
258
+ parent?: string;
259
+
239
260
  /** Declaration span; its file is this node's `file`. */
240
261
  evidence?: ITtscGraphSpan;
241
262
 
@@ -1,4 +1,5 @@
1
1
  import { ITtscGraphDecorator } from "./ITtscGraphDecorator";
2
+ import { ITtscGraphDocTag } from "./ITtscGraphDocTag";
2
3
 
3
4
  /** Targeted symbol lookup when a concrete name or handle is being resolved. */
4
5
  export interface ITtscGraphLookup {
@@ -7,6 +8,19 @@ export interface ITtscGraphLookup {
7
8
 
8
9
  /** Ranked symbol matches for the query. */
9
10
  hits: ITtscGraphLookup.IHit[];
11
+
12
+ /**
13
+ * True when a match was left out by the limit.
14
+ *
15
+ * The audit for the ranked operations already tells a reader that
16
+ * `truncated` marks where more was left out, and this result had no such
17
+ * field — so a cut looked exactly like a complete answer. That is tolerable
18
+ * for a name query, where the ranking is a shortlist by design, and it is not
19
+ * for a documentation target: those hits are an exact match on an address, so
20
+ * a caller asking which code implements a specification is owed the fact that
21
+ * it did not get all of it.
22
+ */
23
+ truncated?: boolean;
10
24
  }
11
25
  export namespace ITtscGraphLookup {
12
26
  /** Find a concrete class, method, function, property, type, or dotted handle. */
@@ -19,6 +33,14 @@ export namespace ITtscGraphLookup {
19
33
  * short phrase (`request handler`). Exact names are not required, but this
20
34
  * is not a second broad entrypoints call; use it for a missing or ambiguous
21
35
  * named handle.
36
+ *
37
+ * It also answers the other direction. Give it a documentation target — a
38
+ * document section (`docs/pricing.md#sale`), an API operation
39
+ * (`POST:/orders`), a data model (`prisma:Sale`) — and the hits are the
40
+ * declarations whose documentation cites it, each carrying the tag that
41
+ * matched. That is the question a repository-wide search would otherwise
42
+ * answer, so it is worth asking here first; a target is matched exactly, so
43
+ * spell it as the code does.
22
44
  */
23
45
  query: string;
24
46
 
@@ -63,6 +85,15 @@ export namespace ITtscGraphLookup {
63
85
  /** Decorators written on this declaration, when any. */
64
86
  decorators?: ITtscGraphDecorator[];
65
87
 
88
+ /**
89
+ * The documentation tags that matched the query, when the query named one.
90
+ *
91
+ * Present only on a hit found through its tags, so it says why this
92
+ * declaration is here: the query named a specification and this is the code
93
+ * that answers to it. A hit matched by name carries none.
94
+ */
95
+ docTags?: ITtscGraphDocTag[];
96
+
66
97
  /** Relative relevance; higher is a better match. */
67
98
  score: number;
68
99
  }
@@ -1,4 +1,5 @@
1
1
  import { ITtscGraphDecorator } from "./ITtscGraphDecorator";
2
+ import { ITtscGraphDocTag } from "./ITtscGraphDocTag";
2
3
  import { ITtscGraphEvidence } from "./ITtscGraphEvidence";
3
4
  import { TtscGraphNodeKind } from "./TtscGraphNodeKind";
4
5
  import { TtscGraphNodeModifier } from "./TtscGraphNodeModifier";
@@ -18,6 +19,17 @@ export interface ITtscGraphNode {
18
19
  /** What this node represents. */
19
20
  kind: TtscGraphNodeKind;
20
21
 
22
+ /**
23
+ * The artifact containing this one, by id.
24
+ *
25
+ * Present only on an artifact node — a section's document or enclosing
26
+ * section, a column's model. A declaration's containment is synthesized by
27
+ * the memory layer from its owner and its file, which an artifact has no
28
+ * equivalent of: a Prisma model has no file in its address, and an API
29
+ * operation has no file at all.
30
+ */
31
+ parent?: string;
32
+
21
33
  /** The simple, unqualified declared name (`create`, `OrderService`, `App`). */
22
34
  name: string;
23
35
 
@@ -105,6 +117,21 @@ export interface ITtscGraphNode {
105
117
  */
106
118
  decorators?: ITtscGraphDecorator[];
107
119
 
120
+ /**
121
+ * Documentation tags TypeScript does not recognize, in source order: raw
122
+ * facts (`@evidence docs/pricing.md#sale`, `@reference …`) a consumer
123
+ * interprets without re-parsing source.
124
+ *
125
+ * This is where a declaration says what outside the type system it answers
126
+ * to, and it is the only place the graph holds it: the other end of such a
127
+ * relation is a document section or an API operation, which is not a node.
128
+ *
129
+ * Read it only when `provenance.capabilities` lists `docTags`. Without that
130
+ * claim the field is absent because the producer never looked, which is a
131
+ * different fact from a declaration that carries none.
132
+ */
133
+ docTags?: ITtscGraphDocTag[];
134
+
108
135
  /**
109
136
  * The declaration head, cut by the producer where the compiler says the body
110
137
  * opens.
@@ -0,0 +1,35 @@
1
+ import type { TtscGraphNodeKind } from "./TtscGraphNodeKind";
2
+
3
+ /**
4
+ * The node kinds that are published artifacts rather than TypeScript
5
+ * declarations.
6
+ *
7
+ * A citation whose target is a TypeScript symbol is a relation the checker
8
+ * resolved. A citation whose target is a document section, a data model field,
9
+ * or an API operation is one of these: a plugin parsed the artifact and
10
+ * published what it is, and the graph indexes that without interpreting any of
11
+ * it. What the linter decided about the citation — covered, excluded, missing —
12
+ * never travels; that is its product and it delivers it as a compile error.
13
+ */
14
+ export const TTSC_GRAPH_ARTIFACT_NODE_KINDS = [
15
+ "markdown_document",
16
+ "markdown_section",
17
+ "prisma_model",
18
+ "prisma_column",
19
+ "prisma_relation",
20
+ "swagger_operation",
21
+ ] as const satisfies readonly TtscGraphNodeKind[];
22
+
23
+ /**
24
+ * Whether a node kind names a published artifact.
25
+ *
26
+ * It is the gate an id parser needs. An artifact's id is the address a citation
27
+ * writes — `docs/sale.md#pricing`, `prisma:Sale.price`, `POST:/orders` — and
28
+ * none of those follow the `path#qualifiedName:kind` grammar
29
+ * {@link TtscGraphNodeId} assumes: a Prisma address carries no path because a
30
+ * model name is unique across the schema folder, and an operation has no file
31
+ * at all.
32
+ */
33
+ export function isArtifactNodeKind(kind: string): boolean {
34
+ return (TTSC_GRAPH_ARTIFACT_NODE_KINDS as readonly string[]).includes(kind);
35
+ }
@@ -5,6 +5,7 @@ export type TtscGraphDumpEdgeKind =
5
5
  | "accesses"
6
6
  | "instantiates"
7
7
  | "type_ref"
8
+ | "doc_ref"
8
9
  | "extends"
9
10
  | "implements"
10
11
  | "overrides"
@@ -1,4 +1,12 @@
1
- /** Declaration kinds the native Go dump producer can write. */
1
+ /**
2
+ * Node kinds the native Go dump producer can write.
3
+ *
4
+ * The last six are not declarations. A plugin materialized them from a Markdown
5
+ * document, a Prisma schema, or an API document — artifacts a citation can name
6
+ * that the type system holds nothing for — and their ids are the address a
7
+ * citation writes rather than the `path#name:kind` grammar, which is why an id
8
+ * is parsed only after its kind says it can be.
9
+ */
2
10
  export type TtscGraphDumpNodeKind =
3
11
  | "module"
4
12
  | "function"
@@ -7,4 +15,10 @@ export type TtscGraphDumpNodeKind =
7
15
  | "type"
8
16
  | "enum"
9
17
  | "variable"
10
- | "method";
18
+ | "method"
19
+ | "markdown_document"
20
+ | "markdown_section"
21
+ | "prisma_model"
22
+ | "prisma_column"
23
+ | "prisma_relation"
24
+ | "swagger_operation";
@@ -8,6 +8,13 @@
8
8
  * — `renders` is a JSX component use. Decorators are facts on their target node,
9
9
  * not edges.
10
10
  *
11
+ * `doc_ref` is a declaration's own documentation naming a symbol through an
12
+ * inline link. The checker resolves that name and counts it as a use, so it is
13
+ * a compiler fact like the rest; it is its own kind rather than a `type_ref`
14
+ * because a link is not a type position and may name a function. The tag around
15
+ * a link decides nothing — one under `@evidence`, under `@see`, and in ordinary
16
+ * prose are one relation.
17
+ *
11
18
  * `dispatches` is the runtime counterpart of `overrides`/`implements`: the
12
19
  * checker resolves a call to the declaration it names, and where that
13
20
  * declaration is abstract or an interface member, the code that runs is its
@@ -22,6 +29,7 @@ export type TtscGraphEdgeKind =
22
29
  | "accesses"
23
30
  | "instantiates"
24
31
  | "type_ref"
32
+ | "doc_ref"
25
33
  | "extends"
26
34
  | "implements"
27
35
  | "overrides"
@@ -17,4 +17,10 @@ export type TtscGraphNodeKind =
17
17
  | "enum"
18
18
  | "variable"
19
19
  | "method"
20
- | "property";
20
+ | "property"
21
+ | "markdown_document"
22
+ | "markdown_section"
23
+ | "prisma_model"
24
+ | "prisma_column"
25
+ | "prisma_relation"
26
+ | "swagger_operation";
@@ -6,6 +6,7 @@
6
6
 
7
7
  export * from "./ITtscGraphApplication";
8
8
  export * from "./ITtscGraphDecorator";
9
+ export * from "./ITtscGraphDocTag";
9
10
  export * from "./ITtscGraphDump";
10
11
  export * from "./ITtscGraphEdge";
11
12
  export * from "./ITtscGraphEvidence";
@@ -23,5 +24,6 @@ export * from "./ITtscGraphTour";
23
24
  export * from "./TtscGraphEdgeKind";
24
25
  export * from "./TtscGraphDumpEdgeKind";
25
26
  export * from "./TtscGraphDumpNodeKind";
27
+ export * from "./TtscGraphArtifactNodeKind";
26
28
  export * from "./TtscGraphNodeKind";
27
29
  export * from "./TtscGraphNodeModifier";
package/src/view.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  projectOptions,
11
11
  } from "./launcherArgs";
12
12
  import { parseDump } from "./model/loadGraph";
13
+ import { publishArtifacts } from "./model/publishedArtifacts";
13
14
  import { captureProcessOutput, ensureExecutable } from "./nativeExecutable";
14
15
  import { reduce } from "./reduce";
15
16
  import { resolveGraphBinary } from "./resolveGraphBinary";
@@ -76,9 +77,24 @@ export function runView(argv: readonly string[]): number | void {
76
77
  let dumpStdout: string;
77
78
  let dumpStderr: string;
78
79
  try {
80
+ // The same artifacts the dump, the loader, and the resident session ask
81
+ // for. Without this the viewer draws a different graph from the one every
82
+ // other entry point answers with — and it already carries the colours for
83
+ // the nodes it would be missing.
84
+ const artifacts = publishArtifacts({
85
+ cwd: opts.cwd,
86
+ tsconfig: opts.tsconfig,
87
+ });
79
88
  dump = spawnSync(
80
89
  binary,
81
- ["dump", "--cwd", opts.cwd, "--tsconfig", opts.tsconfig],
90
+ [
91
+ "dump",
92
+ "--cwd",
93
+ opts.cwd,
94
+ "--tsconfig",
95
+ opts.tsconfig,
96
+ ...(artifacts.file === null ? [] : ["--artifacts", artifacts.file]),
97
+ ],
82
98
  {
83
99
  stdio: ["ignore", capture.stdoutFd, capture.stderrFd],
84
100
  windowsHide: true,
@@ -102,19 +102,11 @@
102
102
  <span id="counts"></span>
103
103
  </header>
104
104
  <div id="graph"></div>
105
- <footer>
106
- <span class="dot"
107
- ><span class="swatch" style="background: #3fb950"></span
108
- >value-call</span
109
- >
110
- <span class="dot"
111
- ><span class="swatch" style="background: #f5b042"></span
112
- >type-ref</span
113
- >
114
- <span class="dot"
115
- ><span class="swatch" style="background: #6ea8ff"></span
116
- >heritage</span
117
- >
105
+ <!-- The edge-family swatches are prepended here from the one colour map
106
+ in legend.ts. They used to be written out by hand, so a new family
107
+ rendered in the graph with no entry beside it until someone opened
108
+ both files. -->
109
+ <footer id="legend">
118
110
  <span>node size = connection count · color = declaration kind</span>
119
111
  </footer>
120
112
  </div>