ferrovue 0.4.0 → 0.5.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/README.md +198 -34
- package/dist/attrs.d.ts +1 -1
- package/dist/attrs.js +24 -22
- package/dist/calls.js +17 -15
- package/dist/children.d.ts +9 -2
- package/dist/children.js +48 -35
- package/dist/classes.js +11 -11
- package/dist/cli.js +31 -5
- package/dist/client.d.ts +35 -2
- package/dist/client.js +77 -2
- package/dist/compiler.d.ts +1 -1
- package/dist/compiler.js +7 -3
- package/dist/component.d.ts +3 -1
- package/dist/component.js +114 -19
- package/dist/constants.d.ts +42 -0
- package/dist/constants.js +292 -0
- package/dist/context.d.ts +25 -1
- package/dist/context.js +11 -5
- package/dist/diagnostics.d.ts +23 -0
- package/dist/diagnostics.js +30 -0
- package/dist/errors.d.ts +867 -0
- package/dist/errors.js +330 -0
- package/dist/expr.d.ts +1 -0
- package/dist/expr.js +120 -49
- package/dist/fixture.d.ts +3 -10
- package/dist/fixture.js +2 -6
- package/dist/hydration.d.ts +15 -0
- package/dist/hydration.js +80 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/link-router.d.ts +24 -0
- package/dist/link-router.js +36 -0
- package/dist/lists.js +30 -21
- package/dist/loops.js +14 -10
- package/dist/model.d.ts +32 -1
- package/dist/model.js +123 -6
- package/dist/narrowing.d.ts +8 -0
- package/dist/narrowing.js +40 -9
- package/dist/plugins/client-only.d.ts +8 -0
- package/dist/plugins/client-only.js +107 -0
- package/dist/plugins/i18n.js +14 -14
- package/dist/plugins/index.js +3 -1
- package/dist/plugins/router-link.js +33 -30
- package/dist/plugins/router.js +9 -9
- package/dist/plugins/scoped.js +1 -1
- package/dist/plugins/shared-types.js +1 -1
- package/dist/plugins/stores.js +26 -24
- package/dist/plugins/teleport.js +3 -3
- package/dist/plugins/twins.d.ts +10 -0
- package/dist/plugins/twins.js +164 -0
- package/dist/routes.d.ts +10 -0
- package/dist/routes.js +6 -0
- package/dist/rust.js +18 -14
- package/dist/script.d.ts +3 -0
- package/dist/script.js +119 -17
- package/dist/settle.d.ts +6 -0
- package/dist/settle.js +34 -0
- package/dist/slots.js +18 -18
- package/dist/strings.js +11 -11
- package/dist/styles.js +13 -13
- package/dist/template.js +11 -11
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +3 -2
- package/dist/typescript.d.ts +2 -1
- package/dist/typescript.js +46 -19
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +10 -9
- package/package.json +9 -5
package/dist/lists.js
CHANGED
|
@@ -4,14 +4,14 @@ import { expr, fieldVal } from "./expr.js";
|
|
|
4
4
|
import { cond } from "./narrowing.js";
|
|
5
5
|
import { asF64, isNumber } from "./numbers.js";
|
|
6
6
|
import { asCow, isTemporary, lonely, meet, yieldsCow } from "./strings.js";
|
|
7
|
-
import { atom, operand, strArg, UNARY } from "./parens.js";
|
|
7
|
+
import { atom, binary, operand, strArg, UNARY } from "./parens.js";
|
|
8
8
|
const COW = "std::borrow::Cow::<str>";
|
|
9
9
|
function itemsTy(s, v, n) {
|
|
10
10
|
if (v.ty.k !== "list")
|
|
11
|
-
return fail(s.comp, "not a list", n);
|
|
11
|
+
return fail(s.comp, "FV0803", "not a list", n);
|
|
12
12
|
const of = v.ty.of;
|
|
13
13
|
if (!["str", "int", "float", "bool", "struct", "child"].includes(of.k)) {
|
|
14
|
-
fail(s.comp, "this method takes a list of strings, numbers, booleans or objects", n);
|
|
14
|
+
fail(s.comp, "FV0804", "this method takes a list of strings, numbers, booleans or objects", n);
|
|
15
15
|
}
|
|
16
16
|
return of;
|
|
17
17
|
}
|
|
@@ -44,13 +44,13 @@ function itemVal(of, param, byRef, lone) {
|
|
|
44
44
|
function arrow(s, fn, list, of, byRef, method, body) {
|
|
45
45
|
const comp = s.comp;
|
|
46
46
|
if (fn?.type !== "ArrowFunctionExpression" || fn.async || fn.params.length > 2) {
|
|
47
|
-
fail(comp, `\`.${method}()\` takes an arrow function of the item, and of its index: \`x => …\`, \`(x, i) => …\``, fn);
|
|
47
|
+
fail(comp, "FV0805", `\`.${method}()\` takes an arrow function of the item, and of its index: \`x => …\`, \`(x, i) => …\``, fn);
|
|
48
48
|
}
|
|
49
49
|
if (fn.body.type === "BlockStatement")
|
|
50
|
-
fail(comp, `\`.${method}()\` takes an arrow function whose body is an expression, without \`{ return … }\``, fn);
|
|
50
|
+
fail(comp, "FV0806", `\`.${method}()\` takes an arrow function whose body is an expression, without \`{ return … }\``, fn);
|
|
51
51
|
const [item, index] = fn.params;
|
|
52
52
|
if (index && index.type !== "Identifier")
|
|
53
|
-
fail(comp, "an arrow function's index is a plain name", index);
|
|
53
|
+
fail(comp, "FV0807", "an arrow function's index is a plain name", index);
|
|
54
54
|
const n = ++ctx.narrowCount;
|
|
55
55
|
const p = of.k === "str" ? `fv_s${n}` : `fv_a${n}`;
|
|
56
56
|
const i = `fv_i${n}`;
|
|
@@ -64,14 +64,14 @@ function arrow(s, fn, list, of, byRef, method, body) {
|
|
|
64
64
|
else if (item?.type === "ObjectPattern" && (of.k === "struct" || of.k === "child")) {
|
|
65
65
|
for (const prop of item.properties) {
|
|
66
66
|
if (prop.type !== "ObjectProperty" || prop.computed || prop.value.type !== "Identifier") {
|
|
67
|
-
fail(comp, "a destructured arrow function parameter binds plain names, without defaults", prop);
|
|
67
|
+
fail(comp, "FV0808", "a destructured arrow function parameter binds plain names, without defaults", prop);
|
|
68
68
|
}
|
|
69
69
|
locals.set(prop.value.name, fieldVal(comp, v.code, of, prop.key.name ?? prop.key.value, prop));
|
|
70
70
|
bound.push(prop.value.name);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
else if (item)
|
|
74
|
-
fail(comp, "an arrow function's item is a name, or an object pattern of an object", item);
|
|
74
|
+
fail(comp, "FV0809", "an arrow function's item is a name, or an object pattern of an object", item);
|
|
75
75
|
if (index) {
|
|
76
76
|
locals.set(index.name, { code: i, ty: INT });
|
|
77
77
|
bound.push(index.name);
|
|
@@ -110,7 +110,7 @@ function mapped(s, v, n) {
|
|
|
110
110
|
case "child":
|
|
111
111
|
return `&${operand(v.code, UNARY)}`;
|
|
112
112
|
default:
|
|
113
|
-
return fail(s.comp, `\`.map()\` makes a list of strings, numbers, booleans or objects, not ${v.ty.k === "opt" ? "optional values" : "these"}`, n);
|
|
113
|
+
return fail(s.comp, "FV0810", `\`.map()\` makes a list of strings, numbers, booleans or objects, not ${v.ty.k === "opt" ? "optional values" : "these"}`, n);
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
export function listMethod(s, target, method, args, n) {
|
|
@@ -121,13 +121,13 @@ export function listMethod(s, target, method, args, n) {
|
|
|
121
121
|
const it = items(target);
|
|
122
122
|
if (method === "slice") {
|
|
123
123
|
if (args.length < 1 || args.length > 2)
|
|
124
|
-
fail(comp, "`.slice()` takes a start, and an end", n);
|
|
124
|
+
fail(comp, "FV0811", "`.slice()` takes a start, and an end", n);
|
|
125
125
|
const [start, end] = args.map((a) => expr(s, a));
|
|
126
126
|
const index = (v) => {
|
|
127
127
|
if (v === undefined || v.ty.k === "undef")
|
|
128
128
|
return null;
|
|
129
129
|
if (!isNumber(v.ty))
|
|
130
|
-
fail(comp, "`.slice()` takes numbers", n);
|
|
130
|
+
fail(comp, "FV0812", "`.slice()` takes numbers", n);
|
|
131
131
|
return asF64(v);
|
|
132
132
|
};
|
|
133
133
|
const from = index(start) ?? "0.0";
|
|
@@ -135,7 +135,7 @@ export function listMethod(s, target, method, args, n) {
|
|
|
135
135
|
return computed(`fv::js_slice_items(${it}, ${from}, ${to === null ? "None" : `Some(${to})`})`, of, target.lone);
|
|
136
136
|
}
|
|
137
137
|
if (args.length !== 1)
|
|
138
|
-
fail(comp, `\`.${method}()\` takes one arrow function`, n);
|
|
138
|
+
fail(comp, "FV0813", `\`.${method}()\` takes one arrow function`, n);
|
|
139
139
|
const fn = args[0];
|
|
140
140
|
switch (method) {
|
|
141
141
|
case "filter": {
|
|
@@ -175,19 +175,19 @@ export function objectCall(s, method, args, n) {
|
|
|
175
175
|
const comp = s.comp;
|
|
176
176
|
const r = args.length === 1 ? expr(s, args[0]) : null;
|
|
177
177
|
if (r?.ty.k !== "record")
|
|
178
|
-
return fail(comp, `\`Object.${method}()\` takes a \`Record<string, T>\``, n);
|
|
178
|
+
return fail(comp, "FV0814", `\`Object.${method}()\` takes a \`Record<string, T>\``, n);
|
|
179
179
|
const of = r.ty.of;
|
|
180
180
|
if (method === "keys")
|
|
181
181
|
return computed(`${atom(r.code)}.keys().map(${COW}::Borrowed)`, STR);
|
|
182
182
|
if (method === "values") {
|
|
183
183
|
if (of.k === "list")
|
|
184
|
-
fail(comp, "`Object.values()` of a record of lists, which these methods cannot walk", n);
|
|
184
|
+
fail(comp, "FV0815", "`Object.values()` of a record of lists, which these methods cannot walk", n);
|
|
185
185
|
const values = of.k === "str" ? `.values().map(|v| ${COW}::Borrowed(&**v))` : of.k === "struct" || of.k === "child" ? ".values()" : ".values().copied()";
|
|
186
186
|
return computed(`${atom(r.code)}${values}`, of);
|
|
187
187
|
}
|
|
188
188
|
if (method === "entries")
|
|
189
|
-
return fail(comp, "`Object.entries()` is supported as the source of a `v-for`: `([key, value], i) in Object.entries(r)`", n);
|
|
190
|
-
return fail(comp, `\`Object.${method}()\` is not supported: \`keys\`, \`values\` and \`entries\` of a record are`, n);
|
|
189
|
+
return fail(comp, "FV0816", "`Object.entries()` is supported as the source of a `v-for`: `([key, value], i) in Object.entries(r)`", n);
|
|
190
|
+
return fail(comp, "FV0817", `\`Object.${method}()\` is not supported: \`keys\`, \`values\` and \`entries\` of a record are`, n);
|
|
191
191
|
}
|
|
192
192
|
export function computedListMethod(s, target, method, args, n) {
|
|
193
193
|
if (target.ty.k !== "list")
|
|
@@ -195,11 +195,20 @@ export function computedListMethod(s, target, method, args, n) {
|
|
|
195
195
|
const of = target.ty.of;
|
|
196
196
|
if (method === "includes" && args.length === 1) {
|
|
197
197
|
const x = expr(s, args[0]);
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
const numbers = isNumber(x.ty) && isNumber(of);
|
|
199
|
+
if ((!sameTy(x.ty, of) && !numbers) || !["str", "int", "float", "bool"].includes(of.k))
|
|
200
|
+
fail(s.comp, "FV0801", "`.includes()` looks for a value of the list's own type", args[0]);
|
|
200
201
|
if (of.k === "str")
|
|
201
202
|
meet(s.comp, target, x, "`.includes()`", n, "equal");
|
|
202
|
-
const test = of.k === "str"
|
|
203
|
+
const test = of.k === "str"
|
|
204
|
+
? `*v == ${x.code.startsWith("&*") ? `*${x.code.slice(2)}` : `*${operand(x.code, UNARY)}`}`
|
|
205
|
+
: of.k === "float" && x.ty.k === "int"
|
|
206
|
+
? binary("v", "==", asF64(x))
|
|
207
|
+
: of.k === "float"
|
|
208
|
+
? `v == ${x.code} || (v.is_nan() && ${atom(x.code)}.is_nan())`
|
|
209
|
+
: of.k === "int" && x.ty.k === "float"
|
|
210
|
+
? binary("v as f64", "==", x.code)
|
|
211
|
+
: `v == ${x.code}`;
|
|
203
212
|
return { code: `${items(target)}.any(|v| ${test})`, ty: BOOL };
|
|
204
213
|
}
|
|
205
214
|
if (method === "join" && args.length <= 1) {
|
|
@@ -207,10 +216,10 @@ export function computedListMethod(s, target, method, args, n) {
|
|
|
207
216
|
return null;
|
|
208
217
|
const sep = args.length ? expr(s, args[0]) : { code: '","', ty: STR };
|
|
209
218
|
if (sep.ty.k !== "str")
|
|
210
|
-
fail(s.comp, "`.join()` takes a string", args[0]);
|
|
219
|
+
fail(s.comp, "FV0802", "`.join()` takes a string", args[0]);
|
|
211
220
|
const parted = /^"[^"]/.test(sep.code);
|
|
212
221
|
if (of.k === "str" && target.lone && (!parted || sep.lone))
|
|
213
|
-
fail(s.comp, lonely("`.join()` with no literal separator"), n);
|
|
222
|
+
fail(s.comp, "FV0706", lonely("`.join()` with no literal separator"), n);
|
|
214
223
|
const each = of.k === "str" ? "" : of.k === "bool" ? `.map(|v| if v { "true" } else { "false" })` : ".map(|v| fv::Js(v).to_string())";
|
|
215
224
|
return { code: `&*${items(target)}${each}.collect::<Vec<_>>().join(${strArg(sep.code)})`, ty: STR, ...(target.lone || sep.lone ? { lone: true } : {}) };
|
|
216
225
|
}
|
package/dist/loops.js
CHANGED
|
@@ -8,15 +8,15 @@ import { statements } from "./template.js";
|
|
|
8
8
|
export function list(s, e, c) {
|
|
9
9
|
const fn = c.arguments[1];
|
|
10
10
|
if (fn.type !== "ArrowFunctionExpression" || fn.body.type !== "BlockStatement") {
|
|
11
|
-
fail(s.comp, "unexpected `ssrRenderList` callback", c);
|
|
11
|
+
fail(s.comp, "FV0006", "unexpected `ssrRenderList` callback", c);
|
|
12
12
|
}
|
|
13
13
|
if (isObjectCall(c.arguments[0], "entries")) {
|
|
14
14
|
const [pair, index] = fn.params;
|
|
15
15
|
const r = c.arguments[0].arguments.length === 1 ? expr(s, c.arguments[0].arguments[0]) : null;
|
|
16
16
|
if (r?.ty.k !== "record")
|
|
17
|
-
return fail(s.comp, "`Object.entries()` takes a `Record<string, T>`", c.arguments[0]);
|
|
17
|
+
return fail(s.comp, "FV0814", "`Object.entries()` takes a `Record<string, T>`", c.arguments[0]);
|
|
18
18
|
if (pair?.type !== "ArrayPattern" || pair.elements.length > 2 || pair.elements.some((x) => x?.type !== "Identifier")) {
|
|
19
|
-
return fail(s.comp, "a `v-for` over `Object.entries()` names its items `[key, value]`", pair ?? c);
|
|
19
|
+
return fail(s.comp, "FV0818", "a `v-for` over `Object.entries()` names its items `[key, value]`", pair ?? c);
|
|
20
20
|
}
|
|
21
21
|
recordLoop(s, e, r, fn, pair.elements[1], pair.elements[0], index);
|
|
22
22
|
return;
|
|
@@ -55,7 +55,7 @@ export function list(s, e, c) {
|
|
|
55
55
|
else if (src.ty.k === "list") {
|
|
56
56
|
of = src.ty.of;
|
|
57
57
|
if (of.k === "undef")
|
|
58
|
-
fail(s.comp, "`v-for` over an empty array literal", c);
|
|
58
|
+
fail(s.comp, "FV0819", "`v-for` over an empty array literal", c);
|
|
59
59
|
const itemCode = `${itemName}_ref`;
|
|
60
60
|
walk = `${atom(src.code)}.iter()`;
|
|
61
61
|
bound = itemCode;
|
|
@@ -64,12 +64,16 @@ export function list(s, e, c) {
|
|
|
64
64
|
e.stmt(`let ${itemName}: &str = ${itemCode};`);
|
|
65
65
|
else if (of.k === "struct" || of.k === "child")
|
|
66
66
|
e.stmt(`let ${itemName} = ${itemCode};`);
|
|
67
|
+
else if (of.k === "opt" && of.of.k === "str")
|
|
68
|
+
e.stmt(`let ${itemName} = ${itemCode}.as_deref();`);
|
|
69
|
+
else if (of.k === "opt" && !["int", "float", "bool"].includes(of.of.k))
|
|
70
|
+
e.stmt(`let ${itemName} = ${itemCode}.as_ref();`);
|
|
67
71
|
else
|
|
68
72
|
e.stmt(`let ${itemName} = *${itemName}_ref;`);
|
|
69
73
|
itemLet = e.lines.length - 1;
|
|
70
74
|
}
|
|
71
75
|
else {
|
|
72
|
-
return fail(s.comp, "`v-for` walks an array, or counts to a number", c);
|
|
76
|
+
return fail(s.comp, "FV0820", "`v-for` walks an array, or counts to a number", c);
|
|
73
77
|
}
|
|
74
78
|
let idxLet = -1;
|
|
75
79
|
if (idx) {
|
|
@@ -82,13 +86,13 @@ export function list(s, e, c) {
|
|
|
82
86
|
else if (item.type === "ObjectPattern") {
|
|
83
87
|
for (const p of item.properties) {
|
|
84
88
|
if (p.type !== "ObjectProperty" || p.computed || p.value.type !== "Identifier") {
|
|
85
|
-
fail(s.comp, "a destructured `v-for` item binds plain names, without defaults", p);
|
|
89
|
+
fail(s.comp, "FV0821", "a destructured `v-for` item binds plain names, without defaults", p);
|
|
86
90
|
}
|
|
87
91
|
inner.set(p.value.name, fieldVal(s.comp, itemName, of, p.key.name ?? p.key.value, p));
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
94
|
else
|
|
91
|
-
fail(s.comp, "a `v-for` item is a name or an object pattern", item);
|
|
95
|
+
fail(s.comp, "FV0822", "a `v-for` item is a name or an object pattern", item);
|
|
92
96
|
if (index)
|
|
93
97
|
inner.set(index.name, { code: idx, ty: INT });
|
|
94
98
|
const before = e.literalBytes;
|
|
@@ -118,7 +122,7 @@ function recordLoop(s, e, r, fn, value, key, index) {
|
|
|
118
122
|
const of = r.ty.of;
|
|
119
123
|
for (const p of [key, index])
|
|
120
124
|
if (p && p.type !== "Identifier")
|
|
121
|
-
fail(s.comp, "a record's key and index in `v-for` are plain names", p);
|
|
125
|
+
fail(s.comp, "FV0823", "a record's key and index in `v-for` are plain names", p);
|
|
122
126
|
const n = ++ctx.narrowCount;
|
|
123
127
|
const v = value?.type === "Identifier" ? snake(value.name) : `fv_value${n}`;
|
|
124
128
|
const k = key ? snake(key.name) : `fv_key${n}`;
|
|
@@ -135,13 +139,13 @@ function recordLoop(s, e, r, fn, value, key, index) {
|
|
|
135
139
|
else if (value?.type === "ObjectPattern" && (of.k === "struct" || of.k === "child")) {
|
|
136
140
|
for (const p of value.properties) {
|
|
137
141
|
if (p.type !== "ObjectProperty" || p.computed || p.value.type !== "Identifier") {
|
|
138
|
-
fail(s.comp, "a destructured `v-for` item binds plain names, without defaults", p);
|
|
142
|
+
fail(s.comp, "FV0821", "a destructured `v-for` item binds plain names, without defaults", p);
|
|
139
143
|
}
|
|
140
144
|
inner.set(p.value.name, fieldVal(s.comp, v, of, p.key.name ?? p.key.value, p));
|
|
141
145
|
}
|
|
142
146
|
}
|
|
143
147
|
else if (value)
|
|
144
|
-
fail(s.comp, "a `v-for` item is a name, or an object pattern of an object", value);
|
|
148
|
+
fail(s.comp, "FV0822", "a `v-for` item is a name, or an object pattern of an object", value);
|
|
145
149
|
if (key)
|
|
146
150
|
inner.set(key.name, { code: k, ty: STR });
|
|
147
151
|
if (index)
|
package/dist/model.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { SourceMapConsumer } from "source-map-js";
|
|
2
2
|
import type { Plugin } from "./plugin.ts";
|
|
3
|
+
import type { Const } from "./constants.ts";
|
|
4
|
+
import type { Code } from "./errors.ts";
|
|
3
5
|
export type N = any;
|
|
4
6
|
export type Ty = {
|
|
5
7
|
k: "str";
|
|
@@ -11,9 +13,12 @@ export type Ty = {
|
|
|
11
13
|
k: "bool";
|
|
12
14
|
} | {
|
|
13
15
|
k: "undef";
|
|
16
|
+
} | {
|
|
17
|
+
k: "null";
|
|
14
18
|
} | {
|
|
15
19
|
k: "opt";
|
|
16
20
|
of: Ty;
|
|
21
|
+
none?: "null" | "either";
|
|
17
22
|
} | {
|
|
18
23
|
k: "list";
|
|
19
24
|
of: Ty;
|
|
@@ -63,7 +68,13 @@ export declare const INT: Ty;
|
|
|
63
68
|
export declare const FLOAT: Ty;
|
|
64
69
|
export declare const BOOL: Ty;
|
|
65
70
|
export declare const UNDEF: Ty;
|
|
71
|
+
export declare const NULL: Ty;
|
|
72
|
+
export type Absence = "undefined" | "null" | "either";
|
|
66
73
|
export declare const opt: (of: Ty) => Ty;
|
|
74
|
+
export declare function absence(ty: Ty): Absence | null;
|
|
75
|
+
export declare function joinAbsence(a: Absence | null, b: Absence | null): Absence | null;
|
|
76
|
+
export declare function withAbsence(of: Ty, none: Absence | null): Ty;
|
|
77
|
+
export declare function nothing(ty: Ty): boolean;
|
|
67
78
|
export declare const RUST_KEYWORDS: Set<string>;
|
|
68
79
|
export declare const RUST_PRELUDE: Set<string>;
|
|
69
80
|
export declare function snake(js: string): string;
|
|
@@ -72,8 +83,24 @@ export declare function declares(comp: Component, key: string): boolean;
|
|
|
72
83
|
export declare function takesAttrs(comp: Component): boolean;
|
|
73
84
|
export declare function rustStr(s: string): string;
|
|
74
85
|
export declare function rustChar(ch: string): string;
|
|
86
|
+
/** Where in a file an error is: lines and columns count from 1, and the end is exclusive. */
|
|
87
|
+
export interface Location {
|
|
88
|
+
file: string;
|
|
89
|
+
line?: number;
|
|
90
|
+
column?: number;
|
|
91
|
+
endLine?: number;
|
|
92
|
+
endColumn?: number;
|
|
93
|
+
}
|
|
94
|
+
/** A construct the compiler refuses: its stable code, the message as shown, where it is, and what
|
|
95
|
+
* is refused without the location and the quoted line. */
|
|
75
96
|
export declare class GenError extends Error {
|
|
97
|
+
readonly code: Code;
|
|
98
|
+
readonly at: Location | null;
|
|
99
|
+
readonly what: string;
|
|
100
|
+
constructor(code: Code, message: string, at?: Location | null, what?: string);
|
|
76
101
|
}
|
|
102
|
+
/** Refuse something in a file that has no source to quote, as a locale or routes file. */
|
|
103
|
+
export declare function failIn(file: string, code: Code, what: string): never;
|
|
77
104
|
export interface Component {
|
|
78
105
|
name: string;
|
|
79
106
|
module: string;
|
|
@@ -84,6 +111,7 @@ export interface Component {
|
|
|
84
111
|
line: number;
|
|
85
112
|
column: number;
|
|
86
113
|
};
|
|
114
|
+
templateAst?: N;
|
|
87
115
|
attrsDropped?: N;
|
|
88
116
|
props: Struct;
|
|
89
117
|
structs: Map<string, Struct>;
|
|
@@ -127,6 +155,8 @@ export interface Scope {
|
|
|
127
155
|
attrs: string | null;
|
|
128
156
|
fallthrough: string | null;
|
|
129
157
|
attrsBindings: Set<string>;
|
|
158
|
+
slotsBindings: Set<string>;
|
|
159
|
+
consts: Map<string, Const>;
|
|
130
160
|
sid: string | null;
|
|
131
161
|
loop?: {
|
|
132
162
|
item: string;
|
|
@@ -136,5 +166,6 @@ export interface Scope {
|
|
|
136
166
|
}
|
|
137
167
|
export type Origin = "source" | "template";
|
|
138
168
|
export declare function tagAst(node: N, origin: Origin, seen?: WeakSet<object>): void;
|
|
139
|
-
export declare function
|
|
169
|
+
export declare function sourceAt(source: string, offset: number): N;
|
|
170
|
+
export declare function fail(comp: Component, code: Code, what: string, node?: N): never;
|
|
140
171
|
export declare function sameTy(a: Ty, b: Ty): boolean;
|
package/dist/model.js
CHANGED
|
@@ -3,7 +3,32 @@ export const INT = { k: "int" };
|
|
|
3
3
|
export const FLOAT = { k: "float" };
|
|
4
4
|
export const BOOL = { k: "bool" };
|
|
5
5
|
export const UNDEF = { k: "undef" };
|
|
6
|
-
export const
|
|
6
|
+
export const NULL = { k: "null" };
|
|
7
|
+
export const opt = (of) => (of.k !== "opt" ? { k: "opt", of } : of.none === undefined ? of : { k: "opt", of: of.of, none: "either" });
|
|
8
|
+
export function absence(ty) {
|
|
9
|
+
if (ty.k === "undef")
|
|
10
|
+
return "undefined";
|
|
11
|
+
if (ty.k === "null")
|
|
12
|
+
return "null";
|
|
13
|
+
if (ty.k === "opt")
|
|
14
|
+
return ty.none ?? "undefined";
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
export function joinAbsence(a, b) {
|
|
18
|
+
if (a === null || a === b)
|
|
19
|
+
return b;
|
|
20
|
+
if (b === null)
|
|
21
|
+
return a;
|
|
22
|
+
return "either";
|
|
23
|
+
}
|
|
24
|
+
export function withAbsence(of, none) {
|
|
25
|
+
if (none === null)
|
|
26
|
+
return of;
|
|
27
|
+
return none === "undefined" ? { k: "opt", of } : { k: "opt", of, none };
|
|
28
|
+
}
|
|
29
|
+
export function nothing(ty) {
|
|
30
|
+
return ty.k === "undef" || ty.k === "null";
|
|
31
|
+
}
|
|
7
32
|
export const RUST_KEYWORDS = new Set("as break const continue crate else enum extern false fn for if impl in let loop match mod move mut pub ref return self Self static struct super trait true type unsafe use where while async await dyn abstract become box do final macro override priv typeof unsized virtual yield try".split(" "));
|
|
8
33
|
const NOT_RAW = new Set(["self", "Self", "super", "crate"]);
|
|
9
34
|
export const RUST_PRELUDE = new Set(["Option", "Some", "None", "Vec", "String", "Box", "Result", "Ok", "Err", "Cow", "Slots", "Default"]);
|
|
@@ -51,7 +76,22 @@ export function rustChar(ch) {
|
|
|
51
76
|
return `'"'`;
|
|
52
77
|
return `'${rustStr(ch).slice(1, -1)}'`;
|
|
53
78
|
}
|
|
79
|
+
/** A construct the compiler refuses: its stable code, the message as shown, where it is, and what
|
|
80
|
+
* is refused without the location and the quoted line. */
|
|
54
81
|
export class GenError extends Error {
|
|
82
|
+
code;
|
|
83
|
+
at;
|
|
84
|
+
what;
|
|
85
|
+
constructor(code, message, at = null, what = message) {
|
|
86
|
+
super(message);
|
|
87
|
+
this.code = code;
|
|
88
|
+
this.at = at;
|
|
89
|
+
this.what = what;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Refuse something in a file that has no source to quote, as a locale or routes file. */
|
|
93
|
+
export function failIn(file, code, what) {
|
|
94
|
+
throw new GenError(code, `${file}: ${what}`, { file }, what);
|
|
55
95
|
}
|
|
56
96
|
export function blankComponent(name, module, file, structs = new Map()) {
|
|
57
97
|
return {
|
|
@@ -105,12 +145,79 @@ function locate(comp, node) {
|
|
|
105
145
|
if (node.__fv === "template" && comp.templateMap && comp.templateStart) {
|
|
106
146
|
const o = comp.templateMap.originalPositionFor({ line: start.line, column: start.column });
|
|
107
147
|
if (o.line == null || o.column == null)
|
|
108
|
-
return
|
|
148
|
+
return locateWithin(comp, node) ?? locate(comp, written(comp, node));
|
|
109
149
|
const t = comp.templateStart;
|
|
110
150
|
return o.line === 1 ? { line: t.line, column: t.column - 1 + o.column } : { line: t.line + o.line - 1, column: o.column };
|
|
111
151
|
}
|
|
112
152
|
return null;
|
|
113
153
|
}
|
|
154
|
+
function locateWithin(comp, node) {
|
|
155
|
+
const start = node.loc.start;
|
|
156
|
+
const inner = Object.entries(node)
|
|
157
|
+
.filter(([k, v]) => k !== "loc" && v && typeof v === "object")
|
|
158
|
+
.flatMap(([, v]) => (Array.isArray(v) ? v : [v]))
|
|
159
|
+
.filter((c) => c?.loc?.start)
|
|
160
|
+
.toSorted((a, b) => a.start - b.start);
|
|
161
|
+
for (const child of inner) {
|
|
162
|
+
const at = locate(comp, child);
|
|
163
|
+
if (!at)
|
|
164
|
+
continue;
|
|
165
|
+
const c = child.loc.start;
|
|
166
|
+
const back = c.line === start.line ? c.column - start.column : 0;
|
|
167
|
+
return { line: at.line, column: Math.max(0, at.column - back) };
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
export function sourceAt(source, offset) {
|
|
172
|
+
const before = source.slice(0, offset);
|
|
173
|
+
const line = before.split("\n").length;
|
|
174
|
+
return { type: "VueSource", loc: { start: { line, column: offset - before.lastIndexOf("\n") - 1 } }, __fv: "source" };
|
|
175
|
+
}
|
|
176
|
+
const plain = (name) => name.replace(/[-_]/g, "").toLowerCase();
|
|
177
|
+
function assetName(n, prefix) {
|
|
178
|
+
if (n?.type === "Identifier")
|
|
179
|
+
return n.name.replace(prefix, "");
|
|
180
|
+
if (n?.type === "MemberExpression")
|
|
181
|
+
return n.computed ? n.property.value : n.property.name;
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
function written(comp, node) {
|
|
185
|
+
const call = node.type === "VariableDeclaration" ? node.declarations[0]?.init : node.type === "ExpressionStatement" ? node.expression : node;
|
|
186
|
+
const callee = call?.type === "CallExpression" && call.callee.type === "Identifier" ? call.callee.name : null;
|
|
187
|
+
const tagged = (name) => name && templateNode(comp, (el) => (plain(el.tag) === plain(name) ? el : null));
|
|
188
|
+
const directive = (name) => name && templateNode(comp, (el) => el.props.find((p) => p.type === 7 && plain(p.name) === plain(name)));
|
|
189
|
+
switch (callee) {
|
|
190
|
+
case "_ssrRenderComponent":
|
|
191
|
+
return tagged(assetName(call.arguments[0], /^_component_/));
|
|
192
|
+
case "_resolveComponent":
|
|
193
|
+
return tagged(call.arguments[0]?.value ?? null);
|
|
194
|
+
case "_resolveDirective":
|
|
195
|
+
return directive(call.arguments[0]?.value ?? null);
|
|
196
|
+
case "_ssrGetDirectiveProps":
|
|
197
|
+
return directive(assetName(call.arguments[1], /^_directive_/)?.replace(/^v(?=[A-Z])/, "") ?? null);
|
|
198
|
+
case "_ssrRenderSlot": {
|
|
199
|
+
const name = call.arguments[1]?.value;
|
|
200
|
+
return templateNode(comp, (el) => (el.tag === "slot" && (el.props.find((p) => p.type === 6 && p.name === "name")?.value?.content ?? "default") === name ? el : null));
|
|
201
|
+
}
|
|
202
|
+
default:
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function templateNode(comp, test) {
|
|
207
|
+
const visit = (n) => {
|
|
208
|
+
const found = n.type === 1 ? test(n) : null;
|
|
209
|
+
if (found)
|
|
210
|
+
return found;
|
|
211
|
+
for (const c of n.children ?? []) {
|
|
212
|
+
const inner = visit(c);
|
|
213
|
+
if (inner)
|
|
214
|
+
return inner;
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
};
|
|
218
|
+
const found = comp.templateAst ? visit(comp.templateAst) : null;
|
|
219
|
+
return found && { type: "VueTemplate", loc: { start: { line: found.loc.start.line, column: found.loc.start.column - 1 } }, __fv: "source" };
|
|
220
|
+
}
|
|
114
221
|
function snippet(comp, at) {
|
|
115
222
|
const text = comp.source?.split("\n")[at.line - 1];
|
|
116
223
|
if (text === undefined)
|
|
@@ -119,12 +226,22 @@ function snippet(comp, at) {
|
|
|
119
226
|
const lead = text.slice(0, at.column).replace(/[^\t]/g, " ");
|
|
120
227
|
return `\n ${n} | ${text}\n ${" ".repeat(n.length)} | ${lead}^`;
|
|
121
228
|
}
|
|
122
|
-
|
|
229
|
+
function locateEnd(node) {
|
|
230
|
+
const end = node?.__fv === "source" ? node.loc?.end : null;
|
|
231
|
+
return end ? { endLine: end.line, endColumn: end.column + 1 } : null;
|
|
232
|
+
}
|
|
233
|
+
export function fail(comp, code, what, node) {
|
|
123
234
|
const at = locate(comp, node);
|
|
124
235
|
if (!at)
|
|
125
|
-
throw new GenError(`${comp.file}: ${what}
|
|
126
|
-
|
|
236
|
+
throw new GenError(code, `${comp.file}: ${what}`, { file: comp.file }, what);
|
|
237
|
+
const where = { file: comp.file, line: at.line, column: at.column + 1, ...locateEnd(node) };
|
|
238
|
+
throw new GenError(code, `${comp.file}:${at.line}:${at.column + 1}: ${what}${snippet(comp, at)}`, where, what);
|
|
239
|
+
}
|
|
240
|
+
function canonical(_key, value) {
|
|
241
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
242
|
+
return value;
|
|
243
|
+
return Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));
|
|
127
244
|
}
|
|
128
245
|
export function sameTy(a, b) {
|
|
129
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
246
|
+
return JSON.stringify(a, canonical) === JSON.stringify(b, canonical);
|
|
130
247
|
}
|
package/dist/narrowing.d.ts
CHANGED
|
@@ -2,6 +2,14 @@ import { type N, type Scope, type Ty, type Val } from "./model.ts";
|
|
|
2
2
|
export declare function known(v: Val): boolean | undefined;
|
|
3
3
|
export declare function truthy(v: Val): string;
|
|
4
4
|
export declare function pathOf(n: N): string | null;
|
|
5
|
+
export interface NullTest {
|
|
6
|
+
target: N;
|
|
7
|
+
literal: "null" | "undefined";
|
|
8
|
+
strict: boolean;
|
|
9
|
+
is: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function nullTest(n: N): NullTest | null;
|
|
12
|
+
export declare function checkNullTest(s: Scope, t: NullTest, v: Val): void;
|
|
5
13
|
export interface Presence {
|
|
6
14
|
path: string;
|
|
7
15
|
pattern: (name: string) => string;
|
package/dist/narrowing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BOOL } from "./model.js";
|
|
1
|
+
import { absence, BOOL, fail, nothing } from "./model.js";
|
|
2
2
|
import { ctx } from "./context.js";
|
|
3
3
|
import { claim } from "./plugin.js";
|
|
4
4
|
import { atom, bare, binary, condition, ifElse, logical, not, occurrences, receiver } from "./parens.js";
|
|
@@ -7,6 +7,8 @@ import { expr } from "./expr.js";
|
|
|
7
7
|
export function known(v) {
|
|
8
8
|
if (v.konst !== undefined)
|
|
9
9
|
return v.konst;
|
|
10
|
+
if (nothing(v.ty))
|
|
11
|
+
return false;
|
|
10
12
|
if (v.ty.k === "str" && /^"(?:[^"\\]|\\.)*"$/.test(v.code))
|
|
11
13
|
return v.code !== '""';
|
|
12
14
|
if (v.num !== undefined)
|
|
@@ -30,6 +32,7 @@ export function truthy(v) {
|
|
|
30
32
|
case "bool":
|
|
31
33
|
return v.code;
|
|
32
34
|
case "undef":
|
|
35
|
+
case "null":
|
|
33
36
|
return "false";
|
|
34
37
|
case "opt":
|
|
35
38
|
return `${atom(v.code)}.is_some_and(|v| ${truthy({ code: "v", ty: v.ty.of })})`;
|
|
@@ -51,6 +54,35 @@ export function pathOf(n) {
|
|
|
51
54
|
return `${base}.${n.property.value}`;
|
|
52
55
|
return null;
|
|
53
56
|
}
|
|
57
|
+
function nothingLiteral(n) {
|
|
58
|
+
if (n.type === "NullLiteral")
|
|
59
|
+
return "null";
|
|
60
|
+
if (n.type === "Identifier" && n.name === "undefined")
|
|
61
|
+
return "undefined";
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
export function nullTest(n) {
|
|
65
|
+
if (n.type !== "BinaryExpression" || !["===", "!==", "==", "!="].includes(n.operator))
|
|
66
|
+
return null;
|
|
67
|
+
const right = nothingLiteral(n.right);
|
|
68
|
+
const left = nothingLiteral(n.left);
|
|
69
|
+
const literal = right ?? left;
|
|
70
|
+
if (literal === null)
|
|
71
|
+
return null;
|
|
72
|
+
return { target: right !== null ? n.left : n.right, literal, strict: n.operator.length === 3, is: n.operator.startsWith("=") };
|
|
73
|
+
}
|
|
74
|
+
export function checkNullTest(s, t, v) {
|
|
75
|
+
const none = absence(v.ty);
|
|
76
|
+
if (none === null || !t.strict || none === t.literal)
|
|
77
|
+
return;
|
|
78
|
+
const op = t.is ? "==" : "!=";
|
|
79
|
+
if (none === "either") {
|
|
80
|
+
fail(s.comp, "FV0626", `\`${op}= ${t.literal}\` of a value that may be \`null\` or \`undefined\`, which its Rust \`Option\` cannot tell apart: test both with \`${op} null\``, t.target);
|
|
81
|
+
}
|
|
82
|
+
const other = t.literal === "null" ? "undefined" : "null";
|
|
83
|
+
const what = none === "null" ? "is `T | null`, never `undefined`" : "is optional, which is `undefined` when absent, never `null`";
|
|
84
|
+
fail(s.comp, "FV0627", `\`${op}= ${t.literal}\` of a value that ${what}: compare with \`${other}\``, t.target);
|
|
85
|
+
}
|
|
54
86
|
export function presence(s, n) {
|
|
55
87
|
for (const p of ctx.plugins) {
|
|
56
88
|
const own = p.presence?.(s, n);
|
|
@@ -64,15 +96,12 @@ export function presence(s, n) {
|
|
|
64
96
|
target = n.argument;
|
|
65
97
|
negated = true;
|
|
66
98
|
}
|
|
67
|
-
else if (n.type === "BinaryExpression"
|
|
68
|
-
const
|
|
69
|
-
if (
|
|
70
|
-
target = n.left;
|
|
71
|
-
else if (isUndef(n.left))
|
|
72
|
-
target = n.right;
|
|
73
|
-
else
|
|
99
|
+
else if (n.type === "BinaryExpression") {
|
|
100
|
+
const test = nullTest(n);
|
|
101
|
+
if (!test)
|
|
74
102
|
return null;
|
|
75
|
-
|
|
103
|
+
target = test.target;
|
|
104
|
+
negated = test.is;
|
|
76
105
|
byTruth = false;
|
|
77
106
|
}
|
|
78
107
|
const path = pathOf(target);
|
|
@@ -81,6 +110,8 @@ export function presence(s, n) {
|
|
|
81
110
|
const v = expr(s, target);
|
|
82
111
|
if (v.ty.k !== "opt")
|
|
83
112
|
return null;
|
|
113
|
+
if (n.type === "BinaryExpression")
|
|
114
|
+
checkNullTest(s, nullTest(n), v);
|
|
84
115
|
const of = v.ty.of;
|
|
85
116
|
const scalar = of.k === "str" || of.k === "int" || of.k === "float" || of.k === "bool";
|
|
86
117
|
const filtered = byTruth && scalar;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Component } from "../model.ts";
|
|
2
|
+
import { type Plugin } from "../plugin.ts";
|
|
3
|
+
interface ClientOnlyRun {
|
|
4
|
+
locals: Map<Component, Set<string>>;
|
|
5
|
+
outside: Map<Component, Set<string>>;
|
|
6
|
+
}
|
|
7
|
+
export declare const clientOnly: Plugin<ClientOnlyRun>;
|
|
8
|
+
export {};
|