ferrovue 0.4.0 → 0.6.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.
Files changed (92) hide show
  1. package/README.md +378 -63
  2. package/dist/attrs.d.ts +3 -2
  3. package/dist/attrs.js +31 -26
  4. package/dist/calls.js +17 -15
  5. package/dist/children.d.ts +11 -4
  6. package/dist/children.js +110 -44
  7. package/dist/classes.d.ts +4 -0
  8. package/dist/classes.js +33 -11
  9. package/dist/cli.js +31 -5
  10. package/dist/client.d.ts +44 -6
  11. package/dist/client.js +182 -18
  12. package/dist/compiler.d.ts +1 -1
  13. package/dist/compiler.js +42 -10
  14. package/dist/component.d.ts +3 -1
  15. package/dist/component.js +114 -20
  16. package/dist/conformance.d.ts +64 -0
  17. package/dist/conformance.js +276 -0
  18. package/dist/constants.d.ts +42 -0
  19. package/dist/constants.js +292 -0
  20. package/dist/context.d.ts +40 -7
  21. package/dist/context.js +14 -5
  22. package/dist/diagnostics.d.ts +23 -0
  23. package/dist/diagnostics.js +30 -0
  24. package/dist/dynamic.d.ts +18 -0
  25. package/dist/dynamic.js +576 -0
  26. package/dist/errors.d.ts +1034 -0
  27. package/dist/errors.js +425 -0
  28. package/dist/expr.d.ts +1 -0
  29. package/dist/expr.js +120 -49
  30. package/dist/file-routes.d.ts +33 -0
  31. package/dist/file-routes.js +224 -0
  32. package/dist/fixture.d.ts +19 -10
  33. package/dist/fixture.js +23 -12
  34. package/dist/hydration.d.ts +15 -0
  35. package/dist/hydration.js +80 -0
  36. package/dist/index.d.ts +3 -2
  37. package/dist/index.js +2 -1
  38. package/dist/link-router.d.ts +24 -0
  39. package/dist/link-router.js +37 -0
  40. package/dist/lists.d.ts +4 -0
  41. package/dist/lists.js +32 -23
  42. package/dist/loops.js +18 -14
  43. package/dist/model.d.ts +43 -1
  44. package/dist/model.js +167 -7
  45. package/dist/narrowing.d.ts +8 -0
  46. package/dist/narrowing.js +40 -9
  47. package/dist/numbers.js +3 -2
  48. package/dist/page-routes.d.ts +9 -0
  49. package/dist/page-routes.js +10 -0
  50. package/dist/plugin.d.ts +13 -1
  51. package/dist/plugin.js +3 -0
  52. package/dist/plugins/client-only.d.ts +8 -0
  53. package/dist/plugins/client-only.js +110 -0
  54. package/dist/plugins/head.d.ts +15 -0
  55. package/dist/plugins/head.js +385 -0
  56. package/dist/plugins/i18n.js +14 -14
  57. package/dist/plugins/index.js +5 -1
  58. package/dist/plugins/provide.d.ts +44 -0
  59. package/dist/plugins/provide.js +528 -0
  60. package/dist/plugins/router-link.js +39 -32
  61. package/dist/plugins/router.d.ts +4 -1
  62. package/dist/plugins/router.js +65 -19
  63. package/dist/plugins/scoped.js +1 -1
  64. package/dist/plugins/shared-types.js +1 -1
  65. package/dist/plugins/stores.js +45 -33
  66. package/dist/plugins/teleport.js +3 -3
  67. package/dist/plugins/twins.d.ts +10 -0
  68. package/dist/plugins/twins.js +164 -0
  69. package/dist/routes.d.ts +13 -0
  70. package/dist/routes.js +12 -0
  71. package/dist/rust.js +64 -32
  72. package/dist/script.d.ts +3 -0
  73. package/dist/script.js +138 -20
  74. package/dist/settle.d.ts +8 -0
  75. package/dist/settle.js +39 -0
  76. package/dist/slots.js +36 -22
  77. package/dist/ssr.d.ts +3 -0
  78. package/dist/ssr.js +29 -0
  79. package/dist/strings.d.ts +3 -0
  80. package/dist/strings.js +27 -13
  81. package/dist/styles.js +13 -13
  82. package/dist/template.js +102 -21
  83. package/dist/testing.d.ts +3 -3
  84. package/dist/testing.js +3 -29
  85. package/dist/types.d.ts +5 -0
  86. package/dist/typescript.d.ts +2 -1
  87. package/dist/typescript.js +52 -21
  88. package/dist/vhtml.d.ts +4 -0
  89. package/dist/vhtml.js +63 -0
  90. package/dist/vite.d.ts +8 -2
  91. package/dist/vite.js +75 -14
  92. package/package.json +23 -5
