@zvndev/darkmown 2.2.0 → 2.3.1-next.1.g3a4a8dc
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 +13 -0
- package/README.md +34 -2
- package/docs/spec-alignment.md +10 -0
- package/package.json +16 -11
- package/site/pages/docs/index.wd +14 -0
- package/site/pages/playground/index.js +47 -46
- package/src/catalog.js +16 -1
- package/src/compiler/actions.js +42 -22
- package/src/compiler/body.js +36 -14
- package/src/compiler/collections.js +16 -10
- package/src/compiler/context.js +39 -10
- package/src/compiler/fetch.js +39 -15
- package/src/compiler/format.js +5 -2
- package/src/compiler/forms.js +91 -26
- package/src/compiler/frontmatter.js +11 -2
- package/src/compiler/fs-reader.js +2 -1
- package/src/compiler/image-size.js +340 -0
- package/src/compiler/includes.js +8 -4
- package/src/compiler/interpolation.js +15 -7
- package/src/compiler/loops.js +63 -35
- package/src/compiler/markdown.js +4 -3
- package/src/compiler/media.js +17 -3
- package/src/compiler/page.js +8 -6
- package/src/compiler/predicates.js +49 -24
- package/src/compiler/reader.js +12 -2
- package/src/compiler/state.js +16 -2
- package/src/compiler/structure.js +24 -14
- package/src/deploy.js +8 -4
- package/src/errors.js +1156 -0
- package/src/router.js +7 -4
- package/src/scaffold.js +4 -2
- package/src/skin.js +17 -6
- package/types/api-runner.d.ts +94 -0
- package/types/builder.d.ts +141 -10
- package/types/catalog.d.ts +97 -0
- package/types/compiler/actions.d.ts +41 -0
- package/types/compiler/body.d.ts +11 -0
- package/types/compiler/collections.d.ts +76 -0
- package/types/compiler/context.d.ts +647 -0
- package/types/compiler/directives.d.ts +6 -0
- package/types/compiler/expr-ast.d.ts +20 -0
- package/types/compiler/fetch.d.ts +29 -0
- package/types/compiler/format.d.ts +49 -0
- package/types/compiler/forms.d.ts +94 -0
- package/types/compiler/frontmatter.d.ts +33 -0
- package/types/compiler/fs-reader.d.ts +7 -0
- package/types/compiler/image-size.d.ts +14 -0
- package/types/compiler/includes.d.ts +100 -0
- package/types/compiler/index.d.ts +5 -0
- package/types/compiler/interpolation.d.ts +115 -0
- package/types/compiler/loops.d.ts +36 -0
- package/types/compiler/markdown.d.ts +34 -0
- package/types/compiler/media.d.ts +32 -0
- package/types/compiler/page.d.ts +109 -0
- package/types/compiler/predicates.d.ts +50 -0
- package/types/compiler/reader.d.ts +48 -0
- package/types/compiler/state.d.ts +68 -0
- package/types/compiler/structure.d.ts +56 -0
- package/types/compiler.d.ts +35 -223
- package/types/dev.d.ts +47 -2
- package/types/errors.d.ts +100 -0
- package/types/feeds.d.ts +150 -0
- package/types/headers.d.ts +44 -0
- package/types/highlight.d.ts +24 -0
- package/types/router.d.ts +42 -3
- package/types/scaffold.d.ts +15 -3
- package/types/skin.d.ts +28 -1
- package/types/src/api-runner.d.ts +94 -0
- package/types/src/builder.d.ts +170 -0
- package/types/src/catalog.d.ts +97 -0
- package/types/src/compiler/actions.d.ts +41 -0
- package/types/src/compiler/body.d.ts +11 -0
- package/types/src/compiler/collections.d.ts +76 -0
- package/types/src/compiler/context.d.ts +647 -0
- package/types/src/compiler/directives.d.ts +6 -0
- package/types/src/compiler/expr-ast.d.ts +20 -0
- package/types/src/compiler/fetch.d.ts +29 -0
- package/types/src/compiler/format.d.ts +49 -0
- package/types/src/compiler/forms.d.ts +94 -0
- package/types/src/compiler/frontmatter.d.ts +33 -0
- package/types/src/compiler/fs-reader.d.ts +7 -0
- package/types/src/compiler/image-size.d.ts +14 -0
- package/types/src/compiler/includes.d.ts +100 -0
- package/types/src/compiler/index.d.ts +5 -0
- package/types/src/compiler/interpolation.d.ts +115 -0
- package/types/src/compiler/loops.d.ts +36 -0
- package/types/src/compiler/markdown.d.ts +34 -0
- package/types/src/compiler/media.d.ts +32 -0
- package/types/src/compiler/page.d.ts +109 -0
- package/types/src/compiler/predicates.d.ts +50 -0
- package/types/src/compiler/reader.d.ts +48 -0
- package/types/src/compiler/state.d.ts +68 -0
- package/types/src/compiler/structure.d.ts +56 -0
- package/types/src/compiler.d.ts +48 -0
- package/types/src/config.d.ts +45 -0
- package/types/src/dev.d.ts +58 -0
- package/types/src/errors.d.ts +100 -0
- package/types/src/feeds.d.ts +150 -0
- package/types/src/headers.d.ts +44 -0
- package/types/src/highlight.d.ts +24 -0
- package/types/src/router.d.ts +78 -0
- package/types/src/scaffold.d.ts +20 -0
- package/types/src/skin.d.ts +48 -0
- package/types/src/statics.d.ts +24 -0
- package/types/statics.d.ts +2 -1
package/src/errors.js
ADDED
|
@@ -0,0 +1,1156 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// The stable compile-error code registry.
|
|
3
|
+
//
|
|
4
|
+
// Every author-facing compile error carries a `WDxxx` code: `wdError()`
|
|
5
|
+
// (src/compiler/context.js) prefixes the message with `[WDxxx] ` and mirrors it
|
|
6
|
+
// onto `err.wd.code`, so a user can paste the code into a search box and tooling
|
|
7
|
+
// can match on an identifier instead of prose. This file is the single source of
|
|
8
|
+
// truth for what those codes MEAN; `docs/errors.md` is generated from it
|
|
9
|
+
// (scripts/gen-errors.mjs) and `directiveCatalog().errors` exposes it to the
|
|
10
|
+
// machine-readable surface AI tooling already consumes.
|
|
11
|
+
//
|
|
12
|
+
// NUMBERING (the hundreds digit is the subsystem; it never moves):
|
|
13
|
+
// WD0xx source, frontmatter, prose interpolation, block structure
|
|
14
|
+
// WD1xx loops (@loop) and typed content collections
|
|
15
|
+
// WD2xx state (:state/:store/:computed/:theme) and the expression whitelists
|
|
16
|
+
// WD3xx actions (:button/:effect/:every)
|
|
17
|
+
// WD4xx forms and form fields
|
|
18
|
+
// WD5xx data fetching (:fetch) and URL safety
|
|
19
|
+
// WD6xx includes and page structure (@include, :::, :if, :carousel, :try)
|
|
20
|
+
// WD7xx media and embeds
|
|
21
|
+
// WD8xx skins and styling
|
|
22
|
+
// WD9xx project, routing, deploy, CLI
|
|
23
|
+
//
|
|
24
|
+
// CODES ARE A PUBLIC CONTRACT. Once a code ships it is permanent:
|
|
25
|
+
// * NEVER renumber a code, and NEVER reuse a retired one. A code that is
|
|
26
|
+
// withdrawn goes in RETIRED_CODES below and stays there forever, so the
|
|
27
|
+
// next number is always fresh even when the error itself is gone.
|
|
28
|
+
// * A code's MEANING may be refined (better wording, a better fix) but must
|
|
29
|
+
// keep pointing at the same authoring mistake.
|
|
30
|
+
// * New errors take the next free number in their subsystem block.
|
|
31
|
+
//
|
|
32
|
+
// WHAT GETS A CODE. Author-facing compile errors (anything a `.wd`/`.md`/
|
|
33
|
+
// `.skin`/frontmatter/CLI input can trigger) all go through `wdError` and carry
|
|
34
|
+
// a code. Genuine INTERNAL invariants do not: `src/compiler/expr-ast.js` parses
|
|
35
|
+
// the compiler's own already-validated output and `src/compiler/reader.js`
|
|
36
|
+
// guards the host reader contract, so a throw there is a framework bug, not
|
|
37
|
+
// something an author can fix, and it stays a plain `Error`. That split is
|
|
38
|
+
// enforced by tests/error-codes.test.js.
|
|
39
|
+
//
|
|
40
|
+
// Examples are drawn from the SAME `*_EXAMPLE` constants the compiler's error
|
|
41
|
+
// hints use, exactly like src/catalog.js, so a documented example cannot drift
|
|
42
|
+
// from what actually compiles.
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
import {
|
|
46
|
+
ACTION_EXAMPLE,
|
|
47
|
+
BUTTON_EXAMPLE,
|
|
48
|
+
EFFECT_EXAMPLE,
|
|
49
|
+
EVERY_EXAMPLE
|
|
50
|
+
} from "./compiler/actions.js";
|
|
51
|
+
import { FETCH_EXAMPLE } from "./compiler/fetch.js";
|
|
52
|
+
import {
|
|
53
|
+
BIND_EXAMPLE,
|
|
54
|
+
CHECKBOX_EXAMPLE,
|
|
55
|
+
FORM_EXAMPLE,
|
|
56
|
+
INPUT_EXAMPLE,
|
|
57
|
+
RADIO_EXAMPLE,
|
|
58
|
+
SELECT_EXAMPLE,
|
|
59
|
+
SLIDER_EXAMPLE,
|
|
60
|
+
SUBMIT_EXAMPLE,
|
|
61
|
+
TEXTAREA_EXAMPLE
|
|
62
|
+
} from "./compiler/forms.js";
|
|
63
|
+
import { LOOP_EXAMPLE } from "./compiler/loops.js";
|
|
64
|
+
import { EMBED_EXAMPLE, VIDEO_EXAMPLE } from "./compiler/media.js";
|
|
65
|
+
import { COMPUTED_EXAMPLE, STATE_EXAMPLE, STORE_EXAMPLE, THEME_EXAMPLE } from "./compiler/state.js";
|
|
66
|
+
import {
|
|
67
|
+
CAROUSEL_EXAMPLE,
|
|
68
|
+
CONTAINER_EXAMPLE,
|
|
69
|
+
IF_EXAMPLE,
|
|
70
|
+
INCLUDE_EXAMPLE
|
|
71
|
+
} from "./compiler/structure.js";
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* One subsystem block of the numbering scheme.
|
|
75
|
+
* @typedef {object} ErrorArea
|
|
76
|
+
* @property {number} block The hundreds digit (0-9).
|
|
77
|
+
* @property {string} range Human label for the block, e.g. `WD1xx`.
|
|
78
|
+
* @property {string} name Short machine name, e.g. `loops`.
|
|
79
|
+
* @property {string} title One-line description of what the block covers.
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A registered error code.
|
|
84
|
+
* @typedef {object} ErrorEntry
|
|
85
|
+
* @property {string} code The stable `WDxxx` identifier.
|
|
86
|
+
* @property {string} area The subsystem name (derived from the hundreds digit).
|
|
87
|
+
* @property {string} title Short name for the mistake.
|
|
88
|
+
* @property {string} cause What the author wrote that triggers it.
|
|
89
|
+
* @property {string} fix The corrective action, in one line.
|
|
90
|
+
* @property {string} [example] A concrete, compilable line, sourced from the
|
|
91
|
+
* compiler's own `*_EXAMPLE` constants.
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
/** The subsystem blocks, in code order. */
|
|
95
|
+
/** @type {ErrorArea[]} */
|
|
96
|
+
export const ERROR_AREAS = [
|
|
97
|
+
{ block: 0, range: "WD0xx", name: "source", title: "Source, frontmatter & block structure" },
|
|
98
|
+
{ block: 1, range: "WD1xx", name: "loops", title: "Loops & collections" },
|
|
99
|
+
{ block: 2, range: "WD2xx", name: "state", title: "State & expressions" },
|
|
100
|
+
{ block: 3, range: "WD3xx", name: "actions", title: "Button, effect & timer actions" },
|
|
101
|
+
{ block: 4, range: "WD4xx", name: "forms", title: "Forms & form fields" },
|
|
102
|
+
{ block: 5, range: "WD5xx", name: "fetch", title: "Data fetching & URL safety" },
|
|
103
|
+
{ block: 6, range: "WD6xx", name: "structure", title: "Includes & page structure" },
|
|
104
|
+
{ block: 7, range: "WD7xx", name: "media", title: "Media & embeds" },
|
|
105
|
+
{ block: 8, range: "WD8xx", name: "skin", title: "Skins & styling" },
|
|
106
|
+
{ block: 9, range: "WD9xx", name: "project", title: "Project, routing & CLI" }
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Codes that were withdrawn. They are NEVER reused: a retired number stays
|
|
111
|
+
* listed here so the next free number in its block is always genuinely free,
|
|
112
|
+
* and so an old error message found in a log or an issue still resolves to a
|
|
113
|
+
* meaningful (if historical) entry. Empty today.
|
|
114
|
+
* @type {string[]}
|
|
115
|
+
*/
|
|
116
|
+
export const RETIRED_CODES = [];
|
|
117
|
+
|
|
118
|
+
/** The registry, in code order. */
|
|
119
|
+
/** @type {Omit<ErrorEntry, "area">[]} */
|
|
120
|
+
const ENTRIES = [
|
|
121
|
+
// --- WD0xx: source, frontmatter, prose, block structure ------------------
|
|
122
|
+
{
|
|
123
|
+
code: "WD001",
|
|
124
|
+
title: "Unterminated frontmatter",
|
|
125
|
+
cause: "The file opens with `---` but never closes the block.",
|
|
126
|
+
fix: "Add a closing `---` on its own line before the page body."
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
code: "WD002",
|
|
130
|
+
title: "Disallowed path segment",
|
|
131
|
+
cause: "A dotted path contains `constructor`, `prototype`, or `__proto__`.",
|
|
132
|
+
fix: "Read a different path; those segments are rejected everywhere for safety."
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
code: "WD003",
|
|
136
|
+
title: "Cannot interpolate an object value",
|
|
137
|
+
cause: "`{ name }` resolved to an object, which has no sensible text form.",
|
|
138
|
+
fix: "Interpolate one of its fields (`{ name.title }`) or iterate it with `@loop`."
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
code: "WD004",
|
|
142
|
+
title: "Unbalanced JSON value",
|
|
143
|
+
cause: "A `:state`/`:store`/`:theme` value opens `[` or `{` and never closes it.",
|
|
144
|
+
fix: "Balance the literal (no blank lines inside it), or quote it as text.",
|
|
145
|
+
example: STATE_EXAMPLE
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
code: "WD005",
|
|
149
|
+
title: "Loop variable used outside a loop",
|
|
150
|
+
cause: "`{ $index }` (or another `$` row variable) appears outside a `@loop` body.",
|
|
151
|
+
fix: "Move the interpolation into a loop body, or rename your value."
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
code: "WD006",
|
|
155
|
+
title: "Unknown format pipe",
|
|
156
|
+
cause: "`{ value | name }` names a formatter that is not in the fixed whitelist.",
|
|
157
|
+
fix: "Use one of the catalogued pipes; the message lists every available name."
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
code: "WD007",
|
|
161
|
+
title: "Stray `:::` close",
|
|
162
|
+
cause: "A bare `:::` line appears with no container open.",
|
|
163
|
+
fix: "Open the container first, or delete the stray closer.",
|
|
164
|
+
example: CONTAINER_EXAMPLE
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
code: "WD008",
|
|
168
|
+
title: "Retired `@repeat` directive",
|
|
169
|
+
cause: "`@repeat` was replaced by the single `@loop` directive.",
|
|
170
|
+
fix: "Rewrite the block as `@loop … into … @endloop`.",
|
|
171
|
+
example: LOOP_EXAMPLE
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
code: "WD009",
|
|
175
|
+
title: "Retired `:for` directive",
|
|
176
|
+
cause: "`:for` was replaced by the single `@loop` directive.",
|
|
177
|
+
fix: "Rewrite the block as `@loop … into … @endloop`.",
|
|
178
|
+
example: LOOP_EXAMPLE
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
code: "WD010",
|
|
182
|
+
title: "Stray block closer",
|
|
183
|
+
cause: "An `@endloop`/`:endif`/`:endform`/`:else` line has no matching opener.",
|
|
184
|
+
fix: "Open the matching block, or delete the stray closer."
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
code: "WD011",
|
|
188
|
+
title: "Missing block closer",
|
|
189
|
+
cause: "A `@loop`/`:form`/`:carousel` block runs to the end of the file unclosed.",
|
|
190
|
+
fix: "Close the block with its own closing token."
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
code: "WD012",
|
|
194
|
+
title: "Missing closing `:::`",
|
|
195
|
+
cause: "A `::: container` block is never closed.",
|
|
196
|
+
fix: "Add a bare `:::` line to close the container.",
|
|
197
|
+
example: CONTAINER_EXAMPLE
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
code: "WD013",
|
|
201
|
+
title: "`:else if` after `:else`",
|
|
202
|
+
cause: "A conditional continues with `:else if` after a bare `:else` already closed it.",
|
|
203
|
+
fix: "Order every `:else if` before the bare `:else`."
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
code: "WD014",
|
|
207
|
+
title: "Duplicate `:else`",
|
|
208
|
+
cause: "A conditional has more than one bare `:else` branch.",
|
|
209
|
+
fix: "Keep one `:else`; turn the others into `:else if` branches."
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
code: "WD015",
|
|
213
|
+
title: "Missing `:endif`",
|
|
214
|
+
cause: "An `:if` region is never closed.",
|
|
215
|
+
fix: "Close the region with `:endif`.",
|
|
216
|
+
example: IF_EXAMPLE
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
// --- WD1xx: loops and collections ----------------------------------------
|
|
220
|
+
{
|
|
221
|
+
code: "WD101",
|
|
222
|
+
title: "Malformed `@loop` header",
|
|
223
|
+
cause: "The header does not read `@loop <source> into <item>`.",
|
|
224
|
+
fix: "Name a source and an item variable, then any clauses in the fixed order.",
|
|
225
|
+
example: LOOP_EXAMPLE
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
code: "WD102",
|
|
229
|
+
title: "Malformed `@loop` clause",
|
|
230
|
+
cause: "A clause is misspelled or written out of the fixed order.",
|
|
231
|
+
fix: "Order clauses `where`, `sort by`, `reverse`, `offset`, `limit`, `paginate`, `sortable`.",
|
|
232
|
+
example: LOOP_EXAMPLE
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
code: "WD103",
|
|
236
|
+
title: "Unknown `sort by { state }` key",
|
|
237
|
+
cause: "A reactive sort key references a `:state`/`:store` that is not declared.",
|
|
238
|
+
fix: "Declare the state first, or sort by a literal item field.",
|
|
239
|
+
example: LOOP_EXAMPLE
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
code: "WD104",
|
|
243
|
+
title: "Sort key is not a loop-item field",
|
|
244
|
+
cause: "`sort by` names something other than the loop item.",
|
|
245
|
+
fix: "Sort by `<item>.field`, using the item name from the `into` clause.",
|
|
246
|
+
example: LOOP_EXAMPLE
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
code: "WD105",
|
|
250
|
+
title: "Disallowed sort key path",
|
|
251
|
+
cause: "The sort key contains `constructor`, `prototype`, or `__proto__`.",
|
|
252
|
+
fix: "Sort by an ordinary data field."
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
code: "WD106",
|
|
256
|
+
title: "Unknown sort direction state",
|
|
257
|
+
cause: "A reactive `{ direction }` references a `:state`/`:store` that is not declared.",
|
|
258
|
+
fix: "Declare the state first, or use the literal `asc`/`desc`.",
|
|
259
|
+
example: LOOP_EXAMPLE
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
code: "WD107",
|
|
263
|
+
title: "Invalid `offset`/`limit` argument",
|
|
264
|
+
cause: "The argument is neither a non-negative integer nor a declared `:state`.",
|
|
265
|
+
fix: "Pass an integer literal or a declared state key.",
|
|
266
|
+
example: LOOP_EXAMPLE
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
code: "WD108",
|
|
270
|
+
title: "`sortable` combined with other clauses",
|
|
271
|
+
cause: "`sortable` reorders the underlying list, so a derived view would desynchronise.",
|
|
272
|
+
fix: "Drop `where`/`sort`/`reverse`/`offset`/`limit`/`paginate` from a sortable loop."
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
code: "WD109",
|
|
276
|
+
title: "`paginate` combined with `offset`/`limit`",
|
|
277
|
+
cause: "`paginate` already slices each page, so an explicit slice conflicts with it.",
|
|
278
|
+
fix: "Keep `paginate N` and remove `offset`/`limit`."
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
code: "WD110",
|
|
282
|
+
title: "`paginate` on a non-collection source",
|
|
283
|
+
cause: "Pagination multiplies static routes, which only makes sense for a collection.",
|
|
284
|
+
fix: "Paginate a collection (a `site/pages/<name>/` subdirectory) by its bare name."
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
code: "WD111",
|
|
288
|
+
title: "Loop data file is not a JSON array",
|
|
289
|
+
cause: "The `.json` source parses to an object or a scalar.",
|
|
290
|
+
fix: "Make the file a top-level JSON array of rows."
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
code: "WD112",
|
|
294
|
+
title: "In-scope loop source is not a list",
|
|
295
|
+
cause: "The name resolved to a scalar or object value in scope.",
|
|
296
|
+
fix: "Point the loop at a list, or omit the field to render the `@empty` branch."
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
code: "WD113",
|
|
300
|
+
title: "Disallowed `@loop` source path",
|
|
301
|
+
cause: "The source path contains `constructor`, `prototype`, or `__proto__`.",
|
|
302
|
+
fix: "Loop an ordinary collection, JSON file, scope value, or state list."
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
code: "WD114",
|
|
306
|
+
title: "`sortable` on a nested loop",
|
|
307
|
+
cause: "An item-relative loop has no top-level state list to reorder.",
|
|
308
|
+
fix: "Make the sortable loop read a top-level `:state`/`:store` list."
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
code: "WD115",
|
|
312
|
+
title: "Unresolved `@loop` source",
|
|
313
|
+
cause: "The source is not a collection, JSON file, in-scope value, or declared state.",
|
|
314
|
+
fix: "Check the name; the message lists the available collections.",
|
|
315
|
+
example: LOOP_EXAMPLE
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
code: "WD116",
|
|
319
|
+
title: "Reactive loop nested too deep",
|
|
320
|
+
cause: "A third reactive `@loop` level would paint empty at runtime.",
|
|
321
|
+
fix: "Unroll the outer data at build time, or move the innermost list into build data."
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
code: "WD117",
|
|
325
|
+
title: "`sortable` needs a state list",
|
|
326
|
+
cause: "`sortable` was used on a JSON file or in-scope value, which cannot be reordered.",
|
|
327
|
+
fix: "Reorder a `:state`/`:store` list instead."
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
code: "WD120",
|
|
331
|
+
title: "Malformed `_schema.wd` line",
|
|
332
|
+
cause: "A schema line does not read `field: type`.",
|
|
333
|
+
fix: "Write one `field: type` rule per line inside the `---` block."
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
code: "WD121",
|
|
337
|
+
title: "Unknown `_schema.wd` type",
|
|
338
|
+
cause: "The type is outside the closed vocabulary.",
|
|
339
|
+
fix: "Use `string`, `number`, `boolean`, `date`, or `string[]`, with `?` for optional."
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
code: "WD122",
|
|
343
|
+
title: "Undeclared frontmatter field",
|
|
344
|
+
cause: "A collection entry has a field the schema does not declare (often a typo).",
|
|
345
|
+
fix: "Add the field to `_schema.wd`, or remove it from the entry."
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
code: "WD123",
|
|
349
|
+
title: "Missing required frontmatter field",
|
|
350
|
+
cause: "A collection entry omits a field the schema requires.",
|
|
351
|
+
fix: "Add the field to the entry, or mark it optional with `?` in the schema."
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
code: "WD124",
|
|
355
|
+
title: "Frontmatter field has the wrong type",
|
|
356
|
+
cause: "An entry's value does not match its schema type.",
|
|
357
|
+
fix: "Fix the entry's value, or widen the type in `_schema.wd`."
|
|
358
|
+
},
|
|
359
|
+
|
|
360
|
+
// --- WD2xx: state and expressions ----------------------------------------
|
|
361
|
+
{
|
|
362
|
+
code: "WD201",
|
|
363
|
+
title: "Malformed `:state`",
|
|
364
|
+
cause: "The line does not read `:state name = value [persist]`.",
|
|
365
|
+
fix: "Give the state a name and an initial value.",
|
|
366
|
+
example: STATE_EXAMPLE
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
code: "WD202",
|
|
370
|
+
title: "`:state` inside a reactive loop",
|
|
371
|
+
cause: "State cannot be declared per row; a loop body has no place to hold it.",
|
|
372
|
+
fix: "Declare the state outside the loop."
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
code: "WD203",
|
|
376
|
+
title: "`:state` collides with a `:store`",
|
|
377
|
+
cause: "A page-global store already owns that name.",
|
|
378
|
+
fix: "Rename one of them, or use the store everywhere.",
|
|
379
|
+
example: STORE_EXAMPLE
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
code: "WD204",
|
|
383
|
+
title: "`:state` declared twice",
|
|
384
|
+
cause: "The same name is declared twice in one section scope.",
|
|
385
|
+
fix: "Remove the duplicate, or scope one declaration to its own `::: section`."
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
code: "WD205",
|
|
389
|
+
title: "Malformed `:store`",
|
|
390
|
+
cause: "The line does not read `:store name = value [ephemeral]`.",
|
|
391
|
+
fix: "Give the store a name and an initial value.",
|
|
392
|
+
example: STORE_EXAMPLE
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
code: "WD206",
|
|
396
|
+
title: "`:store` declared twice",
|
|
397
|
+
cause: "Stores are page-global, so a second declaration is ambiguous.",
|
|
398
|
+
fix: "Declare the store once and reference it everywhere else.",
|
|
399
|
+
example: STORE_EXAMPLE
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
code: "WD207",
|
|
403
|
+
title: "`:store` collides with a `:state`",
|
|
404
|
+
cause: "A `:state` of the same name is already declared on the page.",
|
|
405
|
+
fix: "Rename one of them, or use the store everywhere.",
|
|
406
|
+
example: STORE_EXAMPLE
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
code: "WD208",
|
|
410
|
+
title: "Malformed `:computed`",
|
|
411
|
+
cause: "The line does not read `:computed name = <expression>`.",
|
|
412
|
+
fix: "Give the computed value a name and a right-hand expression.",
|
|
413
|
+
example: COMPUTED_EXAMPLE
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
code: "WD209",
|
|
417
|
+
title: "Malformed `:computed` expression",
|
|
418
|
+
cause: "The right-hand side passes the character whitelist but is not a real expression.",
|
|
419
|
+
fix: "Write a complete expression over declared state, numbers, and operators.",
|
|
420
|
+
example: COMPUTED_EXAMPLE
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
code: "WD210",
|
|
424
|
+
title: "Malformed `:theme`",
|
|
425
|
+
cause: 'The line does not read `:theme [name] [= "auto"]`.',
|
|
426
|
+
fix: "Use a bare `:theme`, or name the store and seed it.",
|
|
427
|
+
example: THEME_EXAMPLE
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
code: "WD220",
|
|
431
|
+
title: "Malformed `where` condition",
|
|
432
|
+
cause: "A `where` condition is not `operand <op> operand`.",
|
|
433
|
+
fix: "Compare a loop-item field with a value using a whitelisted operator."
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
code: "WD221",
|
|
437
|
+
title: "Unsupported `where` operand",
|
|
438
|
+
cause: "An operand is not an item field, a state name, a number, or a quoted string.",
|
|
439
|
+
fix: "Use one of those four operand forms."
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
code: "WD222",
|
|
443
|
+
title: "Disallowed `where` path",
|
|
444
|
+
cause: "An operand path contains `constructor`, `prototype`, or `__proto__`.",
|
|
445
|
+
fix: "Compare an ordinary data field."
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
code: "WD223",
|
|
449
|
+
title: "Unknown name in `where`",
|
|
450
|
+
cause: "An operand names neither the loop item nor a declared `:state`.",
|
|
451
|
+
fix: "Use the loop item's field, or declare the state first."
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
code: "WD224",
|
|
455
|
+
title: "Unsupported `when`/`:if` operand",
|
|
456
|
+
cause: "An operand is not an item field, a state name, a number, or a quoted string.",
|
|
457
|
+
fix: "Use one of those four operand forms.",
|
|
458
|
+
example: IF_EXAMPLE
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
code: "WD225",
|
|
462
|
+
title: "Disallowed `when`/`:if` path",
|
|
463
|
+
cause: "An operand path contains `constructor`, `prototype`, or `__proto__`.",
|
|
464
|
+
fix: "Compare an ordinary data field."
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
code: "WD226",
|
|
468
|
+
title: "Unknown name in `when`/`:if`",
|
|
469
|
+
cause: "An operand names nothing in scope: no loop item, no scope value, no state.",
|
|
470
|
+
fix: "Declare the state first, or reference a value that is in scope.",
|
|
471
|
+
example: IF_EXAMPLE
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
code: "WD227",
|
|
475
|
+
title: "Disallowed aggregate path",
|
|
476
|
+
cause: "A `sum`/`avg`/`min`/`max`/`count` argument contains a prototype-poisoning segment.",
|
|
477
|
+
fix: "Aggregate an ordinary state list."
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
code: "WD228",
|
|
481
|
+
title: "Unknown state in a `:computed` aggregate",
|
|
482
|
+
cause: "The aggregated list is not a declared `:state`/`:store`/`:fetch` key.",
|
|
483
|
+
fix: "Declare the list first.",
|
|
484
|
+
example: COMPUTED_EXAMPLE
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
code: "WD229",
|
|
488
|
+
title: "Unsupported string syntax in `:computed`",
|
|
489
|
+
cause: "The expression contains a quote, backslash, or backtick outside a simple literal.",
|
|
490
|
+
fix: "Use plain double- or single-quoted string literals."
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
code: "WD230",
|
|
494
|
+
title: "Unsupported syntax in `:computed`",
|
|
495
|
+
cause: "The expression contains characters outside the closed whitelist.",
|
|
496
|
+
fix: "Use state names, numbers, strings, arithmetic, comparisons, and `&& || !`.",
|
|
497
|
+
example: COMPUTED_EXAMPLE
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
code: "WD231",
|
|
501
|
+
title: "Assignment in `:computed`",
|
|
502
|
+
cause: "A `:computed` expression derives a value; it may not assign one.",
|
|
503
|
+
fix: "Mutate state from a `:button`/`:effect` action instead."
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
code: "WD232",
|
|
507
|
+
title: "Function call in `:computed`",
|
|
508
|
+
cause: "Only the fixed `sum`/`avg`/`min`/`max`/`count` aggregates may look like calls.",
|
|
509
|
+
fix: "Remove the call, or use one of the aggregates."
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
code: "WD233",
|
|
513
|
+
title: "Disallowed `:computed` path segment",
|
|
514
|
+
cause: "A referenced path contains `constructor`, `prototype`, or `__proto__`.",
|
|
515
|
+
fix: "Read an ordinary state path."
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
code: "WD234",
|
|
519
|
+
title: "Unknown state in `:computed`",
|
|
520
|
+
cause: "The expression references a name that is not declared state.",
|
|
521
|
+
fix: "Declare it with `:state`, `:store`, or `:fetch` first.",
|
|
522
|
+
example: COMPUTED_EXAMPLE
|
|
523
|
+
},
|
|
524
|
+
|
|
525
|
+
// --- WD3xx: actions ------------------------------------------------------
|
|
526
|
+
{
|
|
527
|
+
code: "WD301",
|
|
528
|
+
title: "Malformed `:button`",
|
|
529
|
+
cause: 'The line does not read `:button "Label" -> action`.',
|
|
530
|
+
fix: "Quote the label and give at least one action after the arrow.",
|
|
531
|
+
example: BUTTON_EXAMPLE
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
code: "WD302",
|
|
535
|
+
title: "Malformed `:effect`",
|
|
536
|
+
cause: "The line does not read `:effect watchedState -> action`.",
|
|
537
|
+
fix: "Name one state to watch and the actions to run when it changes.",
|
|
538
|
+
example: EFFECT_EXAMPLE
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
code: "WD303",
|
|
542
|
+
title: "`:effect` watches unknown state",
|
|
543
|
+
cause: "The watched name is not a declared `:state`/`:store`.",
|
|
544
|
+
fix: "Declare the state first.",
|
|
545
|
+
example: EFFECT_EXAMPLE
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
code: "WD304",
|
|
549
|
+
title: "Malformed `:every`",
|
|
550
|
+
cause: "The line does not read `:every <duration> -> action`.",
|
|
551
|
+
fix: "Give a duration and the actions to run on each tick.",
|
|
552
|
+
example: EVERY_EXAMPLE
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
code: "WD305",
|
|
556
|
+
title: "Invalid `:every` duration",
|
|
557
|
+
cause: "The duration is not a positive `<int>[ms|s|m]` value.",
|
|
558
|
+
fix: "Use a duration like `500ms`, `5s`, or `2m`.",
|
|
559
|
+
example: EVERY_EXAMPLE
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
code: "WD306",
|
|
563
|
+
title: "Action targets unknown state",
|
|
564
|
+
cause: "The action mutates a name that is not declared state.",
|
|
565
|
+
fix: "Declare the state first.",
|
|
566
|
+
example: BUTTON_EXAMPLE
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
code: "WD307",
|
|
570
|
+
title: "Row append needs a list state",
|
|
571
|
+
cause: "`list += item` carries the current row into a target that is not a list.",
|
|
572
|
+
fix: "Declare the target as a list, e.g. `:state cart = []`."
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
code: "WD308",
|
|
576
|
+
title: "`+=` needs a number or a list",
|
|
577
|
+
cause: "`+=` with a non-number value requires a list target to append to.",
|
|
578
|
+
fix: "Add a number, or declare the target as a list."
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
code: "WD309",
|
|
582
|
+
title: "Row remove inside a nested loop",
|
|
583
|
+
cause: "An item-relative loop has no top-level list for the runtime to remove from.",
|
|
584
|
+
fix: "Carry the row into a top-level list first, then remove it there."
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
code: "WD310",
|
|
588
|
+
title: "Row remove targets the wrong list",
|
|
589
|
+
cause: "Per-row `remove` must target the same list the loop is iterating.",
|
|
590
|
+
fix: "Name the looped list, or remove a value instead of the current row."
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
code: "WD311",
|
|
594
|
+
title: "Unsupported action",
|
|
595
|
+
cause: "The expression is outside the fixed action vocabulary (no JavaScript is allowed).",
|
|
596
|
+
fix: "Use a catalogued op; the message lists the whole vocabulary.",
|
|
597
|
+
example: ACTION_EXAMPLE
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
code: "WD312",
|
|
601
|
+
title: "`merge` operand is unknown state",
|
|
602
|
+
cause: "The right-hand name of a `merge` is not declared state.",
|
|
603
|
+
fix: "Declare it first, or pass an inline object literal."
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
code: "WD313",
|
|
607
|
+
title: "Unsupported `merge` operand",
|
|
608
|
+
cause: "The operand is neither a state key nor an inline object literal.",
|
|
609
|
+
fix: "Merge a declared object state, or an inline `{…}` literal."
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
code: "WD314",
|
|
613
|
+
title: "Unsupported action literal",
|
|
614
|
+
cause: "The action value is not a quoted string, number, boolean, null, or valid JSON.",
|
|
615
|
+
fix: "Quote strings and use valid JSON for arrays and objects."
|
|
616
|
+
},
|
|
617
|
+
|
|
618
|
+
// --- WD4xx: forms and fields ---------------------------------------------
|
|
619
|
+
{
|
|
620
|
+
code: "WD401",
|
|
621
|
+
title: "Malformed `:form`",
|
|
622
|
+
cause: 'The opener has neither `into <name>` nor `action="…"`, or has leftover text.',
|
|
623
|
+
fix: "Capture into state, post to a URL, or do both for a fetch round-trip.",
|
|
624
|
+
example: FORM_EXAMPLE
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
code: "WD402",
|
|
628
|
+
title: "Malformed `:input`",
|
|
629
|
+
cause: "The line does not start with a field name.",
|
|
630
|
+
fix: "Name the field, then add optional attributes and flags.",
|
|
631
|
+
example: INPUT_EXAMPLE
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
code: "WD403",
|
|
635
|
+
title: "Unknown `:input` flag",
|
|
636
|
+
cause: "The bare flag is not one of `required`/`autofocus`/`disabled`/`readonly`.",
|
|
637
|
+
fix: "Use a supported flag, or write it as `name=value`.",
|
|
638
|
+
example: INPUT_EXAMPLE
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
code: "WD404",
|
|
642
|
+
title: "Unknown `:input` attribute",
|
|
643
|
+
cause: "The attribute is outside the supported set.",
|
|
644
|
+
fix: "Use a supported attribute; the compiler emits no arbitrary HTML attributes.",
|
|
645
|
+
example: INPUT_EXAMPLE
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
code: "WD405",
|
|
649
|
+
title: "Malformed `:bind`",
|
|
650
|
+
cause: "The line does not start with the bound state name.",
|
|
651
|
+
fix: "Name a declared `:state`, then add optional attributes.",
|
|
652
|
+
example: BIND_EXAMPLE
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
code: "WD406",
|
|
656
|
+
title: "`:bind` has no matching state",
|
|
657
|
+
cause: "The bound name is not a declared `:state`/`:store`.",
|
|
658
|
+
fix: "Declare the state before binding to it.",
|
|
659
|
+
example: BIND_EXAMPLE
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
code: "WD407",
|
|
663
|
+
title: "Unknown `:bind` flag",
|
|
664
|
+
cause: "The bare flag is not `required` or `autofocus`.",
|
|
665
|
+
fix: "Use a supported flag.",
|
|
666
|
+
example: BIND_EXAMPLE
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
code: "WD408",
|
|
670
|
+
title: "Unknown `:bind` attribute",
|
|
671
|
+
cause: "The attribute is outside the supported set.",
|
|
672
|
+
fix: "Use `placeholder`, `type`, `autocomplete`, or an `aria-*` attribute.",
|
|
673
|
+
example: BIND_EXAMPLE
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
code: "WD409",
|
|
677
|
+
title: "Malformed `:slider`",
|
|
678
|
+
cause: "The line does not start with the slider's state name.",
|
|
679
|
+
fix: "Name the state, then optionally seed it and set the range.",
|
|
680
|
+
example: SLIDER_EXAMPLE
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
code: "WD410",
|
|
684
|
+
title: "Malformed `:slider` initial value",
|
|
685
|
+
cause: "The `=` is present but no value follows it.",
|
|
686
|
+
fix: "Give the slider a numeric initial value.",
|
|
687
|
+
example: SLIDER_EXAMPLE
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
code: "WD411",
|
|
691
|
+
title: "Unknown `:slider` attribute",
|
|
692
|
+
cause: "The attribute is not `min`, `max`, `step`, or `aria-label`.",
|
|
693
|
+
fix: "Use a supported attribute.",
|
|
694
|
+
example: SLIDER_EXAMPLE
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
code: "WD412",
|
|
698
|
+
title: "Non-numeric `:slider` bound",
|
|
699
|
+
cause: "`min`, `max`, or `step` is not a number.",
|
|
700
|
+
fix: "Give every bound a numeric value.",
|
|
701
|
+
example: SLIDER_EXAMPLE
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
code: "WD413",
|
|
705
|
+
title: "Non-numeric `:slider` initial value",
|
|
706
|
+
cause: "A slider is bound to a number, so its seed must be numeric.",
|
|
707
|
+
fix: "Seed the slider with a number.",
|
|
708
|
+
example: SLIDER_EXAMPLE
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
code: "WD414",
|
|
712
|
+
title: "`:slider persist` without an inline value",
|
|
713
|
+
cause: "`persist` belongs to the declaration, and this slider binds existing state.",
|
|
714
|
+
fix: "Declare the value inline to persist it, or persist it on the `:state` line.",
|
|
715
|
+
example: SLIDER_EXAMPLE
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
code: "WD415",
|
|
719
|
+
title: "`:slider` has no matching state",
|
|
720
|
+
cause: "The slider binds a name that is not declared state.",
|
|
721
|
+
fix: "Declare the state, or seed it inline on the slider.",
|
|
722
|
+
example: SLIDER_EXAMPLE
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
code: "WD416",
|
|
726
|
+
title: "Malformed `:submit`",
|
|
727
|
+
cause: 'The line does not read `:submit "Label"`.',
|
|
728
|
+
fix: "Quote the button label.",
|
|
729
|
+
example: SUBMIT_EXAMPLE
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
code: "WD417",
|
|
733
|
+
title: "Malformed `:textarea`",
|
|
734
|
+
cause: "The line does not start with a field name.",
|
|
735
|
+
fix: "Name the field, then add optional attributes and flags.",
|
|
736
|
+
example: TEXTAREA_EXAMPLE
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
code: "WD418",
|
|
740
|
+
title: "Unknown `:textarea` flag",
|
|
741
|
+
cause: "The bare flag is not one of `required`/`autofocus`/`disabled`/`readonly`.",
|
|
742
|
+
fix: "Use a supported flag.",
|
|
743
|
+
example: TEXTAREA_EXAMPLE
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
code: "WD419",
|
|
747
|
+
title: "Unknown `:textarea` attribute",
|
|
748
|
+
cause: "The attribute is outside the supported set.",
|
|
749
|
+
fix: "Use a supported attribute such as `placeholder`, `rows`, or `maxlength`.",
|
|
750
|
+
example: TEXTAREA_EXAMPLE
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
code: "WD420",
|
|
754
|
+
title: "Malformed `:select`",
|
|
755
|
+
cause: "The line does not start with a field name.",
|
|
756
|
+
fix: "Name the field, then list the options as `- Label` lines.",
|
|
757
|
+
example: SELECT_EXAMPLE
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
code: "WD421",
|
|
761
|
+
title: "Unknown `:select` flag",
|
|
762
|
+
cause: "The bare flag is not `required`, `disabled`, or `autofocus`.",
|
|
763
|
+
fix: "Use a supported flag.",
|
|
764
|
+
example: SELECT_EXAMPLE
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
code: "WD422",
|
|
768
|
+
title: "Unknown `:select` attribute",
|
|
769
|
+
cause: "The attribute is not `autocomplete` or an `aria-*` attribute.",
|
|
770
|
+
fix: "Use a supported attribute.",
|
|
771
|
+
example: SELECT_EXAMPLE
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
code: "WD423",
|
|
775
|
+
title: "`:select` has no options",
|
|
776
|
+
cause: "No `- Label` lines follow the opener.",
|
|
777
|
+
fix: "Add one `- Label` line per option directly beneath it.",
|
|
778
|
+
example: SELECT_EXAMPLE
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
code: "WD424",
|
|
782
|
+
title: "Malformed `:checkbox`/`:radio`",
|
|
783
|
+
cause: "The line does not start with a group name.",
|
|
784
|
+
fix: "Name the group, then list the options as `- Label` lines.",
|
|
785
|
+
example: CHECKBOX_EXAMPLE
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
code: "WD425",
|
|
789
|
+
title: "Unknown `:checkbox`/`:radio` flag",
|
|
790
|
+
cause: "The bare flag is not `required`, `disabled`, or `autofocus`.",
|
|
791
|
+
fix: "Use a supported flag.",
|
|
792
|
+
example: CHECKBOX_EXAMPLE
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
code: "WD426",
|
|
796
|
+
title: "Unknown `:checkbox`/`:radio` attribute",
|
|
797
|
+
cause: "The attribute is not `aria-label` or `aria-describedby`.",
|
|
798
|
+
fix: "Use a supported attribute.",
|
|
799
|
+
example: CHECKBOX_EXAMPLE
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
code: "WD427",
|
|
803
|
+
title: "`:checkbox`/`:radio` has no options",
|
|
804
|
+
cause: "No `- Label` lines follow the opener.",
|
|
805
|
+
fix: "Add one `- Label` line per option directly beneath it.",
|
|
806
|
+
example: RADIO_EXAMPLE
|
|
807
|
+
},
|
|
808
|
+
|
|
809
|
+
// --- WD5xx: data fetching and URL safety ---------------------------------
|
|
810
|
+
{
|
|
811
|
+
code: "WD501",
|
|
812
|
+
title: "Malformed `:fetch`",
|
|
813
|
+
cause: 'The line does not read `:fetch name from "url" [options]`.',
|
|
814
|
+
fix: "Name the state key and quote the URL, then add keyword options.",
|
|
815
|
+
example: FETCH_EXAMPLE
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
code: "WD502",
|
|
819
|
+
title: "Unknown `:fetch` option",
|
|
820
|
+
cause: "An option is not a `name=value` pair from the supported set.",
|
|
821
|
+
fix: "Use `method`, `when`, `timeout`, `retry`, `headers`, `body`, or `refresh`.",
|
|
822
|
+
example: FETCH_EXAMPLE
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
code: "WD503",
|
|
826
|
+
title: "Unsupported `:fetch` method",
|
|
827
|
+
cause: "The method is outside `GET`/`POST`/`PUT`/`PATCH`/`DELETE`.",
|
|
828
|
+
fix: "Use one of the supported HTTP methods.",
|
|
829
|
+
example: FETCH_EXAMPLE
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
code: "WD504",
|
|
833
|
+
title: "Unsupported `:fetch when`",
|
|
834
|
+
cause: "`when` is neither `load` nor `visible`.",
|
|
835
|
+
fix: "Fetch on `load` (the default) or when the marker becomes `visible`.",
|
|
836
|
+
example: FETCH_EXAMPLE
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
code: "WD505",
|
|
840
|
+
title: "Non-integer `:fetch timeout`/`retry`",
|
|
841
|
+
cause: "The value is not a non-negative integer.",
|
|
842
|
+
fix: "Pass whole numbers (milliseconds for `timeout`, attempts for `retry`).",
|
|
843
|
+
example: FETCH_EXAMPLE
|
|
844
|
+
},
|
|
845
|
+
{
|
|
846
|
+
code: "WD506",
|
|
847
|
+
title: "`:fetch refresh=` without `headers=`",
|
|
848
|
+
cause: "A token refresh has nowhere to write the renewed token back to.",
|
|
849
|
+
fix: "Add `headers=<stateKey>` alongside `refresh=`.",
|
|
850
|
+
example: FETCH_EXAMPLE
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
code: "WD507",
|
|
854
|
+
title: "Unsafe URL",
|
|
855
|
+
cause: "The URL is empty, padded with whitespace, or contains control characters.",
|
|
856
|
+
fix: "Use a clean relative path, an http(s) URL, or a `{ state }` interpolation."
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
code: "WD508",
|
|
860
|
+
title: "Protocol-relative URL",
|
|
861
|
+
cause: "A `//host` URL inherits whatever scheme the page was served over.",
|
|
862
|
+
fix: "Write `http://` or `https://` explicitly."
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
code: "WD509",
|
|
866
|
+
title: "Disallowed URL scheme",
|
|
867
|
+
cause: "The scheme is not `http` or `https` (`javascript:`, `data:`, `file:`, and friends).",
|
|
868
|
+
fix: "Use http(s) or a relative path."
|
|
869
|
+
},
|
|
870
|
+
|
|
871
|
+
// --- WD6xx: includes and page structure ----------------------------------
|
|
872
|
+
{
|
|
873
|
+
code: "WD601",
|
|
874
|
+
title: "Include outside the source sandbox",
|
|
875
|
+
cause: "The include path escapes `site/pages` and `site/_`.",
|
|
876
|
+
fix: "Move the partial inside the sandbox and reference it from there.",
|
|
877
|
+
example: INCLUDE_EXAMPLE
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
code: "WD602",
|
|
881
|
+
title: "Include not found",
|
|
882
|
+
cause: "No file matched the spec in `site/pages` or `site/_`.",
|
|
883
|
+
fix: "Check the path, the extension, and the leading slash.",
|
|
884
|
+
example: INCLUDE_EXAMPLE
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
code: "WD603",
|
|
888
|
+
title: "Malformed `@include`",
|
|
889
|
+
cause: 'The line does not read `@include /partial.wd [with key="value"]`.',
|
|
890
|
+
fix: "Give one target path, then optional `with` arguments.",
|
|
891
|
+
example: INCLUDE_EXAMPLE
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
code: "WD604",
|
|
895
|
+
title: "Include argument is not in scope",
|
|
896
|
+
cause: "A `with key={ value }` argument references a name that does not resolve.",
|
|
897
|
+
fix: "Pass a literal, or reference a value that is in scope at the include site.",
|
|
898
|
+
example: INCLUDE_EXAMPLE
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
code: "WD605",
|
|
902
|
+
title: "Unexpected token in a container header",
|
|
903
|
+
cause: "The header holds something that is not a name, `.class`, or `#id`.",
|
|
904
|
+
fix: "Use a leading name, then `.class`/`#id` tokens.",
|
|
905
|
+
example: CONTAINER_EXAMPLE
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
code: "WD606",
|
|
909
|
+
title: "Malformed `:carousel`",
|
|
910
|
+
cause: "The opener carries something other than `autoplay=<ms>`.",
|
|
911
|
+
fix: "Open it bare, or with a numeric `autoplay`.",
|
|
912
|
+
example: CAROUSEL_EXAMPLE
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
code: "WD607",
|
|
916
|
+
title: "Loop variable in `:if` outside a loop",
|
|
917
|
+
cause: "`:if $first` (or another row variable) appears outside a `@loop` body.",
|
|
918
|
+
fix: "Move the conditional into the loop body."
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
code: "WD608",
|
|
922
|
+
title: "`:if` name is not declared",
|
|
923
|
+
cause: "The condition names neither a `:state` nor an in-scope value.",
|
|
924
|
+
fix: "Declare the state first, or reference a value that is in scope.",
|
|
925
|
+
example: IF_EXAMPLE
|
|
926
|
+
},
|
|
927
|
+
{
|
|
928
|
+
code: "WD609",
|
|
929
|
+
title: "Malformed `:if`",
|
|
930
|
+
cause: "The `:if` line carries no condition.",
|
|
931
|
+
fix: "Give a name, or a comparison joined with `and`/`or`/`not`.",
|
|
932
|
+
example: IF_EXAMPLE
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
code: "WD610",
|
|
936
|
+
title: "Unsafe `:try` href",
|
|
937
|
+
cause: "The href has control characters, or a scheme outside http/https/mailto.",
|
|
938
|
+
fix: "Use a relative URL, or an `http:`, `https:`, or `mailto:` URL."
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
code: "WD611",
|
|
942
|
+
title: "Protocol-relative `:try` href",
|
|
943
|
+
cause: "A `//host` href inherits whatever scheme the page was served over.",
|
|
944
|
+
fix: "Write `http:` or `https:` explicitly."
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
code: "WD612",
|
|
948
|
+
title: "Include cycle",
|
|
949
|
+
cause:
|
|
950
|
+
"An `@include` chain reaches a file that is already being compiled, so the compile would never terminate.",
|
|
951
|
+
fix: "Break the loop: remove the `@include` that points back, or move the shared content into a third file both sides include."
|
|
952
|
+
},
|
|
953
|
+
|
|
954
|
+
// --- WD7xx: media and embeds ---------------------------------------------
|
|
955
|
+
{
|
|
956
|
+
code: "WD701",
|
|
957
|
+
title: "Malformed `:video`/`:audio`",
|
|
958
|
+
cause: "The line carries no clip source.",
|
|
959
|
+
fix: "Give the clip path, then optional attributes and flags.",
|
|
960
|
+
example: VIDEO_EXAMPLE
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
code: "WD702",
|
|
964
|
+
title: "Unknown `:video`/`:audio` flag",
|
|
965
|
+
cause: "The bare flag is outside the supported set for that element.",
|
|
966
|
+
fix: "Use `controls`, `autoplay`, `loop`, `muted`, or (video only) `playsinline`.",
|
|
967
|
+
example: VIDEO_EXAMPLE
|
|
968
|
+
},
|
|
969
|
+
{
|
|
970
|
+
code: "WD703",
|
|
971
|
+
title: "Unknown `:video`/`:audio` attribute",
|
|
972
|
+
cause: "The attribute is outside the supported set for that element.",
|
|
973
|
+
fix: "Use `poster`, `width`, `height`, or `preload` (audio takes `preload` only).",
|
|
974
|
+
example: VIDEO_EXAMPLE
|
|
975
|
+
},
|
|
976
|
+
{
|
|
977
|
+
code: "WD704",
|
|
978
|
+
title: "Malformed `:embed`",
|
|
979
|
+
cause: "The line carries no URL.",
|
|
980
|
+
fix: "Give the share or watch URL, then an optional title.",
|
|
981
|
+
example: EMBED_EXAMPLE
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
code: "WD705",
|
|
985
|
+
title: "Unknown `:embed` attribute",
|
|
986
|
+
cause: "`title` is the only supported attribute.",
|
|
987
|
+
fix: "Remove the attribute, or set the accessible title.",
|
|
988
|
+
example: EMBED_EXAMPLE
|
|
989
|
+
},
|
|
990
|
+
|
|
991
|
+
// --- WD8xx: skins ---------------------------------------------------------
|
|
992
|
+
{
|
|
993
|
+
code: "WD801",
|
|
994
|
+
title: "`scoped` is not the first line",
|
|
995
|
+
cause: "The scoping opt-in appears somewhere other than the first meaningful line.",
|
|
996
|
+
fix: "Move `scoped` to the top of the `.skin`, or delete it."
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
code: "WD802",
|
|
1000
|
+
title: "Page-level declaration in a scoped skin",
|
|
1001
|
+
cause: "A declaration with no enclosing selector would write to the whole page.",
|
|
1002
|
+
fix: "Nest it under a selector, or move it to a global skin."
|
|
1003
|
+
},
|
|
1004
|
+
{
|
|
1005
|
+
code: "WD803",
|
|
1006
|
+
title: "Page-level selector in a scoped skin",
|
|
1007
|
+
cause: "A top-level `page`/`body`/`html`/`*`/`::selection` selector escapes the scope.",
|
|
1008
|
+
fix: "Move page-level styles to a global (non-scoped) skin."
|
|
1009
|
+
},
|
|
1010
|
+
|
|
1011
|
+
// --- WD9xx: project, routing, CLI ----------------------------------------
|
|
1012
|
+
{
|
|
1013
|
+
code: "WD901",
|
|
1014
|
+
title: "Duplicate route",
|
|
1015
|
+
cause: "Two source files map to the same public route (often `.md` plus `.wd`).",
|
|
1016
|
+
fix: "Rename or delete one of the files."
|
|
1017
|
+
},
|
|
1018
|
+
{
|
|
1019
|
+
code: "WD902",
|
|
1020
|
+
title: "Route escapes the build output",
|
|
1021
|
+
cause: "A route resolved outside `dist`, which would write outside the build.",
|
|
1022
|
+
fix: "Remove traversal segments from the route."
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
code: "WD903",
|
|
1026
|
+
title: "Unknown init template",
|
|
1027
|
+
cause: "`darkmown init --template` names a template that does not ship.",
|
|
1028
|
+
fix: "Pick a bundled template; the message lists every available name."
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
code: "WD904",
|
|
1032
|
+
title: "Unknown deploy target",
|
|
1033
|
+
cause: "`darkmown deploy` names a platform Darkmown does not drive.",
|
|
1034
|
+
fix: "Deploy to `vercel` or `cloudflare`."
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
code: "WD905",
|
|
1038
|
+
title: "Deploy CLI not signed in",
|
|
1039
|
+
cause: "The platform CLI rejected the deploy with an authentication failure.",
|
|
1040
|
+
fix: "Run the platform login command, then re-run the deploy."
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
code: "WD906",
|
|
1044
|
+
title: "Deploy command failed",
|
|
1045
|
+
cause: "The platform CLI exited non-zero for a reason other than authentication.",
|
|
1046
|
+
fix: "Read the CLI output above the error, fix the cause, and re-run."
|
|
1047
|
+
}
|
|
1048
|
+
];
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* The subsystem an error belongs to, derived from its code so the `area` can
|
|
1052
|
+
* never disagree with the number.
|
|
1053
|
+
* @param {string} code
|
|
1054
|
+
* @returns {string}
|
|
1055
|
+
*/
|
|
1056
|
+
function areaOf(code) {
|
|
1057
|
+
const block = Number(code[2]);
|
|
1058
|
+
const area = ERROR_AREAS.find((a) => a.block === block);
|
|
1059
|
+
return area ? area.name : "unknown";
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* The full error-code registry, in code order. Each entry carries the stable
|
|
1064
|
+
* code, its subsystem, a short title, what causes it, and how to fix it.
|
|
1065
|
+
* @returns {ErrorEntry[]}
|
|
1066
|
+
*/
|
|
1067
|
+
export function errorCatalog() {
|
|
1068
|
+
return ENTRIES.map((entry) => ({ ...entry, area: areaOf(entry.code) }));
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* Look up one code, or undefined when it is not registered.
|
|
1073
|
+
* @param {string} code
|
|
1074
|
+
* @returns {ErrorEntry | undefined}
|
|
1075
|
+
*/
|
|
1076
|
+
export function errorForCode(code) {
|
|
1077
|
+
return errorCatalog().find((entry) => entry.code === code);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Render `docs/errors.md` from the registry. Generated, never hand-edited, so
|
|
1082
|
+
* the reference page cannot drift from the codes the compiler actually throws
|
|
1083
|
+
* (`scripts/gen-errors.mjs` writes it; tests/error-codes.test.js re-checks it).
|
|
1084
|
+
* @returns {string}
|
|
1085
|
+
*/
|
|
1086
|
+
export function errorsMarkdown() {
|
|
1087
|
+
const entries = errorCatalog();
|
|
1088
|
+
const out = [];
|
|
1089
|
+
out.push("# Darkmown compile error codes");
|
|
1090
|
+
out.push("");
|
|
1091
|
+
out.push(
|
|
1092
|
+
"Every Darkmown compile error carries a stable `WDxxx` code. It is the first",
|
|
1093
|
+
"thing in the message (`[WD201] Malformed :state in …`) and it is mirrored on",
|
|
1094
|
+
"the thrown error as `err.wd.code`, alongside `file`, `line`, `hint`, and a",
|
|
1095
|
+
"concrete compilable `example`."
|
|
1096
|
+
);
|
|
1097
|
+
out.push("");
|
|
1098
|
+
out.push("```");
|
|
1099
|
+
out.push("[WD201] Malformed :state in site/pages/index.wd:1: :state x.");
|
|
1100
|
+
out.push(" Use: :state name = value [persist] — e.g. :state count = 0");
|
|
1101
|
+
out.push("```");
|
|
1102
|
+
out.push("");
|
|
1103
|
+
out.push(
|
|
1104
|
+
"Codes are grouped by subsystem, and a shipped code is permanent: it is never",
|
|
1105
|
+
"renumbered and a retired number is never reused. Add new errors at the next",
|
|
1106
|
+
"free number in their block."
|
|
1107
|
+
);
|
|
1108
|
+
out.push("");
|
|
1109
|
+
out.push("| Range | Subsystem |");
|
|
1110
|
+
out.push("| --- | --- |");
|
|
1111
|
+
for (const area of ERROR_AREAS) out.push(`| \`${area.range}\` | ${area.title} |`);
|
|
1112
|
+
out.push("");
|
|
1113
|
+
out.push(
|
|
1114
|
+
"> This page is generated from `src/errors.js` by `node scripts/gen-errors.mjs`.",
|
|
1115
|
+
"> Edit the registry, not this file."
|
|
1116
|
+
);
|
|
1117
|
+
out.push("");
|
|
1118
|
+
|
|
1119
|
+
for (const area of ERROR_AREAS) {
|
|
1120
|
+
const rows = entries.filter((entry) => entry.area === area.name);
|
|
1121
|
+
if (!rows.length) continue;
|
|
1122
|
+
out.push(`## ${area.range} — ${area.title}`);
|
|
1123
|
+
out.push("");
|
|
1124
|
+
out.push("| Code | Error | Cause | Fix | Example |");
|
|
1125
|
+
out.push("| --- | --- | --- | --- | --- |");
|
|
1126
|
+
for (const row of rows) {
|
|
1127
|
+
const example = row.example ? `\`${escapeCell(row.example)}\`` : "";
|
|
1128
|
+
out.push(
|
|
1129
|
+
`| \`${row.code}\` | ${escapeCell(row.title)} | ${escapeCell(row.cause)} | ${escapeCell(row.fix)} | ${example} |`
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
out.push("");
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
out.push("## Errors without a code");
|
|
1136
|
+
out.push("");
|
|
1137
|
+
out.push(
|
|
1138
|
+
"A handful of throws are framework invariants rather than authoring mistakes:",
|
|
1139
|
+
"the expression re-parser (`src/compiler/expr-ast.js`) consumes the compiler's",
|
|
1140
|
+
"own already-validated output, and the source reader (`src/compiler/reader.js`)",
|
|
1141
|
+
"guards the host contract behind `compileFromMemory`. Hitting one of those is a",
|
|
1142
|
+
"bug in Darkmown, not something a page can fix, so they stay uncoded and should",
|
|
1143
|
+
"be reported as issues."
|
|
1144
|
+
);
|
|
1145
|
+
out.push("");
|
|
1146
|
+
return out.join("\n");
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* Escape a value for a markdown table cell (pipes would split the row).
|
|
1151
|
+
* @param {string} value
|
|
1152
|
+
* @returns {string}
|
|
1153
|
+
*/
|
|
1154
|
+
function escapeCell(value) {
|
|
1155
|
+
return value.replaceAll("|", "\\|");
|
|
1156
|
+
}
|