@heroiclands/package-build 22.17.1 → 22.17.2

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.
@@ -1,12 +1,9 @@
1
1
  /**
2
2
  * The one spelling of an address in this space: `type:shortcode`.
3
3
  *
4
- * Written by both readers — the compiled packs on each side of the diff, and
5
- * the declarations read out of the tree — so a predecessor a note names and an
6
- * address a pack publishes cannot come apart over punctuation or case. The
7
- * shortcode is used **verbatim**, not lowercased: `readItemAddresses` reads it
8
- * off the compiled document, where `Tabri` is stored as authored, and folding
9
- * case here would join two addresses the packs keep apart.
4
+ * Used for addresses read from compiled documents. The shortcode is used
5
+ * **verbatim**, not lowercased: folding case would join two addresses the packs
6
+ * keep apart.
10
7
  *
11
8
  * @param {string} type - The Foundry document subtype, not the note type.
12
9
  * @param {string} shortcode - The address's `system.shortcode`.
@@ -39,89 +36,24 @@ export function readItemAddresses(dirs: readonly string[]): Map<string, {
39
36
  shortcode: string;
40
37
  file: string;
41
38
  }>;
42
- /**
43
- * Every rename the tree's notes **declare**, as old address → where it went.
44
- *
45
- * Read from the content tree rather than from compiled output, because a
46
- * declaration is authored and the compiled document does not carry it: nothing
47
- * downstream consumes `renamedFrom:`, so emitting it into every pack to let one
48
- * diagnostic read it back would put a build-time note in shipped data forever.
49
- * The tree is already read by this module for the same reason
50
- * ({@link noteFilesById}) — to place a finding where its author can fix it.
51
- *
52
- * **A declaration is keyed by document subtype, not by note type.** The address
53
- * space is the one consumers resolve against, and it is spelled in compiled
54
- * documents: `hm3` compiles a `projectile` note into a `missilegear` item,
55
- * so that is the address a rename of it moves. {@link referencedSubtype} is the
56
- * function that already answers this for a being's embedded `(type, shortcode)`
57
- * references, so both sides read the same rule rather than a second copy of it.
58
- *
59
- * **An entry is emitted for every system that maps the type**, whether or not
60
- * the note declares that system's block. Over-emitting is inert — the diff uses
61
- * an entry only when the baseline published the old address *and* this build
62
- * publishes the new one, and a system the note does not compile for satisfies
63
- * neither — while asking which blocks a note declares would put a second,
64
- * subtly different answer to that question in a third place.
65
- *
66
- * **First claim wins on a collision.** Two notes naming one predecessor is a
67
- * contradiction — an address has one successor — and it is reported as an error
68
- * by `engine/content-lint.mjs`, where both notes are in hand and can both be
69
- * named. Picking one here keeps this a map; it is not a resolution, and nothing
70
- * rests on which one it picked.
71
- *
72
- * @param {string} contentBase - Root of the content tree.
73
- * @param {object} opts
74
- * @param {readonly string[]} [opts.skipDirectories] - The corpus scope. Stated
75
- * by the caller, never defaulted — see {@link addressCorpus}.
76
- * @param {readonly object[]} [opts.maps] - The document-subtype maps.
77
- * @param {object} [opts.config] - The resolved build configuration.
78
- * @param {readonly object[]} [opts.records] - Index records the caller already
79
- * derived, shared with {@link noteFilesById} so one command reads one corpus.
80
- * @param {object[]} [opts.problems] - Collects the notes the index cannot
81
- * record, so one of them does not abort the diff before it reports.
82
- * @returns {Map<string, {to: string, file: string, shortcode: string}>} Old
83
- * address → the address the declaring note publishes at now, and that note.
84
- */
85
- export function declaredPredecessors(contentBase: string, { skipDirectories, maps, config, records, problems }?: {
86
- skipDirectories?: readonly string[] | undefined;
87
- maps?: readonly object[] | undefined;
88
- config?: object | undefined;
89
- records?: readonly object[] | undefined;
90
- problems?: object[] | undefined;
91
- }): Map<string, {
92
- to: string;
93
- file: string;
94
- shortcode: string;
95
- }>;
96
39
  /**
97
40
  * Every address the baseline published that this build does not.
98
41
  *
99
- * An address that merely *arrived* is not a finding: adding one breaks nobody.
100
- * The arrivals are read only to answer the one question that matters about a
101
- * departure — is the document still here under another name?
42
+ * An address that arrived is not a finding. A departed address is a rename
43
+ * only when its document id appears at another current address; otherwise it
44
+ * is a withdrawal.
102
45
  *
103
46
  * @param {Map<string, object>} baseline - The released address space.
104
47
  * @param {Map<string, object>} current - This build's address space.
105
48
  * @param {object} opts
106
- * @param {string} opts.baseline - What the baseline is, for the message —
107
- * conventionally `<package>@<version>`.
108
- * @param {Map<string, {to: string, file: string}>} [opts.predecessors] - The
109
- * declared renames, from {@link declaredPredecessors}. Omitted, the diff
110
- * falls back to the id join alone and reports an unpinned rename as a
111
- * withdrawal, which is what it did before.
112
- * @returns {Array<object>} One finding per departed address, in address order
113
- * so two runs read the same. `kind` is `"renamed"` (with `to`, and `declared`
114
- * when it was the note's word rather than an id match) or `"withdrawn"`.
49
+ * @param {string} opts.baseline - What the baseline is, for the message.
50
+ * @returns {Array<object>} One finding per departed address, in address order.
115
51
  */