package/dist/attrs.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import { type N, type Scope, type Val } from "./model.ts";
2
2
  import { Emitter } from "./emitter.ts";
3
3
  export declare function display(e: Emitter, v: Val): boolean;
4
- export declare function interpolate(e: Emitter, v: Val): void;
4
+ export declare function interpolate(s: Scope, e: Emitter, v: Val, n: N): void;
5
5
  export declare function attrValue(e: Emitter, v: Val): void;
6
6
  export declare function renderAttr(s: Scope, e: Emitter, key: string, v: Val, n: N): void;
7
- export declare function renderDynamicAttr(s: Scope, e: Emitter, key: string, v: Val, n: N): void;
7
+ export declare function keepsAttrCase(tag: string): boolean;
8
+ export declare function renderDynamicAttr(s: Scope, e: Emitter, key: string, v: Val, n: N, tag?: string | null): void;
8
9
  export declare const IGNORED_PROPS: Set<string>;
9
10
  export declare function directiveProps(s: Scope, n: N): boolean;
10
11
  export declare function isAttrs(n: N): boolean;
package/dist/attrs.js CHANGED
@@ -1,5 +1,5 @@
1
- import { escapeHtml, hyphenate, isBooleanAttr, isSSRSafeAttrName, propsToAttrMap } from "@vue/shared";
2
- import { fail, GenError, rustStr } from "./model.js";
1
+ import { escapeHtml, hyphenate, isBooleanAttr, isSSRSafeAttrName, isSVGTag, propsToAttrMap } from "@vue/shared";
2
+ import { fail, nothing, GenError, rustStr } from "./model.js";
3
3
  import { CONFIG_FILE, ctx } from "./context.js";
4
4
  import { describeTy, expr } from "./expr.js";
5
5
  import { known, truthy } from "./narrowing.js";
@@ -30,39 +30,40 @@ export function display(e, v) {
30
30
  return false;
31
31
  return true;
32
32
  }
