@descent-vtt/spec-graph 0.5.0 → 0.6.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.
- package/CHANGELOG.md +106 -3
- package/README.md +37 -3
- package/dist/cli.d.ts +5 -4
- package/dist/cli.js +67 -9
- package/dist/cli.js.map +1 -1
- package/dist/diff.d.ts +120 -0
- package/dist/diff.js +404 -0
- package/dist/diff.js.map +1 -0
- package/dist/directives.d.ts +18 -1
- package/dist/directives.js +79 -0
- package/dist/directives.js.map +1 -1
- package/dist/extract.js +4 -2
- package/dist/extract.js.map +1 -1
- package/dist/glob.d.ts +34 -0
- package/dist/glob.js +51 -4
- package/dist/glob.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/regex.d.ts +18 -6
- package/dist/regex.js +185 -71
- package/dist/regex.js.map +1 -1
- package/dist/report.d.ts +10 -0
- package/dist/report.js +2 -0
- package/dist/report.js.map +1 -1
- package/dist/resolve.js +11 -0
- package/dist/resolve.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/diff.js
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What changed between two states of the graph, read from two JSON exports.
|
|
3
|
+
*
|
|
4
|
+
* Two files in, one report out, and no git underneath: CI already has both
|
|
5
|
+
* checkouts, and the pipeline stays a pure function of text. See ADR-0020.
|
|
6
|
+
*
|
|
7
|
+
* The rule the whole module follows is that a change is reported only against a
|
|
8
|
+
* name that means the same thing in both exports. A document's id does. An
|
|
9
|
+
* item's numbered id does not - it is an ordinal within a section, so one
|
|
10
|
+
* inserted question renumbers every question after it - and keying on it
|
|
11
|
+
* reports a question as reopened when nobody touched it. So an obligation is
|
|
12
|
+
* paired only by a declared id, or by its document, section and title when
|
|
13
|
+
* nothing else shares them; anything unpaired is said to have appeared or
|
|
14
|
+
* disappeared, and never to have changed.
|
|
15
|
+
*/
|
|
16
|
+
/** Refused input: not JSON, not an export, or a version this cannot read. */
|
|
17
|
+
export class DiffInputError extends Error {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'DiffInputError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/* -------------------------------------------------------------------------- */
|
|
24
|
+
/* Reading an export */
|
|
25
|
+
/* -------------------------------------------------------------------------- */
|
|
26
|
+
function isRecord(value) {
|
|
27
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
function text(value) {
|
|
30
|
+
return typeof value === 'string' ? value : null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Reads one export.
|
|
34
|
+
*
|
|
35
|
+
* Checked as far as the comparison reads it and no further: a field this does
|
|
36
|
+
* not use is not a reason to refuse a file, and a later export that adds fields
|
|
37
|
+
* is still version 1.
|
|
38
|
+
*/
|
|
39
|
+
export function parseGraphExport(raw, source) {
|
|
40
|
+
let parsed;
|
|
41
|
+
try {
|
|
42
|
+
parsed = JSON.parse(raw);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
throw new DiffInputError(`${source} is not JSON; make it with \`spec-graph graph --graph-format json\``);
|
|
46
|
+
}
|
|
47
|
+
if (!isRecord(parsed) || !Array.isArray(parsed['nodes']) || !Array.isArray(parsed['edges'])) {
|
|
48
|
+
throw new DiffInputError(`${source} is not a graph export; make it with \`spec-graph graph --graph-format json\``);
|
|
49
|
+
}
|
|
50
|
+
if (parsed['version'] !== 1) {
|
|
51
|
+
throw new DiffInputError(`${source} is export version ${JSON.stringify(parsed['version'])}, and this reads version 1`);
|
|
52
|
+
}
|
|
53
|
+
const generator = isRecord(parsed['generator']) ? parsed['generator'] : null;
|
|
54
|
+
const name = generator ? text(generator['name']) : null;
|
|
55
|
+
const version = generator ? text(generator['version']) : null;
|
|
56
|
+
const documents = [];
|
|
57
|
+
const items = [];
|
|
58
|
+
parsed['nodes'].forEach((node, index) => {
|
|
59
|
+
const id = isRecord(node) ? text(node['id']) : null;
|
|
60
|
+
if (!isRecord(node) || id === null)
|
|
61
|
+
throw new DiffInputError(`${source}: node ${index} has no id`);
|
|
62
|
+
if (node['kind'] === 'document') {
|
|
63
|
+
documents.push({
|
|
64
|
+
id,
|
|
65
|
+
title: text(node['title']) ?? '',
|
|
66
|
+
path: text(node['path']) ?? '',
|
|
67
|
+
phase: text(node['phase']) ?? 'unknown',
|
|
68
|
+
status: text(node['status']),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else if (node['kind'] === 'item') {
|
|
72
|
+
const section = Array.isArray(node['section']) ? node['section'].filter((part) => typeof part === 'string') : [];
|
|
73
|
+
items.push({
|
|
74
|
+
id,
|
|
75
|
+
title: text(node['title']) ?? '',
|
|
76
|
+
document: text(node['document']) ?? '',
|
|
77
|
+
section,
|
|
78
|
+
openness: text(node['openness']) ?? 'unknown',
|
|
79
|
+
declared: typeof node['declared'] === 'boolean' ? node['declared'] : null,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
throw new DiffInputError(`${source}: node ${id} is neither a document nor an item`);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
const edges = parsed['edges'].map((edge, index) => {
|
|
87
|
+
const kind = isRecord(edge) ? text(edge['kind']) : null;
|
|
88
|
+
const from = isRecord(edge) ? text(edge['from']) : null;
|
|
89
|
+
const to = isRecord(edge) ? text(edge['to']) : null;
|
|
90
|
+
if (kind === null || from === null || to === null)
|
|
91
|
+
throw new DiffInputError(`${source}: edge ${index} is missing its kind or ends`);
|
|
92
|
+
return { kind, from, to };
|
|
93
|
+
});
|
|
94
|
+
return { generator: name !== null && version !== null ? { name, version } : null, documents, items, edges };
|
|
95
|
+
}
|
|
96
|
+
/* -------------------------------------------------------------------------- */
|
|
97
|
+
/* Comparing */
|
|
98
|
+
/* -------------------------------------------------------------------------- */
|
|
99
|
+
// Code-unit order, not locale order: the same input sorts the same way on every
|
|
100
|
+
// machine, which is what keeps a report byte-identical.
|
|
101
|
+
function byText(a, b) {
|
|
102
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
103
|
+
}
|
|
104
|
+
/** Compares two exports. Pure, and deterministic in everything it returns. */
|
|
105
|
+
export function diffExports(before, after) {
|
|
106
|
+
return {
|
|
107
|
+
generators: { before: before.generator, after: after.generator },
|
|
108
|
+
documents: compareDocuments(before, after),
|
|
109
|
+
relations: compareRelations(before, after),
|
|
110
|
+
obligations: compareObligations(before, after),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function compareDocuments(before, after) {
|
|
114
|
+
const was = new Map(before.documents.map((document) => [document.id, document]));
|
|
115
|
+
const now = new Map(after.documents.map((document) => [document.id, document]));
|
|
116
|
+
const changed = [];
|
|
117
|
+
for (const [id, next] of now) {
|
|
118
|
+
const previous = was.get(id);
|
|
119
|
+
if (previous === undefined)
|
|
120
|
+
continue;
|
|
121
|
+
// The id is the decision's name, so the same id at a new path is the same
|
|
122
|
+
// decision moved, never one removed and another added (ADR-0009).
|
|
123
|
+
const change = {
|
|
124
|
+
id,
|
|
125
|
+
...(previous.path !== next.path && { path: [previous.path, next.path] }),
|
|
126
|
+
...(previous.title !== next.title && { title: [previous.title, next.title] }),
|
|
127
|
+
...(previous.phase !== next.phase && { phase: [previous.phase, next.phase] }),
|
|
128
|
+
...(previous.status !== next.status && { status: [previous.status, next.status] }),
|
|
129
|
+
};
|
|
130
|
+
if (Object.keys(change).length > 1)
|
|
131
|
+
changed.push(change);
|
|
132
|
+
}
|
|
133
|
+
const ids = (documents) => [...documents].sort((a, b) => byText(a.id, b.id));
|
|
134
|
+
return {
|
|
135
|
+
added: ids([...now.values()].filter((document) => !was.has(document.id))),
|
|
136
|
+
removed: ids([...was.values()].filter((document) => !now.has(document.id))),
|
|
137
|
+
changed: changed.sort((a, b) => byText(a.id, b.id)),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The relations between documents, projected the way `--documents-only`
|
|
142
|
+
* projects a graph: an item's edges lifted onto its document, and a relation
|
|
143
|
+
* that ends where it began dropped - which is also what becomes of a document
|
|
144
|
+
* containing its own items, so containment survives only between documents.
|
|
145
|
+
* Where a relation was declared is not part of it - moving a supersession from
|
|
146
|
+
* a sentence into front matter changes nothing a reader relies on.
|
|
147
|
+
*/
|
|
148
|
+
function relationsOf(graph) {
|
|
149
|
+
// A document, or a target nothing in the export defines, stands for itself.
|
|
150
|
+
const owners = new Map(graph.items.map((item) => [item.id, item.document]));
|
|
151
|
+
const out = new Map();
|
|
152
|
+
for (const edge of graph.edges) {
|
|
153
|
+
const from = owners.get(edge.from) ?? edge.from;
|
|
154
|
+
const to = owners.get(edge.to) ?? edge.to;
|
|
155
|
+
if (from === to)
|
|
156
|
+
continue;
|
|
157
|
+
// Tab-joined: kinds and ids cannot contain one.
|
|
158
|
+
out.set(`${edge.kind}\t${from}\t${to}`, { kind: edge.kind, from, to });
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
function compareRelations(before, after) {
|
|
163
|
+
const was = relationsOf(before);
|
|
164
|
+
const now = relationsOf(after);
|
|
165
|
+
const sorted = (entries) => entries.sort(([a], [b]) => byText(a, b)).map(([, relation]) => relation);
|
|
166
|
+
return {
|
|
167
|
+
added: sorted([...now].filter(([key]) => !was.has(key))),
|
|
168
|
+
removed: sorted([...was].filter(([key]) => !now.has(key))),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Obligations by where they sit and then by title, heading by heading, so a
|
|
173
|
+
* section's own questions come before those of the sections under it.
|
|
174
|
+
*/
|
|
175
|
+
function byPlace(a, b) {
|
|
176
|
+
const left = [a.document, ...a.section];
|
|
177
|
+
const right = [b.document, ...b.section];
|
|
178
|
+
for (let at = 0; at < left.length && at < right.length; at++) {
|
|
179
|
+
const order = byText(left[at], right[at]);
|
|
180
|
+
if (order !== 0)
|
|
181
|
+
return order;
|
|
182
|
+
}
|
|
183
|
+
return left.length - right.length || byText(a.title, b.title);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The headings an item sits under, below its document's own. A section path
|
|
187
|
+
* starts at the document's heading, which is its title, so without this a
|
|
188
|
+
* retitled document would lose track of every question in it.
|
|
189
|
+
*/
|
|
190
|
+
function headingsIn(graph) {
|
|
191
|
+
const titles = new Map(graph.documents.map((document) => [document.id, document.title]));
|
|
192
|
+
return (item) => (item.section[0] === titles.get(item.document) ? item.section.slice(1) : item.section);
|
|
193
|
+
}
|
|
194
|
+
function compareObligations(before, after) {
|
|
195
|
+
const headingsBefore = headingsIn(before);
|
|
196
|
+
const headingsAfter = headingsIn(after);
|
|
197
|
+
const pairs = [];
|
|
198
|
+
const paired = new Set();
|
|
199
|
+
// A declared id names the same item in both states however it was reworded or
|
|
200
|
+
// moved - provided it names one item on each side. An export without the flag
|
|
201
|
+
// cannot say which ids were declared, so none of its ids are trusted.
|
|
202
|
+
const declaredIn = (items) => {
|
|
203
|
+
const out = new Map();
|
|
204
|
+
for (const item of items) {
|
|
205
|
+
if (item.declared !== true)
|
|
206
|
+
continue;
|
|
207
|
+
out.set(item.id, out.has(item.id) ? null : item);
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
};
|
|
211
|
+
const declaredBefore = declaredIn(before.items);
|
|
212
|
+
const declaredAfter = declaredIn(after.items);
|
|
213
|
+
for (const [id, item] of declaredBefore) {
|
|
214
|
+
const match = declaredAfter.get(id);
|
|
215
|
+
if (item === null || match === null || match === undefined)
|
|
216
|
+
continue;
|
|
217
|
+
pairs.push([item, match]);
|
|
218
|
+
paired.add(item);
|
|
219
|
+
paired.add(match);
|
|
220
|
+
}
|
|
221
|
+
// Otherwise the same document, headings and title, and only when nothing else
|
|
222
|
+
// on either side shares them: two identical titles in one section cannot be
|
|
223
|
+
// told apart, so neither is paired. Joined as JSON so that no two different
|
|
224
|
+
// triples can produce the same key.
|
|
225
|
+
const group = (items, headings) => {
|
|
226
|
+
const out = new Map();
|
|
227
|
+
for (const item of items) {
|
|
228
|
+
if (paired.has(item))
|
|
229
|
+
continue;
|
|
230
|
+
const key = JSON.stringify([item.document, headings(item), item.title]);
|
|
231
|
+
out.set(key, [...(out.get(key) ?? []), item]);
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
};
|
|
235
|
+
const was = group(before.items, headingsBefore);
|
|
236
|
+
const now = group(after.items, headingsAfter);
|
|
237
|
+
for (const [key, previous] of was) {
|
|
238
|
+
const next = now.get(key);
|
|
239
|
+
if (previous.length !== 1 || next?.length !== 1)
|
|
240
|
+
continue;
|
|
241
|
+
const [a, b] = [previous[0], next[0]];
|
|
242
|
+
pairs.push([a, b]);
|
|
243
|
+
paired.add(a);
|
|
244
|
+
paired.add(b);
|
|
245
|
+
}
|
|
246
|
+
const refIn = (headings) => (item) => ({
|
|
247
|
+
document: item.document,
|
|
248
|
+
section: item.section,
|
|
249
|
+
place: headings(item).join(' > '),
|
|
250
|
+
title: item.title,
|
|
251
|
+
});
|
|
252
|
+
const wasRef = refIn(headingsBefore);
|
|
253
|
+
const nowRef = refIn(headingsAfter);
|
|
254
|
+
return {
|
|
255
|
+
transitions: pairs
|
|
256
|
+
.filter(([a, b]) => a.openness !== b.openness)
|
|
257
|
+
.map(([a, b]) => ({ ...nowRef(b), openness: [a.openness, b.openness] }))
|
|
258
|
+
.sort(byPlace),
|
|
259
|
+
appeared: after.items.filter((item) => !paired.has(item)).map((item) => ({ ...nowRef(item), openness: item.openness })).sort(byPlace),
|
|
260
|
+
disappeared: before.items.filter((item) => !paired.has(item)).map((item) => ({ ...wasRef(item), openness: item.openness })).sort(byPlace),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/* -------------------------------------------------------------------------- */
|
|
264
|
+
/* Reporting */
|
|
265
|
+
/* -------------------------------------------------------------------------- */
|
|
266
|
+
function generatorName(generator) {
|
|
267
|
+
return generator === null ? 'an unknown version' : `${generator.name} ${generator.version}`;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* The warning a diff carries when its two exports may disagree for a reason
|
|
271
|
+
* that is not the repository. `null` when both name the same generator.
|
|
272
|
+
*/
|
|
273
|
+
export function generatorWarning(diff) {
|
|
274
|
+
const { before, after } = diff.generators;
|
|
275
|
+
if (before !== null && after !== null && before.name === after.name && before.version === after.version)
|
|
276
|
+
return null;
|
|
277
|
+
return `the exports were made by ${generatorName(before)} and ${generatorName(after)}; a change in extraction between versions reads as a change in the repository`;
|
|
278
|
+
}
|
|
279
|
+
function changeCount(diff) {
|
|
280
|
+
return {
|
|
281
|
+
documents: diff.documents.added.length + diff.documents.removed.length + diff.documents.changed.length,
|
|
282
|
+
relations: diff.relations.added.length + diff.relations.removed.length,
|
|
283
|
+
obligations: diff.obligations.transitions.length + diff.obligations.appeared.length + diff.obligations.disappeared.length,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function plural(n, word) {
|
|
287
|
+
return `${n} ${n === 1 ? word : `${word}s`}`;
|
|
288
|
+
}
|
|
289
|
+
/** One sentence for the whole diff, which is what a pull-request reader sees first. */
|
|
290
|
+
export function diffVerdict(diff) {
|
|
291
|
+
const count = changeCount(diff);
|
|
292
|
+
const parts = [
|
|
293
|
+
count.documents > 0 ? plural(count.documents, 'document') : null,
|
|
294
|
+
count.relations > 0 ? plural(count.relations, 'relation') : null,
|
|
295
|
+
count.obligations > 0 ? plural(count.obligations, 'obligation') : null,
|
|
296
|
+
].filter((part) => part !== null);
|
|
297
|
+
if (parts.length === 0)
|
|
298
|
+
return 'No relational change.';
|
|
299
|
+
const listed = parts.length === 1 ? parts[0] : `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1]}`;
|
|
300
|
+
return `${listed} changed.`;
|
|
301
|
+
}
|
|
302
|
+
function where(ref) {
|
|
303
|
+
return ref.place === '' ? ref.document : `${ref.document} > ${ref.place}`;
|
|
304
|
+
}
|
|
305
|
+
function transitionWord([from, to]) {
|
|
306
|
+
return to === 'closed' ? 'resolved' : from === 'closed' ? 'reopened' : to === 'partial' ? 'narrowed' : 'widened';
|
|
307
|
+
}
|
|
308
|
+
function describeChange(change) {
|
|
309
|
+
const parts = [];
|
|
310
|
+
if (change.path)
|
|
311
|
+
parts.push(`moved from ${change.path[0]} to ${change.path[1]}`);
|
|
312
|
+
if (change.phase)
|
|
313
|
+
parts.push(`${change.phase[0]} -> ${change.phase[1]}`);
|
|
314
|
+
if (change.status)
|
|
315
|
+
parts.push(`status ${change.status[0] ?? 'none'} -> ${change.status[1] ?? 'none'}`);
|
|
316
|
+
if (change.title)
|
|
317
|
+
parts.push(`retitled "${change.title[1]}"`);
|
|
318
|
+
return parts.join('; ');
|
|
319
|
+
}
|
|
320
|
+
/** The report for a terminal. */
|
|
321
|
+
export function formatDiffText(diff) {
|
|
322
|
+
const lines = [];
|
|
323
|
+
const warning = generatorWarning(diff);
|
|
324
|
+
if (warning !== null)
|
|
325
|
+
lines.push(`warning: ${warning}`, '');
|
|
326
|
+
lines.push(diffVerdict(diff));
|
|
327
|
+
const { documents, relations, obligations } = diff;
|
|
328
|
+
if (documents.added.length + documents.removed.length + documents.changed.length > 0) {
|
|
329
|
+
lines.push('', 'Documents');
|
|
330
|
+
for (const document of documents.added)
|
|
331
|
+
lines.push(` + ${document.id} ${document.title} (${document.phase})`);
|
|
332
|
+
for (const document of documents.removed)
|
|
333
|
+
lines.push(` - ${document.id} ${document.title}`);
|
|
334
|
+
for (const change of documents.changed)
|
|
335
|
+
lines.push(` ~ ${change.id} ${describeChange(change)}`);
|
|
336
|
+
}
|
|
337
|
+
if (relations.added.length + relations.removed.length > 0) {
|
|
338
|
+
lines.push('', 'Relations');
|
|
339
|
+
for (const relation of relations.added)
|
|
340
|
+
lines.push(` + ${relation.from} -${relation.kind}-> ${relation.to}`);
|
|
341
|
+
for (const relation of relations.removed)
|
|
342
|
+
lines.push(` - ${relation.from} -${relation.kind}-> ${relation.to}`);
|
|
343
|
+
}
|
|
344
|
+
if (obligations.transitions.length + obligations.appeared.length + obligations.disappeared.length > 0) {
|
|
345
|
+
lines.push('', 'Obligations');
|
|
346
|
+
// Padded to the longest word, so the places line up.
|
|
347
|
+
const word = (text) => text.padEnd('disappeared'.length);
|
|
348
|
+
for (const transition of obligations.transitions) {
|
|
349
|
+
lines.push(` ${word(transitionWord(transition.openness))} ${where(transition)}: "${transition.title}" (${transition.openness[0]} -> ${transition.openness[1]})`);
|
|
350
|
+
}
|
|
351
|
+
for (const item of obligations.appeared)
|
|
352
|
+
lines.push(` ${word('appeared')} ${where(item)}: "${item.title}" (${item.openness})`);
|
|
353
|
+
for (const item of obligations.disappeared)
|
|
354
|
+
lines.push(` disappeared ${where(item)}: "${item.title}" (was ${item.openness})`);
|
|
355
|
+
}
|
|
356
|
+
return `${lines.join('\n')}\n`;
|
|
357
|
+
}
|
|
358
|
+
/** The report for a bot: the diff itself, with the verdict and warning beside it. */
|
|
359
|
+
export function formatDiffJson(diff) {
|
|
360
|
+
return `${JSON.stringify({ version: 1, verdict: diffVerdict(diff), warning: generatorWarning(diff), ...diff }, null, 2)}\n`;
|
|
361
|
+
}
|
|
362
|
+
// A table cell: a pipe would end the cell, and a newline would end the row.
|
|
363
|
+
function cell(value) {
|
|
364
|
+
return value.replace(/[|]/g, '|').replace(/\s+/g, ' ');
|
|
365
|
+
}
|
|
366
|
+
/** The report for a job summary or a pull-request comment. */
|
|
367
|
+
export function formatDiffMarkdown(diff) {
|
|
368
|
+
const lines = ['### spec-graph diff', '', diffVerdict(diff), ''];
|
|
369
|
+
const warning = generatorWarning(diff);
|
|
370
|
+
if (warning !== null)
|
|
371
|
+
lines.push(`> **Warning:** ${cell(warning)}.`, '');
|
|
372
|
+
const { documents, relations, obligations } = diff;
|
|
373
|
+
if (documents.added.length + documents.removed.length + documents.changed.length > 0) {
|
|
374
|
+
lines.push('| | Document | Change |', '| --- | --- | --- |');
|
|
375
|
+
for (const document of documents.added)
|
|
376
|
+
lines.push(`| added | \`${cell(document.id)}\` | ${cell(document.title)} - ${cell(document.phase)} |`);
|
|
377
|
+
for (const document of documents.removed)
|
|
378
|
+
lines.push(`| removed | \`${cell(document.id)}\` | ${cell(document.title)} |`);
|
|
379
|
+
for (const change of documents.changed)
|
|
380
|
+
lines.push(`| changed | \`${cell(change.id)}\` | ${cell(describeChange(change))} |`);
|
|
381
|
+
lines.push('');
|
|
382
|
+
}
|
|
383
|
+
if (relations.added.length + relations.removed.length > 0) {
|
|
384
|
+
lines.push('| | From | Relation | To |', '| --- | --- | --- | --- |');
|
|
385
|
+
for (const relation of relations.added)
|
|
386
|
+
lines.push(`| added | \`${cell(relation.from)}\` | ${cell(relation.kind)} | \`${cell(relation.to)}\` |`);
|
|
387
|
+
for (const relation of relations.removed)
|
|
388
|
+
lines.push(`| removed | \`${cell(relation.from)}\` | ${cell(relation.kind)} | \`${cell(relation.to)}\` |`);
|
|
389
|
+
lines.push('');
|
|
390
|
+
}
|
|
391
|
+
if (obligations.transitions.length + obligations.appeared.length + obligations.disappeared.length > 0) {
|
|
392
|
+
lines.push('| | Where | Obligation |', '| --- | --- | --- |');
|
|
393
|
+
for (const transition of obligations.transitions) {
|
|
394
|
+
lines.push(`| ${transitionWord(transition.openness)} | ${cell(where(transition))} | ${cell(transition.title)} (${transition.openness[0]} -> ${transition.openness[1]}) |`);
|
|
395
|
+
}
|
|
396
|
+
for (const item of obligations.appeared)
|
|
397
|
+
lines.push(`| appeared | ${cell(where(item))} | ${cell(item.title)} (${item.openness}) |`);
|
|
398
|
+
for (const item of obligations.disappeared)
|
|
399
|
+
lines.push(`| disappeared | ${cell(where(item))} | ${cell(item.title)} (was ${item.openness}) |`);
|
|
400
|
+
lines.push('');
|
|
401
|
+
}
|
|
402
|
+
return `${lines.join('\n').trimEnd()}\n`;
|
|
403
|
+
}
|
|
404
|
+
//# sourceMappingURL=diff.js.map
|
package/dist/diff.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What changed between two states of the graph, read from two JSON exports.\n *\n * Two files in, one report out, and no git underneath: CI already has both\n * checkouts, and the pipeline stays a pure function of text. See ADR-0020.\n *\n * The rule the whole module follows is that a change is reported only against a\n * name that means the same thing in both exports. A document's id does. An\n * item's numbered id does not - it is an ordinal within a section, so one\n * inserted question renumbers every question after it - and keying on it\n * reports a question as reopened when nobody touched it. So an obligation is\n * paired only by a declared id, or by its document, section and title when\n * nothing else shares them; anything unpaired is said to have appeared or\n * disappeared, and never to have changed.\n */\n\n/** Refused input: not JSON, not an export, or a version this cannot read. */\nexport class DiffInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'DiffInputError';\n }\n}\n\nexport interface Generator {\n readonly name: string;\n readonly version: string;\n}\n\nexport interface ExportedDocument {\n readonly id: string;\n readonly title: string;\n readonly path: string;\n readonly phase: string;\n readonly status: string | null;\n}\n\nexport interface ExportedItem {\n readonly id: string;\n readonly title: string;\n readonly document: string;\n readonly section: readonly string[];\n readonly openness: string;\n /** `null` when the export predates the flag, so its ids cannot be trusted. */\n readonly declared: boolean | null;\n}\n\nexport interface ExportedRelation {\n readonly kind: string;\n readonly from: string;\n readonly to: string;\n}\n\n/** One side of a diff: a `--graph-format json` export, read and checked. */\nexport interface GraphExport {\n readonly generator: Generator | null;\n readonly documents: readonly ExportedDocument[];\n readonly items: readonly ExportedItem[];\n readonly edges: readonly ExportedRelation[];\n}\n\nexport interface DocumentChange {\n readonly id: string;\n readonly path?: readonly [string, string] | undefined;\n readonly title?: readonly [string, string] | undefined;\n readonly phase?: readonly [string, string] | undefined;\n readonly status?: readonly [string | null, string | null] | undefined;\n}\n\nexport interface ObligationRef {\n readonly document: string;\n readonly section: readonly string[];\n /**\n * The section path as a reader wants it: without the document's own title\n * heading, which every item under it shares and the id already names. Items\n * are matched on the same headings, kept as a list so that no heading holding\n * \" > \" can pass for two.\n */\n readonly place: string;\n readonly title: string;\n}\n\n/** An item with no pair: its state on the one side it exists on, and no more. */\nexport interface UnpairedObligation extends ObligationRef {\n readonly openness: string;\n}\n\nexport interface Transition extends ObligationRef {\n readonly openness: readonly [string, string];\n}\n\nexport interface GraphDiff {\n readonly generators: { readonly before: Generator | null; readonly after: Generator | null };\n readonly documents: {\n readonly added: readonly ExportedDocument[];\n readonly removed: readonly ExportedDocument[];\n readonly changed: readonly DocumentChange[];\n };\n readonly relations: { readonly added: readonly ExportedRelation[]; readonly removed: readonly ExportedRelation[] };\n readonly obligations: {\n readonly transitions: readonly Transition[];\n readonly appeared: readonly UnpairedObligation[];\n readonly disappeared: readonly UnpairedObligation[];\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Reading an export */\n/* -------------------------------------------------------------------------- */\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction text(value: unknown): string | null {\n return typeof value === 'string' ? value : null;\n}\n\n/**\n * Reads one export.\n *\n * Checked as far as the comparison reads it and no further: a field this does\n * not use is not a reason to refuse a file, and a later export that adds fields\n * is still version 1.\n */\nexport function parseGraphExport(raw: string, source: string): GraphExport {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new DiffInputError(`${source} is not JSON; make it with \\`spec-graph graph --graph-format json\\``);\n }\n if (!isRecord(parsed) || !Array.isArray(parsed['nodes']) || !Array.isArray(parsed['edges'])) {\n throw new DiffInputError(`${source} is not a graph export; make it with \\`spec-graph graph --graph-format json\\``);\n }\n if (parsed['version'] !== 1) {\n throw new DiffInputError(`${source} is export version ${JSON.stringify(parsed['version'])}, and this reads version 1`);\n }\n\n const generator = isRecord(parsed['generator']) ? parsed['generator'] : null;\n const name = generator ? text(generator['name']) : null;\n const version = generator ? text(generator['version']) : null;\n\n const documents: ExportedDocument[] = [];\n const items: ExportedItem[] = [];\n parsed['nodes'].forEach((node: unknown, index) => {\n const id = isRecord(node) ? text(node['id']) : null;\n if (!isRecord(node) || id === null) throw new DiffInputError(`${source}: node ${index} has no id`);\n if (node['kind'] === 'document') {\n documents.push({\n id,\n title: text(node['title']) ?? '',\n path: text(node['path']) ?? '',\n phase: text(node['phase']) ?? 'unknown',\n status: text(node['status']),\n });\n } else if (node['kind'] === 'item') {\n const section = Array.isArray(node['section']) ? node['section'].filter((part): part is string => typeof part === 'string') : [];\n items.push({\n id,\n title: text(node['title']) ?? '',\n document: text(node['document']) ?? '',\n section,\n openness: text(node['openness']) ?? 'unknown',\n declared: typeof node['declared'] === 'boolean' ? node['declared'] : null,\n });\n } else {\n throw new DiffInputError(`${source}: node ${id} is neither a document nor an item`);\n }\n });\n\n const edges: ExportedRelation[] = parsed['edges'].map((edge: unknown, index) => {\n const kind = isRecord(edge) ? text(edge['kind']) : null;\n const from = isRecord(edge) ? text(edge['from']) : null;\n const to = isRecord(edge) ? text(edge['to']) : null;\n if (kind === null || from === null || to === null) throw new DiffInputError(`${source}: edge ${index} is missing its kind or ends`);\n return { kind, from, to };\n });\n\n return { generator: name !== null && version !== null ? { name, version } : null, documents, items, edges };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Comparing */\n/* -------------------------------------------------------------------------- */\n\n// Code-unit order, not locale order: the same input sorts the same way on every\n// machine, which is what keeps a report byte-identical.\nfunction byText(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/** Compares two exports. Pure, and deterministic in everything it returns. */\nexport function diffExports(before: GraphExport, after: GraphExport): GraphDiff {\n return {\n generators: { before: before.generator, after: after.generator },\n documents: compareDocuments(before, after),\n relations: compareRelations(before, after),\n obligations: compareObligations(before, after),\n };\n}\n\nfunction compareDocuments(before: GraphExport, after: GraphExport): GraphDiff['documents'] {\n const was = new Map(before.documents.map((document) => [document.id, document]));\n const now = new Map(after.documents.map((document) => [document.id, document]));\n const changed: DocumentChange[] = [];\n for (const [id, next] of now) {\n const previous = was.get(id);\n if (previous === undefined) continue;\n // The id is the decision's name, so the same id at a new path is the same\n // decision moved, never one removed and another added (ADR-0009).\n const change: DocumentChange = {\n id,\n ...(previous.path !== next.path && { path: [previous.path, next.path] as const }),\n ...(previous.title !== next.title && { title: [previous.title, next.title] as const }),\n ...(previous.phase !== next.phase && { phase: [previous.phase, next.phase] as const }),\n ...(previous.status !== next.status && { status: [previous.status, next.status] as const }),\n };\n if (Object.keys(change).length > 1) changed.push(change);\n }\n const ids = (documents: Iterable<ExportedDocument>) => [...documents].sort((a, b) => byText(a.id, b.id));\n return {\n added: ids([...now.values()].filter((document) => !was.has(document.id))),\n removed: ids([...was.values()].filter((document) => !now.has(document.id))),\n changed: changed.sort((a, b) => byText(a.id, b.id)),\n };\n}\n\n/**\n * The relations between documents, projected the way `--documents-only`\n * projects a graph: an item's edges lifted onto its document, and a relation\n * that ends where it began dropped - which is also what becomes of a document\n * containing its own items, so containment survives only between documents.\n * Where a relation was declared is not part of it - moving a supersession from\n * a sentence into front matter changes nothing a reader relies on.\n */\nfunction relationsOf(graph: GraphExport): Map<string, ExportedRelation> {\n // A document, or a target nothing in the export defines, stands for itself.\n const owners = new Map(graph.items.map((item) => [item.id, item.document]));\n const out = new Map<string, ExportedRelation>();\n for (const edge of graph.edges) {\n const from = owners.get(edge.from) ?? edge.from;\n const to = owners.get(edge.to) ?? edge.to;\n if (from === to) continue;\n // Tab-joined: kinds and ids cannot contain one.\n out.set(`${edge.kind}\\t${from}\\t${to}`, { kind: edge.kind, from, to });\n }\n return out;\n}\n\nfunction compareRelations(before: GraphExport, after: GraphExport): GraphDiff['relations'] {\n const was = relationsOf(before);\n const now = relationsOf(after);\n const sorted = (entries: [string, ExportedRelation][]) => entries.sort(([a], [b]) => byText(a, b)).map(([, relation]) => relation);\n return {\n added: sorted([...now].filter(([key]) => !was.has(key))),\n removed: sorted([...was].filter(([key]) => !now.has(key))),\n };\n}\n\n/**\n * Obligations by where they sit and then by title, heading by heading, so a\n * section's own questions come before those of the sections under it.\n */\nfunction byPlace(a: ObligationRef, b: ObligationRef): number {\n const left = [a.document, ...a.section];\n const right = [b.document, ...b.section];\n for (let at = 0; at < left.length && at < right.length; at++) {\n const order = byText(left[at] as string, right[at] as string);\n if (order !== 0) return order;\n }\n return left.length - right.length || byText(a.title, b.title);\n}\n\n/**\n * The headings an item sits under, below its document's own. A section path\n * starts at the document's heading, which is its title, so without this a\n * retitled document would lose track of every question in it.\n */\nfunction headingsIn(graph: GraphExport): (item: ExportedItem) => readonly string[] {\n const titles = new Map(graph.documents.map((document) => [document.id, document.title]));\n return (item) => (item.section[0] === titles.get(item.document) ? item.section.slice(1) : item.section);\n}\n\nfunction compareObligations(before: GraphExport, after: GraphExport): GraphDiff['obligations'] {\n const headingsBefore = headingsIn(before);\n const headingsAfter = headingsIn(after);\n const pairs: [ExportedItem, ExportedItem][] = [];\n const paired = new Set<ExportedItem>();\n\n // A declared id names the same item in both states however it was reworded or\n // moved - provided it names one item on each side. An export without the flag\n // cannot say which ids were declared, so none of its ids are trusted.\n const declaredIn = (items: readonly ExportedItem[]) => {\n const out = new Map<string, ExportedItem | null>();\n for (const item of items) {\n if (item.declared !== true) continue;\n out.set(item.id, out.has(item.id) ? null : item);\n }\n return out;\n };\n const declaredBefore = declaredIn(before.items);\n const declaredAfter = declaredIn(after.items);\n for (const [id, item] of declaredBefore) {\n const match = declaredAfter.get(id);\n if (item === null || match === null || match === undefined) continue;\n pairs.push([item, match]);\n paired.add(item);\n paired.add(match);\n }\n\n // Otherwise the same document, headings and title, and only when nothing else\n // on either side shares them: two identical titles in one section cannot be\n // told apart, so neither is paired. Joined as JSON so that no two different\n // triples can produce the same key.\n const group = (items: readonly ExportedItem[], headings: (item: ExportedItem) => readonly string[]) => {\n const out = new Map<string, ExportedItem[]>();\n for (const item of items) {\n if (paired.has(item)) continue;\n const key = JSON.stringify([item.document, headings(item), item.title]);\n out.set(key, [...(out.get(key) ?? []), item]);\n }\n return out;\n };\n const was = group(before.items, headingsBefore);\n const now = group(after.items, headingsAfter);\n for (const [key, previous] of was) {\n const next = now.get(key);\n if (previous.length !== 1 || next?.length !== 1) continue;\n const [a, b] = [previous[0] as ExportedItem, next[0] as ExportedItem];\n pairs.push([a, b]);\n paired.add(a);\n paired.add(b);\n }\n\n const refIn = (headings: (item: ExportedItem) => readonly string[]) => (item: ExportedItem): ObligationRef => ({\n document: item.document,\n section: item.section,\n place: headings(item).join(' > '),\n title: item.title,\n });\n const wasRef = refIn(headingsBefore);\n const nowRef = refIn(headingsAfter);\n return {\n transitions: pairs\n .filter(([a, b]) => a.openness !== b.openness)\n .map(([a, b]) => ({ ...nowRef(b), openness: [a.openness, b.openness] as const }))\n .sort(byPlace),\n appeared: after.items.filter((item) => !paired.has(item)).map((item) => ({ ...nowRef(item), openness: item.openness })).sort(byPlace),\n disappeared: before.items.filter((item) => !paired.has(item)).map((item) => ({ ...wasRef(item), openness: item.openness })).sort(byPlace),\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Reporting */\n/* -------------------------------------------------------------------------- */\n\nfunction generatorName(generator: Generator | null): string {\n return generator === null ? 'an unknown version' : `${generator.name} ${generator.version}`;\n}\n\n/**\n * The warning a diff carries when its two exports may disagree for a reason\n * that is not the repository. `null` when both name the same generator.\n */\nexport function generatorWarning(diff: GraphDiff): string | null {\n const { before, after } = diff.generators;\n if (before !== null && after !== null && before.name === after.name && before.version === after.version) return null;\n return `the exports were made by ${generatorName(before)} and ${generatorName(after)}; a change in extraction between versions reads as a change in the repository`;\n}\n\nfunction changeCount(diff: GraphDiff): { documents: number; relations: number; obligations: number } {\n return {\n documents: diff.documents.added.length + diff.documents.removed.length + diff.documents.changed.length,\n relations: diff.relations.added.length + diff.relations.removed.length,\n obligations: diff.obligations.transitions.length + diff.obligations.appeared.length + diff.obligations.disappeared.length,\n };\n}\n\nfunction plural(n: number, word: string): string {\n return `${n} ${n === 1 ? word : `${word}s`}`;\n}\n\n/** One sentence for the whole diff, which is what a pull-request reader sees first. */\nexport function diffVerdict(diff: GraphDiff): string {\n const count = changeCount(diff);\n const parts = [\n count.documents > 0 ? plural(count.documents, 'document') : null,\n count.relations > 0 ? plural(count.relations, 'relation') : null,\n count.obligations > 0 ? plural(count.obligations, 'obligation') : null,\n ].filter((part): part is string => part !== null);\n if (parts.length === 0) return 'No relational change.';\n const listed = parts.length === 1 ? (parts[0] as string) : `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1] as string}`;\n return `${listed} changed.`;\n}\n\nfunction where(ref: ObligationRef): string {\n return ref.place === '' ? ref.document : `${ref.document} > ${ref.place}`;\n}\n\nfunction transitionWord([from, to]: readonly [string, string]): string {\n return to === 'closed' ? 'resolved' : from === 'closed' ? 'reopened' : to === 'partial' ? 'narrowed' : 'widened';\n}\n\nfunction describeChange(change: DocumentChange): string {\n const parts: string[] = [];\n if (change.path) parts.push(`moved from ${change.path[0]} to ${change.path[1]}`);\n if (change.phase) parts.push(`${change.phase[0]} -> ${change.phase[1]}`);\n if (change.status) parts.push(`status ${change.status[0] ?? 'none'} -> ${change.status[1] ?? 'none'}`);\n if (change.title) parts.push(`retitled \"${change.title[1]}\"`);\n return parts.join('; ');\n}\n\n/** The report for a terminal. */\nexport function formatDiffText(diff: GraphDiff): string {\n const lines: string[] = [];\n const warning = generatorWarning(diff);\n if (warning !== null) lines.push(`warning: ${warning}`, '');\n lines.push(diffVerdict(diff));\n\n const { documents, relations, obligations } = diff;\n if (documents.added.length + documents.removed.length + documents.changed.length > 0) {\n lines.push('', 'Documents');\n for (const document of documents.added) lines.push(` + ${document.id} ${document.title} (${document.phase})`);\n for (const document of documents.removed) lines.push(` - ${document.id} ${document.title}`);\n for (const change of documents.changed) lines.push(` ~ ${change.id} ${describeChange(change)}`);\n }\n if (relations.added.length + relations.removed.length > 0) {\n lines.push('', 'Relations');\n for (const relation of relations.added) lines.push(` + ${relation.from} -${relation.kind}-> ${relation.to}`);\n for (const relation of relations.removed) lines.push(` - ${relation.from} -${relation.kind}-> ${relation.to}`);\n }\n if (obligations.transitions.length + obligations.appeared.length + obligations.disappeared.length > 0) {\n lines.push('', 'Obligations');\n // Padded to the longest word, so the places line up.\n const word = (text: string) => text.padEnd('disappeared'.length);\n for (const transition of obligations.transitions) {\n lines.push(` ${word(transitionWord(transition.openness))} ${where(transition)}: \"${transition.title}\" (${transition.openness[0]} -> ${transition.openness[1]})`);\n }\n for (const item of obligations.appeared) lines.push(` ${word('appeared')} ${where(item)}: \"${item.title}\" (${item.openness})`);\n for (const item of obligations.disappeared) lines.push(` disappeared ${where(item)}: \"${item.title}\" (was ${item.openness})`);\n }\n return `${lines.join('\\n')}\\n`;\n}\n\n/** The report for a bot: the diff itself, with the verdict and warning beside it. */\nexport function formatDiffJson(diff: GraphDiff): string {\n return `${JSON.stringify({ version: 1, verdict: diffVerdict(diff), warning: generatorWarning(diff), ...diff }, null, 2)}\\n`;\n}\n\n// A table cell: a pipe would end the cell, and a newline would end the row.\nfunction cell(value: string): string {\n return value.replace(/[|]/g, '|').replace(/\\s+/g, ' ');\n}\n\n/** The report for a job summary or a pull-request comment. */\nexport function formatDiffMarkdown(diff: GraphDiff): string {\n const lines: string[] = ['### spec-graph diff', '', diffVerdict(diff), ''];\n const warning = generatorWarning(diff);\n if (warning !== null) lines.push(`> **Warning:** ${cell(warning)}.`, '');\n\n const { documents, relations, obligations } = diff;\n if (documents.added.length + documents.removed.length + documents.changed.length > 0) {\n lines.push('| | Document | Change |', '| --- | --- | --- |');\n for (const document of documents.added) lines.push(`| added | \\`${cell(document.id)}\\` | ${cell(document.title)} - ${cell(document.phase)} |`);\n for (const document of documents.removed) lines.push(`| removed | \\`${cell(document.id)}\\` | ${cell(document.title)} |`);\n for (const change of documents.changed) lines.push(`| changed | \\`${cell(change.id)}\\` | ${cell(describeChange(change))} |`);\n lines.push('');\n }\n if (relations.added.length + relations.removed.length > 0) {\n lines.push('| | From | Relation | To |', '| --- | --- | --- | --- |');\n for (const relation of relations.added) lines.push(`| added | \\`${cell(relation.from)}\\` | ${cell(relation.kind)} | \\`${cell(relation.to)}\\` |`);\n for (const relation of relations.removed) lines.push(`| removed | \\`${cell(relation.from)}\\` | ${cell(relation.kind)} | \\`${cell(relation.to)}\\` |`);\n lines.push('');\n }\n if (obligations.transitions.length + obligations.appeared.length + obligations.disappeared.length > 0) {\n lines.push('| | Where | Obligation |', '| --- | --- | --- |');\n for (const transition of obligations.transitions) {\n lines.push(`| ${transitionWord(transition.openness)} | ${cell(where(transition))} | ${cell(transition.title)} (${transition.openness[0]} -> ${transition.openness[1]}) |`);\n }\n for (const item of obligations.appeared) lines.push(`| appeared | ${cell(where(item))} | ${cell(item.title)} (${item.openness}) |`);\n for (const item of obligations.disappeared) lines.push(`| disappeared | ${cell(where(item))} | ${cell(item.title)} (was ${item.openness}) |`);\n lines.push('');\n }\n return `${lines.join('\\n').trimEnd()}\\n`;\n}\n"]}
|
package/dist/directives.d.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* A directive always wins over anything inferred, and says so in the report, so
|
|
18
18
|
* an override is visible rather than mysterious.
|
|
19
19
|
*/
|
|
20
|
-
import type { HtmlComment } from './markdown.js';
|
|
20
|
+
import type { HtmlComment, ListItem, ScannedDocument } from './markdown.js';
|
|
21
21
|
export type DirectiveName = 'spec-node' | 'spec-item' | 'spec-edge' | 'spec-ignore' | 'spec-history';
|
|
22
22
|
export interface Attribute {
|
|
23
23
|
readonly value: string;
|
|
@@ -47,6 +47,9 @@ export declare function attrList(directive: Directive, name: string): string[];
|
|
|
47
47
|
* A `@spec-item` directive may sit on the line above the item, on the same
|
|
48
48
|
* line, or inside the item body. All three read naturally in the source, so all
|
|
49
49
|
* three are accepted, with the closest preceding directive winning.
|
|
50
|
+
*
|
|
51
|
+
* It answers for one region with no view of the others, so a directive in reach
|
|
52
|
+
* of two items is found for both. Extraction uses `bindItemDirectives`.
|
|
50
53
|
*/
|
|
51
54
|
export declare function directiveFor(directives: readonly Directive[], name: DirectiveName, region: {
|
|
52
55
|
start: number;
|
|
@@ -54,3 +57,17 @@ export declare function directiveFor(directives: readonly Directive[], name: Dir
|
|
|
54
57
|
},
|
|
55
58
|
/** How far above the region a directive may sit and still apply. */
|
|
56
59
|
lookBehind: number): Directive | null;
|
|
60
|
+
/**
|
|
61
|
+
* Pairs each `@spec-item` directive with the one list item it annotates.
|
|
62
|
+
*
|
|
63
|
+
* A directive is written for one item, so it binds to one: the item directly
|
|
64
|
+
* below it, with nothing but blank lines and other comments between, or else
|
|
65
|
+
* the item it is written in, the innermost where items nest. An item that
|
|
66
|
+
* several bind to takes the last one written.
|
|
67
|
+
*
|
|
68
|
+
* A directive alone on its line is in an item only when indented further than
|
|
69
|
+
* that item's marker, which is how CommonMark reads it. Indented under an item,
|
|
70
|
+
* it annotates that item and not the sibling below; flush between two items, it
|
|
71
|
+
* annotates the second.
|
|
72
|
+
*/
|
|
73
|
+
export declare function bindItemDirectives(scanned: ScannedDocument, directives: readonly Directive[]): Map<ListItem, Directive>;
|
package/dist/directives.js
CHANGED
|
@@ -105,6 +105,9 @@ export function attrList(directive, name) {
|
|
|
105
105
|
* A `@spec-item` directive may sit on the line above the item, on the same
|
|
106
106
|
* line, or inside the item body. All three read naturally in the source, so all
|
|
107
107
|
* three are accepted, with the closest preceding directive winning.
|
|
108
|
+
*
|
|
109
|
+
* It answers for one region with no view of the others, so a directive in reach
|
|
110
|
+
* of two items is found for both. Extraction uses `bindItemDirectives`.
|
|
108
111
|
*/
|
|
109
112
|
export function directiveFor(directives, name, region,
|
|
110
113
|
/** How far above the region a directive may sit and still apply. */
|
|
@@ -122,4 +125,80 @@ lookBehind) {
|
|
|
122
125
|
}
|
|
123
126
|
return best;
|
|
124
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Pairs each `@spec-item` directive with the one list item it annotates.
|
|
130
|
+
*
|
|
131
|
+
* A directive is written for one item, so it binds to one: the item directly
|
|
132
|
+
* below it, with nothing but blank lines and other comments between, or else
|
|
133
|
+
* the item it is written in, the innermost where items nest. An item that
|
|
134
|
+
* several bind to takes the last one written.
|
|
135
|
+
*
|
|
136
|
+
* A directive alone on its line is in an item only when indented further than
|
|
137
|
+
* that item's marker, which is how CommonMark reads it. Indented under an item,
|
|
138
|
+
* it annotates that item and not the sibling below; flush between two items, it
|
|
139
|
+
* annotates the second.
|
|
140
|
+
*/
|
|
141
|
+
export function bindItemDirectives(scanned, directives) {
|
|
142
|
+
const bound = new Map();
|
|
143
|
+
const wanted = directives.filter((directive) => directive.name === 'spec-item');
|
|
144
|
+
// Most documents carry none. Sparing them the pass over their lines changes
|
|
145
|
+
// how long this takes and nothing else.
|
|
146
|
+
if (wanted.length === 0)
|
|
147
|
+
return bound;
|
|
148
|
+
const { lines, listItems, masked } = scanned;
|
|
149
|
+
// The indentation of each line holding nothing but whitespace and comments.
|
|
150
|
+
// Code is masked too, so a fence has to be told apart by its flag.
|
|
151
|
+
const quiet = new Map();
|
|
152
|
+
// For every other line, where the run of quiet lines directly above it began,
|
|
153
|
+
// when there was one.
|
|
154
|
+
const quietFrom = new Map();
|
|
155
|
+
let run;
|
|
156
|
+
for (const line of lines) {
|
|
157
|
+
if (!line.code && masked.slice(line.contentStart, line.end).trim() === '') {
|
|
158
|
+
quiet.set(line.line, line.indent);
|
|
159
|
+
run ??= line.start;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
quietFrom.set(line.line, run);
|
|
163
|
+
run = undefined;
|
|
164
|
+
}
|
|
165
|
+
// The items whose text the cursor is in, outermost first. Items nest, so one
|
|
166
|
+
// that has ended is always on top. An item ends at the end of a line, the next
|
|
167
|
+
// thing starts on a later one, and no two things start together, so `<` and
|
|
168
|
+
// `<=` read alike in every comparison of offsets below.
|
|
169
|
+
const open = [];
|
|
170
|
+
const close = (offset) => {
|
|
171
|
+
while (open.length > 0 && open[open.length - 1].end <= offset)
|
|
172
|
+
open.pop();
|
|
173
|
+
};
|
|
174
|
+
let next = 0;
|
|
175
|
+
for (const directive of wanted) {
|
|
176
|
+
while (next < listItems.length && listItems[next].start < directive.start) {
|
|
177
|
+
const item = listItems[next];
|
|
178
|
+
close(item.start);
|
|
179
|
+
open.push(item);
|
|
180
|
+
next += 1;
|
|
181
|
+
}
|
|
182
|
+
close(directive.start);
|
|
183
|
+
// Sharing its line with text, a directive is in whatever that text is in.
|
|
184
|
+
const indent = quiet.get(directive.line) ?? Infinity;
|
|
185
|
+
// No deeper than the scanner already went to find where these items end.
|
|
186
|
+
let depth = open.length - 1;
|
|
187
|
+
while (depth >= 0 && open[depth].indent >= indent)
|
|
188
|
+
depth -= 1;
|
|
189
|
+
const holder = open[depth];
|
|
190
|
+
const below = listItems[next];
|
|
191
|
+
const directlyAbove = below !== undefined && (quietFrom.get(below.line) ?? Infinity) <= directive.start;
|
|
192
|
+
if (directlyAbove && (holder === undefined || below.start < holder.end)) {
|
|
193
|
+
bound.set(below, directive);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
// Flush under an item and above no other, it continues the item as any
|
|
197
|
+
// unindented line does.
|
|
198
|
+
const written = holder ?? open[open.length - 1];
|
|
199
|
+
if (written !== undefined)
|
|
200
|
+
bound.set(written, directive);
|
|
201
|
+
}
|
|
202
|
+
return bound;
|
|
203
|
+
}
|
|
125
204
|
//# sourceMappingURL=directives.js.map
|