116
- export function diffItemAddresses(baseline: Map<string, object>, current: Map<string, object>, { baseline: label, predecessors }: {
52
+ export function diffItemAddresses(baseline: Map<string, object>, current: Map<string, object>, { baseline: label }: {
117
53
  baseline: string;
118
- predecessors?: Map<string, {
119
- to: string;
120
- file: string;
121
- }> | undefined;
122
54
  }): Array<object>;
123
55
  /**
124
- * Every content note in a tree, indexed by the document id it authors.
56
+ * Every content note in a tree, indexed by the document id it compiles under.
125
57
  *
126
58
  * The address space is read from compiled output because that is what actually
127
59
  * ships; the tree is read only to place a finding somewhere a reader can open
@@ -135,7 +67,7 @@ export function diffItemAddresses(baseline: Map<string, object>, current: Map<st
135
67
  * @param {object} [opts.config] - The resolved build configuration, which the
136
68
  * id is derived against. See {@link addressCorpus} for why that matters.
137
69
  * @param {readonly object[]} [opts.records] - Index records the caller already
138
- * derived, shared with {@link declaredPredecessors}.
70
+ * derived by the caller.
139
71
  * @param {object[]} [opts.problems] - Collects the notes the index cannot
140
72
  * record, so one of them does not abort the diff before it reports.
141
73
  * @returns {Map<string, string>} Document id → the note's absolute path.
@@ -149,22 +81,13 @@ export function noteFilesById(contentBase: string, { skipDirectories, config, re
149
81
  /**
150
82
  * Where to send the reader for one finding.
151
83
  *
152
- * A rename is fixed in the note that made it, so a finding whose id is still in
153
- * this tree is reported at that note's `shortcode:` line — the line the author
154
- * just edited. A withdrawal has no such note by definition, so it degrades to
155
- * the baseline document, which is the only artefact left that records the
156
- * address existing. When neither is readable the position is **dropped**, never
157
- * defaulted to `1:1`.
158
- *
159
- * A **declared** rename knows its note without any lookup — the declaration is
160
- * how it was found — and is reported at the `renamedFrom:` line rather than the
161
- * `shortcode:` line, because that is the line the finding is about and the one
162
- * the author deletes once the declaration has done its work.
84
+ * A matched document is located at its current `shortcode:` line. A withdrawal
85
+ * is located in the baseline artifact, the remaining evidence for its address.
86
+ * When neither source has a usable position, the location is dropped.
163
87
  *
164
88
  * @param {object} finding - One finding from {@link diffItemAddresses}.
165
89
  * @param {Map<string, string>} noteFiles - From {@link noteFilesById}.
166
- * @returns {{file?: string, line?: number, column?: number}} Spreadable
167
- * position fields for {@link formatDiagnostic}.
90
+ * @returns {{file?: string, line?: number, column?: number}} Position fields.
168
91
  */
169
92
  export function locateAddressFinding(finding: object, noteFiles: Map<string, string>): {
170
93
  file?: string;
@@ -174,21 +97,9 @@ export function locateAddressFinding(finding: object, noteFiles: Map<string, str
174
97
  /**
175
98
  * What one finding says, without a locator or a severity.
176
99
  *
177
- * The rename message names the identity it matched on, because that is what
178
- * separates this from a spelling suggestion: the reader can check the id in
179
- * both artefacts. The withdrawal message names no successor, because none is
180
- * known — and says so, rather than leaving the reader to wonder whether one was
181
- * looked for.
182
- *
183
- * **A declared rename says it is declared**. The two claims are not
184
- * equally checkable: an id match is a fact in the artefacts, while a
185
- * declaration is an author's word, and a reader deciding whether to trust the
186
- * successor needs to know which one they have. Saying "the same document" of a
187
- * declared rename would spend the id match's credibility on it.
188
- *
189
- * The consequence sentence is the same for both, because it is the same
190
- * consequence: the old address stops resolving either way, and that is what
191
- * the reader has to act on.
100
+ * A matched document names the address where it now appears. A withdrawal
101
+ * names no successor because the current build publishes no document with
102
+ * that identity.
192
103
  *
193
104
  * @param {object} finding - One finding from {@link diffItemAddresses}.
194
105
  * @returns {string} The message.
@@ -142,14 +142,13 @@ export function artPathname(index: object, value: unknown, defaultType: string,
142
142
  type?: string;
143
143
  };
144
144
  /**
145
- * The default art address a being's own tags choose, or `null`.
145
+ * The default art address a being's subtype chooses, or `null`.
146
146
  *
147
- * `character` means a **person**, not a human, and `creature` everything else;
148
- * a being carries exactly one of the two, which is what makes the choice a
149
- * lookup rather than a precedence rule.
147
+ * An NPC and a character use person art; a creature uses creature art. The
148
+ * tag lookup preserves defaults for package trees that have not migrated.
150
149
  *
151
150
  * @param {object} fm - The note's frontmatter.
152
- * @returns {string|null} The address, or `null` for a note carrying neither tag.
151
+ * @returns {string|null} The address, or `null` when no subtype or legacy tag applies.
153
152
  */
154
153
  export function beingDefaultArt(fm: object): string | null;
155
154
  /**
@@ -12,6 +12,20 @@
12
12
  * @returns {boolean} Whether it is acceptable.
13
13
  */
14
14
  export function matchesKind(value: unknown, kind: string, context?: object): boolean;
15
+ /**
16
+ * Resolve system-block addresses declared by the system's own field schema.
17
+ * @param {object} note - A note from the link index.
18
+ * @param {object} opts
19
+ * @param {object} opts.index - The address index.
20
+ * @param {Record<string, readonly object[]>} [opts.schemas] - Note schemas.
21
+ * @param {Readonly<Record<string, object>>} [opts.systems] - Declared system blocks.
22
+ * @returns {object[]} Located findings for unresolved addresses.
23
+ */
24
+ export function systemAddressFindings(note: object, { index, schemas, systems }?: {
25
+ index: object;
26
+ schemas?: Record<string, readonly object[]> | undefined;
27
+ systems?: Readonly<Record<string, object>> | undefined;
28
+ }): object[];
15
29
  /**
16
30
  * Check one note against its type's schema.
17
31
  *
@@ -14,7 +14,8 @@ export function renderFoundryMarkdown(body: any, captions: any, footnoteNumbers:
14
14
  * @param {string} filePath - Markdown file to read.
15
15
  * @param {{addressContext?: object}} [opts] - Address defaults for decoded fields.
16
16
  * @returns {{frontmatter: object|null, body: string, description: string,
17
- * bodyLine?: number, bodyColumn?: number}} Parsed note and body position.
17
+ * bodyLine?: number, bodyColumn?: number, parseError?: {message: string,
18
+ * line?: number, column?: number}}} Parsed note and body position.
18
19
  */
19
20
  export function parseMarkdownFile(filePath: string, { addressContext }?: {
20
21
  addressContext?: object;
@@ -24,6 +25,11 @@ export function parseMarkdownFile(filePath: string, { addressContext }?: {
24
25
  description: string;
25
26
  bodyLine?: number;
26
27
  bodyColumn?: number;
28
+ parseError?: {
29
+ message: string;
30
+ line?: number;
31
+ column?: number;
32
+ };
27
33
  };
28
34
  /**
29
35
  * Refuse a corpus read whose scope its caller did not state.
@@ -98,6 +104,11 @@ export function walkMarkdownTree(rootDir: string, { skipDirectories, addressCont
98
104
  description: string;
99
105
  bodyLine?: number;
100
106
  bodyColumn?: number;
107
+ parseError?: {
108
+ message: string;
109
+ line?: number;
110
+ column?: number;
111
+ };
101
112
  }, void, unknown>;
102
113
  /**
103
114
  * The template priority a note states, for a system that treats an unstated one
@@ -47,7 +47,6 @@ export * as infobox from "./infobox.mjs";
47
47
  export * as infoboxRegistry from "./infobox-registry.mjs";
48
48
  export * as infoboxRender from "./infobox-render.mjs";
49
49
  export * as noteIds from "./note-ids.mjs";
50
- export * as noteRenames from "./note-renames.mjs";
51
50
  export * as metadataIndex from "./metadata-index.mjs";
52
51
  export * as foundryEntries from "./foundry-entries.mjs";
53
52
  export * as contentIndex from "./content-index.mjs";
@@ -233,15 +233,6 @@ export const DECLARED_TAGS: Readonly<{
233
233
  types: string[];
234
234
  tags: readonly string[];
235
235
  }>;
236
- /**
237
- * What kind of being this is — a person, or one of the beasts and made
238
- * things. A being is one or the other, so the group is a slot.
239
- */
240
- beingKind: Readonly<{
241
- types: string[];
242
- exclusive: "kind";
243
- tags: readonly string[];
244
- }>;
245
236
  /** A note's working state, which any note may carry. */
246
237
  state: Readonly<{
247
238
  types: null;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The map one system ships, by its id.
3
3
  *
4
- * @param {string|undefined} system - The system id (`"sohl"`, `"hm3"`).
4
+ * @param {string|undefined} system - The system id.
5
5
  * @returns {import("./document-subtypes.mjs").DocumentSubtypeMap|undefined} Its
6
6
  * map, or `undefined` where this toolchain ships none for it.
7
7
  */
@@ -29,10 +29,6 @@ export function schemaSubtypeOf(system: string | undefined, type: string): strin
29
29
  /**
30
30
  * The note-type → document-subtype maps this toolchain ships.
31
31
  *
32
- * Two, since `hm3/` landed — and it joined this list rather than the
33
- * claim table growing a second copy of the same fact, which is what the list
34
- * was for.
35
- *
36
32
  * The union is what makes the vocabulary wider than any one repository's
37
33
  * configuration: `armorlocation` is a real content type because HM3 maps it,
38
34
  * however a given repository is configured, so a tree full of them is a
@@ -71,7 +67,7 @@ export const KNOWN_DOCUMENT_SUBTYPE_MAPS: readonly import("./document-subtypes.m
71
67
  */
72
68
  export const DEFAULT_DOCUMENT_SUBTYPES: import("./document-subtypes.mjs").DocumentSubtypeMap;
73
69
  /**
74
- * Every system this toolchain ships a map for, by id.
70
+ * Every system identifier with declared system-specific document types.
75
71
  *
76
72
  * Derived from {@link KNOWN_DOCUMENT_SUBTYPE_MAPS}, so registering a map is the
77
73
  * whole of adding a system to every reader that asks "which systems are there?"
@@ -28,8 +28,8 @@ export function isSystemSegment(value: unknown): boolean;
28
28
  *
29
29
  * The nullish case is called out by name because it is the likely mistake and
30
30
  * the least legible failure: a YAML `null`, a `~`, or a key written with no
31
- * value at all reaches here as `undefined`, and "expected one of sohl, hm3,
32
- * none" would leave a reader hunting for the value they cannot see.
31
+ * value at all reaches here as `undefined`, and a list of accepted values
32
+ * would leave a reader hunting for the value they cannot see.
33
33
  *
34
34
  * @param {unknown} value - The offending value.
35
35
  * @param {string} [where] - What carried it, for the message — a note path, a
@@ -83,11 +83,11 @@ export const NO_SYSTEM: string;
83
83
  /** The Address segment for a note's readable content. */
84
84
  export const NOTE_SYSTEM: "note";
85
85
  /**
86
- * Every game system this toolchain compiles for.
86
+ * Every game system identifier this toolchain recognizes.
87
87
  *
88
- * The registry, and the only place the list is written down. Adding a system
89
- * is an edit to this set plus the map and compilers that make it real — a data
90
- * change, not a search for every place a name was spelled.
88
+ * The registry, and the only place the list is written down. A recognized id
89
+ * can have no system-specific document types; adding one of those requires a
90
+ * subtype map and compiler as well.
91
91
  *
92
92
  * `none` is **not** a member: it marks the absence of a system, and a caller
93
93
  * asking "is this a system" and a caller asking "is this a well-formed
@@ -1,134 +0,0 @@
1
- /*
2
- * This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
3
- * Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
4
- *
5
- * This work is licensed under the GNU General Public License v3.0 (GPLv3).
6
- * You may copy, modify, and distribute it under the terms of that license.
7
- *
8
- * For full terms, see the LICENSE.md file in the project root or visit:
9
- * https://www.gnu.org/licenses/gpl-3.0.html
10
- *
11
- * SPDX-License-Identifier: GPL-3.0-or-later
12
- */
13
-
14
- /**
15
- * The shortcodes a note declares it used to be published under.
16
- *
17
- * A package's `(type, shortcode)` addresses are a published interface, and
18
- * `addresses diff` reports what a build stopped publishing — telling a
19
- * **rename** from a **withdrawal** by matching document ids across two
20
- * releases. The property that rested on is gone: an id is derived from
21
- * the canonical address, which carries the shortcode, so renaming a shortcode
22
- * moves the id too: both sides of the join move together, the match finds
23
- * nothing, and a rename is reported as a withdrawal with no successor named.
24
- *
25
- * **The remaining lever needed foresight, which is the wrong thing to ask for.**
26
- * An authored `id:` still wins, and pinning one does keep a document's identity
27
- * across a rename — but it has to be written *before* the rename, by an author
28
- * who does not yet know they will make one. An author who has just renamed a
29
- * shortcode knows exactly what the old one was, and that is the only moment
30
- * anyone does.
31
- *
32
- * So a note states it:
33
- *
34
- * ```yaml
35
- * type: weapongear
36
- * shortcode: Taburi
37
- * renamedFrom: Tabri
38
- * ```
39
- *
40
- * **A declaration is an assertion, not a match, and the diagnostic says which
41
- * it had.** That distinction is the whole ethos of `address-diff.mjs`: it
42
- * refuses to guess a successor from a similar-looking string, because a wrong
43
- * one sends the reader to the wrong fix. An author's declaration is neither a
44
- * guess nor an identity match — it is testimony from the one party that knows —
45
- * so it is used, attributed, and never silently blended with an id match.
46
- *
47
- * **It is a list, because renames chain.** The diff is release-to-release and a
48
- * shortcode may be renamed more than once between two releases; a baseline
49
- * several releases back published a name two renames ago. A single value would
50
- * report that as a withdrawal again, which is the bug this closes.
51
- *
52
- * **It is transient.** Once every baseline a build is compared against post-dates
53
- * the rename, the declaration has nothing left to say and may be deleted. That
54
- * is the difference from an `id:` pin, which is permanent, opaque, and a second
55
- * identity for a thing that already has one.
56
- *
57
- * **It is one key per note, at the top level, however many systems the note
58
- * compiles into.** A shortcode is the note's, not a system block's: a note
59
- * carrying `sohl:` and `hm3:` blocks compiles into two documents that share one
60
- * shortcode, so a rename moves both and one declaration covers both.
61
- *
62
- * This module reads the key and says nothing about whether the values are
63
- * *sound* — that a value is a well-formed shortcode, is not the note's own, and
64
- * does not name an address some live note still publishes are cross-note
65
- * questions, and `engine/content-lint.mjs` owns them. Composing a value into an
66
- * address key is likewise not here: the diff defines that space and spells the
67
- * key once, in `engine/address-diff.mjs`.
68
- *
69
- * @module
70
- */
71
-
72
- /**
73
- * The `renamedFrom:` entries a note authors, exactly as authored.
74
- *
75
- * Scalar or list, because one predecessor is the overwhelmingly common case and
76
- * requiring `- ` on it would be friction with nothing behind it; both normalize
77
- * here so no reader has to ask which form it got.
78
- *
79
- * Entries are returned **unvalidated** — a number, a nested list, a blank
80
- * string all come back as they were written. The lint needs to see them to
81
- * report them, and a reader that quietly dropped them would report a correct
82
- * tree clean while a typo silently did nothing.
83
- *
84
- * @param {object|null|undefined} fm - Parsed frontmatter.
85
- * @returns {readonly unknown[]} The authored entries, in authored order; empty
86
- * when the note declares none.
87
- */
88
- export function renamedFromEntries(fm) {
89
- if (!fm || typeof fm !== "object") return [];
90
- const raw = /** @type {{renamedFrom?: unknown}} */ (fm).renamedFrom;
91
- if (raw == null) return [];
92
- return Array.isArray(raw) ? raw : [raw];
93
- }
94
-
95
- /**
96
- * The well-formed shortcodes among a note's `renamedFrom:` entries.
97
- *
98
- * Trimmed, de-duplicated, and in authored order. Anything that is not a
99
- * non-blank string is skipped rather than coerced: it is reported by the lint,
100
- * and a diff that guessed at what a number meant would claim a rename nobody
101
- * declared.
102
- *
103
- * De-duplicating here rather than leaving it to callers is not tidiness — the
104
- * predecessor index is a map, so a repeated entry would otherwise be indexed
105
- * twice and the second write would look like a conflicting claim.
106
- *
107
- * @param {object|null|undefined} fm - Parsed frontmatter.
108
- * @returns {string[]} The declared predecessor shortcodes.
109
- */
110
- export function renamedFrom(fm) {
111
- const seen = new Set();
112
- for (const entry of renamedFromEntries(fm)) {
113
- if (typeof entry !== "string") continue;
114
- const trimmed = entry.trim();
115
- if (trimmed) seen.add(trimmed);
116
- }
117
- return [...seen];
118
- }
119
-
120
- /**
121
- * Whether a note declares the key at all, however malformed its value.
122
- *
123
- * Separate from {@link renamedFrom} returning nothing, because the two mean
124
- * opposite things to a lint: a note declaring no key is silent and correct,
125
- * while one declaring `renamedFrom: []` — or a single blank string — has said
126
- * something that does nothing, which is worth a word.
127
- *
128
- * @param {object|null|undefined} fm - Parsed frontmatter.
129
- * @returns {boolean} `true` when the key is present and not null.
130
- */
131
- export function declaresRenamedFrom(fm) {
132
- if (!fm || typeof fm !== "object") return false;
133
- return /** @type {{renamedFrom?: unknown}} */ (fm).renamedFrom != null;
134
- }
@@ -1,102 +0,0 @@
1
- /**
2
- * The shortcodes a note declares it used to be published under.
3
- *
4
- * A package's `(type, shortcode)` addresses are a published interface, and
5
- * `addresses diff` reports what a build stopped publishing — telling a
6
- * **rename** from a **withdrawal** by matching document ids across two
7
- * releases. The property that rested on is gone: an id is derived from
8
- * the canonical address, which carries the shortcode, so renaming a shortcode
9
- * moves the id too: both sides of the join move together, the match finds
10
- * nothing, and a rename is reported as a withdrawal with no successor named.
11
- *
12
- * **The remaining lever needed foresight, which is the wrong thing to ask for.**
13
- * An authored `id:` still wins, and pinning one does keep a document's identity
14
- * across a rename — but it has to be written *before* the rename, by an author
15
- * who does not yet know they will make one. An author who has just renamed a
16
- * shortcode knows exactly what the old one was, and that is the only moment
17
- * anyone does.
18
- *
19
- * So a note states it:
20
- *
21
- * ```yaml
22
- * type: weapongear
23
- * shortcode: Taburi
24
- * renamedFrom: Tabri
25
- * ```
26
- *
27
- * **A declaration is an assertion, not a match, and the diagnostic says which
28
- * it had.** That distinction is the whole ethos of `address-diff.mjs`: it
29
- * refuses to guess a successor from a similar-looking string, because a wrong
30
- * one sends the reader to the wrong fix. An author's declaration is neither a
31
- * guess nor an identity match — it is testimony from the one party that knows —
32
- * so it is used, attributed, and never silently blended with an id match.
33
- *
34
- * **It is a list, because renames chain.** The diff is release-to-release and a
35
- * shortcode may be renamed more than once between two releases; a baseline
36
- * several releases back published a name two renames ago. A single value would
37
- * report that as a withdrawal again, which is the bug this closes.
38
- *
39
- * **It is transient.** Once every baseline a build is compared against post-dates
40
- * the rename, the declaration has nothing left to say and may be deleted. That
41
- * is the difference from an `id:` pin, which is permanent, opaque, and a second
42
- * identity for a thing that already has one.
43
- *
44
- * **It is one key per note, at the top level, however many systems the note
45
- * compiles into.** A shortcode is the note's, not a system block's: a note
46
- * carrying `sohl:` and `hm3:` blocks compiles into two documents that share one
47
- * shortcode, so a rename moves both and one declaration covers both.
48
- *
49
- * This module reads the key and says nothing about whether the values are
50
- * *sound* — that a value is a well-formed shortcode, is not the note's own, and
51
- * does not name an address some live note still publishes are cross-note
52
- * questions, and `engine/content-lint.mjs` owns them. Composing a value into an
53
- * address key is likewise not here: the diff defines that space and spells the
54
- * key once, in `engine/address-diff.mjs`.
55
- *
56
- * @module
57
- */
58
- /**
59
- * The `renamedFrom:` entries a note authors, exactly as authored.
60
- *
61
- * Scalar or list, because one predecessor is the overwhelmingly common case and
62
- * requiring `- ` on it would be friction with nothing behind it; both normalize
63
- * here so no reader has to ask which form it got.
64
- *
65
- * Entries are returned **unvalidated** — a number, a nested list, a blank
66
- * string all come back as they were written. The lint needs to see them to
67
- * report them, and a reader that quietly dropped them would report a correct
68
- * tree clean while a typo silently did nothing.
69
- *
70
- * @param {object|null|undefined} fm - Parsed frontmatter.
71
- * @returns {readonly unknown[]} The authored entries, in authored order; empty
72
- * when the note declares none.
73
- */
74
- export function renamedFromEntries(fm: object | null | undefined): readonly unknown[];
75
- /**
76
- * The well-formed shortcodes among a note's `renamedFrom:` entries.
77
- *
78
- * Trimmed, de-duplicated, and in authored order. Anything that is not a
79
- * non-blank string is skipped rather than coerced: it is reported by the lint,
80
- * and a diff that guessed at what a number meant would claim a rename nobody
81
- * declared.
82
- *
83
- * De-duplicating here rather than leaving it to callers is not tidiness — the
84
- * predecessor index is a map, so a repeated entry would otherwise be indexed
85
- * twice and the second write would look like a conflicting claim.
86
- *
87
- * @param {object|null|undefined} fm - Parsed frontmatter.
88
- * @returns {string[]} The declared predecessor shortcodes.
89
- */
90
- export function renamedFrom(fm: object | null | undefined): string[];
91
- /**
92
- * Whether a note declares the key at all, however malformed its value.
93
- *
94
- * Separate from {@link renamedFrom} returning nothing, because the two mean
95
- * opposite things to a lint: a note declaring no key is silent and correct,
96
- * while one declaring `renamedFrom: []` — or a single blank string — has said
97
- * something that does nothing, which is worth a word.
98
- *
99
- * @param {object|null|undefined} fm - Parsed frontmatter.
100
- * @returns {boolean} `true` when the key is present and not null.
101
- */
102
- export function declaresRenamedFrom(fm: object | null | undefined): boolean;