@heroiclands/package-build 17.1.0 → 18.0.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 +1773 -0
- package/CONTENT.md +288 -72
- package/README.md +1 -1
- package/bin/content-build.mjs +218 -137
- package/bin/package-build.mjs +43 -0
- package/content-config.mjs +62 -67
- package/docs/content-format.md +768 -58
- package/engine/actor-compiler.mjs +586 -0
- package/engine/address-charset.mjs +11 -8
- package/engine/address-diff.mjs +266 -22
- package/engine/anchored-sections.mjs +83 -0
- package/engine/anchors.mjs +83 -0
- package/engine/base-compiler.mjs +148 -18
- package/engine/bundle-notes.mjs +276 -0
- package/engine/bundles.mjs +307 -0
- package/engine/code-fences.mjs +103 -0
- package/engine/compile-corpus.mjs +89 -0
- package/engine/content-address.mjs +267 -5
- package/engine/content-format-check.mjs +54 -11
- package/engine/content-format.mjs +37 -5
- package/engine/content-index.mjs +394 -111
- package/engine/content-links.mjs +185 -77
- package/engine/content-lint.mjs +232 -14
- package/engine/content-package.mjs +2 -1
- package/engine/content-tables.mjs +99 -3
- package/engine/document-subtypes.mjs +49 -6
- package/engine/field-reference.mjs +1 -1
- package/engine/field-spec.mjs +56 -6
- package/engine/folder-notes.mjs +470 -0
- package/engine/foreign-catalog.mjs +195 -4
- package/engine/{manifest-emit.mjs → foundry-entries.mjs} +78 -81
- package/engine/frontmatter-lint.mjs +245 -19
- package/engine/frontmatter.mjs +88 -8
- package/engine/generate.mjs +304 -13
- package/engine/helpers.mjs +293 -64
- package/engine/ids.mjs +137 -2
- package/engine/index-records.mjs +126 -0
- package/engine/index.mjs +23 -5
- package/engine/item-compiler.mjs +349 -0
- package/engine/item-docs.mjs +7 -1
- package/engine/item-registry.mjs +6 -0
- package/engine/journals.mjs +93 -27
- package/engine/macros.mjs +4 -2
- package/engine/metadata-index.mjs +495 -0
- package/engine/note-claims.mjs +146 -25
- package/engine/note-ids.mjs +112 -0
- package/engine/note-renames.mjs +134 -0
- package/engine/note-schemas.mjs +25 -0
- package/engine/note-vocabulary.mjs +110 -27
- package/engine/pack-config.mjs +4 -0
- package/engine/pack-router.mjs +23 -0
- package/engine/retired-fields.mjs +137 -3
- package/engine/scenes.mjs +66 -34
- package/engine/schema-check.mjs +58 -17
- package/engine/site-build.mjs +71 -12
- package/engine/site-index.mjs +20 -7
- package/engine/sql-tables.mjs +485 -0
- package/engine/subtype-registry.mjs +102 -0
- package/engine/system-block.mjs +96 -12
- package/engine/systems.mjs +201 -0
- package/engine/web-wikilinks.mjs +50 -20
- package/engine/wikilink-syntax.mjs +17 -8
- package/engine/wikilinks.mjs +124 -51
- package/engine/yaml-lint.mjs +282 -0
- package/hm3/actors.mjs +352 -0
- package/hm3/default-item-art.mjs +75 -0
- package/hm3/document-subtypes.mjs +134 -0
- package/hm3/index.mjs +56 -0
- package/hm3/item-builders.mjs +84 -0
- package/hm3/item-fields.mjs +180 -0
- package/hm3/items.mjs +96 -0
- package/hm3/template-priority.mjs +85 -0
- package/labels.mjs +191 -0
- package/manifest.mjs +43 -2
- package/package.json +19 -4
- package/release.mjs +62 -7
- package/sohl/actors.mjs +33 -487
- package/sohl/being-info.mjs +16 -7
- package/sohl/default-item-art.mjs +14 -3
- package/sohl/document-subtypes.mjs +16 -10
- package/sohl/item-builders.mjs +14 -5
- package/sohl/item-fields.mjs +68 -7
- package/sohl/items.mjs +44 -258
- package/sohl/note-schemas.mjs +2 -2
- package/types/content-config.d.mts +21 -42
- package/types/engine/actor-compiler.d.mts +204 -0
- package/types/engine/address-charset.d.mts +11 -8
- package/types/engine/address-diff.d.mts +53 -5
- package/types/engine/anchored-sections.d.mts +21 -0
- package/types/engine/anchors.d.mts +20 -0
- package/types/engine/base-compiler.d.mts +17 -17
- package/types/engine/bundle-notes.d.mts +173 -0
- package/types/engine/bundles.d.mts +60 -0
- package/types/engine/code-fences.d.mts +43 -0
- package/types/engine/compile-corpus.d.mts +32 -0
- package/types/engine/content-address.d.mts +205 -5
- package/types/engine/content-format-check.d.mts +6 -2
- package/types/engine/content-format.d.mts +57 -1
- package/types/engine/content-index.d.mts +64 -80
- package/types/engine/content-links.d.mts +52 -7
- package/types/engine/content-lint.d.mts +10 -1
- package/types/engine/content-package.d.mts +2 -1
- package/types/engine/content-tables.d.mts +21 -39
- package/types/engine/document-subtypes.d.mts +37 -3
- package/types/engine/field-spec.d.mts +76 -5
- package/types/engine/folder-notes.d.mts +159 -0
- package/types/engine/foreign-catalog.d.mts +53 -0
- package/types/engine/{manifest-emit.d.mts → foundry-entries.d.mts} +16 -38
- package/types/engine/frontmatter-lint.d.mts +10 -2
- package/types/engine/frontmatter.d.mts +64 -0
- package/types/engine/generate.d.mts +38 -0
- package/types/engine/helpers.d.mts +94 -30
- package/types/engine/ids.d.mts +96 -0
- package/types/engine/index-records.d.mts +68 -0
- package/types/engine/index.d.mts +9 -3
- package/types/engine/item-compiler.d.mts +131 -0
- package/types/engine/journals.d.mts +47 -9
- package/types/engine/metadata-index.d.mts +226 -0
- package/types/engine/note-claims.d.mts +57 -11
- package/types/engine/note-ids.d.mts +38 -0
- package/types/engine/note-renames.d.mts +102 -0
- package/types/engine/note-vocabulary.d.mts +44 -8
- package/types/engine/retired-fields.d.mts +75 -0
- package/types/engine/scenes.d.mts +3 -2
- package/types/engine/schema-check.d.mts +25 -4
- package/types/engine/site-build.d.mts +4 -4
- package/types/engine/site-index.d.mts +1 -1
- package/types/engine/sql-tables.d.mts +185 -0
- package/types/engine/subtype-registry.d.mts +49 -0
- package/types/engine/system-block.d.mts +40 -1
- package/types/engine/systems.d.mts +106 -0
- package/types/engine/web-wikilinks.d.mts +4 -2
- package/types/engine/wikilink-syntax.d.mts +10 -3
- package/types/engine/wikilinks.d.mts +41 -13
- package/types/engine/yaml-lint.d.mts +107 -0
- package/types/hm3/actors.d.mts +48 -0
- package/types/hm3/default-item-art.d.mts +42 -0
- package/types/hm3/document-subtypes.d.mts +24 -0
- package/types/hm3/index.d.mts +7 -0
- package/types/hm3/item-builders.d.mts +11 -0
- package/types/hm3/item-fields.d.mts +12 -0
- package/types/hm3/items.d.mts +23 -0
- package/types/hm3/template-priority.d.mts +21 -0
- package/types/manifest.d.mts +22 -1
- package/types/release.d.mts +9 -4
- package/types/sohl/actors.d.mts +7 -74
- package/types/sohl/being-info.d.mts +8 -27
- package/types/sohl/default-item-art.d.mts +5 -3
- package/types/sohl/items.d.mts +17 -32
- package/engine/foreign-manifests.mjs +0 -126
- package/engine/kb-manifest.mjs +0 -490
- package/types/engine/foreign-manifests.d.mts +0 -43
- package/types/engine/kb-manifest.d.mts +0 -241
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every `sql` fence in a markdown body, with the position each occupies.
|
|
3
|
+
*
|
|
4
|
+
* Positions are 0-based lines into the body as given, which is what a diagnostic
|
|
5
|
+
* about a directive needs (#17) and what the expander uses to splice results
|
|
6
|
+
* back in.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} markdown - The note body, frontmatter already stripped.
|
|
9
|
+
* @returns {Array<{line: number, close: number, indent: string, query: string,
|
|
10
|
+
* allowEmpty: boolean, sectionLevel: number, block: string}>} One entry per
|
|
11
|
+
* fence, in document order.
|
|
12
|
+
*/
|
|
13
|
+
export function findSqlBlocks(markdown: string): Array<{
|
|
14
|
+
line: number;
|
|
15
|
+
close: number;
|
|
16
|
+
indent: string;
|
|
17
|
+
query: string;
|
|
18
|
+
allowEmpty: boolean;
|
|
19
|
+
sectionLevel: number;
|
|
20
|
+
block: string;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Open a DuckDB view over the content index.
|
|
24
|
+
*
|
|
25
|
+
* The records are written as JSON Lines to a temporary file and read with
|
|
26
|
+
* `read_json_auto`, rather than inserted row by row, because that is what makes
|
|
27
|
+
* the queries readable: DuckDB infers a `STRUCT` for every nested object, so a
|
|
28
|
+
* note's `sohl.weight` and `name.full` are addressed in a query exactly as they
|
|
29
|
+
* are authored in the note. A column-per-path table would force
|
|
30
|
+
* `"sohl.weight"` in quotes, and a JSON column would force `sohl->>'weight'`.
|
|
31
|
+
*
|
|
32
|
+
* `union_by_name` is what makes that work across a heterogeneous corpus: a
|
|
33
|
+
* `sohl:` block differs by note type, and the inferred struct is the union of
|
|
34
|
+
* every type's fields, with `NULL` where a record does not have one.
|
|
35
|
+
*
|
|
36
|
+
* `threads=1` so a result is byte-identical between runs. Rows tied under the
|
|
37
|
+
* authored `ORDER BY` then fall back to the index's own order, which is itself
|
|
38
|
+
* deterministic — the index is emitted sorted and byte-stable.
|
|
39
|
+
*
|
|
40
|
+
* ## A dependency is a schema
|
|
41
|
+
*
|
|
42
|
+
* A package that depends on another can tabulate what it depends on —
|
|
43
|
+
* `FROM sohl.notes` — because each declared dependency's published index is
|
|
44
|
+
* attached as a **schema** named after the package, with this package's own
|
|
45
|
+
* notes staying at the unqualified `notes`.
|
|
46
|
+
*
|
|
47
|
+
* It is `FROM` rather than a fence property naming a file, for two reasons. A
|
|
48
|
+
* path in authored content is a build artifact's name written into the corpus,
|
|
49
|
+
* so renaming the artifact means sweeping every note that cites it — the
|
|
50
|
+
* coupling #126 exists to undo. And *which dataset a query reads* is what
|
|
51
|
+
* `FROM` is for: the same rule that keeps `_ref` and `_section` ordinary SQL,
|
|
52
|
+
* visible where an author is already looking, rather than fence options.
|
|
53
|
+
*
|
|
54
|
+
* It costs no fetch. Every dependency's JSONL is already in the metadata cache
|
|
55
|
+
* when a compile starts, because resolving addresses across packages needs it.
|
|
56
|
+
*
|
|
57
|
+
* @param {object[]} records - Content-index records, as
|
|
58
|
+
* {@link module:engine/content-index.collectContentIndex} returns them.
|
|
59
|
+
* @param {object} [opts]
|
|
60
|
+
* @param {string} [opts.dir] - Directory for the temporary file.
|
|
61
|
+
* @param {Array<{id: string, file: string}>} [opts.dependencies] - Each
|
|
62
|
+
* declared dependency's cached index, attached as a schema named `id`.
|
|
63
|
+
* @returns {Promise<{query: (sql: string) => Promise<object[]>,
|
|
64
|
+
* close: () => Promise<void>}>} The open database.
|
|
65
|
+
*/
|
|
66
|
+
export function openNotesDatabase(records: object[], { dir, dependencies }?: {
|
|
67
|
+
dir?: string | undefined;
|
|
68
|
+
dependencies?: {
|
|
69
|
+
id: string;
|
|
70
|
+
file: string;
|
|
71
|
+
}[] | undefined;
|
|
72
|
+
}): Promise<{
|
|
73
|
+
query: (sql: string) => Promise<object[]>;
|
|
74
|
+
close: () => Promise<void>;
|
|
75
|
+
}>;
|
|
76
|
+
/**
|
|
77
|
+
* Run one authored query and shape its result for the renderer.
|
|
78
|
+
*
|
|
79
|
+
* @param {object} db - From {@link openNotesDatabase}.
|
|
80
|
+
* @param {string} sql - The query, as authored.
|
|
81
|
+
* @returns {Promise<{columns: string[], rows: object[]}>} The rendered columns
|
|
82
|
+
* — every selected alias except the underscore-prefixed ones — and the rows.
|
|
83
|
+
*/
|
|
84
|
+
export function runSqlQuery(db: object, sql: string): Promise<{
|
|
85
|
+
columns: string[];
|
|
86
|
+
rows: object[];
|
|
87
|
+
}>;
|
|
88
|
+
/**
|
|
89
|
+
* Render one query's result as markdown.
|
|
90
|
+
*
|
|
91
|
+
* A `_ref` alias makes the row's **first rendered column** a wikilink to that
|
|
92
|
+
* address; the reference is dropped when nothing can be linked, so a table never
|
|
93
|
+
* ships a link that does not resolve. A `_section` alias splits the result into
|
|
94
|
+
* a headed table per distinct value, in the order the rows arrive — so the
|
|
95
|
+
* authored `ORDER BY` decides the section order too, and one query replaces the
|
|
96
|
+
* forty near-identical blocks the language used to require.
|
|
97
|
+
*
|
|
98
|
+
* @param {{columns: string[], rows: object[]}} result - From
|
|
99
|
+
* {@link runSqlQuery}.
|
|
100
|
+
* @param {object} [opts]
|
|
101
|
+
* @param {(ref: string) => boolean} [opts.linkable] - Whether an address can be
|
|
102
|
+
* linked to; defaults to linking any non-empty `_ref`.
|
|
103
|
+
* @param {number} [opts.sectionLevel=2] - Heading level for `_section`.
|
|
104
|
+
* @returns {string} The markdown.
|
|
105
|
+
*/
|
|
106
|
+
export function renderSqlTable(result: {
|
|
107
|
+
columns: string[];
|
|
108
|
+
rows: object[];
|
|
109
|
+
}, { linkable, sectionLevel }?: {
|
|
110
|
+
linkable?: ((ref: string) => boolean) | undefined;
|
|
111
|
+
sectionLevel?: number | undefined;
|
|
112
|
+
}): string;
|
|
113
|
+
/**
|
|
114
|
+
* Run every `sql` directive in a set of note bodies, ahead of expansion.
|
|
115
|
+
*
|
|
116
|
+
* **Why a separate pass.** DuckDB's API is asynchronous and
|
|
117
|
+
* {@link module:engine/content-tables.expandContentTables} is not — nor should
|
|
118
|
+
* it become so: two of its three callers are synchronous, and one of those,
|
|
119
|
+
* `renderPages`, is exported. Preparing the results first keeps every one of
|
|
120
|
+
* those signatures intact, and it is the shape #243 is heading for anyway —
|
|
121
|
+
* the corpus enumerated once, each pass reading the answer rather than
|
|
122
|
+
* deriving it again.
|
|
123
|
+
*
|
|
124
|
+
* A query that fails is recorded rather than thrown, so one bad directive costs
|
|
125
|
+
* its own table and not the whole build's report.
|
|
126
|
+
*
|
|
127
|
+
* @param {object} db - From {@link openNotesDatabase}.
|
|
128
|
+
* @param {Array<{source: string, markdown: string}>} sources - The bodies to
|
|
129
|
+
* scan.
|
|
130
|
+
* @param {object} [opts]
|
|
131
|
+
* @param {(ref: string) => boolean} [opts.linkable] - Passed to
|
|
132
|
+
* {@link renderSqlTable}.
|
|
133
|
+
* **Keyed by note, then by the directive's ordinal within it** — not by its
|
|
134
|
+
* line. The passes do not agree on what a body is: `walkMarkdownTree` trims it,
|
|
135
|
+
* while the link checker strips the frontmatter fence and leaves the newlines
|
|
136
|
+
* that followed, so the same directive sits on different lines in each. Its
|
|
137
|
+
* position in the sequence of fences is the same in both.
|
|
138
|
+
*
|
|
139
|
+
* @returns {Promise<Map<string, object[]>>} Note to results, in document order,
|
|
140
|
+
* each carrying either a rendered `markdown` and its `rows`, or a `reason`.
|
|
141
|
+
*/
|
|
142
|
+
export function prepareSqlTables(db: object, sources: Array<{
|
|
143
|
+
source: string;
|
|
144
|
+
markdown: string;
|
|
145
|
+
}>, { linkable }?: {
|
|
146
|
+
linkable?: ((ref: string) => boolean) | undefined;
|
|
147
|
+
}): Promise<Map<string, object[]>>;
|
|
148
|
+
/**
|
|
149
|
+
* Answer every `sql` directive in a content tree.
|
|
150
|
+
*
|
|
151
|
+
* The one entry point each pass uses, so the compiler, the link checker and the
|
|
152
|
+
* site build cannot disagree about what a table selects — the failure mode #243
|
|
153
|
+
* describes, where N passes each derive the corpus their own way.
|
|
154
|
+
*
|
|
155
|
+
* **Nothing is opened for a tree with no `sql` directive.** The corpus is still
|
|
156
|
+
* written entirely in the retiring language, so until a table is converted this
|
|
157
|
+
* costs one walk and no database at all — which is what lets every pass call it
|
|
158
|
+
* unconditionally.
|
|
159
|
+
*
|
|
160
|
+
* @param {string} contentBase - Root of the content tree.
|
|
161
|
+
* @param {object} [opts]
|
|
162
|
+
* @param {object} [opts.config] - Resolved configuration, defaulting to ambient.
|
|
163
|
+
* @param {readonly string[]} [opts.skipDirectories] - The walk's scope.
|
|
164
|
+
* @param {object[]} [opts.records] - Index records the caller already derived.
|
|
165
|
+
* A command that also builds a link index holds them already, and deriving
|
|
166
|
+
* them twice is the duplicated-corpus failure #243 is closing.
|
|
167
|
+
* @returns {Promise<Map<string, object[]>|undefined>} Results by note path, or
|
|
168
|
+
* nothing when the tree has no such directive.
|
|
169
|
+
*/
|
|
170
|
+
export function prepareTreeSqlTables(contentBase: string, { config, skipDirectories, records }?: {
|
|
171
|
+
config?: object | undefined;
|
|
172
|
+
skipDirectories?: readonly string[] | undefined;
|
|
173
|
+
records?: object[] | undefined;
|
|
174
|
+
}): Promise<Map<string, object[]> | undefined>;
|
|
175
|
+
/**
|
|
176
|
+
* Aliases the renderer reads rather than printing.
|
|
177
|
+
*
|
|
178
|
+
* `_ref` is the note a row points at, as the `type-shortcode` address a wikilink
|
|
179
|
+
* resolves — `address.slug` in the index. `_section` partitions the result into
|
|
180
|
+
* headed tables.
|
|
181
|
+
*/
|
|
182
|
+
export const RENDER_ALIASES: Readonly<{
|
|
183
|
+
ref: "_ref";
|
|
184
|
+
section: "_section";
|
|
185
|
+
}>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The map one system ships, by its id.
|
|
3
|
+
*
|
|
4
|
+
* @param {string|undefined} system - The system id (`"sohl"`, `"hm3"`).
|
|
5
|
+
* @returns {import("./document-subtypes.mjs").DocumentSubtypeMap|undefined} Its
|
|
6
|
+
* map, or `undefined` where this toolchain ships none for it.
|
|
7
|
+
*/
|
|
8
|
+
export function subtypeMapFor(system: string | undefined): import("./document-subtypes.mjs").DocumentSubtypeMap | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* The document subtype a note type compiles into for one system — the
|
|
11
|
+
* translation the *schema* check needs, and the reason it needs one.
|
|
12
|
+
*
|
|
13
|
+
* A schema artifact is keyed by document subtype; a field declaration is keyed
|
|
14
|
+
* by note type. Those were the same string until #78 renamed three of them, and
|
|
15
|
+
* a check that went on joining them by name simply stopped reporting on
|
|
16
|
+
* `armorgear` — a warning that vanishes is worse than one that fires, because
|
|
17
|
+
* nothing says it went.
|
|
18
|
+
*
|
|
19
|
+
* Falls back to the note type on a one-to-many row, where nothing but a note
|
|
20
|
+
* can say which subtype is meant and this has none: that is the answer the
|
|
21
|
+
* identity default already gave, and HM3's four such rows all name the note
|
|
22
|
+
* type among their subtypes.
|
|
23
|
+
*
|
|
24
|
+
* @param {string|undefined} system - The system whose schema is being checked.
|
|
25
|
+
* @param {string} type - The note type a field declaration is keyed by.
|
|
26
|
+
* @returns {string} The document subtype to look up.
|
|
27
|
+
*/
|
|
28
|
+
export function schemaSubtypeOf(system: string | undefined, type: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* The note-type → document-subtype maps this toolchain ships.
|
|
31
|
+
*
|
|
32
|
+
* Two, since `hm3/` landed (#139) — 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
|
+
* The union is what makes the vocabulary wider than any one repository's
|
|
37
|
+
* configuration: `armorlocation` is a real content type because HM3 maps it,
|
|
38
|
+
* however a given repository is configured, so a tree full of them is a
|
|
39
|
+
* repository that has not finished configuring itself rather than an author who
|
|
40
|
+
* invented a word.
|
|
41
|
+
*
|
|
42
|
+
* `engine/` importing from `sohl/` is the arrangement `generate.mjs` already
|
|
43
|
+
* has — its `COMPILERS` table names the SoHL compilers by class — and for the
|
|
44
|
+
* same reason: the engine owns the *mechanism* that asks each system what it
|
|
45
|
+
* compiles, and the systems own the answers.
|
|
46
|
+
*
|
|
47
|
+
* @type {readonly import("./document-subtypes.mjs").DocumentSubtypeMap[]}
|
|
48
|
+
*/
|
|
49
|
+
export const KNOWN_DOCUMENT_SUBTYPE_MAPS: readonly import("./document-subtypes.mjs").DocumentSubtypeMap[];
|
|
@@ -93,6 +93,27 @@ export function sharedProperty(fm: object, source: string, defaultValue?: any):
|
|
|
93
93
|
* @returns {any} The value.
|
|
94
94
|
*/
|
|
95
95
|
export function blockProperty(fm: object, block: string, key: string, defaultValue?: any): any;
|
|
96
|
+
/**
|
|
97
|
+
* The key a field is authored at **inside** a system block — step 2.
|
|
98
|
+
*
|
|
99
|
+
* `legacyKey` when the field declares one, and `name` otherwise. The fallback
|
|
100
|
+
* is what makes this change invisible to every declaration written before it:
|
|
101
|
+
* a field whose shared source and in-block key are the same word says so once,
|
|
102
|
+
* as it always did.
|
|
103
|
+
*
|
|
104
|
+
* Named and exported rather than spelled inline because three readers ask the
|
|
105
|
+
* question and must agree — the resolver here, the frontmatter lint building
|
|
106
|
+
* the set of keys a block may carry, and the author-facing surfaces naming
|
|
107
|
+
* where a value was written.
|
|
108
|
+
*
|
|
109
|
+
* @param {{name?: string, legacyKey?: string}} field - The declaration.
|
|
110
|
+
* @returns {string|undefined} The in-block key, or `undefined` for a field that
|
|
111
|
+
* is not authored at all.
|
|
112
|
+
*/
|
|
113
|
+
export function legacyKeyOf(field: {
|
|
114
|
+
name?: string;
|
|
115
|
+
legacyKey?: string;
|
|
116
|
+
}): string | undefined;
|
|
96
117
|
/**
|
|
97
118
|
* Where a declared field's value came from.
|
|
98
119
|
*
|
|
@@ -151,6 +172,24 @@ export function systemDataPaths(data: Record<string, unknown>, prefix?: string):
|
|
|
151
172
|
* this is the difference between "the field is lost at load" and "the build
|
|
152
173
|
* told you where".
|
|
153
174
|
*
|
|
175
|
+
* **A declared *leaf* holds values, not fields.** A schema declares a path that
|
|
176
|
+
* has no children of its own for two ordinary reasons — a map with **dynamic
|
|
177
|
+
* keys** (`mystery.skillAptitudes` is skill selector → modifier) and a
|
|
178
|
+
* **TypedSchemaField** (`strikeModes`, discriminated by `type`) — and in both
|
|
179
|
+
* the contents are data an author wrote, not paths the schema names. Walking
|
|
180
|
+
* into one reports every entry as an undeclared field: one finding per skill
|
|
181
|
+
* aptitude, per strike mode, per standing.
|
|
182
|
+
*
|
|
183
|
+
* It stayed invisible while those maps were authored *outside* `<system>.system`
|
|
184
|
+
* and so were never walked. The moment the corpus moves them to the destination
|
|
185
|
+
* (#126) every one of them lights up — 62 findings on `sohl-thalorna` alone,
|
|
186
|
+
* none of them a defect.
|
|
187
|
+
*
|
|
188
|
+
* So descent is conditional on the schema declaring something *beneath* the
|
|
189
|
+
* path. `body.structure` declares `parts` and `zones`, so it is a real
|
|
190
|
+
* container and an undeclared `adjacent` under it is a real finding;
|
|
191
|
+
* `skillAptitudes` declares nothing beneath it, so what is beneath is a value.
|
|
192
|
+
*
|
|
154
193
|
* @param {Record<string, unknown>} data - The authored `system` data.
|
|
155
194
|
* @param {ReadonlySet<string>} declared - Every field path the schema declares
|
|
156
195
|
* for this subtype, inherited ones included.
|
|
@@ -265,7 +304,7 @@ export const BLOCK_DIRECTIVES: readonly string[];
|
|
|
265
304
|
/**
|
|
266
305
|
* Every key any system block may carry, whatever the system.
|
|
267
306
|
*
|
|
268
|
-
* A system adds its own on top — `
|
|
307
|
+
* A system adds its own on top — `templatePriority`, `kbcat` and the generators — and
|
|
269
308
|
* names them where it declares its vocabulary; see {@link unknownBlockKeys}.
|
|
270
309
|
*
|
|
271
310
|
* @type {ReadonlySet<string>}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a value names a game system this toolchain knows.
|
|
3
|
+
*
|
|
4
|
+
* `none` is rejected: it is a real answer to "which system", but it is not a
|
|
5
|
+
* system, and a pack, a document-subtype map or a compiler table keyed by it
|
|
6
|
+
* would be keyed by nothing.
|
|
7
|
+
*
|
|
8
|
+
* @param {unknown} value - The candidate id.
|
|
9
|
+
* @returns {boolean} `true` when {@link SYSTEM_IDS} declares it.
|
|
10
|
+
*/
|
|
11
|
+
export function isSystemId(value: unknown): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Whether a value is something the `<system>` segment may hold.
|
|
14
|
+
*
|
|
15
|
+
* The predicate an address parser wants: a declared system, or `none`.
|
|
16
|
+
*
|
|
17
|
+
* @param {unknown} value - The candidate segment.
|
|
18
|
+
* @returns {boolean} `true` when {@link SYSTEM_SEGMENTS} declares it.
|
|
19
|
+
*/
|
|
20
|
+
export function isSystemSegment(value: unknown): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* What a caller writing an unknown system is told.
|
|
23
|
+
*
|
|
24
|
+
* Separate from {@link assertSystemSegment} so that a pass collecting findings
|
|
25
|
+
* can report the same sentence it would have thrown — the message is the part
|
|
26
|
+
* worth sharing, and a reporter that had to invent its own wording is how two
|
|
27
|
+
* halves of a build come to explain one rule two ways.
|
|
28
|
+
*
|
|
29
|
+
* The nullish case is called out by name because it is the likely mistake and
|
|
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.
|
|
33
|
+
*
|
|
34
|
+
* @param {unknown} value - The offending value.
|
|
35
|
+
* @param {string} [where] - What carried it, for the message — a note path, a
|
|
36
|
+
* configuration key, an address.
|
|
37
|
+
* @returns {string} The message, with no trailing period, in the style of the
|
|
38
|
+
* other vocabulary messages.
|
|
39
|
+
*/
|
|
40
|
+
export function unknownSystemMessage(value: unknown, where?: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Refuse a value the `<system>` segment may not hold.
|
|
43
|
+
*
|
|
44
|
+
* Throws rather than returning a finding, matching the nearest neighbour —
|
|
45
|
+
* `assertVocabularyCharset` in `engine/note-vocabulary.mjs`, and the
|
|
46
|
+
* `configFromData` validators — because every caller of this is validating a
|
|
47
|
+
* declaration rather than surveying content. A note's bad value belongs in a
|
|
48
|
+
* report; a build asking to route, address or compile an undeclared system has
|
|
49
|
+
* nowhere to continue to.
|
|
50
|
+
*
|
|
51
|
+
* @param {unknown} value - The candidate segment.
|
|
52
|
+
* @param {string} [where] - What carried it, for the message.
|
|
53
|
+
* @returns {string} The value, unchanged, so a caller may validate inline.
|
|
54
|
+
* @throws {Error} Naming the value and the whole known vocabulary.
|
|
55
|
+
*/
|
|
56
|
+
export function assertSystemSegment(value: unknown, where?: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* Refuse a registry declaring an id that could not be an address segment.
|
|
59
|
+
*
|
|
60
|
+
* Run over {@link SYSTEM_SEGMENTS} as this module loads, so a declaration that
|
|
61
|
+
* breaks the charset cannot be imported, let alone shipped. Same reasoning as
|
|
62
|
+
* `assertVocabularyCharset`: a bad value in one note is one author's mistake,
|
|
63
|
+
* while a bad *declaration* puts an unreadable segment into every address that
|
|
64
|
+
* names the system.
|
|
65
|
+
*
|
|
66
|
+
* @param {Iterable<string>} segments - The declared ids, including `none`.
|
|
67
|
+
* @param {string} [where] - What declares them, for the message.
|
|
68
|
+
* @throws {Error} Naming every offending id at once — a reader fixing a list
|
|
69
|
+
* wants the whole list.
|
|
70
|
+
*/
|
|
71
|
+
export function assertSystemCharset(segments: Iterable<string>, where?: string): void;
|
|
72
|
+
/**
|
|
73
|
+
* The `<system>` segment of a note that belongs to no system.
|
|
74
|
+
*
|
|
75
|
+
* Most notes are that: a `doc`, a `place`, a `macro` or a map compiles into a
|
|
76
|
+
* JournalEntry, a Scene or a Macro, which Foundry defines and no game system
|
|
77
|
+
* does. `none` is the format's word for it, in the address and in the content
|
|
78
|
+
* index alike, so the two say "no system" the same way.
|
|
79
|
+
*
|
|
80
|
+
* @type {string}
|
|
81
|
+
*/
|
|
82
|
+
export const NO_SYSTEM: string;
|
|
83
|
+
/**
|
|
84
|
+
* Every game system this toolchain compiles for.
|
|
85
|
+
*
|
|
86
|
+
* The registry, and the only place the list is written down. Adding a system
|
|
87
|
+
* is an edit to this set plus the map and compilers that make it real — a data
|
|
88
|
+
* change, not a search for every place a name was spelled.
|
|
89
|
+
*
|
|
90
|
+
* `none` is **not** a member: it marks the absence of a system, and a caller
|
|
91
|
+
* asking "is this a system" and a caller asking "is this a well-formed
|
|
92
|
+
* segment" want different answers. See {@link isSystemId} and
|
|
93
|
+
* {@link isSystemSegment}.
|
|
94
|
+
*
|
|
95
|
+
* @type {ReadonlySet<string>}
|
|
96
|
+
*/
|
|
97
|
+
export const SYSTEM_IDS: ReadonlySet<string>;
|
|
98
|
+
/**
|
|
99
|
+
* Everything the `<system>` segment of an address may say.
|
|
100
|
+
*
|
|
101
|
+
* The systems, plus {@link NO_SYSTEM}. Derived from the registry rather than
|
|
102
|
+
* listed again, so the two cannot drift.
|
|
103
|
+
*
|
|
104
|
+
* @type {ReadonlySet<string>}
|
|
105
|
+
*/
|
|
106
|
+
export const SYSTEM_SEGMENTS: ReadonlySet<string>;
|
|
@@ -32,8 +32,10 @@ export function frontmatterWikilinks(fm: unknown): Array<{
|
|
|
32
32
|
*
|
|
33
33
|
* **Every target is an address**, parsed by {@link readQualifier} and looked up
|
|
34
34
|
* case-insensitively in the KB-wide `ctx.index` (the canonical
|
|
35
|
-
* `package-type-shortcode`, `type/shortcode`, and the site's own
|
|
36
|
-
* `section/slug`), then in the vendored `ctx.foreign` manifests. A
|
|
35
|
+
* `package-system-type-shortcode`, `type/shortcode`, and the site's own
|
|
36
|
+
* `section/slug`), then in the vendored `ctx.foreign` manifests. A target is a
|
|
37
|
+
* *partial* address: an omitted package means this package, an omitted system
|
|
38
|
+
* is a wildcard, and exactly one match resolves — two are `ambiguous`. A link written
|
|
37
39
|
* without a label addresses nothing at all and is reported as such (#180) —
|
|
38
40
|
* there is no second namespace left for it to name.
|
|
39
41
|
*
|
|
@@ -77,11 +77,11 @@ export function unlabelledLinkMessage(target: string): string;
|
|
|
77
77
|
*
|
|
78
78
|
* **Both corrections, because the author cannot tell which applies.** An
|
|
79
79
|
* address lands nowhere either because the shortcode is wrong or because the
|
|
80
|
-
* package publishing it
|
|
81
|
-
* looks identical in
|
|
80
|
+
* package publishing it is not a declared dependency — or is one whose index
|
|
81
|
+
* has not been fetched — and the link itself looks identical in every case.
|
|
82
82
|
*
|
|
83
83
|
* This used to be a **warning** in the checker and, in the site build, nothing
|
|
84
|
-
* at all until every linkable package
|
|
84
|
+
* at all until every linkable package's manifest was accounted for — on the
|
|
85
85
|
* reasoning that a bare `[[Name]]` might be a placeholder for a note nobody had
|
|
86
86
|
* written yet. That reasoning was a property of the bare form, which is retired
|
|
87
87
|
* (#180); the intent behind it now has a real spelling, a `draft`-tagged note
|
|
@@ -101,6 +101,13 @@ export function unresolvedAddressMessage(target: string): string;
|
|
|
101
101
|
* message names the claimants so the author can choose between them without
|
|
102
102
|
* going looking.
|
|
103
103
|
*
|
|
104
|
+
* **The correction is the canonical form**, all four segments (#59). Omission
|
|
105
|
+
* runs strictly left to right, so there is no `package-type-shortcode` to offer
|
|
106
|
+
* — naming a package means naming the system before the type. That used to be
|
|
107
|
+
* the correction here, back when a written target could state three segments at
|
|
108
|
+
* most; the grammar is positional now, and a three-segment target names a
|
|
109
|
+
* *system*, not a package.
|
|
110
|
+
*
|
|
104
111
|
* @param {string} target - The address as authored.
|
|
105
112
|
* @param {Iterable<string>} [packages] - The packages that publish it.
|
|
106
113
|
* @returns {string} The message.
|
|
@@ -31,31 +31,58 @@ export function resolveItemDocType(qualifier: string, types: Set<string>): strin
|
|
|
31
31
|
* Two separators are accepted, and they are **not** interchangeable in how
|
|
32
32
|
* confidently they mark a target as qualified:
|
|
33
33
|
*
|
|
34
|
-
* - **`type-shortcode`**
|
|
35
|
-
* a wikilink as a *path* and resolves it
|
|
36
|
-
* slash-qualified link is a broken link in
|
|
37
|
-
*
|
|
38
|
-
* note names contain hyphens too (`Grukar-ahk`), and a target that is one is
|
|
39
|
-
* reported as not an address rather than split at an arbitrary place. The
|
|
40
|
-
* split is at the **first** hyphen, so a shortcode may itself contain one
|
|
41
|
-
* (`trauma-self-pro` → `trauma` + `self-pro`).
|
|
34
|
+
* - **`type-shortcode`** and its qualified forms — the canonical spelling
|
|
35
|
+
* (#1398). Obsidian reads `/` inside a wikilink as a *path* and resolves it
|
|
36
|
+
* against the vault's folders, so a slash-qualified link is a broken link in
|
|
37
|
+
* the editor where the content is now authored.
|
|
42
38
|
* - **`type/shortcode`** — the legacy form, still resolved so that a link
|
|
43
39
|
* written before the vault migrated does not silently die. A slash is
|
|
44
40
|
* *unconditionally* a qualifier: nothing else uses one, so an unknown type
|
|
45
41
|
* before it is reported rather than guessed at. The split is at the **last**
|
|
46
42
|
* slash, as it always was.
|
|
47
43
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
44
|
+
* **The grammar is strict, and omission runs left to right** (#59):
|
|
45
|
+
*
|
|
46
|
+
* ```text
|
|
47
|
+
* [[[[<package>-]<system>-]<type>-]<shortcode>]
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* So the written forms are exactly the suffixes of the canonical address —
|
|
51
|
+
* `type-shortcode`, `system-type-shortcode`, `package-system-type-shortcode` —
|
|
52
|
+
* and **`package-type-shortcode` is not one of them**. A link into another
|
|
53
|
+
* package must therefore be fully qualified, which is the price of the segment
|
|
54
|
+
* being positional rather than tagged.
|
|
55
|
+
*
|
|
56
|
+
* **Parsing is plain positional counting**, the same rule
|
|
57
|
+
* {@link readCanonicalKey} follows, and it is sound for the same reason: every
|
|
58
|
+
* segment is `^[A-Za-z0-9]+$` (`ADDRESS_SEGMENT_PATTERN`, enforced on
|
|
59
|
+
* shortcodes by `content-lint.mjs` since #1397), so the hyphen is purely a
|
|
60
|
+
* separator and the count alone determines every field. Verified across the
|
|
61
|
+
* four content trees: 138,204 authored shortcodes, none carrying a separator.
|
|
62
|
+
*
|
|
63
|
+
* That replaced a first-hyphen split which let a shortcode contain a hyphen
|
|
64
|
+
* (`trauma-self-pro` → `trauma` + `self-pro`). The tolerance predates #1397 and
|
|
65
|
+
* outlived it; no tree has used it, and keeping it would make a three-segment
|
|
66
|
+
* target ambiguous between a system and a hyphenated shortcode.
|
|
67
|
+
*
|
|
68
|
+
* **`sohl` is both a package and a system**, and positional counting is what
|
|
69
|
+
* makes that harmless: three segments is `<system>-<type>-<shortcode>` whatever
|
|
70
|
+
* the first segment could also have named, and four is the full form. Nothing
|
|
71
|
+
* has to guess which sense was meant.
|
|
72
|
+
*
|
|
73
|
+
* **A partial address states what it states, and the rest is not invented.** An
|
|
74
|
+
* omitted **system** is a *wildcard* — most links target items, which belong to
|
|
75
|
+
* a system — so the caller matches on the segments supplied and requires
|
|
76
|
+
* exactly one hit. An omitted **package** is instead *defaulted* to the citing
|
|
77
|
+
* note's own, so an unqualified link resolves locally and only locally.
|
|
52
78
|
*
|
|
53
79
|
* @param {string} target - The link target, anchor already removed.
|
|
54
80
|
* @param {Set<string>} types - Every type the content tree contains.
|
|
55
81
|
* @param {Set<string>} [packages] - Every package an address may name. Omitted
|
|
56
82
|
* by callers that resolve within one package, where the form cannot occur.
|
|
57
83
|
* @returns {{type: string, shortcode: string, itemDoc: boolean,
|
|
58
|
-
* package?: string,
|
|
84
|
+
* package?: string, system?: string, reason?: undefined}
|
|
85
|
+
* | {reason: "unknown-type"} | null}
|
|
59
86
|
* The resolved qualifier; a `reason` when the target is definitely qualified
|
|
60
87
|
* but names no known type; or `null` when it is not an address at all.
|
|
61
88
|
*/
|
|
@@ -64,6 +91,7 @@ export function readQualifier(target: string, types: Set<string>, packages?: Set
|
|
|
64
91
|
shortcode: string;
|
|
65
92
|
itemDoc: boolean;
|
|
66
93
|
package?: string;
|
|
94
|
+
system?: string;
|
|
67
95
|
reason?: undefined;
|
|
68
96
|
} | {
|
|
69
97
|
reason: "unknown-type";
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared rule set, as an ESLint flat configuration.
|
|
3
|
+
*
|
|
4
|
+
* **Deliberately narrow, for the same reason the markdown and stylesheet rule
|
|
5
|
+
* sets are** — Prettier already owns YAML's whitespace, quoting and line
|
|
6
|
+
* breaks, including inside a frontmatter fence, so a rule about any of those
|
|
7
|
+
* would either duplicate the formatter or fight it. What is left is the class
|
|
8
|
+
* the formatter cannot see: text that parses to something other than what it
|
|
9
|
+
* looks like.
|
|
10
|
+
*
|
|
11
|
+
* - **A parse error is reported, not swallowed.** A duplicate key, a tab used
|
|
12
|
+
* as indentation, a mapping whose items start at different columns: the YAML
|
|
13
|
+
* parser detects all three and `parseMarkdownFile` discards all three.
|
|
14
|
+
* - `no-empty-mapping-value` — `folder:` and `folder: null` parse identically
|
|
15
|
+
* and read as opposites: one is a decision, the other is a key somebody began
|
|
16
|
+
* and did not finish. YAML cannot tell them apart, so the distinction has to
|
|
17
|
+
* be made where the text still exists. A key with a block under it is not
|
|
18
|
+
* empty, which is what separates 65 unfinished keys in `sohl-thalorna` from
|
|
19
|
+
* the tens of thousands that are ordinary containers.
|
|
20
|
+
* - `no-irregular-whitespace` — a non-breaking space in a key or an unquoted
|
|
21
|
+
* scalar is invisible in every editor and changes the value.
|
|
22
|
+
* - `no-empty-key`, `no-empty-document` — a fence or a file that parses to
|
|
23
|
+
* nothing at all.
|
|
24
|
+
*
|
|
25
|
+
* **GitHub workflows are exempt from `no-empty-mapping-value`**, because an
|
|
26
|
+
* empty value is the language there: `on:` `push:` and `workflow_dispatch:`
|
|
27
|
+
* carry their meaning by being present, and writing `push: null` to satisfy a
|
|
28
|
+
* linter would be worse YAML, not better.
|
|
29
|
+
*
|
|
30
|
+
* @param {object} plugin - The `eslint-plugin-yml` module.
|
|
31
|
+
* @returns {object[]} A complete flat config, for `overrideConfig`.
|
|
32
|
+
*/
|
|
33
|
+
export function yamlLintConfig(plugin: object): object[];
|
|
34
|
+
/**
|
|
35
|
+
* The files to lint: every YAML file git would consider, and no other.
|
|
36
|
+
*
|
|
37
|
+
* `--cached --others --exclude-standard` is tracked files plus untracked ones
|
|
38
|
+
* that are not ignored, which is the same set `gitignore: true` gives the
|
|
39
|
+
* markdown linter — and it matters more than it sounds. Asking ESLint for
|
|
40
|
+
* `**\/*.md` in `Song-of-Heroic-Lands-FoundryVTT` offers it 60,792 files, almost
|
|
41
|
+
* all of them inside `nogit/` and `.claude/worktrees/`; this offers 1,888, in
|
|
42
|
+
* 20ms. Untracked-but-not-ignored is included so a note gets linted while it is
|
|
43
|
+
* being written, not only once it has been staged.
|
|
44
|
+
*
|
|
45
|
+
* `-z` because git otherwise quotes any path outside ASCII, and these trees are
|
|
46
|
+
* full of them.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} root - Repository root.
|
|
49
|
+
* @param {readonly string[]} [globs] - Path globs, defaulting to
|
|
50
|
+
* {@link YAML_GLOBS}.
|
|
51
|
+
* @returns {string[]} Repository-relative paths.
|
|
52
|
+
*/
|
|
53
|
+
export function candidateFiles(root: string, globs?: readonly string[]): string[];
|
|
54
|
+
/**
|
|
55
|
+
* Lint a repository's YAML against the shared rule set.
|
|
56
|
+
*
|
|
57
|
+
* ESLint is run through its Node API with `overrideConfigFile: true`, which
|
|
58
|
+
* stops it looking for an `eslint.config.js` at all. That is what lets this ship
|
|
59
|
+
* as a command rather than as a configuration a consumer has to adopt: a
|
|
60
|
+
* repository needs no ESLint of its own, and one that *has* an ESLint — as
|
|
61
|
+
* `Song-of-Heroic-Lands-FoundryVTT` does, for `src/` — keeps it untouched and
|
|
62
|
+
* unconsulted.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} root - Repository to lint.
|
|
65
|
+
* @param {object} [opts]
|
|
66
|
+
* @param {readonly string[]} [opts.paths] - Globs to lint instead of every
|
|
67
|
+
* YAML file git would consider.
|
|
68
|
+
* @param {object} [opts.plugin] - The `eslint-plugin-yml` module, for tests.
|
|
69
|
+
* @param {Function} [opts.ESLint] - The `ESLint` class, for tests.
|
|
70
|
+
* @returns {Promise<{findings: object[], checked: number}>} The findings, and
|
|
71
|
+
* how many files were linted.
|
|
72
|
+
*/
|
|
73
|
+
export function lintYaml(root: string, opts?: {
|
|
74
|
+
paths?: readonly string[] | undefined;
|
|
75
|
+
plugin?: object | undefined;
|
|
76
|
+
ESLint?: Function | undefined;
|
|
77
|
+
}): Promise<{
|
|
78
|
+
findings: object[];
|
|
79
|
+
checked: number;
|
|
80
|
+
}>;
|
|
81
|
+
/** File extensions that carry YAML, either wholly or in a frontmatter fence. */
|
|
82
|
+
export const YAML_GLOBS: readonly string[];
|
|
83
|
+
/**
|
|
84
|
+
* Present a markdown file to ESLint as the YAML it begins with.
|
|
85
|
+
*
|
|
86
|
+
* An ESLint *processor* carves virtual files out of a container file — the
|
|
87
|
+
* mechanism `eslint-plugin-markdown` uses for fenced code blocks. Frontmatter is
|
|
88
|
+
* the easy case of it: the block is always at the top of the file, so mapping a
|
|
89
|
+
* finding back to the file it came from is a constant `+1` for the opening
|
|
90
|
+
* `---`, with no offset table to keep.
|
|
91
|
+
*
|
|
92
|
+
* The virtual file is named `0.yaml` so the flat config's `**` + `*.yaml`
|
|
93
|
+
* patterns select it; ESLint addresses it as `<the note>.md/0.yaml`.
|
|
94
|
+
*
|
|
95
|
+
* @type {{meta: object, supportsAutofix: boolean,
|
|
96
|
+
* preprocess: (text: string) => Array<{text: string, filename: string}>,
|
|
97
|
+
* postprocess: (messages: object[][]) => object[]}}
|
|
98
|
+
*/
|
|
99
|
+
export const frontmatterProcessor: {
|
|
100
|
+
meta: object;
|
|
101
|
+
supportsAutofix: boolean;
|
|
102
|
+
preprocess: (text: string) => Array<{
|
|
103
|
+
text: string;
|
|
104
|
+
filename: string;
|
|
105
|
+
}>;
|
|
106
|
+
postprocess: (messages: object[][]) => object[];
|
|
107
|
+
};
|