ferrovue 0.2.0 → 0.4.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 +103 -15
- package/dist/attrs.d.ts +8 -42
- package/dist/attrs.js +148 -297
- package/dist/calls.d.ts +4 -0
- package/dist/calls.js +219 -0
- package/dist/children.d.ts +9 -0
- package/dist/children.js +273 -0
- package/dist/classes.d.ts +13 -0
- package/dist/classes.js +176 -0
- package/dist/cli.js +121 -27
- package/dist/client.d.ts +11 -2
- package/dist/client.js +45 -9
- package/dist/compiler.d.ts +4 -0
- package/dist/compiler.js +37 -90
- package/dist/component.js +19 -82
- package/dist/context.d.ts +6 -68
- package/dist/context.js +25 -38
- package/dist/diff.d.ts +1 -0
- package/dist/diff.js +68 -0
- package/dist/emitter.d.ts +0 -8
- package/dist/emitter.js +0 -12
- package/dist/expr.d.ts +0 -70
- package/dist/expr.js +30 -855
- package/dist/fallthrough.d.ts +9 -0
- package/dist/fallthrough.js +78 -0
- package/dist/fixture.d.ts +2 -1
- package/dist/fixture.js +37 -22
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/islands.d.ts +6 -0
- package/dist/islands.js +5 -0
- package/dist/lists.d.ts +0 -11
- package/dist/lists.js +4 -39
- package/dist/loops.d.ts +3 -0
- package/dist/loops.js +164 -0
- package/dist/model.d.ts +22 -130
- package/dist/model.js +37 -9
- package/dist/narrowing.d.ts +19 -0
- package/dist/narrowing.js +126 -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/i18n.d.ts +23 -0
- package/dist/{i18n.js → plugins/i18n.js} +76 -19
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.js +8 -0
- package/dist/plugins/router-link.d.ts +8 -0
- package/dist/{router.js → plugins/router-link.js} +65 -57
- 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/{scoped.js → plugins/scoped.js} +55 -20
- 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 +377 -0
- package/dist/plugins/teleport.d.ts +5 -0
- package/dist/plugins/teleport.js +55 -0
- package/dist/rust.d.ts +2 -5
- package/dist/rust.js +66 -161
- package/dist/script.d.ts +0 -6
- package/dist/script.js +22 -121
- 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 +2 -40
- package/dist/template.js +40 -691
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +1 -8
- package/dist/types.d.ts +2 -0
- package/dist/typescript.d.ts +0 -13
- package/dist/typescript.js +8 -41
- package/dist/vite.d.ts +1 -0
- package/dist/vite.js +35 -14
- package/package.json +11 -2
- package/dist/i18n.d.ts +0 -27
- package/dist/router.d.ts +0 -17
- package/dist/scoped.d.ts +0 -10
- package/dist/stores.d.ts +0 -6
- package/dist/stores.js +0 -227
package/dist/calls.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
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, "`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, "`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, "`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, `\`${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, "`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, `\`${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, `\`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, "`.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 (!sameTy(v.ty, of))
|
|
166
|
+
fail(comp, "`.includes()` looks for a value of the list's own type", args[0]);
|
|
167
|
+
if (of.k === "int")
|
|
168
|
+
return { code: `${atom(target.code)}.contains(&${operand(v.code, UNARY)})`, ty: BOOL };
|
|
169
|
+
const test = v.code === '""' ? "v.is_empty()" : binary("&**v", "==", v.code);
|
|
170
|
+
return { code: `${atom(target.code)}.iter().any(|v| ${test})`, ty: BOOL };
|
|
171
|
+
}
|
|
172
|
+
if (method === "join" && args.length <= 1) {
|
|
173
|
+
const sep = args.length ? expr(s, args[0]) : { code: '","', ty: STR };
|
|
174
|
+
if (sep.ty.k !== "str")
|
|
175
|
+
fail(comp, "`.join()` takes a string", args[0]);
|
|
176
|
+
const each = of.k === "str" ? `${atom(target.code)}.iter().map(|v| &**v)` : `${atom(target.code)}.iter().map(|v| fv::Js(*v).to_string())`;
|
|
177
|
+
return { code: `&*${each}.collect::<Vec<_>>().join(${strArg(sep.code)})`, ty: STR, ...loneOf(sep) };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return fail(comp, `\`.${method}()\` is not supported`, n);
|
|
181
|
+
}
|
|
182
|
+
return fail(comp, "this call is not supported", n);
|
|
183
|
+
}
|
|
184
|
+
export function isObjectCall(n, method) {
|
|
185
|
+
return (n?.type === "CallExpression" && n.callee.type === "MemberExpression" && !n.callee.computed &&
|
|
186
|
+
n.callee.object.type === "Identifier" && n.callee.object.name === "Object" && n.callee.property.name === method);
|
|
187
|
+
}
|
|
188
|
+
function json(s, v, n) {
|
|
189
|
+
if (v.lone)
|
|
190
|
+
fail(s.comp, lonely("`JSON.stringify()`"), n);
|
|
191
|
+
const one = (code, ty) => {
|
|
192
|
+
switch (ty.k) {
|
|
193
|
+
case "str":
|
|
194
|
+
return `fv::js_json_string(${code === "v" ? "&v" : strArg(code)})`;
|
|
195
|
+
case "int":
|
|
196
|
+
return `fv::Js(${bare(code)}).to_string()`;
|
|
197
|
+
case "float":
|
|
198
|
+
return `fv::js_json_number(${bare(code)})`;
|
|
199
|
+
case "bool":
|
|
200
|
+
return `(if ${condition(code)} { "true" } else { "false" }).to_owned()`;
|
|
201
|
+
default:
|
|
202
|
+
return fail(s.comp, "`JSON.stringify()` of a string, a number, a boolean or a list of those, present", n);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
if (v.ty.k === "list") {
|
|
206
|
+
const each = one("v", v.ty.of);
|
|
207
|
+
const f = /^([\w:]+)\(v\)$/.exec(each)?.[1];
|
|
208
|
+
return `format!("[{}]", ${items(v)}.map(${f ?? `|v| ${each}`}).collect::<Vec<_>>().join(","))`;
|
|
209
|
+
}
|
|
210
|
+
return one(v.code, v.ty);
|
|
211
|
+
}
|
|
212
|
+
export function helperCall(s, name, args, n) {
|
|
213
|
+
const h = ctx.helpers[name];
|
|
214
|
+
if (args.length !== h.params.length)
|
|
215
|
+
fail(s.comp, `\`${name}\` takes ${h.params.length} argument(s)`, n);
|
|
216
|
+
const code = args.map((a, i) => bare(coerce(s.comp, expr(s, a), h.params[i], a))).join(", ");
|
|
217
|
+
s.helperBytes.n += h.maxLen;
|
|
218
|
+
return { code: `${h.rust}(${code})`, ty: h.ret };
|
|
219
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Component, type N, type Scope, type Ty, type Val } from "./model.ts";
|
|
2
|
+
import { Emitter } from "./emitter.ts";
|
|
3
|
+
export declare function renderChild(s: Scope, e: Emitter, n: N): void;
|
|
4
|
+
export declare function array(key: string): boolean;
|
|
5
|
+
export declare function fieldInit(name: string, value: string): string;
|
|
6
|
+
export declare function takesSlots(c: Component): boolean;
|
|
7
|
+
export declare function extraParams(c: Component): string;
|
|
8
|
+
export declare function callChild(s: Scope, e: Emitter, child: Component, propsCode: string, slots: N, attrs: string | null): void;
|
|
9
|
+
export declare function ownInto(comp: Component, v: Val, want: Ty, node: N): string;
|
package/dist/children.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { isSSRSafeAttrName, propsToAttrMap } from "@vue/shared";
|
|
2
|
+
import { camelize, declares, fail, GenError, rustStr, sameTy, snake, takesAttrs } from "./model.js";
|
|
3
|
+
import { ctx } from "./context.js";
|
|
4
|
+
import { markHome } from "./typescript.js";
|
|
5
|
+
import { expr, fieldVal } 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) {
|
|
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 = childName ? s.components.get(childName) : undefined;
|
|
23
|
+
if (!child)
|
|
24
|
+
fail(s.comp, "a child component must be an imported island", 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, `\`${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
|
+
const v = expr(s, objects[0]);
|
|
43
|
+
const own = child.name === s.comp.name && v.ty.k === "struct" && v.ty.name === "Props";
|
|
44
|
+
if (own || (v.ty.k === "child" && v.ty.name === child.name)) {
|
|
45
|
+
callChild(s, e, child, v.code, slots, childAttrsArg(s, child, parts, merges, null, ids));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
fail(s.comp, `child props must be an object literal, or \`v-bind\` of ${child.name}'s own \`Props\``, n);
|
|
49
|
+
}
|
|
50
|
+
for (const p of objects) {
|
|
51
|
+
if (p.type !== "ObjectExpression")
|
|
52
|
+
fail(s.comp, `child props must be an object literal, or \`v-bind\` of ${child.name}'s own \`Props\``, p);
|
|
53
|
+
}
|
|
54
|
+
const given = new Map();
|
|
55
|
+
const fallthrough = new Set();
|
|
56
|
+
for (const obj of objects) {
|
|
57
|
+
for (const p of obj.properties) {
|
|
58
|
+
if (p.type !== "ObjectProperty" || p.computed)
|
|
59
|
+
fail(s.comp, "child props hold plain keys", p);
|
|
60
|
+
const key = p.key.type === "Identifier" ? p.key.name : String(p.key.value);
|
|
61
|
+
if (!passedKey(child, key))
|
|
62
|
+
continue;
|
|
63
|
+
const field = child.props.fields.find((f) => camelize(f.js) === camelize(key));
|
|
64
|
+
if (field) {
|
|
65
|
+
given.set(field.js, p.value);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (array(key))
|
|
69
|
+
fail(s.comp, `an attribute named \`${key}\`, which JavaScript would put before the others`, p);
|
|
70
|
+
const name = propsToAttrMap[key] ?? key.toLowerCase();
|
|
71
|
+
if (key !== "class" && key !== "style" && !isSSRSafeAttrName(name))
|
|
72
|
+
fail(s.comp, `unsafe attribute name \`${name}\``, p);
|
|
73
|
+
fallthrough.add(p);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const inits = child.props.fields.map((f) => {
|
|
77
|
+
const node = given.get(f.js);
|
|
78
|
+
if (!node) {
|
|
79
|
+
if (f.ty.k !== "opt")
|
|
80
|
+
fail(s.comp, `${child.name} requires \`${f.js}\``, n);
|
|
81
|
+
return `${f.rust}: None`;
|
|
82
|
+
}
|
|
83
|
+
const v = expr(s, node);
|
|
84
|
+
return fieldInit(f.rust, ownInto(s.comp, { ...v, ty: markHome(v.ty, s.comp.name) }, markHome(f.ty, child.name), node));
|
|
85
|
+
});
|
|
86
|
+
callChild(s, e, child, `&super::${child.module}::Props { ${inits.join(", ")} }`, slots, childAttrsArg(s, child, parts, merges, fallthrough, ids));
|
|
87
|
+
}
|
|
88
|
+
export function array(key) {
|
|
89
|
+
return /^(0|[1-9]\d*)$/.test(key) && Number(key) < 2 ** 32 - 1;
|
|
90
|
+
}
|
|
91
|
+
function childAttrsArg(s, child, parts, merges, fallthrough, ids) {
|
|
92
|
+
const sources = [];
|
|
93
|
+
for (const p of parts) {
|
|
94
|
+
if (s.fallthrough !== null && ((isAttrs(p) && s.comp.inheritAttrs) || dollarAttrs(s, p))) {
|
|
95
|
+
sources.push(`${s.fallthrough}.list()`);
|
|
96
|
+
}
|
|
97
|
+
else if (p.type === "ObjectExpression" && fallthrough !== null) {
|
|
98
|
+
const entries = p.properties.filter((q) => fallthrough.has(q)).map((q) => {
|
|
99
|
+
const key = q.key.type === "Identifier" ? q.key.name : String(q.key.value);
|
|
100
|
+
return `(${rustStr(key)}, ${attrOf(s, key, q.value, "vnode")})`;
|
|
101
|
+
});
|
|
102
|
+
if (entries.length)
|
|
103
|
+
sources.push(`&[${entries.join(", ")}]`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!takesAttrs(child)) {
|
|
107
|
+
if (sources.length)
|
|
108
|
+
throw new GenError(`${s.comp.file}: ${child.name} is passed attributes it does not take`);
|
|
109
|
+
return ids;
|
|
110
|
+
}
|
|
111
|
+
if (!sources.length)
|
|
112
|
+
return ids === null ? "&fv::Attrs::NONE" : `&fv::Attrs::scoped(${ids})`;
|
|
113
|
+
if (sources.length === 1 && !merges)
|
|
114
|
+
return `&fv::Attrs::new(${sources[0]}, ${ids ?? '""'})`;
|
|
115
|
+
return `&fv::Attrs::merged(&[${sources.join(", ")}], ${ids ?? '""'})`;
|
|
116
|
+
}
|
|
117
|
+
export function fieldInit(name, value) {
|
|
118
|
+
return value === name ? name : `${name}: ${bare(value)}`;
|
|
119
|
+
}
|
|
120
|
+
export function takesSlots(c) {
|
|
121
|
+
return c.slotNames.length > 0 || slotFieldsOf(c).length > 0;
|
|
122
|
+
}
|
|
123
|
+
export function extraParams(c) {
|
|
124
|
+
return (takesSlots(c) ? ", fv_slots: Slots<'_>" : "") + paramsOf(c).map((p) => `, ${p.name}: ${p.ty}`).join("");
|
|
125
|
+
}
|
|
126
|
+
export function callChild(s, e, child, propsCode, slots, attrs) {
|
|
127
|
+
const given = new Map();
|
|
128
|
+
if (slots && slots.type !== "NullLiteral") {
|
|
129
|
+
if (slots.type !== "ObjectExpression")
|
|
130
|
+
fail(s.comp, "slots must be an object literal", slots);
|
|
131
|
+
for (const p of slots.properties) {
|
|
132
|
+
const key = p.key?.type === "Identifier" ? p.key.name : p.key?.value;
|
|
133
|
+
if (key === "_")
|
|
134
|
+
continue;
|
|
135
|
+
if (p.type !== "ObjectProperty" || p.computed)
|
|
136
|
+
fail(s.comp, "slots hold plain keys", p);
|
|
137
|
+
if (!child.slotNames.includes(key))
|
|
138
|
+
fail(s.comp, `${child.name} has no slot \`${key}\``, p);
|
|
139
|
+
given.set(key, p.value);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const m = `super::${child.module}`;
|
|
143
|
+
const scoped = child.inherits || takesAttrs(child);
|
|
144
|
+
const trailing = paramsOf(child).map((p) => `, ${p.name}`).join("") + (scoped ? `, ${attrs ?? (takesAttrs(child) ? "&fv::Attrs::NONE" : '""')}` : "");
|
|
145
|
+
const render = scoped ? "render_scoped" : "render";
|
|
146
|
+
if (!takesSlots(child)) {
|
|
147
|
+
e.stmt(`${m}::${render}(out, ${propsCode}${trailing});`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
e.open(`${m}::${render}(out, ${propsCode}, ${m}::Slots`);
|
|
151
|
+
for (const name of child.slotNames) {
|
|
152
|
+
const field = snake(name);
|
|
153
|
+
const value = given.get(name);
|
|
154
|
+
if (!value) {
|
|
155
|
+
e.stmt(`${field}: None,`);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const { body, param } = slotContent(s, value);
|
|
159
|
+
const shape = child.slotShapes.get(name);
|
|
160
|
+
const takesNone = param?.type === "Identifier" && param.name === "_";
|
|
161
|
+
const sid = child.passesSlotIds ? `fv_sid${++ctx.narrowCount}` : null;
|
|
162
|
+
const sidParam = sid === null ? "" : `, ${sid}: &str`;
|
|
163
|
+
const content = (inner) => {
|
|
164
|
+
if (staticallyFilled(body)) {
|
|
165
|
+
statements({ ...inner, fill: false }, e, body);
|
|
166
|
+
e.stmt("true");
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
e.stmt("let mut filled = false;");
|
|
170
|
+
statements({ ...inner, fill: true }, e, body);
|
|
171
|
+
e.stmt("filled");
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const unread = (opened, binding) => {
|
|
175
|
+
if (binding !== null && !e.reads(binding, opened + 1))
|
|
176
|
+
e.replace(opened, `${binding}:`, `_${binding}:`);
|
|
177
|
+
};
|
|
178
|
+
if (shape) {
|
|
179
|
+
const sp = `fv_sp${++ctx.narrowCount}`;
|
|
180
|
+
const ty = { k: "struct", name: shape.name, home: child.name };
|
|
181
|
+
const locals = new Map(s.locals);
|
|
182
|
+
if (param?.type === "ObjectPattern") {
|
|
183
|
+
for (const p of param.properties) {
|
|
184
|
+
if (p.type !== "ObjectProperty" || p.computed || p.value.type !== "Identifier") {
|
|
185
|
+
fail(s.comp, "slot props are destructured into plain names, without defaults", p);
|
|
186
|
+
}
|
|
187
|
+
locals.set(p.value.name, fieldVal(s.comp, sp, ty, p.key.name ?? p.key.value, p));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else if (param?.type === "Identifier" && !takesNone) {
|
|
191
|
+
locals.set(param.name, { code: sp, ty });
|
|
192
|
+
}
|
|
193
|
+
else if (!takesNone)
|
|
194
|
+
fail(s.comp, "slot props are a name or an object pattern", param);
|
|
195
|
+
const life = shape.fields.some((f) => slotFieldBorrows(f.ty)) ? "<'_>" : "";
|
|
196
|
+
e.open(`${field}: Some(&|out: &mut String, ${sp}: &${m}::${shape.name}${life}${sidParam}| -> bool`);
|
|
197
|
+
const opened = e.lines.length - 1;
|
|
198
|
+
content({ ...s, locals, sid });
|
|
199
|
+
unread(opened, sp);
|
|
200
|
+
unread(opened, sid);
|
|
201
|
+
e.close("),");
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (!takesNone)
|
|
205
|
+
fail(s.comp, `\`<slot${name === "default" ? "" : ` name="${name}"`}>\` in ${child.name} passes no props`, param);
|
|
206
|
+
const inner = { ...s, sid };
|
|
207
|
+
if (sid !== null) {
|
|
208
|
+
e.open(`${field}: Some(fv::Slot::slotted(&|out: &mut String${sidParam}| -> bool`);
|
|
209
|
+
const opened = e.lines.length - 1;
|
|
210
|
+
content(inner);
|
|
211
|
+
unread(opened, sid);
|
|
212
|
+
e.close(")),");
|
|
213
|
+
}
|
|
214
|
+
else if (staticallyFilled(body)) {
|
|
215
|
+
e.open(`${field}: Some(fv::Slot::new(&|out: &mut String|`);
|
|
216
|
+
statements({ ...inner, fill: false }, e, body);
|
|
217
|
+
e.close(")),");
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
e.open(`${field}: Some(fv::Slot::markup(&|out: &mut String| -> bool`);
|
|
221
|
+
e.stmt("let mut filled = false;");
|
|
222
|
+
statements({ ...inner, fill: true }, e, body);
|
|
223
|
+
e.stmt("filled");
|
|
224
|
+
e.close(")),");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
e.close(`${trailing});`);
|
|
228
|
+
}
|
|
229
|
+
export function ownInto(comp, v, want, node) {
|
|
230
|
+
if (want.k === "str") {
|
|
231
|
+
if (v.ty.k !== "str")
|
|
232
|
+
fail(comp, "a string prop needs a string", node);
|
|
233
|
+
return `std::borrow::Cow::Borrowed(${bare(v.code)})`;
|
|
234
|
+
}
|
|
235
|
+
if (want.k === "opt" && want.of.k === "str") {
|
|
236
|
+
if (v.ty.k === "undef")
|
|
237
|
+
return "None";
|
|
238
|
+
if (v.ty.k === "str")
|
|
239
|
+
return `Some(std::borrow::Cow::Borrowed(${bare(v.code)}))`;
|
|
240
|
+
if (v.ty.k === "opt" && v.ty.of.k === "str")
|
|
241
|
+
return `${atom(v.code)}.map(std::borrow::Cow::Borrowed)`;
|
|
242
|
+
}
|
|
243
|
+
if (sameTy(v.ty, want) && (want.k === "int" || want.k === "float" || want.k === "bool"))
|
|
244
|
+
return v.code;
|
|
245
|
+
if (want.k === "opt" && sameTy(v.ty, want.of) && (v.ty.k === "int" || v.ty.k === "float" || v.ty.k === "bool"))
|
|
246
|
+
return `Some(${bare(v.code)})`;
|
|
247
|
+
if (want.k === "float" && v.ty.k === "int")
|
|
248
|
+
return asF64(v);
|
|
249
|
+
if (want.k === "opt" && want.of.k === "float" && v.ty.k === "int")
|
|
250
|
+
return `Some(${asF64(v)})`;
|
|
251
|
+
if (v.iter !== undefined && v.ty.k === "list" && want.k === "list" && sameTy(v.ty.of, want.of)) {
|
|
252
|
+
return want.of.k === "struct" || want.of.k === "child" ? `${v.iter}.cloned().collect()` : `${v.iter}.collect()`;
|
|
253
|
+
}
|
|
254
|
+
const objecty = (t) => t.k === "struct" || t.k === "child" || t.k === "record" || ((t.k === "list" || t.k === "opt") && objecty(t.of));
|
|
255
|
+
if (objecty(want) && sameTy(v.ty, want))
|
|
256
|
+
return `${atom(v.code)}.${v.ty.k === "opt" ? "cloned" : "to_owned"}()`;
|
|
257
|
+
if (want.k === "opt" && objecty(want.of) && sameTy(v.ty, want.of))
|
|
258
|
+
return `Some(${atom(v.code)}.to_owned())`;
|
|
259
|
+
if (objecty(want) && v.ty.k === want.k && JSON.stringify(v.ty).includes('"struct"')) {
|
|
260
|
+
fail(comp, `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);
|
|
261
|
+
}
|
|
262
|
+
if (want.k === "list" && v.ty.k === "list" && v.ty.of.k === "undef")
|
|
263
|
+
return "Vec::new()";
|
|
264
|
+
if (want.k === "opt" && want.of.k === "list" && v.ty.k === "list")
|
|
265
|
+
return `Some(${ownInto(comp, v, want.of, node)})`;
|
|
266
|
+
if (want.k === "list" && v.ty.k === "list" && sameTy(v.ty.of, want.of)) {
|
|
267
|
+
if (want.of.k === "str")
|
|
268
|
+
return `${atom(v.code)}.iter().map(|v| std::borrow::Cow::Borrowed(&**v)).collect()`;
|
|
269
|
+
if (want.of.k === "int" || want.of.k === "float" || want.of.k === "bool")
|
|
270
|
+
return `${atom(v.code)}.to_vec()`;
|
|
271
|
+
}
|
|
272
|
+
return fail(comp, `a ${JSON.stringify(v.ty)} into a ${JSON.stringify(want)} prop`, node);
|
|
273
|
+
}
|
|
@@ -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;
|