ferrovue 0.3.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 +225 -42
- package/dist/attrs.d.ts +3 -53
- package/dist/attrs.js +41 -469
- package/dist/calls.d.ts +4 -0
- package/dist/calls.js +221 -0
- package/dist/children.d.ts +16 -0
- package/dist/children.js +286 -0
- package/dist/classes.d.ts +13 -0
- package/dist/classes.js +176 -0
- package/dist/cli.js +31 -22
- package/dist/client.d.ts +35 -2
- package/dist/client.js +77 -14
- package/dist/compiler.d.ts +1 -1
- package/dist/compiler.js +31 -92
- package/dist/component.d.ts +3 -1
- package/dist/component.js +123 -104
- package/dist/constants.d.ts +42 -0
- package/dist/constants.js +292 -0
- package/dist/context.d.ts +29 -67
- package/dist/context.js +13 -38
- package/dist/diagnostics.d.ts +23 -0
- package/dist/diagnostics.js +30 -0
- package/dist/diff.d.ts +0 -3
- package/dist/diff.js +0 -9
- package/dist/emitter.d.ts +0 -8
- package/dist/emitter.js +0 -12
- package/dist/errors.d.ts +867 -0
- package/dist/errors.js +330 -0
- package/dist/expr.d.ts +1 -70
- package/dist/expr.js +142 -934
- package/dist/fallthrough.d.ts +9 -0
- package/dist/fallthrough.js +78 -0
- package/dist/fixture.d.ts +4 -10
- package/dist/fixture.js +38 -27
- package/dist/hydration.d.ts +15 -0
- package/dist/hydration.js +80 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/islands.d.ts +1 -0
- package/dist/islands.js +1 -0
- package/dist/link-router.d.ts +24 -0
- package/dist/link-router.js +36 -0
- package/dist/lists.d.ts +0 -11
- package/dist/lists.js +34 -60
- package/dist/loops.d.ts +3 -0
- package/dist/loops.js +168 -0
- package/dist/model.d.ts +46 -144
- package/dist/model.js +150 -18
- package/dist/narrowing.d.ts +27 -0
- package/dist/narrowing.js +157 -0
- package/dist/numbers.d.ts +8 -0
- package/dist/numbers.js +73 -0
- package/dist/parens.d.ts +0 -27
- package/dist/parens.js +0 -44
- package/dist/plugin.d.ts +84 -0
- package/dist/plugin.js +26 -0
- package/dist/plugins/client-only.d.ts +8 -0
- package/dist/plugins/client-only.js +107 -0
- package/dist/plugins/i18n.d.ts +23 -0
- package/dist/{i18n.js → plugins/i18n.js} +88 -31
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.js +10 -0
- package/dist/plugins/router-link.d.ts +8 -0
- package/dist/{router.js → plugins/router-link.js} +52 -91
- package/dist/plugins/router.d.ts +42 -0
- package/dist/plugins/router.js +256 -0
- package/dist/plugins/scoped.d.ts +12 -0
- package/dist/plugins/scoped.js +125 -0
- package/dist/plugins/shared-types.d.ts +2 -0
- package/dist/plugins/shared-types.js +7 -0
- package/dist/plugins/stores.d.ts +37 -0
- package/dist/plugins/stores.js +379 -0
- package/dist/plugins/teleport.d.ts +5 -0
- package/dist/plugins/teleport.js +55 -0
- 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.d.ts +1 -7
- package/dist/rust.js +56 -174
- package/dist/script.d.ts +3 -6
- package/dist/script.js +127 -140
- package/dist/settle.d.ts +6 -0
- package/dist/settle.js +34 -0
- package/dist/slots.d.ts +13 -0
- package/dist/slots.js +160 -0
- package/dist/strings.d.ts +13 -0
- package/dist/strings.js +208 -0
- package/dist/styles.d.ts +14 -0
- package/dist/styles.js +181 -0
- package/dist/template.d.ts +1 -42
- package/dist/template.js +25 -749
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +4 -10
- package/dist/types.d.ts +2 -0
- package/dist/typescript.d.ts +2 -14
- package/dist/typescript.js +53 -59
- package/dist/vite.d.ts +2 -1
- package/dist/vite.js +11 -32
- package/package.json +15 -6
- package/dist/i18n.d.ts +0 -27
- package/dist/router.d.ts +0 -17
- package/dist/scoped.d.ts +0 -24
- package/dist/scoped.js +0 -177
- package/dist/stores.d.ts +0 -6
- package/dist/stores.js +0 -229
package/dist/calls.d.ts
ADDED
package/dist/calls.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { BOOL, fail, FLOAT, INT, sameTy, STR } from "./model.js";
|
|
2
|
+
import { ctx } from "./context.js";
|
|
3
|
+
import { claim } from "./plugin.js";
|
|
4
|
+
import { atom, bare, binary, condition, operand, receiver, strArg, UNARY } from "./parens.js";
|
|
5
|
+
import { computedListMethod, items, listMethod, objectCall } from "./lists.js";
|
|
6
|
+
import { lonely, loneOf, meet, stringMethod, stringsEqual } from "./strings.js";
|
|
7
|
+
import { asF64, intFromF64, isNumber } from "./numbers.js";
|
|
8
|
+
import { boolOf, truthy } from "./narrowing.js";
|
|
9
|
+
import { coerce, expr } from "./expr.js";
|
|
10
|
+
export function call(s, n) {
|
|
11
|
+
const comp = s.comp;
|
|
12
|
+
const callee = n.callee;
|
|
13
|
+
const args = n.arguments;
|
|
14
|
+
if (callee.type === "Identifier") {
|
|
15
|
+
switch (callee.name) {
|
|
16
|
+
case "_ssrLooseEqual": {
|
|
17
|
+
const a = expr(s, args[0]);
|
|
18
|
+
const b = expr(s, args[1]);
|
|
19
|
+
if (a.ty.k === "str" && b.ty.k === "str") {
|
|
20
|
+
meet(comp, a, b, "`v-model`", n, "equal");
|
|
21
|
+
return boolOf(stringsEqual(a, b));
|
|
22
|
+
}
|
|
23
|
+
return fail(comp, "FV0413", "`v-model` comparison between these types", n);
|
|
24
|
+
}
|
|
25
|
+
case "_ssrIncludeBooleanAttr": {
|
|
26
|
+
const a = expr(s, args[0]);
|
|
27
|
+
if (a.konst !== undefined)
|
|
28
|
+
return a;
|
|
29
|
+
if (a.ty.k === "str")
|
|
30
|
+
return { code: "true", ty: BOOL, konst: true };
|
|
31
|
+
if (a.ty.k === "opt" && a.ty.of.k === "str")
|
|
32
|
+
return { code: `${atom(a.code)}.is_some()`, ty: BOOL };
|
|
33
|
+
return boolOf(truthy(a));
|
|
34
|
+
}
|
|
35
|
+
case "_ssrLooseContain":
|
|
36
|
+
return fail(comp, "FV0414", "`v-model` over an array", n);
|
|
37
|
+
}
|
|
38
|
+
const own = claim((p) => p.call?.(s, n));
|
|
39
|
+
if (own)
|
|
40
|
+
return own;
|
|
41
|
+
const helper = s.helpers.get(callee.name);
|
|
42
|
+
if (helper)
|
|
43
|
+
return helperCall(s, helper, args, n);
|
|
44
|
+
if (callee.name === "String" && args.length === 1) {
|
|
45
|
+
const a = expr(s, args[0]);
|
|
46
|
+
if (a.ty.k === "str")
|
|
47
|
+
return a;
|
|
48
|
+
if (isNumber(a.ty))
|
|
49
|
+
return { code: `&*fv::Js(${bare(a.code)}).to_string()`, ty: STR };
|
|
50
|
+
if (a.ty.k === "bool")
|
|
51
|
+
return { code: `if ${condition(a.code)} { "true" } else { "false" }`, ty: STR };
|
|
52
|
+
}
|
|
53
|
+
if (callee.name === "Number" && args.length === 1) {
|
|
54
|
+
const a = expr(s, args[0]);
|
|
55
|
+
if (a.ty.k === "str")
|
|
56
|
+
return { code: `fv::js_number(${strArg(a.code)})`, ty: FLOAT };
|
|
57
|
+
if (isNumber(a.ty))
|
|
58
|
+
return a;
|
|
59
|
+
if (a.ty.k === "bool")
|
|
60
|
+
return { code: `i64::from(${bare(a.code)})`, ty: INT };
|
|
61
|
+
return fail(comp, "FV0701", "`Number()` takes a string, a number or a boolean that is present", n);
|
|
62
|
+
}
|
|
63
|
+
if ((callee.name === "parseInt" && (args.length === 1 || args.length === 2)) || (callee.name === "parseFloat" && args.length === 1)) {
|
|
64
|
+
const a = expr(s, args[0]);
|
|
65
|
+
if (a.ty.k !== "str")
|
|
66
|
+
fail(comp, "FV0702", `\`${callee.name}()\` takes a string`, args[0]);
|
|
67
|
+
if (callee.name === "parseFloat")
|
|
68
|
+
return { code: `fv::js_parse_float(${strArg(a.code)})`, ty: FLOAT };
|
|
69
|
+
const radix = args[1];
|
|
70
|
+
if (radix && !(radix.type === "NumericLiteral" && (radix.value === 10 || radix.value === 16))) {
|
|
71
|
+
fail(comp, "FV0703", "`parseInt()` takes a radix of 10 or 16, written as a literal", radix);
|
|
72
|
+
}
|
|
73
|
+
return { code: `fv::js_parse_int(${strArg(a.code)}, ${radix ? radix.value : 0})`, ty: FLOAT };
|
|
74
|
+
}
|
|
75
|
+
return fail(comp, "FV0601", `\`${callee.name}()\` is not available when rendering on the server`, n);
|
|
76
|
+
}
|
|
77
|
+
if (callee.type === "MemberExpression" && !callee.computed) {
|
|
78
|
+
const method = callee.property.name;
|
|
79
|
+
if (callee.object.type === "Identifier" && callee.object.name === "Array" && method === "isArray") {
|
|
80
|
+
const a = expr(s, args[0]);
|
|
81
|
+
const own = claim((p) => p.values?.isArray?.(a));
|
|
82
|
+
if (own)
|
|
83
|
+
return own;
|
|
84
|
+
const is = a.ty.k === "list";
|
|
85
|
+
return { code: String(is), ty: BOOL, konst: is };
|
|
86
|
+
}
|
|
87
|
+
if (callee.object.type === "Identifier" && callee.object.name === "Object")
|
|
88
|
+
return objectCall(s, method, args, n);
|
|
89
|
+
if (callee.object.type === "Identifier" && callee.object.name === "JSON" && method === "stringify" && args.length === 1) {
|
|
90
|
+
const v = expr(s, args[0]);
|
|
91
|
+
if (v.ty.k === "bool")
|
|
92
|
+
return { code: `if ${condition(v.code)} { "true" } else { "false" }`, ty: STR };
|
|
93
|
+
return { code: `&*${json(s, v, args[0])}`, ty: STR };
|
|
94
|
+
}
|
|
95
|
+
const own = claim((p) => p.call?.(s, n));
|
|
96
|
+
if (own)
|
|
97
|
+
return own;
|
|
98
|
+
if (callee.object.type === "Identifier" && (callee.object.name === "$setup" || callee.object.name === "_ctx")) {
|
|
99
|
+
const helper = s.helpers.get(method);
|
|
100
|
+
if (helper)
|
|
101
|
+
return helperCall(s, helper, args, n);
|
|
102
|
+
}
|
|
103
|
+
if (callee.object.type === "Identifier" && callee.object.name === "Math") {
|
|
104
|
+
const vals = args.map((a) => expr(s, a));
|
|
105
|
+
if (vals.length && vals.every((v) => isNumber(v.ty))) {
|
|
106
|
+
const ints = vals.every((v) => v.ty.k === "int");
|
|
107
|
+
if ((method === "max" || method === "min") && vals.length >= 2) {
|
|
108
|
+
if (ints)
|
|
109
|
+
return { code: vals.slice(1).reduce((acc, v) => `${atom(acc)}.${method}(${bare(v.code)})`, vals[0].code), ty: INT };
|
|
110
|
+
return { code: vals.slice(1).reduce((acc, v) => `fv::js_${method}(${bare(acc)}, ${bare(asF64(v))})`, asF64(vals[0])), ty: FLOAT };
|
|
111
|
+
}
|
|
112
|
+
if (vals.length === 1) {
|
|
113
|
+
const x = vals[0];
|
|
114
|
+
if (method === "abs")
|
|
115
|
+
return ints ? intFromF64(`${receiver(asF64(x))}.abs()`) : { code: `${receiver(x.code)}.abs()`, ty: FLOAT };
|
|
116
|
+
if (method === "round")
|
|
117
|
+
return { code: `fv::js_round(${bare(asF64(x))})`, ty: FLOAT };
|
|
118
|
+
if (method === "floor" || method === "ceil" || method === "trunc")
|
|
119
|
+
return { code: `${receiver(asF64(x))}.${method}()`, ty: FLOAT };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return fail(comp, "FV0704", `\`Math.${method}()\` is supported on numbers as \`max\`, \`min\`, \`abs\`, \`round\`, \`floor\`, \`ceil\` and \`trunc\``, n);
|
|
123
|
+
}
|
|
124
|
+
const target = expr(s, callee.object);
|
|
125
|
+
if (target.ty.k === "str") {
|
|
126
|
+
const lone = loneOf(target);
|
|
127
|
+
switch (args.length === 0 ? method : "") {
|
|
128
|
+
case "trim":
|
|
129
|
+
return { code: `fv::js_trim(${strArg(target.code)})`, ty: STR, ...lone };
|
|
130
|
+
case "trimStart":
|
|
131
|
+
return { code: `fv::js_trim_start(${strArg(target.code)})`, ty: STR, ...lone };
|
|
132
|
+
case "trimEnd":
|
|
133
|
+
return { code: `fv::js_trim_end(${strArg(target.code)})`, ty: STR, ...lone };
|
|
134
|
+
case "toUpperCase":
|
|
135
|
+
return { code: `&*${receiver(target.code)}.to_uppercase()`, ty: STR, ...lone };
|
|
136
|
+
case "toLowerCase":
|
|
137
|
+
return { code: `&*${receiver(target.code)}.to_lowercase()`, ty: STR, ...lone };
|
|
138
|
+
case "toString":
|
|
139
|
+
return target;
|
|
140
|
+
}
|
|
141
|
+
const m = stringMethod(s, target, method, args, n);
|
|
142
|
+
if (m)
|
|
143
|
+
return m;
|
|
144
|
+
}
|
|
145
|
+
if (isNumber(target.ty) && method === "toFixed" && args.length <= 1) {
|
|
146
|
+
const d = args.length ? args[0] : { type: "NumericLiteral", value: 0 };
|
|
147
|
+
if (d.type !== "NumericLiteral" || !Number.isInteger(d.value) || d.value < 0 || d.value > 100) {
|
|
148
|
+
fail(comp, "FV0705", "`.toFixed()` takes a literal number of digits, from 0 to 100", n);
|
|
149
|
+
}
|
|
150
|
+
return { code: `&*fv::js_to_fixed(${bare(asF64(target))}, ${d.value})`, ty: STR };
|
|
151
|
+
}
|
|
152
|
+
if (isNumber(target.ty) && method === "toString" && args.length === 0) {
|
|
153
|
+
return { code: `&*fv::Js(${bare(target.code)}).to_string()`, ty: STR };
|
|
154
|
+
}
|
|
155
|
+
if (target.ty.k === "list") {
|
|
156
|
+
const inPlace = target.iter === undefined && !target.lone && (target.ty.of.k === "str" || target.ty.of.k === "int");
|
|
157
|
+
const listed = listMethod(s, target, method, args, n) ?? (inPlace ? null : computedListMethod(s, target, method, args, n));
|
|
158
|
+
if (listed)
|
|
159
|
+
return listed;
|
|
160
|
+
}
|
|
161
|
+
if (target.ty.k === "list" && (target.ty.of.k === "str" || target.ty.of.k === "int")) {
|
|
162
|
+
const of = target.ty.of;
|
|
163
|
+
if (method === "includes" && args.length === 1) {
|
|
164
|
+
const v = expr(s, args[0]);
|
|
165
|
+
if (of.k === "int" && v.ty.k === "float")
|
|
166
|
+
return { code: `${atom(target.code)}.iter().any(|v| ${binary("*v as f64", "==", v.code)})`, ty: BOOL };
|
|
167
|
+
if (!sameTy(v.ty, of))
|
|
168
|
+
fail(comp, "FV0801", "`.includes()` looks for a value of the list's own type", args[0]);
|
|
169
|
+
if (of.k === "int")
|
|
170
|
+
return { code: `${atom(target.code)}.contains(&${operand(v.code, UNARY)})`, ty: BOOL };
|
|
171
|
+
const test = v.code === '""' ? "v.is_empty()" : binary("&**v", "==", v.code);
|
|
172
|
+
return { code: `${atom(target.code)}.iter().any(|v| ${test})`, ty: BOOL };
|
|
173
|
+
}
|
|
174
|
+
if (method === "join" && args.length <= 1) {
|
|
175
|
+
const sep = args.length ? expr(s, args[0]) : { code: '","', ty: STR };
|
|
176
|
+
if (sep.ty.k !== "str")
|
|
177
|
+
fail(comp, "FV0802", "`.join()` takes a string", args[0]);
|
|
178
|
+
const each = of.k === "str" ? `${atom(target.code)}.iter().map(|v| &**v)` : `${atom(target.code)}.iter().map(|v| fv::Js(*v).to_string())`;
|
|
179
|
+
return { code: `&*${each}.collect::<Vec<_>>().join(${strArg(sep.code)})`, ty: STR, ...loneOf(sep) };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return fail(comp, "FV0602", `\`.${method}()\` is not supported`, n);
|
|
183
|
+
}
|
|
184
|
+
return fail(comp, "FV0603", "this call is not supported", n);
|
|
185
|
+
}
|
|
186
|
+
export function isObjectCall(n, method) {
|
|
187
|
+
return (n?.type === "CallExpression" && n.callee.type === "MemberExpression" && !n.callee.computed &&
|
|
188
|
+
n.callee.object.type === "Identifier" && n.callee.object.name === "Object" && n.callee.property.name === method);
|
|
189
|
+
}
|
|
190
|
+
function json(s, v, n) {
|
|
191
|
+
if (v.lone)
|
|
192
|
+
fail(s.comp, "FV0706", lonely("`JSON.stringify()`"), n);
|
|
193
|
+
const one = (code, ty) => {
|
|
194
|
+
switch (ty.k) {
|
|
195
|
+
case "str":
|
|
196
|
+
return `fv::js_json_string(${code === "v" ? "&v" : strArg(code)})`;
|
|
197
|
+
case "int":
|
|
198
|
+
return `fv::Js(${bare(code)}).to_string()`;
|
|
199
|
+
case "float":
|
|
200
|
+
return `fv::js_json_number(${bare(code)})`;
|
|
201
|
+
case "bool":
|
|
202
|
+
return `(if ${condition(code)} { "true" } else { "false" }).to_owned()`;
|
|
203
|
+
default:
|
|
204
|
+
return fail(s.comp, "FV0707", "`JSON.stringify()` of a string, a number, a boolean or a list of those, present", n);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
if (v.ty.k === "list") {
|
|
208
|
+
const each = one("v", v.ty.of);
|
|
209
|
+
const f = /^([\w:]+)\(v\)$/.exec(each)?.[1];
|
|
210
|
+
return `format!("[{}]", ${items(v)}.map(${f ?? `|v| ${each}`}).collect::<Vec<_>>().join(","))`;
|
|
211
|
+
}
|
|
212
|
+
return one(v.code, v.ty);
|
|
213
|
+
}
|
|
214
|
+
export function helperCall(s, name, args, n) {
|
|
215
|
+
const h = ctx.helpers[name];
|
|
216
|
+
if (args.length !== h.params.length)
|
|
217
|
+
fail(s.comp, "FV1101", `\`${name}\` takes ${h.params.length} argument(s)`, n);
|
|
218
|
+
const code = args.map((a, i) => bare(coerce(s.comp, expr(s, a), h.params[i], a))).join(", ");
|
|
219
|
+
s.helperBytes.n += h.maxLen;
|
|
220
|
+
return { code: `${h.rust}(${code})`, ty: h.ret };
|
|
221
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Component, type Field, type N, type Scope, type Ty, type Val } from "./model.ts";
|
|
2
|
+
import { Emitter } from "./emitter.ts";
|
|
3
|
+
/** A component the project renders with a Rust function of its own, in place of a compiled one. */
|
|
4
|
+
export interface TwinCall {
|
|
5
|
+
comp: Component;
|
|
6
|
+
init(s: Scope, f: Field, node: N | undefined, n: N): string;
|
|
7
|
+
call(s: Scope, e: Emitter, inits: string[], slots: N, attrs: string): void;
|
|
8
|
+
}
|
|
9
|
+
export declare function renderChild(s: Scope, e: Emitter, n: N, twin?: TwinCall): void;
|
|
10
|
+
export declare function array(key: string): boolean;
|
|
11
|
+
export declare function fieldInit(name: string, value: string): string;
|
|
12
|
+
export declare function takesSlots(c: Component): boolean;
|
|
13
|
+
export declare function extraParams(c: Component): string;
|
|
14
|
+
export declare function callChild(s: Scope, e: Emitter, child: Component, propsCode: string, slots: N, attrs: string | null): void;
|
|
15
|
+
export declare function callWith(s: Scope, e: Emitter, child: Component, m: string, head: string, slotsTy: string, trailing: string, slots: N, vnode?: boolean): void;
|
|
16
|
+
export declare function ownInto(comp: Component, v: Val, want: Ty, node: N): string;
|
package/dist/children.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { isSSRSafeAttrName, propsToAttrMap } from "@vue/shared";
|
|
2
|
+
import { camelize, declares, fail, nothing, rustStr, sameTy, snake, takesAttrs } from "./model.js";
|
|
3
|
+
import { ctx } from "./context.js";
|
|
4
|
+
import { markHome } from "./typescript.js";
|
|
5
|
+
import { expr, fieldVal, holdsNothing } from "./expr.js";
|
|
6
|
+
import { asF64 } from "./numbers.js";
|
|
7
|
+
import { atom, bare } from "./parens.js";
|
|
8
|
+
import { Emitter } from "./emitter.js";
|
|
9
|
+
import { attrOf, dollarAttrs, IGNORED_PROPS, isAttrs, mergedParts } from "./attrs.js";
|
|
10
|
+
import { passedKey } from "./fallthrough.js";
|
|
11
|
+
import { claim, paramsOf, slotFieldsOf } from "./plugin.js";
|
|
12
|
+
import { statements } from "./template.js";
|
|
13
|
+
import { slotContent, slotFieldBorrows, staticallyFilled } from "./slots.js";
|
|
14
|
+
export function renderChild(s, e, n, twin) {
|
|
15
|
+
const [target, rawProps, slots, , slotScopeId] = n.arguments;
|
|
16
|
+
let local = null;
|
|
17
|
+
if (target.type === "MemberExpression" && target.object.name === "$setup") {
|
|
18
|
+
local = target.computed ? target.property.value : target.property.name;
|
|
19
|
+
}
|
|
20
|
+
const isSelf = target.type === "Identifier" && target.name === s.selfAlias.name;
|
|
21
|
+
const childName = isSelf ? s.comp.name : local ? s.children.get(local) : undefined;
|
|
22
|
+
const child = twin?.comp ?? (childName ? s.components.get(childName) : undefined);
|
|
23
|
+
if (!child)
|
|
24
|
+
fail(s.comp, "FV0501", `a child component must be imported from a \`.vue\` file among the components compiled, in ${ctx.componentsDir.replace(/\/$/, "")}`, n);
|
|
25
|
+
for (const p of ctx.plugins)
|
|
26
|
+
p.child?.(s, child, n);
|
|
27
|
+
const parts = !rawProps || rawProps.type === "NullLiteral" ? [] : mergedParts(rawProps);
|
|
28
|
+
const merges = parts.length > 0 && parts[0] !== rawProps;
|
|
29
|
+
const passesAttrs = parts.some(isAttrs);
|
|
30
|
+
const passed = (p) => s.fallthrough !== null && ((isAttrs(p) && s.comp.inheritAttrs) || dollarAttrs(s, p));
|
|
31
|
+
const marker = (p) => p.type === "ObjectExpression" && p.properties.length > 0 && p.properties.every((q) => q.type === "ObjectProperty" && IGNORED_PROPS.has(q.key.name ?? q.key.value));
|
|
32
|
+
const objects = parts.filter((p) => !isAttrs(p) && !dollarAttrs(s, p) && !marker(p));
|
|
33
|
+
if (parts.some(passed)) {
|
|
34
|
+
for (const name of s.comp.attrNames) {
|
|
35
|
+
if (declares(child, name)) {
|
|
36
|
+
fail(s.comp, "FV0502", `\`${name}\`, an attribute ${s.comp.name} may be passed, would reach ${child.name} as its prop \`${camelize(name)}\`, which an attribute has no type for`, n);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const ids = claim((p) => p.childIds?.(s, child, passesAttrs, !!slotScopeId, n)) ?? null;
|
|
41
|
+
if (objects.length === 1 && objects[0].type !== "ObjectExpression") {
|
|
42
|
+
if (twin)
|
|
43
|
+
fail(s.comp, "FV1501", `the props of ${child.name}, a Rust twin, are attributes or an object literal`, objects[0]);
|
|
44
|
+
const v = expr(s, objects[0]);
|
|
45
|
+
const own = child.name === s.comp.name && v.ty.k === "struct" && v.ty.name === "Props";
|
|
46
|
+
if (own || (v.ty.k === "child" && v.ty.name === child.name)) {
|
|
47
|
+
callChild(s, e, child, v.code, slots, childAttrsArg(s, child, parts, merges, null, ids, n));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
fail(s.comp, "FV0503", `child props must be an object literal, or \`v-bind\` of ${child.name}'s own \`Props\``, n);
|
|
51
|
+
}
|
|
52
|
+
for (const p of objects) {
|
|
53
|
+
if (p.type !== "ObjectExpression")
|
|
54
|
+
fail(s.comp, "FV0503", `child props must be an object literal, or \`v-bind\` of ${child.name}'s own \`Props\``, p);
|
|
55
|
+
}
|
|
56
|
+
const given = new Map();
|
|
57
|
+
const fallthrough = new Set();
|
|
58
|
+
for (const obj of objects) {
|
|
59
|
+
for (const p of obj.properties) {
|
|
60
|
+
if (p.type !== "ObjectProperty" || p.computed)
|
|
61
|
+
fail(s.comp, "FV0504", "child props hold plain keys", p);
|
|
62
|
+
const key = p.key.type === "Identifier" ? p.key.name : String(p.key.value);
|
|
63
|
+
if (!passedKey(child, key))
|
|
64
|
+
continue;
|
|
65
|
+
const field = child.props.fields.find((f) => camelize(f.js) === camelize(key));
|
|
66
|
+
if (field) {
|
|
67
|
+
given.set(field.js, p.value);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (array(key))
|
|
71
|
+
fail(s.comp, "FV0505", `an attribute named \`${key}\`, which JavaScript would put before the others`, p);
|
|
72
|
+
const name = propsToAttrMap[key] ?? key.toLowerCase();
|
|
73
|
+
if (key !== "class" && key !== "style" && !isSSRSafeAttrName(name))
|
|
74
|
+
fail(s.comp, "FV0404", `unsafe attribute name \`${name}\``, p);
|
|
75
|
+
fallthrough.add(p);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (twin) {
|
|
79
|
+
const inits = child.props.fields.map((f) => twin.init(s, f, given.get(f.js), n));
|
|
80
|
+
twin.call(s, e, inits, slots, childAttrsArg(s, child, parts, merges, fallthrough, ids, n));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const inits = child.props.fields.map((f) => {
|
|
84
|
+
const node = given.get(f.js);
|
|
85
|
+
if (!node) {
|
|
86
|
+
if (f.ty.k !== "opt")
|
|
87
|
+
fail(s.comp, "FV0506", `${child.name} requires \`${f.js}\``, n);
|
|
88
|
+
if (f.ty.none !== undefined)
|
|
89
|
+
fail(s.comp, "FV0507", `${child.name} requires \`${f.js}\`, which is \`T | null\`: Vue would hand it \`undefined\`; pass \`null\` for none`, n);
|
|
90
|
+
return `${f.rust}: None`;
|
|
91
|
+
}
|
|
92
|
+
const v = expr(s, node);
|
|
93
|
+
return fieldInit(f.rust, ownInto(s.comp, { ...v, ty: markHome(v.ty, s.comp.name) }, markHome(f.ty, child.name), node));
|
|
94
|
+
});
|
|
95
|
+
callChild(s, e, child, `&super::${child.module}::Props { ${inits.join(", ")} }`, slots, childAttrsArg(s, child, parts, merges, fallthrough, ids, n));
|
|
96
|
+
}
|
|
97
|
+
export function array(key) {
|
|
98
|
+
return /^(0|[1-9]\d*)$/.test(key) && Number(key) < 2 ** 32 - 1;
|
|
99
|
+
}
|
|
100
|
+
function childAttrsArg(s, child, parts, merges, fallthrough, ids, n) {
|
|
101
|
+
const sources = [];
|
|
102
|
+
for (const p of parts) {
|
|
103
|
+
if (s.fallthrough !== null && ((isAttrs(p) && s.comp.inheritAttrs) || dollarAttrs(s, p))) {
|
|
104
|
+
sources.push(`${s.fallthrough}.list()`);
|
|
105
|
+
}
|
|
106
|
+
else if (p.type === "ObjectExpression" && fallthrough !== null) {
|
|
107
|
+
const entries = p.properties.filter((q) => fallthrough.has(q)).map((q) => {
|
|
108
|
+
const key = q.key.type === "Identifier" ? q.key.name : String(q.key.value);
|
|
109
|
+
return `(${rustStr(key)}, ${attrOf(s, key, q.value, "vnode")})`;
|
|
110
|
+
});
|
|
111
|
+
if (entries.length)
|
|
112
|
+
sources.push(`&[${entries.join(", ")}]`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (!takesAttrs(child)) {
|
|
116
|
+
if (sources.length)
|
|
117
|
+
fail(s.comp, "FV0508", `${child.name} is passed attributes it does not take`, n);
|
|
118
|
+
return ids;
|
|
119
|
+
}
|
|
120
|
+
if (!sources.length)
|
|
121
|
+
return ids === null ? "&fv::Attrs::NONE" : `&fv::Attrs::scoped(${ids})`;
|
|
122
|
+
if (sources.length === 1 && !merges)
|
|
123
|
+
return `&fv::Attrs::new(${sources[0]}, ${ids ?? '""'})`;
|
|
124
|
+
return `&fv::Attrs::merged(&[${sources.join(", ")}], ${ids ?? '""'})`;
|
|
125
|
+
}
|
|
126
|
+
export function fieldInit(name, value) {
|
|
127
|
+
return value === name ? name : `${name}: ${bare(value)}`;
|
|
128
|
+
}
|
|
129
|
+
export function takesSlots(c) {
|
|
130
|
+
return c.slotNames.length > 0 || slotFieldsOf(c).length > 0;
|
|
131
|
+
}
|
|
132
|
+
export function extraParams(c) {
|
|
133
|
+
return (takesSlots(c) ? ", fv_slots: Slots<'_>" : "") + paramsOf(c).map((p) => `, ${p.name}: ${p.ty}`).join("");
|
|
134
|
+
}
|
|
135
|
+
export function callChild(s, e, child, propsCode, slots, attrs) {
|
|
136
|
+
const m = `super::${child.module}`;
|
|
137
|
+
const scoped = child.inherits || takesAttrs(child);
|
|
138
|
+
const trailing = paramsOf(child).map((p) => `, ${p.name}`).join("") + (scoped ? `, ${attrs ?? (takesAttrs(child) ? "&fv::Attrs::NONE" : '""')}` : "");
|
|
139
|
+
const render = scoped ? "render_scoped" : "render";
|
|
140
|
+
callWith(s, e, child, m, `${m}::${render}(out, ${propsCode}`, `${m}::Slots`, trailing, slots);
|
|
141
|
+
}
|
|
142
|
+
export function callWith(s, e, child, m, head, slotsTy, trailing, slots, vnode = false) {
|
|
143
|
+
const given = new Map();
|
|
144
|
+
if (slots && slots.type !== "NullLiteral") {
|
|
145
|
+
if (slots.type !== "ObjectExpression")
|
|
146
|
+
fail(s.comp, "FV0901", "slots must be an object literal", slots);
|
|
147
|
+
for (const p of slots.properties) {
|
|
148
|
+
const key = p.key?.type === "Identifier" ? p.key.name : p.key?.value;
|
|
149
|
+
if (key === "_")
|
|
150
|
+
continue;
|
|
151
|
+
if (p.type !== "ObjectProperty" || p.computed)
|
|
152
|
+
fail(s.comp, "FV0902", "slots hold plain keys", p);
|
|
153
|
+
if (!child.slotNames.includes(key))
|
|
154
|
+
fail(s.comp, "FV0903", `${child.name} has no slot \`${key}\``, p);
|
|
155
|
+
given.set(key, p.value);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (!takesSlots(child)) {
|
|
159
|
+
e.stmt(`${head}${trailing});`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
e.open(`${head}, ${slotsTy}`);
|
|
163
|
+
for (const name of child.slotNames) {
|
|
164
|
+
const field = snake(name);
|
|
165
|
+
const value = given.get(name);
|
|
166
|
+
if (!value) {
|
|
167
|
+
e.stmt(`${field}: None,`);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const { body, param } = slotContent(s, value);
|
|
171
|
+
const shape = child.slotShapes.get(name);
|
|
172
|
+
const takesNone = param?.type === "Identifier" && param.name === "_";
|
|
173
|
+
const sid = child.passesSlotIds ? `fv_sid${++ctx.narrowCount}` : null;
|
|
174
|
+
const sidParam = sid === null ? "" : `, ${sid}: &str`;
|
|
175
|
+
const content = (inner) => {
|
|
176
|
+
if (staticallyFilled(body)) {
|
|
177
|
+
statements({ ...inner, fill: false }, e, body);
|
|
178
|
+
e.stmt("true");
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
e.stmt("let mut filled = false;");
|
|
182
|
+
statements({ ...inner, fill: true }, e, body);
|
|
183
|
+
e.stmt("filled");
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
const unread = (opened, binding) => {
|
|
187
|
+
if (binding !== null && !e.reads(binding, opened + 1))
|
|
188
|
+
e.replace(opened, `${binding}:`, `_${binding}:`);
|
|
189
|
+
};
|
|
190
|
+
if (shape) {
|
|
191
|
+
const sp = `fv_sp${++ctx.narrowCount}`;
|
|
192
|
+
const ty = { k: "struct", name: shape.name, home: child.name };
|
|
193
|
+
const locals = new Map(s.locals);
|
|
194
|
+
if (param?.type === "ObjectPattern") {
|
|
195
|
+
for (const p of param.properties) {
|
|
196
|
+
if (p.type !== "ObjectProperty" || p.computed || p.value.type !== "Identifier") {
|
|
197
|
+
fail(s.comp, "FV0904", "slot props are destructured into plain names, without defaults", p);
|
|
198
|
+
}
|
|
199
|
+
locals.set(p.value.name, fieldVal(s.comp, sp, ty, p.key.name ?? p.key.value, p));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else if (param?.type === "Identifier" && !takesNone) {
|
|
203
|
+
locals.set(param.name, { code: sp, ty });
|
|
204
|
+
}
|
|
205
|
+
else if (!takesNone)
|
|
206
|
+
fail(s.comp, "FV0905", "slot props are a name or an object pattern", param);
|
|
207
|
+
const life = shape.fields.some((f) => slotFieldBorrows(f.ty)) ? "<'_>" : "";
|
|
208
|
+
e.open(`${field}: Some(&|out: &mut String, ${sp}: &${m}::${shape.name}${life}${sidParam}| -> bool`);
|
|
209
|
+
const opened = e.lines.length - 1;
|
|
210
|
+
content({ ...s, locals, sid, vnode });
|
|
211
|
+
unread(opened, sp);
|
|
212
|
+
unread(opened, sid);
|
|
213
|
+
e.close("),");
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (!takesNone)
|
|
217
|
+
fail(s.comp, "FV0906", `\`<slot${name === "default" ? "" : ` name="${name}"`}>\` in ${child.name} passes no props`, param);
|
|
218
|
+
const inner = { ...s, sid, vnode };
|
|
219
|
+
if (sid !== null) {
|
|
220
|
+
e.open(`${field}: Some(fv::Slot::slotted(&|out: &mut String${sidParam}| -> bool`);
|
|
221
|
+
const opened = e.lines.length - 1;
|
|
222
|
+
content(inner);
|
|
223
|
+
unread(opened, sid);
|
|
224
|
+
e.close(")),");
|
|
225
|
+
}
|
|
226
|
+
else if (staticallyFilled(body)) {
|
|
227
|
+
e.open(`${field}: Some(fv::Slot::new(&|out: &mut String|`);
|
|
228
|
+
statements({ ...inner, fill: false }, e, body);
|
|
229
|
+
e.close(")),");
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
e.open(`${field}: Some(fv::Slot::markup(&|out: &mut String| -> bool`);
|
|
233
|
+
e.stmt("let mut filled = false;");
|
|
234
|
+
statements({ ...inner, fill: true }, e, body);
|
|
235
|
+
e.stmt("filled");
|
|
236
|
+
e.close(")),");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
e.close(`${trailing});`);
|
|
240
|
+
}
|
|
241
|
+
export function ownInto(comp, v, want, node) {
|
|
242
|
+
holdsNothing(comp, v, want, node, "the prop");
|
|
243
|
+
if (want.k === "opt" && nothing(v.ty))
|
|
244
|
+
return "None";
|
|
245
|
+
if (want.k === "str") {
|
|
246
|
+
if (v.ty.k !== "str")
|
|
247
|
+
fail(comp, "FV0509", "a string prop needs a string", node);
|
|
248
|
+
return `std::borrow::Cow::Borrowed(${bare(v.code)})`;
|
|
249
|
+
}
|
|
250
|
+
if (want.k === "opt" && want.of.k === "str") {
|
|
251
|
+
if (v.ty.k === "str")
|
|
252
|
+
return `Some(std::borrow::Cow::Borrowed(${bare(v.code)}))`;
|
|
253
|
+
if (v.ty.k === "opt" && v.ty.of.k === "str")
|
|
254
|
+
return `${atom(v.code)}.map(std::borrow::Cow::Borrowed)`;
|
|
255
|
+
}
|
|
256
|
+
if (sameTy(v.ty, want) && (want.k === "int" || want.k === "float" || want.k === "bool"))
|
|
257
|
+
return v.code;
|
|
258
|
+
if (want.k === "opt" && sameTy(v.ty, want.of) && (v.ty.k === "int" || v.ty.k === "float" || v.ty.k === "bool"))
|
|
259
|
+
return `Some(${bare(v.code)})`;
|
|
260
|
+
if (want.k === "float" && v.ty.k === "int")
|
|
261
|
+
return asF64(v);
|
|
262
|
+
if (want.k === "opt" && want.of.k === "float" && v.ty.k === "int")
|
|
263
|
+
return `Some(${asF64(v)})`;
|
|
264
|
+
if (v.iter !== undefined && v.ty.k === "list" && want.k === "list" && sameTy(v.ty.of, want.of)) {
|
|
265
|
+
return want.of.k === "struct" || want.of.k === "child" ? `${v.iter}.cloned().collect()` : `${v.iter}.collect()`;
|
|
266
|
+
}
|
|
267
|
+
const objecty = (t) => t.k === "struct" || t.k === "child" || t.k === "record" || ((t.k === "list" || t.k === "opt") && objecty(t.of));
|
|
268
|
+
if (objecty(want) && sameTy(v.ty, want))
|
|
269
|
+
return `${atom(v.code)}.${v.ty.k === "opt" ? "cloned" : "to_owned"}()`;
|
|
270
|
+
if (want.k === "opt" && objecty(want.of) && sameTy(v.ty, want.of))
|
|
271
|
+
return `Some(${atom(v.code)}.to_owned())`;
|
|
272
|
+
if (objecty(want) && v.ty.k === want.k && JSON.stringify(v.ty).includes('"struct"')) {
|
|
273
|
+
fail(comp, "FV0510", `a ${JSON.stringify(v.ty)} where the child takes a ${JSON.stringify(want)}: two components share a type only when both import it from one \`.ts\` file`, node);
|
|
274
|
+
}
|
|
275
|
+
if (want.k === "list" && v.ty.k === "list" && v.ty.of.k === "undef")
|
|
276
|
+
return "Vec::new()";
|
|
277
|
+
if (want.k === "opt" && want.of.k === "list" && v.ty.k === "list")
|
|
278
|
+
return `Some(${ownInto(comp, v, want.of, node)})`;
|
|
279
|
+
if (want.k === "list" && v.ty.k === "list" && sameTy(v.ty.of, want.of)) {
|
|
280
|
+
if (want.of.k === "str")
|
|
281
|
+
return `${atom(v.code)}.iter().map(|v| std::borrow::Cow::Borrowed(&**v)).collect()`;
|
|
282
|
+
if (want.of.k === "int" || want.of.k === "float" || want.of.k === "bool")
|
|
283
|
+
return `${atom(v.code)}.to_vec()`;
|
|
284
|
+
}
|
|
285
|
+
return fail(comp, "FV0511", `a ${JSON.stringify(v.ty)} into a ${JSON.stringify(want)} prop`, node);
|
|
286
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type N, type Scope } from "./model.ts";
|
|
2
|
+
import { Emitter } from "./emitter.ts";
|
|
3
|
+
export type ClassItem = {
|
|
4
|
+
lit: string;
|
|
5
|
+
} | {
|
|
6
|
+
code: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function classItems(s: Scope, n: N): ClassItem[];
|
|
9
|
+
export declare function renderClass(s: Scope, e: Emitter, n: N, after?: boolean): void;
|
|
10
|
+
export declare function classPresent(s: Scope, n: N): string;
|
|
11
|
+
export declare function maybeEqual(s: Scope, n: N): boolean;
|
|
12
|
+
export declare function literalClass(n: N): string | null;
|
|
13
|
+
export declare function classAttr(s: Scope, n: N, side: "vnode" | "own"): string;
|