33
- export function interpolate(e, v) {
33
+ export function interpolate(s, e, v, n) {
34
34
  if (display(e, v))
35
35
  return;
36
36
  switch (v.ty.k) {
37
37
  case "undef":
38
+ case "null":
38
39
  return;
39
40
  case "opt":
40
41
  e.open(`if let Some(v) = ${v.code}`);
41
- interpolate(e, { code: "v", ty: v.ty.of });
42
+ interpolate(s, e, { code: "v", ty: v.ty.of }, n);
42
43
  e.close();
43
44
  return;
44
45
  default:
45
46
  if (ctx.plugins.some((p) => p.values?.interpolate?.(e, v)))
46
47
  return;
47
- throw new GenError("only strings, numbers and booleans can be interpolated");
48
+ fail(s.comp, "FV0401", `\`{{ }}\` of ${describeTy(v.ty)}: only strings, numbers and booleans can be interpolated`, n);
48
49
  }
49
50
  }
50
51
  export function attrValue(e, v) {
51
52
  if (!display(e, v))
52
- throw new GenError("an attribute value must be a string, a number or a boolean");
53
+ throw new GenError("FV0402", "an attribute value must be a string, a number or a boolean");
53
54
  }
54
55
  function renderable(s, key, v, n) {
55
56
  const ty = v.ty.k === "opt" ? v.ty.of : v.ty;
56
- if (ty.k === "str" || ty.k === "int" || ty.k === "float" || ty.k === "bool" || ty.k === "undef")
57
+ if (ty.k === "str" || ty.k === "int" || ty.k === "float" || ty.k === "bool" || nothing(ty))
57
58
  return;
58
59
  const own = claim((p) => p.values?.unbindable?.(ty));
59
60
  const what = own?.what ?? describeTy(ty);
60
61
  const fix = own?.fix ?? (ty.k === "list" ? 'join it into one string, as `.join(",")`' : "bind a string, a number or a boolean");
61
- fail(s.comp, `\`${key}\` is bound to ${what}: Vue's server renderer leaves the attribute out, and hydration then sets it, changing the page; ${fix}`, n);
62
+ fail(s.comp, "FV0403", `\`${key}\` is bound to ${what}: Vue's server renderer leaves the attribute out, and hydration then sets it, changing the page; ${fix}`, n);
62
63
  }
63
64
  export function renderAttr(s, e, key, v, n) {
64
65
  renderable(s, key, v, n);
65
- if (v.ty.k === "undef")
66
+ if (nothing(v.ty))
66
67
  return;
67
68
  if (v.ty.k === "opt") {
68
69
  e.open(`if let Some(v) = ${v.code}`);
@@ -74,12 +75,15 @@ export function renderAttr(s, e, key, v, n) {
74
75
  attrValue(e, v);
75
76
  e.lit(`"`);
76
77
  }
77
- export function renderDynamicAttr(s, e, key, v, n) {
78
- const name = propsToAttrMap[key] ?? key.toLowerCase();
78
+ export function keepsAttrCase(tag) {
79
+ return tag.indexOf("-") > 0 || isSVGTag(tag);
80
+ }
81
+ export function renderDynamicAttr(s, e, key, v, n, tag = null) {
82
+ const name = tag !== null && keepsAttrCase(tag) ? key : (propsToAttrMap[key] ?? key.toLowerCase());
79
83
  if (!isSSRSafeAttrName(name))
80
- fail(s.comp, `unsafe attribute name \`${name}\``, n);
84
+ fail(s.comp, "FV0404", `unsafe attribute name \`${name}\``, n);
81
85
  renderable(s, key, v, n);
82
- if (v.ty.k === "undef")
86
+ if (nothing(v.ty))
83
87
  return;
84
88
  const boolean = (t) => isBooleanAttr(name) || (name === "hidden" && (t.k === "bool" || t.k === "int" || t.k === "float"));
85
89
  if (v.ty.k === "opt" && boolean(v.ty.of)) {
@@ -90,7 +94,7 @@ export function renderDynamicAttr(s, e, key, v, n) {
90
94
  }
91
95
  if (v.ty.k === "opt") {
92
96
  e.open(`if let Some(v) = ${v.code}`);
93
- renderDynamicAttr(s, e, key, { code: "v", ty: v.ty.of }, n);
97
+ renderDynamicAttr(s, e, key, { code: "v", ty: v.ty.of }, n, tag);
94
98
  e.close();
95
99
  return;
96
100
  }
@@ -140,7 +144,7 @@ export function directiveProps(s, n) {
140
144
  else if (dir?.type === "Identifier")
141
145
  name = s.directives.get(dir.name) ?? null;
142
146
  if (name === null || !ctx.clientDirectives.has(name)) {
143
- fail(s.comp, `custom directive \`v-${name ?? "?"}\` may add attributes on the server; if it has no \`getSSRProps\`, list \`${name ?? "?"}\` in \`clientDirectives\` in ${CONFIG_FILE}`, n);
147
+ fail(s.comp, "FV0405", `custom directive \`v-${name ?? "?"}\` may add attributes on the server; if it has no \`getSSRProps\`, list \`${name ?? "?"}\` in \`clientDirectives\` in ${CONFIG_FILE}`, n);
144
148
  }
145
149
  return true;
146
150
  }
@@ -151,7 +155,7 @@ export function dollarAttrs(s, n) {
151
155
  if (!isDollarAttrs(n, s.attrsBindings))
152
156
  return false;
153
157
  if (s.comp.idsInAttrs) {
154
- fail(s.comp, `\`$attrs\` in ${s.comp.name}, the root of a component that may be handed scope ids: it would hold them as attributes too`, n);
158
+ fail(s.comp, "FV0406", `\`$attrs\` in ${s.comp.name}, the root of a component that may be handed scope ids: it would hold them as attributes too`, n);
155
159
  }
156
160
  return true;
157
161
  }
@@ -192,7 +196,7 @@ export function renderAttrs(s, e, n) {
192
196
  else if (p.type === "ObjectExpression")
193
197
  sources.push(`&[${attrEntries(s, p, "own").join(", ")}]`);
194
198
  else
195
- fail(s.comp, "attributes must be an object literal", p);
199
+ fail(s.comp, "FV0407", "attributes must be an object literal", p);
196
200
  }
197
201
  const ids = idsAt >= 0 && s.attrs !== null ? s.attrs : '""';
198
202
  e.stmt(`fv::attrs_into(out, &[${sources.join(", ")}], ${idsAt >= 0 ? idsAt : sources.length}, ${ids});`);
@@ -223,16 +227,16 @@ function staticAttrs(s, e, n) {
223
227
  }
224
228
  function objectAttrs(s, e, n, merged) {
225
229
  if (n.type !== "ObjectExpression")
226
- fail(s.comp, "attributes must be an object literal", n);
230
+ fail(s.comp, "FV0407", "attributes must be an object literal", n);
227
231
  for (const p of n.properties) {
228
232
  if (p.type !== "ObjectProperty" || p.computed)
229
- fail(s.comp, "attribute objects hold plain keys", p);
233
+ fail(s.comp, "FV0408", "attribute objects hold plain keys", p);
230
234
  const raw = p.key.type === "Identifier" ? p.key.name : p.key.value;
231
235
  if (IGNORED_PROPS.has(raw) || /^on[^a-z]/.test(raw) || raw.startsWith("."))
232
236
  continue;
233
237
  const key = raw.startsWith("^") ? raw.slice(1) : raw;
234
238
  if (key === "className") {
235
- fail(s.comp, "`className` renders by its own rule in Vue; bind `class`", p);
239
+ fail(s.comp, "FV0409", "`className` renders by its own rule in Vue; bind `class`", p);
236
240
  }
237
241
  else if (key === "class") {
238
242
  const present = merged ? classPresent(s, p.value) : "true";
@@ -267,16 +271,16 @@ export function mergeProps(s, n) {
267
271
  if (directiveProps(s, a))
268
272
  continue;
269
273
  if (a.type !== "ObjectExpression")
270
- fail(s.comp, "`_mergeProps` of object literals is supported", a);
274
+ fail(s.comp, "FV0410", "`_mergeProps` of object literals is supported", a);
271
275
  for (const p of a.properties) {
272
276
  if (p.type !== "ObjectProperty" || p.computed)
273
- fail(s.comp, "attribute objects hold plain keys", p);
277
+ fail(s.comp, "FV0408", "attribute objects hold plain keys", p);
274
278
  const key = p.key.type === "Identifier" ? p.key.name : p.key.value;
275
279
  const had = merged.get(key);
276
280
  if (had && key === "class" && maybeEqual(s, p.value)) {
277
281
  const before = literalClass(had.value);
278
282
  if (before === null || p.value.type !== "StringLiteral") {
279
- fail(s.comp, "a class merged with a string that may equal the class before it, which Vue then writes once: bind an array", p);
283
+ fail(s.comp, "FV0411", "a class merged with a string that may equal the class before it, which Vue then writes once: bind an array", p);
280
284
  }
281
285
  if (before !== p.value.value)
282
286
  merged.set(key, { ...p, value: { type: "ArrayExpression", elements: [had.value, p.value], fvMerged: true } });
@@ -297,7 +301,7 @@ export function attrEntries(s, obj, side) {
297
301
  const out = [];
298
302
  for (const p of obj.properties) {
299
303
  if (p.type !== "ObjectProperty" || p.computed)
300
- fail(s.comp, "attribute objects hold plain keys", p);
304
+ fail(s.comp, "FV0408", "attribute objects hold plain keys", p);
301
305
  const key = p.key.type === "Identifier" ? p.key.name : String(p.key.value);
302
306
  if (IGNORED_PROPS.has(key) || /^on[^a-z]/.test(key) || key.startsWith("."))
303
307
  continue;
@@ -307,7 +311,7 @@ export function attrEntries(s, obj, side) {
307
311
  }
308
312
  export function attrOf(s, key, n, side) {
309
313
  if (key === "className")
310
- fail(s.comp, "`className` renders by its own rule in Vue; bind `class`", n);
314
+ fail(s.comp, "FV0409", "`className` renders by its own rule in Vue; bind `class`", n);
311
315
  if (key === "class")
312
316
  return classAttr(s, n, side);
313
317
  if (key === "style")
@@ -327,6 +331,7 @@ export function valueAttr(s, v, n) {
327
331
  case "bool":
328
332
  return `fv::Attr::Bool(${bare(v.code)})`;
329
333
  case "undef":
334
+ case "null":
330
335
  return "fv::Attr::Undefined";
331
336
  case "opt": {
332
337
  const of = v.ty.of.k;
@@ -335,5 +340,5 @@ export function valueAttr(s, v, n) {
335
340
  return `${atom(v.code)}.map_or(fv::Attr::Undefined, ${make})`;
336
341
  }
337
342
  }
338
- return fail(s.comp, "an attribute that may fall through is a string, a number or a boolean", n);
343
+ return fail(s.comp, "FV0412", "an attribute that may fall through is a string, a number or a boolean", n);
339
344
  }
package/dist/calls.js CHANGED
@@ -20,7 +20,7 @@ export function call(s, n) {
20
20
  meet(comp, a, b, "`v-model`", n, "equal");
21
21
  return boolOf(stringsEqual(a, b));
22
22
  }
23
- return fail(comp, "`v-model` comparison between these types", n);
23
+ return fail(comp, "FV0413", "`v-model` comparison between these types", n);
24
24
  }
25
25
  case "_ssrIncludeBooleanAttr": {
26
26
  const a = expr(s, args[0]);
@@ -33,7 +33,7 @@ export function call(s, n) {
33
33
  return boolOf(truthy(a));
34
34
  }
35
35
  case "_ssrLooseContain":
36
- return fail(comp, "`v-model` over an array", n);
36
+ return fail(comp, "FV0414", "`v-model` over an array", n);
37
37
  }
38
38
  const own = claim((p) => p.call?.(s, n));
39
39
  if (own)
@@ -58,21 +58,21 @@ export function call(s, n) {
58
58
  return a;
59
59
  if (a.ty.k === "bool")
60
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);
61
+ return fail(comp, "FV0701", "`Number()` takes a string, a number or a boolean that is present", n);
62
62
  }
63
63
  if ((callee.name === "parseInt" && (args.length === 1 || args.length === 2)) || (callee.name === "parseFloat" && args.length === 1)) {
64
64
  const a = expr(s, args[0]);
65
65
  if (a.ty.k !== "str")
66
- fail(comp, `\`${callee.name}()\` takes a string`, args[0]);
66
+ fail(comp, "FV0702", `\`${callee.name}()\` takes a string`, args[0]);
67
67
  if (callee.name === "parseFloat")
68
68
  return { code: `fv::js_parse_float(${strArg(a.code)})`, ty: FLOAT };
69
69
  const radix = args[1];
70
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);
71
+ fail(comp, "FV0703", "`parseInt()` takes a radix of 10 or 16, written as a literal", radix);
72
72
  }
73
73
  return { code: `fv::js_parse_int(${strArg(a.code)}, ${radix ? radix.value : 0})`, ty: FLOAT };
74
74
  }
75
- return fail(comp, `\`${callee.name}()\` is not available when rendering on the server`, n);
75
+ return fail(comp, "FV0601", `\`${callee.name}()\` is not available when rendering on the server`, n);
76
76
  }
77
77
  if (callee.type === "MemberExpression" && !callee.computed) {
78
78
  const method = callee.property.name;
@@ -119,7 +119,7 @@ export function call(s, n) {
119
119
  return { code: `${receiver(asF64(x))}.${method}()`, ty: FLOAT };
120
120
  }
121
121
  }
122
- return fail(comp, `\`Math.${method}()\` is supported on numbers as \`max\`, \`min\`, \`abs\`, \`round\`, \`floor\`, \`ceil\` and \`trunc\``, n);
122
+ return fail(comp, "FV0704", `\`Math.${method}()\` is supported on numbers as \`max\`, \`min\`, \`abs\`, \`round\`, \`floor\`, \`ceil\` and \`trunc\``, n);
123
123
  }
124
124
  const target = expr(s, callee.object);
125
125
  if (target.ty.k === "str") {
@@ -145,7 +145,7 @@ export function call(s, n) {
145
145
  if (isNumber(target.ty) && method === "toFixed" && args.length <= 1) {
146
146
  const d = args.length ? args[0] : { type: "NumericLiteral", value: 0 };
147
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);
148
+ fail(comp, "FV0705", "`.toFixed()` takes a literal number of digits, from 0 to 100", n);
149
149
  }
150
150
  return { code: `&*fv::js_to_fixed(${bare(asF64(target))}, ${d.value})`, ty: STR };
151
151
  }
@@ -162,8 +162,10 @@ export function call(s, n) {
162
162
  const of = target.ty.of;
163
163
  if (method === "includes" && args.length === 1) {
164
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 };
165
167
  if (!sameTy(v.ty, of))
166
- fail(comp, "`.includes()` looks for a value of the list's own type", args[0]);
168
+ fail(comp, "FV0801", "`.includes()` looks for a value of the list's own type", args[0]);
167
169
  if (of.k === "int")
168
170
  return { code: `${atom(target.code)}.contains(&${operand(v.code, UNARY)})`, ty: BOOL };
169
171
  const test = v.code === '""' ? "v.is_empty()" : binary("&**v", "==", v.code);
@@ -172,14 +174,14 @@ export function call(s, n) {
172
174
  if (method === "join" && args.length <= 1) {
173
175
  const sep = args.length ? expr(s, args[0]) : { code: '","', ty: STR };
174
176
  if (sep.ty.k !== "str")
175
- fail(comp, "`.join()` takes a string", args[0]);
177
+ fail(comp, "FV0802", "`.join()` takes a string", args[0]);
176
178
  const each = of.k === "str" ? `${atom(target.code)}.iter().map(|v| &**v)` : `${atom(target.code)}.iter().map(|v| fv::Js(*v).to_string())`;
177
179
  return { code: `&*${each}.collect::<Vec<_>>().join(${strArg(sep.code)})`, ty: STR, ...loneOf(sep) };
178
180
  }
179
181
  }
180
- return fail(comp, `\`.${method}()\` is not supported`, n);
182
+ return fail(comp, "FV0602", `\`.${method}()\` is not supported`, n);
181
183
  }
182
- return fail(comp, "this call is not supported", n);
184
+ return fail(comp, "FV0603", "this call is not supported", n);
183
185
  }
184
186
  export function isObjectCall(n, method) {
185
187
  return (n?.type === "CallExpression" && n.callee.type === "MemberExpression" && !n.callee.computed &&
@@ -187,7 +189,7 @@ export function isObjectCall(n, method) {
187
189
  }
188
190
  function json(s, v, n) {
189
191
  if (v.lone)
190
- fail(s.comp, lonely("`JSON.stringify()`"), n);
192
+ fail(s.comp, "FV0706", lonely("`JSON.stringify()`"), n);
191
193
  const one = (code, ty) => {
192
194
  switch (ty.k) {
193
195
  case "str":
@@ -199,7 +201,7 @@ function json(s, v, n) {
199
201
  case "bool":
200
202
  return `(if ${condition(code)} { "true" } else { "false" }).to_owned()`;
201
203
  default:
202
- return fail(s.comp, "`JSON.stringify()` of a string, a number, a boolean or a list of those, present", n);
204
+ return fail(s.comp, "FV0707", "`JSON.stringify()` of a string, a number, a boolean or a list of those, present", n);
203
205
  }
204
206
  };
205
207
  if (v.ty.k === "list") {
@@ -212,7 +214,7 @@ function json(s, v, n) {
212
214
  export function helperCall(s, name, args, n) {
213
215
  const h = ctx.helpers[name];
214
216
  if (args.length !== h.params.length)
215
- fail(s.comp, `\`${name}\` takes ${h.params.length} argument(s)`, n);
217
+ fail(s.comp, "FV1101", `\`${name}\` takes ${h.params.length} argument(s)`, n);
216
218
  const code = args.map((a, i) => bare(coerce(s.comp, expr(s, a), h.params[i], a))).join(", ");
217
219
  s.helperBytes.n += h.maxLen;
218
220
  return { code: `${h.rust}(${code})`, ty: h.ret };
@@ -1,9 +1,16 @@
1
- import { type Component, type N, type Scope, type Ty, type Val } from "./model.ts";
1
+ import { type Component, type Field, type N, type Scope, type Ty, type Val } from "./model.ts";
2
2
  import { Emitter } from "./emitter.ts";
3
- export declare function renderChild(s: Scope, e: Emitter, n: N): void;
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;
4
10
  export declare function array(key: string): boolean;
5
11
  export declare function fieldInit(name: string, value: string): string;
6
12
  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;
13
+ export declare function extraParams(c: Component, unread?: (name: string) => boolean): string;
14
+ export declare function callChild(s: Scope, e: Emitter, child: Component, propsCode: string, slots: N, attrs: string | null, later?: boolean): 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;
9
16
  export declare function ownInto(comp: Component, v: Val, want: Ty, node: N): string;