@hozu/validator 0.1.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/LICENSE +21 -0
- package/README.md +15 -0
- package/dist/context.d.ts +13 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +27 -0
- package/dist/context.js.map +1 -0
- package/dist/contracts/lock.d.ts +19 -0
- package/dist/contracts/lock.d.ts.map +1 -0
- package/dist/contracts/lock.js +61 -0
- package/dist/contracts/lock.js.map +1 -0
- package/dist/contracts/run.d.ts +14 -0
- package/dist/contracts/run.d.ts.map +1 -0
- package/dist/contracts/run.js +130 -0
- package/dist/contracts/run.js.map +1 -0
- package/dist/contracts/skeleton.d.ts +4 -0
- package/dist/contracts/skeleton.d.ts.map +1 -0
- package/dist/contracts/skeleton.js +108 -0
- package/dist/contracts/skeleton.js.map +1 -0
- package/dist/contracts/verify.d.ts +5 -0
- package/dist/contracts/verify.d.ts.map +1 -0
- package/dist/contracts/verify.js +58 -0
- package/dist/contracts/verify.js.map +1 -0
- package/dist/env.d.ts +40 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +79 -0
- package/dist/env.js.map +1 -0
- package/dist/impact.d.ts +27 -0
- package/dist/impact.d.ts.map +1 -0
- package/dist/impact.js +154 -0
- package/dist/impact.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/resolve.d.ts +13 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +58 -0
- package/dist/resolve.js.map +1 -0
- package/dist/rules/classes.d.ts +3 -0
- package/dist/rules/classes.d.ts.map +1 -0
- package/dist/rules/classes.js +60 -0
- package/dist/rules/classes.js.map +1 -0
- package/dist/rules/data.d.ts +4 -0
- package/dist/rules/data.d.ts.map +1 -0
- package/dist/rules/data.js +34 -0
- package/dist/rules/data.js.map +1 -0
- package/dist/rules/dom.d.ts +3 -0
- package/dist/rules/dom.d.ts.map +1 -0
- package/dist/rules/dom.js +104 -0
- package/dist/rules/dom.js.map +1 -0
- package/dist/rules/errors.d.ts +3 -0
- package/dist/rules/errors.d.ts.map +1 -0
- package/dist/rules/errors.js +97 -0
- package/dist/rules/errors.js.map +1 -0
- package/dist/rules/events.d.ts +5 -0
- package/dist/rules/events.d.ts.map +1 -0
- package/dist/rules/events.js +87 -0
- package/dist/rules/events.js.map +1 -0
- package/dist/rules/forms.d.ts +3 -0
- package/dist/rules/forms.d.ts.map +1 -0
- package/dist/rules/forms.js +19 -0
- package/dist/rules/forms.js.map +1 -0
- package/dist/rules/html.d.ts +3 -0
- package/dist/rules/html.d.ts.map +1 -0
- package/dist/rules/html.js +55 -0
- package/dist/rules/html.js.map +1 -0
- package/dist/rules/http.d.ts +3 -0
- package/dist/rules/http.d.ts.map +1 -0
- package/dist/rules/http.js +71 -0
- package/dist/rules/http.js.map +1 -0
- package/dist/rules/i18n.d.ts +3 -0
- package/dist/rules/i18n.d.ts.map +1 -0
- package/dist/rules/i18n.js +201 -0
- package/dist/rules/i18n.js.map +1 -0
- package/dist/rules/images.d.ts +3 -0
- package/dist/rules/images.d.ts.map +1 -0
- package/dist/rules/images.js +32 -0
- package/dist/rules/images.js.map +1 -0
- package/dist/rules/links.d.ts +3 -0
- package/dist/rules/links.d.ts.map +1 -0
- package/dist/rules/links.js +60 -0
- package/dist/rules/links.js.map +1 -0
- package/dist/rules/literals.d.ts +3 -0
- package/dist/rules/literals.d.ts.map +1 -0
- package/dist/rules/literals.js +147 -0
- package/dist/rules/literals.js.map +1 -0
- package/dist/rules/paths.d.ts +3 -0
- package/dist/rules/paths.d.ts.map +1 -0
- package/dist/rules/paths.js +177 -0
- package/dist/rules/paths.js.map +1 -0
- package/dist/rules/pwa.d.ts +3 -0
- package/dist/rules/pwa.d.ts.map +1 -0
- package/dist/rules/pwa.js +28 -0
- package/dist/rules/pwa.js.map +1 -0
- package/dist/rules/refs.d.ts +5 -0
- package/dist/rules/refs.d.ts.map +1 -0
- package/dist/rules/refs.js +128 -0
- package/dist/rules/refs.js.map +1 -0
- package/dist/rules/rendering.d.ts +3 -0
- package/dist/rules/rendering.d.ts.map +1 -0
- package/dist/rules/rendering.js +37 -0
- package/dist/rules/rendering.js.map +1 -0
- package/dist/rules/routes.d.ts +4 -0
- package/dist/rules/routes.d.ts.map +1 -0
- package/dist/rules/routes.js +108 -0
- package/dist/rules/routes.js.map +1 -0
- package/dist/rules/states.d.ts +6 -0
- package/dist/rules/states.d.ts.map +1 -0
- package/dist/rules/states.js +167 -0
- package/dist/rules/states.js.map +1 -0
- package/dist/rules/text.d.ts +3 -0
- package/dist/rules/text.d.ts.map +1 -0
- package/dist/rules/text.js +148 -0
- package/dist/rules/text.js.map +1 -0
- package/dist/rules/widgets.d.ts +3 -0
- package/dist/rules/widgets.d.ts.map +1 -0
- package/dist/rules/widgets.js +33 -0
- package/dist/rules/widgets.js.map +1 -0
- package/dist/schema.d.ts +19 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +142 -0
- package/dist/schema.js.map +1 -0
- package/dist/sites.d.ts +15 -0
- package/dist/sites.d.ts.map +1 -0
- package/dist/sites.js +151 -0
- package/dist/sites.js.map +1 -0
- package/dist/suggest.d.ts +4 -0
- package/dist/suggest.d.ts.map +1 -0
- package/dist/suggest.js +30 -0
- package/dist/suggest.js.map +1 -0
- package/dist/walk.d.ts +37 -0
- package/dist/walk.d.ts.map +1 -0
- package/dist/walk.js +138 -0
- package/dist/walk.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { at, eventFields, resolveAt, } from '@hozu/core/ir';
|
|
2
|
+
import { closest, didYouMean } from '../suggest.js';
|
|
3
|
+
import { walkView } from '../walk.js';
|
|
4
|
+
function domRefs(v, pointer, visit) {
|
|
5
|
+
if ('ref' in v) {
|
|
6
|
+
if (v.ref === 'dom')
|
|
7
|
+
visit(v.path[0] ?? '', at(pointer, 'path', 0));
|
|
8
|
+
}
|
|
9
|
+
else if ('object' in v)
|
|
10
|
+
for (const k in v.object)
|
|
11
|
+
domRefs(v.object[k], at(pointer, 'object', k), visit);
|
|
12
|
+
else if ('fn' in v)
|
|
13
|
+
domRefs(v.arg, at(pointer, 'arg'), visit);
|
|
14
|
+
else if ('test' in v)
|
|
15
|
+
guardDomRefs(v.test, at(pointer, 'test'), visit);
|
|
16
|
+
else if ('link' in v) {
|
|
17
|
+
domRefs(v.params, at(pointer, 'params'), visit);
|
|
18
|
+
domRefs(v.search, at(pointer, 'search'), visit);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function guardDomRefs(g, pointer, visit) {
|
|
22
|
+
switch (g.op) {
|
|
23
|
+
case 'and':
|
|
24
|
+
case 'or':
|
|
25
|
+
g.args.forEach((a, i) => guardDomRefs(a, at(pointer, 'args', i), visit));
|
|
26
|
+
return;
|
|
27
|
+
case 'not':
|
|
28
|
+
guardDomRefs(g.arg, at(pointer, 'arg'), visit);
|
|
29
|
+
return;
|
|
30
|
+
case 'fn':
|
|
31
|
+
domRefs(g.arg, at(pointer, 'arg'), visit);
|
|
32
|
+
return;
|
|
33
|
+
default:
|
|
34
|
+
domRefs(g.left, at(pointer, 'left'), visit);
|
|
35
|
+
domRefs(g.right, at(pointer, 'right'), visit);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function domFields(ctx) {
|
|
39
|
+
const { ir } = ctx;
|
|
40
|
+
for (const f of Object.values(ir.features))
|
|
41
|
+
for (const [vid, view] of Object.entries(f.views))
|
|
42
|
+
walkView(ir, f, vid, view, ({ node, pointer }) => {
|
|
43
|
+
const outside = (field, p) => ctx.report('HZ027', f.id, p, `ui.dom.${field} is read outside an event handler`, 'DOM fields only exist while an event is being handled: read them in ui.send(…) payloads.', {
|
|
44
|
+
summary: 'Send the DOM value into the machine and read it from context',
|
|
45
|
+
snippet: null,
|
|
46
|
+
patch: null,
|
|
47
|
+
});
|
|
48
|
+
const fieldsFor = (on, pointer) => {
|
|
49
|
+
for (const [event, send] of Object.entries(on)) {
|
|
50
|
+
const allowed = eventFields[event] ?? [];
|
|
51
|
+
domRefs(send.payload, at(pointer, 'on', event, 'payload'), (field, p) => {
|
|
52
|
+
if (allowed.includes(field))
|
|
53
|
+
return;
|
|
54
|
+
const guess = closest(field, allowed);
|
|
55
|
+
ctx.report('HZ027', f.id, p, `"${event}" events have no DOM field "${field}".${didYouMean(guess)}`, allowed.length
|
|
56
|
+
? `Fields available on ${event}: ${allowed.join(', ')}.`
|
|
57
|
+
: `${event} events carry no DOM fields.`, {
|
|
58
|
+
summary: guess ? `Read ui.dom.${guess}` : 'Remove the DOM field from the payload',
|
|
59
|
+
snippet: null,
|
|
60
|
+
patch: guess ? [{ op: 'replace', path: resolveAt(p), value: guess }] : null,
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
switch (node.kind) {
|
|
66
|
+
case 'widget':
|
|
67
|
+
domRefs(node.props, at(pointer, 'props'), outside);
|
|
68
|
+
for (const key of ['toggle', 'vars'])
|
|
69
|
+
for (const [name, v] of Object.entries(node[key]))
|
|
70
|
+
domRefs(v, at(pointer, key, name), outside);
|
|
71
|
+
for (const [event, send] of Object.entries(node.on))
|
|
72
|
+
domRefs(send.payload, at(pointer, 'on', event, 'payload'), (field, p) => {
|
|
73
|
+
if (field !== 'detail')
|
|
74
|
+
ctx.report('HZ027', f.id, p, `Widget event "${event}" carries only its detail`, 'Read the widget event payload through the handler argument: (detail) => ui.send(…, { x: detail.x }).');
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
case 'el':
|
|
78
|
+
for (const key of ['attrs', 'toggle', 'vars'])
|
|
79
|
+
for (const [name, v] of Object.entries(node[key]))
|
|
80
|
+
domRefs(v, at(pointer, key, name), outside);
|
|
81
|
+
fieldsFor(node.on, pointer);
|
|
82
|
+
return;
|
|
83
|
+
case 'text':
|
|
84
|
+
case 'html':
|
|
85
|
+
domRefs(node.value, at(pointer, 'value'), outside);
|
|
86
|
+
return;
|
|
87
|
+
case 'if':
|
|
88
|
+
guardDomRefs(node.test, at(pointer, 'test'), outside);
|
|
89
|
+
return;
|
|
90
|
+
case 'global':
|
|
91
|
+
fieldsFor(node.on, pointer);
|
|
92
|
+
return;
|
|
93
|
+
case 'each':
|
|
94
|
+
domRefs(node.source, at(pointer, 'source'), outside);
|
|
95
|
+
return;
|
|
96
|
+
case 'query':
|
|
97
|
+
domRefs(node.input, at(pointer, 'input'), outside);
|
|
98
|
+
return;
|
|
99
|
+
default:
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=dom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dom.js","sourceRoot":"","sources":["../../src/rules/dom.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/rules/errors.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AAQxC,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,QAkHtC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { at, resolveAt } from '@hozu/core/ir';
|
|
2
|
+
import { effectSchemas } from '../env.js';
|
|
3
|
+
import { resolveRef } from '../resolve.js';
|
|
4
|
+
import { closest, didYouMean } from '../suggest.js';
|
|
5
|
+
import { featurePointer, walkView } from '../walk.js';
|
|
6
|
+
const required = (errors) => [...Object.keys(errors), 'Unexpected'];
|
|
7
|
+
export function declaredErrors(ctx) {
|
|
8
|
+
for (const f of Object.values(ctx.ir.features)) {
|
|
9
|
+
const initial = f.machine?.initial ?? '';
|
|
10
|
+
for (const [state, s] of Object.entries(f.machine?.states ?? {})) {
|
|
11
|
+
if (!s.invoke)
|
|
12
|
+
continue;
|
|
13
|
+
const schemas = effectSchemas(ctx.ir, s.invoke.effect);
|
|
14
|
+
if (!schemas)
|
|
15
|
+
continue;
|
|
16
|
+
const names = required(schemas.errors);
|
|
17
|
+
const base = featurePointer(f.id, 'machine', 'states', state, 'invoke', 'failed');
|
|
18
|
+
const template = s.invoke.failed.Unexpected?.length
|
|
19
|
+
? s.invoke.failed.Unexpected
|
|
20
|
+
: [{ guard: null, target: initial, assign: [], navigate: null }];
|
|
21
|
+
for (const name of names) {
|
|
22
|
+
if (s.invoke.failed[name]?.length)
|
|
23
|
+
continue;
|
|
24
|
+
ctx.report('HZ004', f.id, at(base, name), `"${state}" invokes ${s.invoke.effect} but does not handle its "${name}" error`, `${s.invoke.effect} declares errors ${names.join(', ')}; every one needs a failed transition.`, {
|
|
25
|
+
summary: `Add failed.${name}`,
|
|
26
|
+
snippet: `failed: { ${name}: [{ target: '${template[0].target}' }] }`,
|
|
27
|
+
patch: [{ op: 'add', path: resolveAt(at(base, name)), value: template }],
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
for (const name of Object.keys(s.invoke.failed)) {
|
|
31
|
+
if (names.includes(name) || (name === 'Invalid' && schemas.invalid))
|
|
32
|
+
continue;
|
|
33
|
+
const guess = closest(name, names);
|
|
34
|
+
ctx.report('HZ007', f.id, at(base, name), `${s.invoke.effect} has no error "${name}".${didYouMean(guess)}`, `Declared errors: ${names.join(', ')}.`, {
|
|
35
|
+
summary: 'Remove the handler',
|
|
36
|
+
snippet: null,
|
|
37
|
+
patch: [{ op: 'remove', path: resolveAt(at(base, name)) }],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const [vid, view] of Object.entries(f.views))
|
|
42
|
+
walkView(ctx.ir, f, vid, view, ({ node, pointer }) => {
|
|
43
|
+
if (node.kind !== 'query')
|
|
44
|
+
return;
|
|
45
|
+
const r = resolveRef(ctx.ir, node.query, 'query');
|
|
46
|
+
if (!r)
|
|
47
|
+
return;
|
|
48
|
+
const names = required(r.feature.queries[r.symbol].errors);
|
|
49
|
+
for (const name of names) {
|
|
50
|
+
if (node.failed[name])
|
|
51
|
+
continue;
|
|
52
|
+
const template = node.failed.Unexpected ?? {
|
|
53
|
+
id: `${node.id}/failed/${name}`,
|
|
54
|
+
kind: 'text',
|
|
55
|
+
value: { literal: '' },
|
|
56
|
+
};
|
|
57
|
+
ctx.report('HZ004', f.id, at(pointer, 'failed', name), `Query ${node.query} can fail with "${name}" but the view does not render it`, `Declared errors: ${names.join(', ')}.`, {
|
|
58
|
+
summary: `Add failed.${name}`,
|
|
59
|
+
snippet: `failed: { ${name}: () => ui.p({}, ['…']) }`,
|
|
60
|
+
patch: [
|
|
61
|
+
{
|
|
62
|
+
op: 'add',
|
|
63
|
+
path: resolveAt(at(pointer, 'failed', name)),
|
|
64
|
+
value: template,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
for (const name of Object.keys(node.failed))
|
|
70
|
+
if (!names.includes(name))
|
|
71
|
+
ctx.report('HZ007', f.id, at(pointer, 'failed', name), `${node.query} has no error "${name}"`, `Declared errors: ${names.join(', ')}.`, {
|
|
72
|
+
summary: 'Remove the branch',
|
|
73
|
+
snippet: null,
|
|
74
|
+
patch: [{ op: 'remove', path: resolveAt(at(pointer, 'failed', name)) }],
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
for (const [cid, c] of Object.entries(f.contracts))
|
|
78
|
+
c.when.forEach((step, i) => {
|
|
79
|
+
if (!('failed' in step))
|
|
80
|
+
return;
|
|
81
|
+
const schemas = effectSchemas(ctx.ir, step.failed);
|
|
82
|
+
if (!schemas)
|
|
83
|
+
return;
|
|
84
|
+
const names = required(schemas.errors);
|
|
85
|
+
if (names.includes(step.error) || (step.error === 'Invalid' && schemas.invalid))
|
|
86
|
+
return;
|
|
87
|
+
const guess = closest(step.error, names);
|
|
88
|
+
const p = featurePointer(f.id, 'contracts', cid, 'when', i, 'error');
|
|
89
|
+
ctx.report('HZ007', f.id, p, `${step.failed} has no error "${step.error}".${didYouMean(guess)}`, `Declared errors: ${names.join(', ')}.`, {
|
|
90
|
+
summary: guess ? `Use "${guess}"` : 'Use a declared error',
|
|
91
|
+
snippet: null,
|
|
92
|
+
patch: guess ? [{ op: 'replace', path: resolveAt(p), value: guess }] : null,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/rules/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/rules/events.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AAIxC,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,QAkCvC;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,QA4ClC;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,GAAG,QAoB1C"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { at, resolveAt } from '@hozu/core/ir';
|
|
2
|
+
import { resolveRef } from '../resolve.js';
|
|
3
|
+
import { featurePointer, walkView } from '../walk.js';
|
|
4
|
+
export function unhandledEvents(ctx) {
|
|
5
|
+
for (const f of Object.values(ctx.ir.features)) {
|
|
6
|
+
const states = Object.values(f.machine?.states ?? {});
|
|
7
|
+
for (const symbol of Object.keys(f.events)) {
|
|
8
|
+
const ref = `${f.id}.${symbol}`;
|
|
9
|
+
if (states.some((s) => s.on[ref]?.length))
|
|
10
|
+
continue;
|
|
11
|
+
const p = featurePointer(f.id, 'events', symbol);
|
|
12
|
+
const exported = f.exports.events.indexOf(symbol);
|
|
13
|
+
ctx.report('HZ002', f.id, p, `Event ${ref} is declared but no state handles it`, f.machine
|
|
14
|
+
? `No state of the ${f.id} machine has on(${symbol}, …).`
|
|
15
|
+
: `${f.id} has no machine, so its events can never be handled.`, {
|
|
16
|
+
summary: `Handle ${symbol} in a state, or remove the event`,
|
|
17
|
+
snippet: `on(${symbol}, { target: '${f.machine?.initial ?? 'someState'}' })`,
|
|
18
|
+
patch: [
|
|
19
|
+
...(exported >= 0
|
|
20
|
+
? [
|
|
21
|
+
{
|
|
22
|
+
op: 'remove',
|
|
23
|
+
path: resolveAt(featurePointer(f.id, 'exports', 'events', exported)),
|
|
24
|
+
},
|
|
25
|
+
]
|
|
26
|
+
: []),
|
|
27
|
+
{ op: 'remove', path: resolveAt(p) },
|
|
28
|
+
],
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function viewEvents(ctx) {
|
|
34
|
+
for (const f of Object.values(ctx.ir.features))
|
|
35
|
+
for (const [vid, view] of Object.entries(f.views))
|
|
36
|
+
walkView(ctx.ir, f, vid, view, ({ node, pointer, visible }) => {
|
|
37
|
+
if (node.kind !== 'el' && node.kind !== 'widget' && node.kind !== 'global')
|
|
38
|
+
return;
|
|
39
|
+
for (const [dom, send] of Object.entries(node.on)) {
|
|
40
|
+
const r = resolveRef(ctx.ir, send.event, 'event');
|
|
41
|
+
const m = r?.feature.machine;
|
|
42
|
+
if (!r || !m)
|
|
43
|
+
continue;
|
|
44
|
+
const own = r.feature.id === f.id && visible !== null;
|
|
45
|
+
const shown = own ? visible : Object.keys(m.states);
|
|
46
|
+
const handling = new Set(Object.entries(m.states)
|
|
47
|
+
.filter(([, s]) => s.on[send.event]?.length || s.ignore.includes(send.event))
|
|
48
|
+
.map(([n]) => n));
|
|
49
|
+
const missing = shown.filter((s) => !handling.has(s));
|
|
50
|
+
if (!missing.length)
|
|
51
|
+
continue;
|
|
52
|
+
ctx.report('HZ005', f.id, at(pointer, 'on', dom), `${send.event} can be sent while ${r.feature.id} is in ${missing.map((s) => `"${s}"`).join(', ')}, where it is not handled`, own
|
|
53
|
+
? 'A node may only send an event when every state in which it is visible handles that event.'
|
|
54
|
+
: `The sender cannot observe the state of ${r.feature.id}, so every one of its states must handle ${send.event}.`, own
|
|
55
|
+
? {
|
|
56
|
+
summary: `Ignore ${send.event} in ${missing.join(', ')} (ignore: [${send.event.split('.')[1]}]), or show the node only in the states that handle it`,
|
|
57
|
+
snippet: `ignore: [${send.event.split('.')[1]}]`,
|
|
58
|
+
patch: missing.map((s) => ({
|
|
59
|
+
op: 'add',
|
|
60
|
+
path: resolveAt(featurePointer(r.feature.id, 'machine', 'states', s, 'ignore', '-')),
|
|
61
|
+
value: send.event,
|
|
62
|
+
})),
|
|
63
|
+
}
|
|
64
|
+
: {
|
|
65
|
+
summary: `Handle ${send.event} in ${missing.join(', ')}, or stop sending it from here`,
|
|
66
|
+
snippet: null,
|
|
67
|
+
patch: [{ op: 'remove', path: resolveAt(at(pointer, 'on', dom)) }],
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
export function conflictingIgnores(ctx) {
|
|
73
|
+
for (const f of Object.values(ctx.ir.features))
|
|
74
|
+
for (const [name, s] of Object.entries(f.machine?.states ?? {}))
|
|
75
|
+
s.ignore.forEach((event, i) => {
|
|
76
|
+
if (!s.on[event]?.length)
|
|
77
|
+
return;
|
|
78
|
+
ctx.report('HZ034', f.id, featurePointer(f.id, 'machine', 'states', name, 'ignore', i), `State "${name}" both handles and ignores ${event}`, 'An ignored event is dropped; a handled one takes a transition. A state must choose one.', {
|
|
79
|
+
summary: `Remove ${event} from ignore`,
|
|
80
|
+
snippet: null,
|
|
81
|
+
patch: [
|
|
82
|
+
{ op: 'remove', path: resolveAt(featurePointer(f.id, 'machine', 'states', name, 'ignore', i)) },
|
|
83
|
+
],
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/rules/events.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forms.d.ts","sourceRoot":"","sources":["../../src/rules/forms.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AAGxC,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,QAqBxC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { at, formRunnable } from '@hozu/core/ir';
|
|
2
|
+
import { walkView } from '../walk.js';
|
|
3
|
+
export function progressiveForms(ctx) {
|
|
4
|
+
const { ir } = ctx;
|
|
5
|
+
for (const f of Object.values(ir.features))
|
|
6
|
+
for (const [vid, view] of Object.entries(f.views))
|
|
7
|
+
walkView(ir, f, vid, view, ({ node, pointer }) => {
|
|
8
|
+
if (node.kind !== 'el' || node.tag !== 'form' || !node.on.submit)
|
|
9
|
+
return;
|
|
10
|
+
if (formRunnable(node.on.submit.payload))
|
|
11
|
+
return;
|
|
12
|
+
ctx.report('HZ036', f.id, at(pointer, 'on', 'submit', 'payload'), `This form only works with JavaScript: its ${node.on.submit.event} payload reads values the server cannot see`, 'Without JavaScript the browser posts the named form fields; the server can use those, context, params and search, but not other DOM fields or each/query bindings.', {
|
|
13
|
+
summary: "Read the fields with ui.dom.form('name') so the form also works before hydration and without JavaScript",
|
|
14
|
+
snippet: null,
|
|
15
|
+
patch: null,
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=forms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forms.js","sourceRoot":"","sources":["../../src/rules/forms.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAEhD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAErC,MAAM,UAAU,gBAAgB,CAAC,GAAQ;IACvC,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAA;IAClB,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC;QACxC,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;YAC/C,QAAQ,CAAC,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;gBAC/C,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,GAAG,KAAK,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM;oBAAE,OAAM;gBACxE,IAAI,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC;oBAAE,OAAM;gBAChD,GAAG,CAAC,MAAM,CACR,OAAO,EACP,CAAC,CAAC,EAAE,EACJ,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,CAAC,EACtC,6CAA6C,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,6CAA6C,EAC9G,oKAAoK,EACpK;oBACE,OAAO,EACL,yGAAyG;oBAC3G,OAAO,EAAE,IAAI;oBACb,KAAK,EAAE,IAAI;iBACZ,CACF,CAAA;YACH,CAAC,CAAC,CAAA;AACR,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/rules/html.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AAaxC,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,QAsDlC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { anyRef, at, resolveAt } from '@hozu/core/ir';
|
|
2
|
+
import { featurePointer } from '../walk.js';
|
|
3
|
+
const untrusted = (v, tainted) => anyRef(v, (r) => r.ref === 'context' ||
|
|
4
|
+
r.ref === 'dom' ||
|
|
5
|
+
r.ref === 'params' ||
|
|
6
|
+
(r.ref === 'binding' && tainted[r.depth] === true));
|
|
7
|
+
export function unsafeHtml(ctx) {
|
|
8
|
+
for (const f of Object.values(ctx.ir.features))
|
|
9
|
+
for (const [vid, view] of Object.entries(f.views)) {
|
|
10
|
+
const walk = (n, pointer, tainted) => {
|
|
11
|
+
const kids = (list, key) => list.forEach((c, i) => walk(c, at(pointer, key, i), tainted));
|
|
12
|
+
switch (n.kind) {
|
|
13
|
+
case 'html':
|
|
14
|
+
if (untrusted(n.value, tainted))
|
|
15
|
+
ctx.report('HZ030', f.id, at(pointer, 'value'), 'ui.html renders a value the client or URL controls', 'Context, DOM fields and route params can carry user input; rendering them as HTML allows script injection (XSS). Only query results (sanitized by the server) and literals may be rendered as HTML.', {
|
|
16
|
+
summary: 'Render it as text, or sanitize it on the server and read it from a query',
|
|
17
|
+
snippet: null,
|
|
18
|
+
patch: [
|
|
19
|
+
{
|
|
20
|
+
op: 'replace',
|
|
21
|
+
path: resolveAt(pointer),
|
|
22
|
+
value: { id: n.id, kind: 'text', value: n.value },
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
return;
|
|
27
|
+
case 'el':
|
|
28
|
+
case 'when':
|
|
29
|
+
case 'widget':
|
|
30
|
+
kids(n.children, 'children');
|
|
31
|
+
return;
|
|
32
|
+
case 'if':
|
|
33
|
+
kids(n.ifTrue, 'ifTrue');
|
|
34
|
+
kids(n.ifFalse, 'ifFalse');
|
|
35
|
+
return;
|
|
36
|
+
case 'each':
|
|
37
|
+
walk(n.item, at(pointer, 'item'), [...tainted, untrusted(n.source, tainted)]);
|
|
38
|
+
return;
|
|
39
|
+
case 'query': {
|
|
40
|
+
const input = untrusted(n.input, tainted);
|
|
41
|
+
walk(n.ready, at(pointer, 'ready'), [...tainted, false]);
|
|
42
|
+
if (n.pending)
|
|
43
|
+
walk(n.pending, at(pointer, 'pending'), tainted);
|
|
44
|
+
for (const [name, c] of Object.entries(n.failed))
|
|
45
|
+
walk(c, at(pointer, 'failed', name), [...tainted, input]);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
default:
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
walk(view.root, featurePointer(f.id, 'views', vid, 'root'), []);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/rules/html.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/rules/http.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AAoBxC,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,QAoFjC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { join, routePattern } from '@hozu/core/ir';
|
|
2
|
+
const RESERVED = new Set([
|
|
3
|
+
'content-type',
|
|
4
|
+
'content-length',
|
|
5
|
+
'cache-control',
|
|
6
|
+
'vary',
|
|
7
|
+
'location',
|
|
8
|
+
'set-cookie',
|
|
9
|
+
'content-security-policy',
|
|
10
|
+
'x-hozu-cache',
|
|
11
|
+
]);
|
|
12
|
+
const TOKEN = /^[a-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
13
|
+
const BASE = /^(\/[A-Za-z0-9._~-]+)+$/;
|
|
14
|
+
const overlaps = (a, b) => {
|
|
15
|
+
const [x, y] = [routePattern(a), routePattern(b)];
|
|
16
|
+
return x.pattern.test(y.sample) || y.pattern.test(x.sample);
|
|
17
|
+
};
|
|
18
|
+
export function httpRules(ctx) {
|
|
19
|
+
const { http } = ctx.ir;
|
|
20
|
+
if (http.basePath !== '' && !BASE.test(http.basePath))
|
|
21
|
+
ctx.report('HZ039', null, join('', 'http', 'basePath'), `basePath "${http.basePath}" is not a path prefix`, 'The prefix is joined to every URL, so it must be empty or /segment[/segment…] without a trailing slash.', {
|
|
22
|
+
summary: 'Use a prefix such as "/shop"',
|
|
23
|
+
snippet: null,
|
|
24
|
+
patch: [
|
|
25
|
+
{
|
|
26
|
+
op: 'replace',
|
|
27
|
+
path: join('', 'http', 'basePath'),
|
|
28
|
+
value: `/${http.basePath.split('/').filter(Boolean).join('/')}`.replace(/^\/$/, ''),
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
const pages = Object.keys(ctx.ir.pages).flatMap((id) => ctx.ir.routes[id] ? [[id, ctx.ir.routes[id].path]] : []);
|
|
33
|
+
http.redirects.forEach((r, i) => {
|
|
34
|
+
const at = join('', 'http', 'redirects', i);
|
|
35
|
+
const remove = {
|
|
36
|
+
summary: 'Remove this redirect',
|
|
37
|
+
snippet: null,
|
|
38
|
+
patch: [{ op: 'remove', path: at }],
|
|
39
|
+
};
|
|
40
|
+
const invalid = (message, cause) => ctx.report('HZ037', null, at, message, cause, remove);
|
|
41
|
+
if (!r.from.startsWith('/'))
|
|
42
|
+
return invalid(`Redirect "${r.from}" is not a path`, 'A redirect matches a request path.');
|
|
43
|
+
const page = pages.find(([, path]) => overlaps(r.from, path));
|
|
44
|
+
if (page)
|
|
45
|
+
return invalid(`Redirect "${r.from}" matches route "${page[0]}" (${page[1]})`, 'A URL has one owner: a redirect never hides a page.');
|
|
46
|
+
const other = http.redirects.slice(0, i).find((o) => overlaps(r.from, o.from));
|
|
47
|
+
if (other)
|
|
48
|
+
return invalid(`Redirect "${r.from}" overlaps redirect "${other.from}"`, 'Two redirects for one URL make the target depend on order.');
|
|
49
|
+
if ('link' in r.to && !ctx.ir.routes[r.to.link])
|
|
50
|
+
return invalid(`Redirect "${r.from}" targets an unknown route`, 'Point `to` at a registered route with ui.link.');
|
|
51
|
+
if ('literal' in r.to && typeof r.to.literal === 'string' && r.to.literal.startsWith('/'))
|
|
52
|
+
ctx.report('HZ032', null, join(at, 'to'), `Redirect "${r.from}" targets the internal path "${r.to.literal}" as a string`, 'Internal URLs are built from route declarations so they stay typed and canonical.', { summary: 'Use to: (params) => ui.link(route, params)', snippet: null, patch: null });
|
|
53
|
+
});
|
|
54
|
+
http.headers.forEach((rule, i) => {
|
|
55
|
+
for (const [name, value] of Object.entries(rule.set)) {
|
|
56
|
+
const at = join('', 'http', 'headers', i, 'set', name);
|
|
57
|
+
const why = RESERVED.has(name)
|
|
58
|
+
? `Header "${name}" is owned by the framework`
|
|
59
|
+
: !TOKEN.test(name)
|
|
60
|
+
? `Header name "${name}" is not a lowercase token`
|
|
61
|
+
: /[\r\n]/.test(value)
|
|
62
|
+
? `Header "${name}" has a line break in its value`
|
|
63
|
+
: null;
|
|
64
|
+
if (why)
|
|
65
|
+
ctx.report('HZ038', null, at, why, RESERVED.has(name)
|
|
66
|
+
? 'It is derived (cache-control, content-type), set by sessions (set-cookie) or configured on the server (content-security-policy).'
|
|
67
|
+
: 'Header names are lowercase tokens and values are one line.', { summary: 'Remove the header', snippet: null, patch: [{ op: 'remove', path: at }] });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/rules/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/rules/i18n.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AA+ExC,wBAAgB,IAAI,CAAC,GAAG,EAAE,GAAG,QA4K5B"}
|