ferrovue 0.14.0 → 0.14.1
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 +2 -2
- package/dist/attrs.d.ts +2 -1
- package/dist/attrs.js +37 -16
- package/dist/classes.d.ts +2 -1
- package/dist/classes.js +45 -6
- package/dist/conditional.js +13 -4
- package/dist/constants.d.ts +22 -2
- package/dist/constants.js +87 -5
- package/dist/context.d.ts +3 -5
- package/dist/dynamic.js +3 -2
- package/dist/functions.d.ts +1 -4
- package/dist/functions.js +10 -11
- package/dist/link-routes.d.ts +1 -1
- package/dist/link-routes.js +2 -1
- package/dist/model.d.ts +8 -0
- package/dist/plugins/loaders.js +3 -4
- package/dist/plugins/provide.js +6 -6
- package/dist/script.js +10 -12
- package/dist/strings.d.ts +3 -1
- package/dist/strings.js +7 -0
- package/dist/typescript.js +4 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ the configuration at your project root yourself:
|
|
|
57
57
|
|
|
58
58
|
```json
|
|
59
59
|
{
|
|
60
|
-
"$schema": "https://cdn.jsdelivr.net/npm/ferrovue@0.14.
|
|
60
|
+
"$schema": "https://cdn.jsdelivr.net/npm/ferrovue@0.14.1/schema.json",
|
|
61
61
|
"components": "client/components",
|
|
62
62
|
"out": "src/generated"
|
|
63
63
|
}
|
|
@@ -226,7 +226,7 @@ ferrovue compiles `<script setup lang="ts">` components, and components with no
|
|
|
226
226
|
|---|---|
|
|
227
227
|
| Prop types | `string`, `number` (integers, `i64`, written and computed as JavaScript does, exact within ±2⁵³), `Float` from `ferrovue/types` (fractions, `f64`), `boolean`, unions of two or more string literals (`"sm" \| "md"`, an alias, or written in place), each a Rust enum generated for it (`Size::Md`, `"md".parse::<Size>()`, written and serialised as its literal; see the crate's [`props`](https://docs.rs/ferrovue/latest/ferrovue/guide/props/index.html#unions-of-string-literals) guide), or an enum of the application's own that `unions` names for the alias; a list of such a union, a list of its enum (`Size[]` is `Vec<Size>`); `T \| undefined`, `T \| null` (an `Option` written as `null`), arrays (`T[]`, `Array<T>`, `readonly T[]`), interfaces and object type aliases (recursive ones too), object types written in place, mapped types and `Record`s over a union of names, indexed types (`Card["size"]`), tuples (`[label: string, n: number]`, a tuple struct written as an array), dictionaries (`Record<string, T>`, `{ [key: string]: T }`, `ferrovue::props::Record` in Rust, which keeps JavaScript's order of keys), another component's exported `Props`, `TrustedHtml`, `InlineHtml`, `Date` (a prop, optional or nullable too, and inside lists, dictionaries and interfaces: `ferrovue::date::JsDate`, or `chrono::DateTime<Utc>` with the `chrono` feature) |
|
|
228
228
|
| Shared types | Interfaces and type aliases imported from `.ts` files (generated once, into `types.rs`; imported by a relative path or through an alias the project declares, in its tsconfig.json's `compilerOptions.paths` or, with the Vite plugin, Vite's `resolve.alias`: `@/lib/format`), from a store's file, or from another component's `.vue` file; objects of a shared type can be passed between components. A TypeScript `enum` of string members is a Rust enum, as a union of string literals is, its variants named after its members; one of numbers is a number |
|
|
229
|
-
| Constants | Constants imported from `.ts` files, object constants declared in `<script setup>`, and `enum`s (imported, or declared in the component, with members set to such constants or to earlier members), evaluated when the component is compiled: strings, numbers, booleans, `null`, arithmetic and joined strings of them as JavaScript evaluates them (`60 * MINUTE`, `` `${BASE}/feed` ``, `"#" + 1.5`, and `**` where it gives an exact integer within ±2⁵³), lists of them, objects of them read field by field (`LABELS.save`, `Tone.Loud`, `Rank[5]`), and lists of objects of them, which become a `const` in `types.rs` (`{ value, label }` items get a type named after the constant, `OptionsItem`, unless the constant is typed with an interface: `OPTIONS: Option[]`; a field that is `null` in some items is `T | null`); lookup tables: an object constant (`as const`, `Record<Size, T>` or untyped, keys computed from enum members too) read by a key whose type is a union of string or integer literals or an `enum` (a prop, a `computed`, a `v-for` item's field, a `?:` of literals, or built from one by `.toUpperCase()`, `.toLowerCase()` or a template literal), or by a constant number (`O[HALF]` reads `O["0.5"]`), compiled to a `match` over the members (over the variants of a union's enum, with no catch-all arm; a table of a union's members, `Record<Tone, Tone>`, reaches a union prop as a `match` from variant to variant), read on by field (`THEME[tone].fg`) in text, attributes, `:class` and `:style`, with `undefined` for a member the object lacks (`ICONS[kind] ?? "none"`, `ICONS[kind]?.label`, `v-if="ICONS[kind]"`, `kind && ICONS[kind]`); a free-text key tested with `Object.hasOwn(ROLES, key)`, against the object's own keys, which narrows the key for `ROLES[key]` in what follows; `Object.keys()` of an object constant; a constant `new Map([[key, value], …])` of string or integer keys, read with `.get(key)` (`undefined` for a key it lacks) and `.has(key)`. See the crate's [`props`](https://docs.rs/ferrovue/latest/ferrovue/guide/props/index.html#constants) guide |
|
|
229
|
+
| Constants | Constants imported from `.ts` files (exported on their declaration, by name in `export { A, B as C }`, as `export default A`, or passed on with `export { A } from` and `export * from`), object constants declared in `<script setup>`, and `enum`s (imported, or declared in the component, with members set to such constants or to earlier members), evaluated when the component is compiled: strings, numbers, booleans, `null`, arithmetic and joined strings of them as JavaScript evaluates them (`60 * MINUTE`, `` `${BASE}/feed` ``, `"#" + 1.5`, and `**` where it gives an exact integer within ±2⁵³), lists of them, objects of them read field by field (`LABELS.save`, `Tone.Loud`, `Rank[5]`), and lists of objects of them, which become a `const` in `types.rs` (`{ value, label }` items get a type named after the constant, `OptionsItem`, unless the constant is typed with an interface: `OPTIONS: Option[]`; a field that is `null` in some items is `T | null`); lookup tables: an object constant (`as const`, `Record<Size, T>` or untyped, keys computed from enum members too) read by a key whose type is a union of string or integer literals or an `enum` (a prop, a `computed`, a `v-for` item's field, a `?:` of literals, or built from one by `.toUpperCase()`, `.toLowerCase()` or a template literal), or by a constant number (`O[HALF]` reads `O["0.5"]`), compiled to a `match` over the members (over the variants of a union's enum, with no catch-all arm; a table of a union's members, `Record<Tone, Tone>`, reaches a union prop as a `match` from variant to variant), read on by field (`THEME[tone].fg`) in text, attributes, `:class` and `:style`, with `undefined` for a member the object lacks (`ICONS[kind] ?? "none"`, `ICONS[kind]?.label`, `v-if="ICONS[kind]"`, `kind && ICONS[kind]`); a free-text key tested with `Object.hasOwn(ROLES, key)`, against the object's own keys, which narrows the key for `ROLES[key]` in what follows; `Object.keys()` of an object constant; a constant `new Map([[key, value], …])` of string or integer keys, read with `.get(key)` (`undefined` for a key it lacks) and `.has(key)`. See the crate's [`props`](https://docs.rs/ferrovue/latest/ferrovue/guide/props/index.html#constants) guide |
|
|
230
230
|
| Props | Props declared in place, by an interface of the component, or by one imported from a `.ts` file by a relative path (`defineProps<CardProps>()`), extended or joined with `&`; `withDefaults`, destructured props with defaults and new names (`const { size = "md", label: text } = defineProps<…>()`), the props object (`const props = defineProps<…>()`, then `props.label` in the template or in script code), optional booleans (Vue casts an absent one to `false`), `defineModel` (named, required, with defaults), components with no props. A generic component (`<script setup generic="T extends Item">`) renders each type parameter as its constraint, which is all the template can rely on |
|
|
231
231
|
| Setup | `ref`/`shallowRef`, `computed` (an expression, or a body of statements: see Statements; one that may throw, through `.repeat()`, `.padStart()`, a date, `$t` or a helper, is evaluated where the render first reads it, once, as Vue does), refs that start empty, typed by their type argument (`ref<Row[]>([])`, `ref<Row>()`, `ref<string \| undefined>(undefined)`), refs of a union of string literals, held as its enum (`ref<Choice>(props.tone ?? "")`, a member, a value of the union, a lookup table's value, or a `?:` or `??` of them), plain `const`/`let`, `.value` in script code, helpers with Rust twins, functions (see Functions), a plain `<script>` block beside setup. Lifecycle hooks, `watch` (not `immediate`), `defineEmits`, `defineSlots`, `defineOptions`, `defineExpose` and template refs (`ref(null)`, `useTemplateRef`) are client-only, as is a function only an event handler calls. The template may name them only from event handlers, which the server drops |
|
|
232
232
|
| Statements | in a `computed`'s body and in a list method's callback (`.map()`, `.filter()`, `.find()`, `.some()`, `.reduce()`, a `.toSorted()` comparator and the others): `const` and `let` of any value the template may compute, destructured too (`const { name, posts: count } = author`); `if` / `else if` / `else` and early `return`, narrowing what follows as TypeScript narrows it (`if (!user) return ""; return user.name`, `if (!a \|\| !a.title) return "-"`); `switch` over a string or a number, with stacked `case`s, `default` and `break`; assignments to the body's `let`s (`=`, `+=`, `-=`, `*=`, `++`, `--` and the others), with the type TypeScript gives them; `for (const x of list)` over a list, `list.entries()`, `Object.keys()` or `Object.entries()`, `for (const ch of text)` over a string by its characters (a character beyond U+FFFF is one), and counting loops `for (let i = 0; i < n; i++)` to an integer bound, with `break`, `continue`, nested loops and `return` inside them, carrying strings, numbers and booleans from one item to the next (`let total = 0; for (const r of rows) total += r.n;`, thousands separators, the largest of a list). Each value is evaluated once, where JavaScript evaluates it, and only on the path that reaches it. `while`, `do…while` and `for…in` loops, assignments to props, refs, `const`s, parameters and fields, `try`, `throw`, `var` and a path that ends without `return` are refused. See the crate's [`script_logic`](https://docs.rs/ferrovue/latest/ferrovue/guide/script_logic/index.html) guide |
|
package/dist/attrs.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { type N, type Scope, type Val } from "./model.ts";
|
|
1
|
+
import { type N, type Piece, type Scope, type Val } from "./model.ts";
|
|
2
2
|
import { type ErrorCode } from "./errors.ts";
|
|
3
3
|
import { Emitter } from "./emitter.ts";
|
|
4
|
+
export declare function displayPieces(e: Emitter, pieces: Piece[]): void;
|
|
4
5
|
export declare function display(e: Emitter, v: Val): boolean;
|
|
5
6
|
export declare function interpolate(s: Scope, e: Emitter, v: Val, n: N): void;
|
|
6
7
|
export declare function attrValue(e: Emitter, v: Val): void;
|
package/dist/attrs.js
CHANGED
|
@@ -6,23 +6,29 @@ import { CONFIG_FILE } from "./config.js";
|
|
|
6
6
|
import { ctx } from "./context.js";
|
|
7
7
|
import { describeTy, expr, shown } from "./expr.js";
|
|
8
8
|
import { known, truthy } from "./narrowing.js";
|
|
9
|
-
import { asCow, isTemporary, unquote } from "./strings.js";
|
|
10
|
-
import { atom, bare, condition, ifElse, not, receiver, scrutinee, strArg } from "./parens.js";
|
|
9
|
+
import { asCow, isTemporary, piecesOf, unquote } from "./strings.js";
|
|
10
|
+
import { atom, bare, condition, ifElse, logical, not, receiver, scrutinee, strArg } from "./parens.js";
|
|
11
11
|
import { Emitter } from "./emitter.js";
|
|
12
12
|
import { isDollarAttrs } from "./fallthrough.js";
|
|
13
13
|
import { claim } from "./plugin.js";
|
|
14
14
|
import { classAttr, classPresent, literalClass, maybeEqual, renderClass } from "./classes.js";
|
|
15
15
|
import { mergedStyle, renderStyle, styleAttr } from "./styles.js";
|
|
16
16
|
const NUMBER_BYTES = 6;
|
|
17
|
-
function piecesOf(v) {
|
|
18
|
-
const f = v.format;
|
|
19
|
-
if (v.ty.k !== "str" || f === undefined || v.code !== `&*format!(${rustStr(f.text)}, ${f.args.join(", ")})`)
|
|
20
|
-
return undefined;
|
|
21
|
-
return f.pieces;
|
|
22
|
-
}
|
|
23
17
|
function emptyTest(v) {
|
|
24
18
|
if (neverEmpty(v.ty))
|
|
25
19
|
return "false";
|
|
20
|
+
const branches = v.branches;
|
|
21
|
+
if (branches !== undefined && branches.of === v.code) {
|
|
22
|
+
const [yes, no] = [emptyTest(branches.yes), emptyTest(branches.no)];
|
|
23
|
+
const binds = branches.test.startsWith("let ");
|
|
24
|
+
if (yes === no && !binds)
|
|
25
|
+
return yes;
|
|
26
|
+
if (!binds && yes === "false")
|
|
27
|
+
return logical(not(branches.test), "&&", no);
|
|
28
|
+
if (!binds && no === "false")
|
|
29
|
+
return logical(branches.test, "&&", yes);
|
|
30
|
+
return `if ${branches.test} { ${yes} } else { ${no} }`;
|
|
31
|
+
}
|
|
26
32
|
const pieces = piecesOf(v);
|
|
27
33
|
if (pieces === undefined)
|
|
28
34
|
return `${receiver(v.code)}.is_empty()`;
|
|
@@ -31,17 +37,32 @@ function emptyTest(v) {
|
|
|
31
37
|
return "false";
|
|
32
38
|
return strs.map((p) => `${receiver(p.code)}.is_empty()`).join(" && ");
|
|
33
39
|
}
|
|
40
|
+
export function displayPieces(e, pieces) {
|
|
41
|
+
for (const p of pieces) {
|
|
42
|
+
if (typeof p === "string")
|
|
43
|
+
e.lit(escapeHtml(p));
|
|
44
|
+
else if ("shown" in p)
|
|
45
|
+
e.stmt(`{ use std::fmt::Write as _; let _ = write!(out, "{}", ${p.shown}); }`);
|
|
46
|
+
else
|
|
47
|
+
display(e, p);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
34
50
|
export function display(e, v) {
|
|
35
51
|
const pieces = piecesOf(v);
|
|
36
52
|
if (pieces !== undefined) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
displayPieces(e, pieces);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
const branches = v.branches;
|
|
57
|
+
if (branches !== undefined && branches.of === v.code) {
|
|
58
|
+
e.open(`if ${branches.test}`);
|
|
59
|
+
display(e, branches.yes);
|
|
60
|
+
e.close(" else {");
|
|
61
|
+
const at = e.lines.length;
|
|
62
|
+
display(e, branches.no);
|
|
63
|
+
e.close();
|
|
64
|
+
if (e.lines.length === at + 1)
|
|
65
|
+
e.lines.splice(at - 1, 2, e.lines[at - 1].replace(/\} else \{$/, "}"));
|
|
45
66
|
return true;
|
|
46
67
|
}
|
|
47
68
|
if (v.ty.k === "str" && known(v) !== undefined)
|
package/dist/classes.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { type N, type Scope } from "./model.ts";
|
|
1
|
+
import { type N, type Scope, type Val } from "./model.ts";
|
|
2
2
|
import { Emitter } from "./emitter.ts";
|
|
3
3
|
export type ClassItem = {
|
|
4
4
|
lit: string;
|
|
5
5
|
} | {
|
|
6
6
|
code: string;
|
|
7
|
+
join?: Val;
|
|
7
8
|
};
|
|
8
9
|
export declare function classItems(s: Scope, n: N): ClassItem[];
|
|
9
10
|
export declare function renderClass(s: Scope, e: Emitter, n: N, after?: boolean): void;
|
package/dist/classes.js
CHANGED
|
@@ -3,10 +3,10 @@ import { directiveOwner, fail, nothing, rustStr, STR } from "./model.js";
|
|
|
3
3
|
import { expr } from "./expr.js";
|
|
4
4
|
import { choice, cond, known, narrowing, narrowTo, presence, truthy } from "./narrowing.js";
|
|
5
5
|
import { pickPresent } from "./constants.js";
|
|
6
|
-
import { meet, unquote } from "./strings.js";
|
|
6
|
+
import { meet, piecesOf, unquote } from "./strings.js";
|
|
7
7
|
import { atom, bare, condition, logical, not, occurrences, strArg } from "./parens.js";
|
|
8
8
|
import { Emitter } from "./emitter.js";
|
|
9
|
-
import { stringAttr } from "./attrs.js";
|
|
9
|
+
import { displayPieces, stringAttr } from "./attrs.js";
|
|
10
10
|
import { fresh, jsHint } from "./names.js";
|
|
11
11
|
export function classItems(s, n) {
|
|
12
12
|
switch (n.type) {
|
|
@@ -79,7 +79,7 @@ export function classItems(s, n) {
|
|
|
79
79
|
if (literal !== null && literal === literal.trim())
|
|
80
80
|
return [{ lit: literal }];
|
|
81
81
|
if (v.ty.k === "str")
|
|
82
|
-
return [{ code: v.code }];
|
|
82
|
+
return [piecesOf(v) === undefined ? { code: v.code } : { code: v.code, join: v }];
|
|
83
83
|
if (v.ty.k === "opt" && v.ty.of.k === "str")
|
|
84
84
|
return [{ code: `${atom(v.code)}.unwrap_or("")` }];
|
|
85
85
|
if (nothing(v.ty))
|
|
@@ -118,10 +118,49 @@ export function renderClass(s, e, n, after = false) {
|
|
|
118
118
|
e.lit((wrote ? " " : "") + escapeHtml(it.lit));
|
|
119
119
|
wrote = true;
|
|
120
120
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
let group = [];
|
|
122
|
+
let at = null;
|
|
123
|
+
for (; i < items.length; i++) {
|
|
124
|
+
const it = items[i];
|
|
125
|
+
const parts = "join" in it && it.join !== undefined ? trimmedParts(it.join) : null;
|
|
126
|
+
if (parts === null) {
|
|
127
|
+
if (!group.length && !wrote && items.slice(i + 1).some((later) => "join" in later && later.join !== undefined)) {
|
|
128
|
+
at = fresh("at", "class");
|
|
129
|
+
e.stmt(`let ${at} = out.len();`);
|
|
130
|
+
}
|
|
131
|
+
group.push("lit" in it ? rustStr(it.lit) : it.code);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (group.length)
|
|
135
|
+
e.stmt(`fv::attrs::class_into(out, ${wrote}, &[${group.join(", ")}]);`);
|
|
136
|
+
if (wrote)
|
|
137
|
+
e.lit(" ");
|
|
138
|
+
else if (group.length && at !== null)
|
|
139
|
+
e.stmt(`if out.len() != ${at} { out.push(' '); }`);
|
|
140
|
+
displayPieces(e, parts);
|
|
141
|
+
[group, at, wrote] = [[], null, true];
|
|
142
|
+
}
|
|
143
|
+
if (group.length)
|
|
144
|
+
e.stmt(`fv::attrs::class_into(out, ${wrote}, &[${group.join(", ")}]);`);
|
|
145
|
+
}
|
|
146
|
+
/** The pieces of a joined class name with the spaces `normalizeClass` trims taken off its ends,
|
|
147
|
+
* when only its first or last piece may hold them and the join writes a name: null otherwise. */
|
|
148
|
+
function trimmedParts(v) {
|
|
149
|
+
const pieces = [...piecesOf(v)];
|
|
150
|
+
const solid = (p, start) => p !== undefined && (typeof p === "string" ? p !== "" && p === (start ? p.trimStart() : p.trimEnd()) : "shown" in p || p.ty.k !== "str");
|
|
151
|
+
for (const start of [true, false]) {
|
|
152
|
+
const edge = start ? 0 : pieces.length - 1;
|
|
153
|
+
const next = start ? 1 : pieces.length - 2;
|
|
154
|
+
const p = pieces[edge];
|
|
155
|
+
if (typeof p === "string")
|
|
156
|
+
pieces[edge] = start ? p.trimStart() : p.trimEnd();
|
|
157
|
+
if (solid(pieces[edge], start))
|
|
158
|
+
continue;
|
|
159
|
+
if (typeof p === "string" || "shown" in p || pieces.length < 2 || !solid(pieces[next], start))
|
|
160
|
+
return null;
|
|
161
|
+
pieces[edge] = { code: `fv::strings::js_trim_${start ? "start" : "end"}(${strArg(p.code)})`, ty: STR };
|
|
124
162
|
}
|
|
163
|
+
return pieces;
|
|
125
164
|
}
|
|
126
165
|
const UNWRITTEN = new Set(["on", "if", "else-if", "else", "for", "slot", "once", "memo", "cloak", "pre"]);
|
|
127
166
|
/** The class list of an element in content Vue renders from virtual nodes, in the order Vue's
|
package/dist/conditional.js
CHANGED
|
@@ -2,9 +2,9 @@ import { absence, enumOf, fail, FLOAT, joinAbsence, joinTy, literalsOf, literalT
|
|
|
2
2
|
import { GenError } from "./errors.js";
|
|
3
3
|
import { annotatedStr, exactlyEnum, intoEnum, knownLiteral } from "./enums.js";
|
|
4
4
|
import { ctx } from "./context.js";
|
|
5
|
-
import { atom, bare, enclosed, occurrences, strArg } from "./parens.js";
|
|
5
|
+
import { atom, bare, condition, enclosed, occurrences, strArg } from "./parens.js";
|
|
6
6
|
import { collected, sliced, typedEmpty, untypedEmpty } from "./lists.js";
|
|
7
|
-
import { asCow, isTemporary, loneOf } from "./strings.js";
|
|
7
|
+
import { asCow, isTemporary, loneOf, piecesOf } from "./strings.js";
|
|
8
8
|
import { asF64, intFromF64, isCast, isNumber } from "./numbers.js";
|
|
9
9
|
import { choice, known, literalNarrowing, narrowTo, presence, truthTested, truthy } from "./narrowing.js";
|
|
10
10
|
import { ownedStruct, ownedStructs } from "./objects.js";
|
|
@@ -65,7 +65,14 @@ function branches(s, n, t) {
|
|
|
65
65
|
a = { ...a, ty: b.ty };
|
|
66
66
|
if (emptyB && a.ty.k === "list")
|
|
67
67
|
b = { ...b, ty: a.ty };
|
|
68
|
-
|
|
68
|
+
const [x, y] = [a, b];
|
|
69
|
+
const head = () => {
|
|
70
|
+
if (!p)
|
|
71
|
+
return { test: condition(truthy(t)), yes: x, no: y };
|
|
72
|
+
const [present, absent] = p.negated ? [y, x] : [x, y];
|
|
73
|
+
return { test: occurrences(present.code, name) ? p.pattern(name) : condition(p.present), yes: present, no: absent };
|
|
74
|
+
};
|
|
75
|
+
return choiceOf({ comp, n, a, b, emptyA, emptyB, choose: chooser(t, p, name, a), head, lone: loneOf(a, b) });
|
|
69
76
|
}
|
|
70
77
|
function chooser(t, p, name, a) {
|
|
71
78
|
const second = (code) => (a.ty.k === "str" ? strArg(code) : bare(code));
|
|
@@ -90,7 +97,9 @@ function choiceOf(c) {
|
|
|
90
97
|
if (owned !== null)
|
|
91
98
|
return owned;
|
|
92
99
|
if (sameTy(a.ty, b.ty) && a.ty.k === "str" && (isTemporary(a) || isTemporary(b))) {
|
|
93
|
-
|
|
100
|
+
const code = `&*(${choose(asCow(a), asCow(b))})`;
|
|
101
|
+
const joins = (v) => piecesOf(v) !== undefined || v.branches?.of === v.code;
|
|
102
|
+
return { code, ty: STR, ...lone, ...(joins(a) || joins(b) ? { branches: { of: code, ...c.head() } } : {}) };
|
|
94
103
|
}
|
|
95
104
|
if (sameTy(a.ty, b.ty) && a.ty.k === "list" && a.ty.of.k !== "undef")
|
|
96
105
|
return listChoice(c);
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Component, type N, type Scope, type Val } from "./model.ts";
|
|
1
|
+
import { type Component, type Fn, type N, type Scope, type Val } from "./model.ts";
|
|
2
2
|
export type Const = {
|
|
3
3
|
k: "scalar";
|
|
4
4
|
js: Scalar;
|
|
@@ -25,10 +25,30 @@ type Scalar = string | number | boolean | null;
|
|
|
25
25
|
export interface Declared {
|
|
26
26
|
node: N;
|
|
27
27
|
home: Component;
|
|
28
|
-
exported: boolean;
|
|
29
28
|
}
|
|
30
29
|
/** Remember the constants and enums a file declares at its top level, to evaluate when one is used. */
|
|
31
30
|
export declare function declareConsts(home: Component, body: N[]): Map<string, Declared>;
|
|
31
|
+
/** A name a `.ts` file exports: one of its own, by its local name, or one of another file's, by the
|
|
32
|
+
* name that file exports it as (`null` for a module that is no `.ts` file of the project, or for a
|
|
33
|
+
* namespace, `export * as ns from`). */
|
|
34
|
+
export type Export = {
|
|
35
|
+
local: string;
|
|
36
|
+
} | {
|
|
37
|
+
file: string | null;
|
|
38
|
+
name: string | null;
|
|
39
|
+
};
|
|
40
|
+
/** What a `.ts` file exports: its names, and the files whose names `export * from` passes on. */
|
|
41
|
+
export interface Exports {
|
|
42
|
+
names: Map<string, Export>;
|
|
43
|
+
stars: (string | null)[];
|
|
44
|
+
}
|
|
45
|
+
/** The names a `.ts` file exports: by `export const`, `export function`, `export enum`,
|
|
46
|
+
* `export { a, b as c }`, `export default a`, and from other files by `export { a } from` and `export * from`. */
|
|
47
|
+
export declare function exportsOf(rel: string, body: N[]): Exports;
|
|
48
|
+
/** The constant or enum a `.ts` file exports as `name`. */
|
|
49
|
+
export declare function exportedConst(file: string, name: string): Declared | undefined;
|
|
50
|
+
/** The function a `.ts` file exports as `name`. */
|
|
51
|
+
export declare function exportedFunction(file: string, name: string): Fn | undefined;
|
|
32
52
|
/** What a declared constant or enum stands for, evaluated once. */
|
|
33
53
|
export declare function constOfDecl(d: Declared): Const;
|
|
34
54
|
/** The type an enum stands for: the union of its members' literal types. */
|
package/dist/constants.js
CHANGED
|
@@ -2,7 +2,8 @@ import { absence, blankComponent, BOOL, distinctFields, enumOf, enumTy, fail, FL
|
|
|
2
2
|
import { GenError } from "./errors.js";
|
|
3
3
|
import { aliasEnum, knownLiteral, matchOver, overVariants } from "./enums.js";
|
|
4
4
|
const TYPES_HOME = blankComponent("types", "types", "types");
|
|
5
|
-
import { ONE_NOTHING, reachType, tyOfTs } from "./typescript.js";
|
|
5
|
+
import { ONE_NOTHING, reachType, readTypeFile, tyOfTs } from "./typescript.js";
|
|
6
|
+
import { resolveImport } from "./resolve.js";
|
|
6
7
|
import { ctx } from "./context.js";
|
|
7
8
|
import { asF64, exactPower, numberVal } from "./numbers.js";
|
|
8
9
|
import { describeTy, expr } from "./expr.js";
|
|
@@ -18,19 +19,100 @@ let enumScope = null;
|
|
|
18
19
|
export function declareConsts(home, body) {
|
|
19
20
|
const found = new Map();
|
|
20
21
|
for (const st of body) {
|
|
21
|
-
const
|
|
22
|
-
const d = exported ? st.declaration : st;
|
|
22
|
+
const d = st.type === "ExportNamedDeclaration" ? st.declaration : st;
|
|
23
23
|
if (d?.type === "TSEnumDeclaration")
|
|
24
|
-
found.set(d.id.name, { node: d, home
|
|
24
|
+
found.set(d.id.name, { node: d, home });
|
|
25
25
|
if (d?.type === "VariableDeclaration" && d.kind === "const") {
|
|
26
26
|
for (const v of d.declarations)
|
|
27
27
|
if (v.id.type === "Identifier" && v.init)
|
|
28
|
-
found.set(v.id.name, { node: v, home
|
|
28
|
+
found.set(v.id.name, { node: v, home });
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
files.set(home, found);
|
|
32
32
|
return found;
|
|
33
33
|
}
|
|
34
|
+
/** The names a `.ts` file exports: by `export const`, `export function`, `export enum`,
|
|
35
|
+
* `export { a, b as c }`, `export default a`, and from other files by `export { a } from` and `export * from`. */
|
|
36
|
+
export function exportsOf(rel, body) {
|
|
37
|
+
const names = new Map();
|
|
38
|
+
const stars = [];
|
|
39
|
+
const nameOf = (n) => n.name ?? n.value;
|
|
40
|
+
for (const st of body) {
|
|
41
|
+
if (st.type === "ExportDefaultDeclaration") {
|
|
42
|
+
const d = st.declaration;
|
|
43
|
+
names.set("default", { local: d.type === "Identifier" ? d.name : "default" });
|
|
44
|
+
}
|
|
45
|
+
if (st.exportKind === "type")
|
|
46
|
+
continue;
|
|
47
|
+
if (st.type === "ExportAllDeclaration") {
|
|
48
|
+
const file = resolveImport(rel, st.source.value);
|
|
49
|
+
if (st.exported)
|
|
50
|
+
names.set(nameOf(st.exported), { file: null, name: null });
|
|
51
|
+
else
|
|
52
|
+
stars.push(file);
|
|
53
|
+
}
|
|
54
|
+
if (st.type !== "ExportNamedDeclaration")
|
|
55
|
+
continue;
|
|
56
|
+
const d = st.declaration;
|
|
57
|
+
if (d?.type === "VariableDeclaration")
|
|
58
|
+
for (const v of d.declarations)
|
|
59
|
+
if (v.id.type === "Identifier")
|
|
60
|
+
names.set(v.id.name, { local: v.id.name });
|
|
61
|
+
if (d?.id?.type === "Identifier")
|
|
62
|
+
names.set(d.id.name, { local: d.id.name });
|
|
63
|
+
if (d)
|
|
64
|
+
continue;
|
|
65
|
+
const from = st.source ? resolveImport(rel, st.source.value) : undefined;
|
|
66
|
+
for (const sp of st.specifiers) {
|
|
67
|
+
if (sp.exportKind === "type")
|
|
68
|
+
continue;
|
|
69
|
+
const as = nameOf(sp.exported);
|
|
70
|
+
if (sp.type === "ExportNamespaceSpecifier")
|
|
71
|
+
names.set(as, { file: null, name: null });
|
|
72
|
+
else if (sp.type === "ExportSpecifier")
|
|
73
|
+
names.set(as, from === undefined ? { local: sp.local.name } : { file: from, name: nameOf(sp.local) });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { names, stars };
|
|
77
|
+
}
|
|
78
|
+
/** The file and local name of what a `.ts` file exports as `name`, followed through `export … from`
|
|
79
|
+
* and `export * from`; `undefined` when nothing is exported by that name, or two `export *` give it. */
|
|
80
|
+
function exportOrigin(file, name, seen = new Set()) {
|
|
81
|
+
const key = `${file}\0${name}`;
|
|
82
|
+
if (seen.has(key))
|
|
83
|
+
return undefined;
|
|
84
|
+
seen.add(key);
|
|
85
|
+
readTypeFile(file);
|
|
86
|
+
const exports = ctx.tsModules.get(file)?.exports;
|
|
87
|
+
const e = exports?.names.get(name);
|
|
88
|
+
if (e !== undefined) {
|
|
89
|
+
if ("local" in e)
|
|
90
|
+
return { file, local: e.local };
|
|
91
|
+
return e.file === null || e.name === null ? undefined : exportOrigin(e.file, e.name, seen);
|
|
92
|
+
}
|
|
93
|
+
if (name === "default")
|
|
94
|
+
return undefined;
|
|
95
|
+
let found;
|
|
96
|
+
for (const star of exports?.stars ?? []) {
|
|
97
|
+
const o = star === null ? undefined : exportOrigin(star, name, new Set(seen));
|
|
98
|
+
if (o === undefined)
|
|
99
|
+
continue;
|
|
100
|
+
if (found !== undefined && (found.file !== o.file || found.local !== o.local))
|
|
101
|
+
return undefined;
|
|
102
|
+
found = o;
|
|
103
|
+
}
|
|
104
|
+
return found;
|
|
105
|
+
}
|
|
106
|
+
/** The constant or enum a `.ts` file exports as `name`. */
|
|
107
|
+
export function exportedConst(file, name) {
|
|
108
|
+
const o = exportOrigin(file, name);
|
|
109
|
+
return o === undefined ? undefined : ctx.constDecls.get(o.file)?.get(o.local);
|
|
110
|
+
}
|
|
111
|
+
/** The function a `.ts` file exports as `name`. */
|
|
112
|
+
export function exportedFunction(file, name) {
|
|
113
|
+
const o = exportOrigin(file, name);
|
|
114
|
+
return o === undefined ? undefined : ctx.tsModules.get(o.file)?.functions.get(o.local);
|
|
115
|
+
}
|
|
34
116
|
/** What a declared constant or enum stands for, evaluated once. */
|
|
35
117
|
export function constOfDecl(d) {
|
|
36
118
|
const done = evaluated.get(d.node);
|
package/dist/context.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Config, TypeName } from "./config.ts";
|
|
2
2
|
import type { Plugin } from "./plugin.ts";
|
|
3
|
-
import type { Declared } from "./constants.ts";
|
|
3
|
+
import type { Declared, Exports } from "./constants.ts";
|
|
4
4
|
import type { Alias } from "./compiler.ts";
|
|
5
5
|
import type { TsProject } from "./resolve.ts";
|
|
6
6
|
import { type Component, type EnumDef, type Fn, type N, type Struct, type Ty } from "./model.ts";
|
|
@@ -51,10 +51,8 @@ export declare const ctx: {
|
|
|
51
51
|
tsModules: Map<string, {
|
|
52
52
|
home: Component;
|
|
53
53
|
body: N[];
|
|
54
|
-
functions: Map<string,
|
|
55
|
-
|
|
56
|
-
exported: boolean;
|
|
57
|
-
}>;
|
|
54
|
+
functions: Map<string, Fn>;
|
|
55
|
+
exports: Exports;
|
|
58
56
|
}>;
|
|
59
57
|
typeConsts: Map<string, {
|
|
60
58
|
of: unknown;
|
package/dist/dynamic.js
CHANGED
|
@@ -4,6 +4,7 @@ import { basename } from "node:path";
|
|
|
4
4
|
import { camelize, directiveOwner, fail, isAttribute, literalsOf, placeOf, rustStr, templateAst } from "./model.js";
|
|
5
5
|
import { GenError } from "./errors.js";
|
|
6
6
|
import { ctx } from "./context.js";
|
|
7
|
+
import { exportedConst } from "./constants.js";
|
|
7
8
|
import { matchOver } from "./enums.js";
|
|
8
9
|
import { expr } from "./expr.js";
|
|
9
10
|
import { cond, known } from "./narrowing.js";
|
|
@@ -208,8 +209,8 @@ function tableOf(s, n) {
|
|
|
208
209
|
if (!sp)
|
|
209
210
|
continue;
|
|
210
211
|
const file = resolveImport(s.comp.file, st.source.value);
|
|
211
|
-
const d = file === null ? undefined :
|
|
212
|
-
if (!d
|
|
212
|
+
const d = file === null ? undefined : exportedConst(file, sp.imported.name ?? sp.imported.value);
|
|
213
|
+
if (!d || d.node.type !== "VariableDeclarator")
|
|
213
214
|
return null;
|
|
214
215
|
const entries = objectEntries(d.home, name, d.node.init);
|
|
215
216
|
if (!entries)
|
package/dist/functions.d.ts
CHANGED
|
@@ -5,10 +5,7 @@ export declare function functionOf(st: N): {
|
|
|
5
5
|
node: N;
|
|
6
6
|
} | null;
|
|
7
7
|
/** The functions a `.ts` file declares at its top level, exported or not. */
|
|
8
|
-
export declare function declareFunctions(home: Component, body: N[], file: string): Map<string,
|
|
9
|
-
fn: Fn;
|
|
10
|
-
exported: boolean;
|
|
11
|
-
}>;
|
|
8
|
+
export declare function declareFunctions(home: Component, body: N[], file: string): Map<string, Fn>;
|
|
12
9
|
/** A call of a function the server translates: its arguments evaluated in order at the call, each
|
|
13
10
|
* bound once, then its body, with JavaScript's defaults for what is not passed. */
|
|
14
11
|
export declare function callFunction(s: Scope, name: string, fn: Fn, args: N[], n: N): Val;
|
package/dist/functions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fail, placeOf } from "./model.js";
|
|
2
2
|
import { GenError } from "./errors.js";
|
|
3
|
-
import { constOfDecl } from "./constants.js";
|
|
3
|
+
import { constOfDecl, exportedConst, exportedFunction } from "./constants.js";
|
|
4
4
|
import { ctx } from "./context.js";
|
|
5
5
|
import { expr, typedBy } from "./expr.js";
|
|
6
6
|
import { bodyExpr, bound, destructured } from "./blocks.js";
|
|
@@ -21,16 +21,15 @@ export function declareFunctions(home, body, file) {
|
|
|
21
21
|
const found = new Map();
|
|
22
22
|
for (const st of body) {
|
|
23
23
|
if (st.type === "ExportDefaultDeclaration" && (st.declaration.type === "FunctionDeclaration" || FUNCTION_INITS.has(st.declaration.type))) {
|
|
24
|
-
found.set("default", {
|
|
24
|
+
found.set("default", { name: st.declaration.id?.name ?? "default", node: st.declaration, home, file });
|
|
25
25
|
continue;
|
|
26
26
|
}
|
|
27
|
-
const
|
|
28
|
-
const d = exported ? st.declaration : st;
|
|
27
|
+
const d = st.type === "ExportNamedDeclaration" ? st.declaration : st;
|
|
29
28
|
const decls = d?.type === "VariableDeclaration" && d.kind === "const" ? d.declarations : [d];
|
|
30
29
|
for (const x of decls) {
|
|
31
30
|
const f = functionOf(x);
|
|
32
31
|
if (f)
|
|
33
|
-
found.set(f.name, {
|
|
32
|
+
found.set(f.name, { name: f.name, node: f.node, home, file });
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
35
|
return found;
|
|
@@ -54,7 +53,7 @@ function moduleOf(file) {
|
|
|
54
53
|
m.refusedBy.set(local, e);
|
|
55
54
|
}
|
|
56
55
|
};
|
|
57
|
-
for (const [name,
|
|
56
|
+
for (const [name, fn] of read.functions) {
|
|
58
57
|
m.functions.set(name, fn);
|
|
59
58
|
m.clientOnly.set(name, `it is a function of ${read.home.file}, which a template or a function may call, but not read as a value`);
|
|
60
59
|
}
|
|
@@ -72,11 +71,11 @@ function moduleOf(file) {
|
|
|
72
71
|
if (sp.importKind === "type")
|
|
73
72
|
continue;
|
|
74
73
|
const imported = sp.type === "ImportSpecifier" ? (sp.imported.name ?? sp.imported.value) : "default";
|
|
75
|
-
const f =
|
|
76
|
-
const d = from !== null ?
|
|
77
|
-
if (f
|
|
78
|
-
m.functions.set(sp.local.name, f
|
|
79
|
-
else if (d
|
|
74
|
+
const f = from !== null ? exportedFunction(from, imported) : undefined;
|
|
75
|
+
const d = from !== null ? exportedConst(from, imported) : undefined;
|
|
76
|
+
if (f)
|
|
77
|
+
m.functions.set(sp.local.name, f);
|
|
78
|
+
else if (d && sp.type === "ImportSpecifier")
|
|
80
79
|
constant(sp.local.name, d, `it is imported from ${other.home.file}`);
|
|
81
80
|
else
|
|
82
81
|
m.clientOnly.set(sp.local.name, `it is imported from \`${st.source.value}\`, which the server does not run`);
|
package/dist/link-routes.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export interface LinkRoutesOptions {
|
|
|
20
20
|
/** A Vue plugin giving `<RouterLink>` and `useRoute()` the page's route, with no router. */
|
|
21
21
|
export interface LinkRoutes {
|
|
22
22
|
/** Registers vue-router's `<RouterLink>` and provides the page's route to it, to `useRoute()`
|
|
23
|
-
* and to `$route`. */
|
|
23
|
+
* and to `$route`, and the small router to `useRouter()` and `$router`. */
|
|
24
24
|
install(app: App): void;
|
|
25
25
|
/** `to` resolved as vue-router's `router.resolve(to)` resolves it on this page. */
|
|
26
26
|
resolve(to: RouteLocationRaw): RouteLocationResolved;
|
package/dist/link-routes.js
CHANGED
|
@@ -427,7 +427,8 @@ export function linkRoutes(routes, options) {
|
|
|
427
427
|
resolve: resolve,
|
|
428
428
|
install(app) {
|
|
429
429
|
app.component("RouterLink", RouterLink);
|
|
430
|
-
app.config.globalProperties.$
|
|
430
|
+
app.config.globalProperties.$router = router;
|
|
431
|
+
Object.defineProperty(app.config.globalProperties, "$route", { enumerable: true, get: () => router.currentRoute.value });
|
|
431
432
|
app.provide(routerKey, router);
|
|
432
433
|
app.provide(routeLocationKey, reactive);
|
|
433
434
|
},
|
package/dist/model.d.ts
CHANGED
|
@@ -82,6 +82,14 @@ export interface Val {
|
|
|
82
82
|
args: string[];
|
|
83
83
|
pieces: Piece[];
|
|
84
84
|
};
|
|
85
|
+
/** For a string `?:` chooses where a branch is a join: the test the Rust `if` takes and the
|
|
86
|
+
* value of each branch, while `code` is `of`. */
|
|
87
|
+
branches?: {
|
|
88
|
+
of: string;
|
|
89
|
+
test: string;
|
|
90
|
+
yes: Val;
|
|
91
|
+
no: Val;
|
|
92
|
+
};
|
|
85
93
|
owned?: true;
|
|
86
94
|
/** For a value of a generated enum, the Rust expression of the enum (an `Option` of it when the
|
|
87
95
|
* value is optional), where `code` is its string. */
|
package/dist/plugins/loaders.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { absence, distinctFields, enumOf, fail, nothing, rustStr, sameTy, snake } from "../model.js";
|
|
2
2
|
import { GenError } from "../errors.js";
|
|
3
3
|
import { ctx } from "../context.js";
|
|
4
|
-
import {} from "../constants.js";
|
|
4
|
+
import { exportedConst } from "../constants.js";
|
|
5
5
|
import { resolveImport } from "../resolve.js";
|
|
6
6
|
import { bareDates, datePlan, lookupStruct, markHome, readTypeFile, tyOfTs } from "../typescript.js";
|
|
7
7
|
import { coerce, describeTy, expr, fieldVal } from "../expr.js";
|
|
@@ -368,12 +368,11 @@ export const loaders = {
|
|
|
368
368
|
if (file === null)
|
|
369
369
|
return false;
|
|
370
370
|
readTypeFile(file);
|
|
371
|
-
const decls = ctx.constDecls.get(file);
|
|
372
371
|
for (const sp of st.specifiers) {
|
|
373
372
|
if (sp.type !== "ImportSpecifier" || sp.importKind === "type")
|
|
374
373
|
continue;
|
|
375
|
-
const d =
|
|
376
|
-
if (d
|
|
374
|
+
const d = exportedConst(file, sp.imported.name ?? sp.imported.value);
|
|
375
|
+
if (d && isLoader(d, file))
|
|
377
376
|
scopeOf(loaders, s).locals.set(sp.local.name, { d, file });
|
|
378
377
|
}
|
|
379
378
|
return false;
|
package/dist/plugins/provide.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { absence, blankComponent, camelize, fail, nothing, opt, sameTy, snake } from "../model.js";
|
|
2
2
|
import { ctx } from "../context.js";
|
|
3
|
-
import {} from "../constants.js";
|
|
3
|
+
import { exportedConst } from "../constants.js";
|
|
4
4
|
import { lookupStruct, markHome, refuseDates, tyOfTs } from "../typescript.js";
|
|
5
5
|
import { resolveImport } from "../resolve.js";
|
|
6
6
|
import { describeTy, expr } from "../expr.js";
|
|
@@ -8,7 +8,6 @@ import { slotFieldBorrows, slotFieldTy, slotFieldValue } from "../slots.js";
|
|
|
8
8
|
import { fieldInit, ownInto } from "../children.js";
|
|
9
9
|
import { setupSource } from "../script.js";
|
|
10
10
|
import { header } from "../rust.js";
|
|
11
|
-
import { relativePath } from "../paths.js";
|
|
12
11
|
import { heldStr, isTemporary } from "../strings.js";
|
|
13
12
|
import { runOf, scopeOf, slotFieldsOf } from "../plugin.js";
|
|
14
13
|
const REF_CALLS = new Set(["ref", "shallowRef", "computed", "toRef", "customRef", "defineModel"]);
|
|
@@ -105,10 +104,11 @@ function keyOf(comp, n, script) {
|
|
|
105
104
|
if (n?.type === "Identifier") {
|
|
106
105
|
const imported = importOf(script, n.name);
|
|
107
106
|
const file = imported ? resolveImport(comp.file, imported.from) : null;
|
|
108
|
-
const decl = file !== null ?
|
|
109
|
-
if (decl?.
|
|
110
|
-
const rel =
|
|
111
|
-
|
|
107
|
+
const decl = file !== null ? exportedConst(file, imported.name) : undefined;
|
|
108
|
+
if (decl?.node.type === "VariableDeclarator" && symbolInit(decl.node.init)) {
|
|
109
|
+
const rel = decl.home.file;
|
|
110
|
+
const own = decl.node.id.name;
|
|
111
|
+
return keyNamed(comp, n, `${rel}#${own}`, `\`${own}\``, rel, snake(own), () => declaredOf(decl));
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
return fail(comp, "FV1601", "an injection key is a string literal, or a `Symbol` exported from one of the project's `.ts` files: `export const ThemeKey: InjectionKey<string> = Symbol()`", n);
|
package/dist/script.js
CHANGED
|
@@ -5,7 +5,7 @@ import { CONFIG_FILE } from "./config.js";
|
|
|
5
5
|
import { ctx } from "./context.js";
|
|
6
6
|
import { definePropsType, readTypeFile, refuseDates, tyOfTs } from "./typescript.js";
|
|
7
7
|
import { resolveImport } from "./resolve.js";
|
|
8
|
-
import { constOfDecl, declareConsts } from "./constants.js";
|
|
8
|
+
import { constOfDecl, declareConsts, exportedConst, exportedFunction } from "./constants.js";
|
|
9
9
|
import { localTy } from "./rust.js";
|
|
10
10
|
import { expr, fieldVal, typedBy } from "./expr.js";
|
|
11
11
|
import { atom, occurrences } from "./parens.js";
|
|
@@ -278,29 +278,27 @@ function importedKey(sp) {
|
|
|
278
278
|
return sp.imported.name ?? sp.imported.value;
|
|
279
279
|
return sp.type === "ImportDefaultSpecifier" ? "default" : undefined;
|
|
280
280
|
}
|
|
281
|
-
function importedValue(scope, sp,
|
|
281
|
+
function importedValue(scope, sp, file) {
|
|
282
282
|
const key = importedKey(sp);
|
|
283
283
|
if (key === undefined)
|
|
284
284
|
return;
|
|
285
|
-
const f =
|
|
286
|
-
if (f
|
|
287
|
-
scope.functions.set(sp.local.name, f
|
|
288
|
-
scope.clientOnly.set(sp.local.name, `it is a function imported from ${f.
|
|
285
|
+
const f = exportedFunction(file, key);
|
|
286
|
+
if (f) {
|
|
287
|
+
scope.functions.set(sp.local.name, f);
|
|
288
|
+
scope.clientOnly.set(sp.local.name, `it is a function imported from ${f.home.file}, which the template and a \`computed\` may call, and only an event handler may name without calling it`);
|
|
289
289
|
return;
|
|
290
290
|
}
|
|
291
|
-
const d =
|
|
292
|
-
if (d
|
|
291
|
+
const d = exportedConst(file, key);
|
|
292
|
+
if (d)
|
|
293
293
|
evaluable(scope, sp.local.name, d, `it is imported from ${d.home.file}, where it is not a constant the compiler evaluates`);
|
|
294
294
|
}
|
|
295
295
|
function moduleValues(scope, st, file) {
|
|
296
296
|
if (st.specifiers.some((sp) => sp.type === "ImportDefaultSpecifier"))
|
|
297
297
|
readTypeFile(file);
|
|
298
|
-
|
|
299
|
-
if (decls === undefined)
|
|
298
|
+
if (!ctx.constDecls.has(file))
|
|
300
299
|
return;
|
|
301
|
-
const fns = ctx.tsModules.get(file)?.functions;
|
|
302
300
|
for (const sp of st.specifiers)
|
|
303
|
-
importedValue(scope, sp,
|
|
301
|
+
importedValue(scope, sp, file);
|
|
304
302
|
}
|
|
305
303
|
function moduleImport(scope, st, from) {
|
|
306
304
|
const typeOnly = st.importKind === "type";
|
package/dist/strings.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Component, type N, type Scope, type Ty, type Val } from "./model.ts";
|
|
1
|
+
import { type Component, type N, type Piece, type Scope, type Ty, type Val } from "./model.ts";
|
|
2
2
|
import type { ErrorCode } from "./errors.ts";
|
|
3
3
|
/** \`/pattern/flags.test(s)\` of a regular expression literal. */
|
|
4
4
|
export declare function regexTest(s: Scope, re: N, args: N[], n: N): Val;
|
|
@@ -25,6 +25,8 @@ export declare function stringOf(comp: Component, v: Val, n: N, what: string, co
|
|
|
25
25
|
/** Whether every character `ty` may write as text is ASCII: a number, a boolean, an absent value
|
|
26
26
|
* (`undefined`, `null`), a string known to be ASCII, or a list of them. */
|
|
27
27
|
export declare function writesAscii(ty: Ty): boolean;
|
|
28
|
+
/** The pieces of a string `format!` builds, while its code is that `format!`. */
|
|
29
|
+
export declare function piecesOf(v: Val): Piece[] | undefined;
|
|
28
30
|
export declare function formatted(comp: Component, given: (string | Val)[], n: N, what: string): Val;
|
|
29
31
|
type Surrogates = "none" | "all" | "some";
|
|
30
32
|
interface RegexPiece {
|
package/dist/strings.js
CHANGED
|
@@ -190,6 +190,13 @@ export function writesAscii(ty) {
|
|
|
190
190
|
function mayBeEmpty(v) {
|
|
191
191
|
return v.ty.k === "str" && !nonEmptyLiteral(v);
|
|
192
192
|
}
|
|
193
|
+
/** The pieces of a string `format!` builds, while its code is that `format!`. */
|
|
194
|
+
export function piecesOf(v) {
|
|
195
|
+
const f = v.format;
|
|
196
|
+
if (v.ty.k !== "str" || f === undefined || v.code !== `&*format!(${rustStr(f.text)}, ${f.args.join(", ")})`)
|
|
197
|
+
return undefined;
|
|
198
|
+
return f.pieces;
|
|
199
|
+
}
|
|
193
200
|
export function formatted(comp, given, n, what) {
|
|
194
201
|
let fmt = "";
|
|
195
202
|
const args = [];
|
package/dist/typescript.js
CHANGED
|
@@ -9,7 +9,7 @@ import { CONFIG_FILE, TYPES_MODULE } from "./config.js";
|
|
|
9
9
|
import { ctx, INLINE_HTML } from "./context.js";
|
|
10
10
|
import { childOf } from "./expr.js";
|
|
11
11
|
import { claim } from "./plugin.js";
|
|
12
|
-
import { declareConsts, enumType } from "./constants.js";
|
|
12
|
+
import { declareConsts, enumType, exportsOf } from "./constants.js";
|
|
13
13
|
import { parseTs } from "./files.js";
|
|
14
14
|
import { declareFunctions } from "./functions.js";
|
|
15
15
|
import { refuseUnresolved, resolveImport } from "./resolve.js";
|
|
@@ -360,7 +360,7 @@ export function readTypeFile(file) {
|
|
|
360
360
|
tagAst(body, "source");
|
|
361
361
|
typesImports(home, body);
|
|
362
362
|
ctx.constDecls.set(file, declareConsts(home, body));
|
|
363
|
-
ctx.tsModules.set(file, { home, body, functions: declareFunctions(home, body, file) });
|
|
363
|
+
ctx.tsModules.set(file, { home, body, functions: declareFunctions(home, body, file), exports: exportsOf(rel, body) });
|
|
364
364
|
for (const d of declareTypes(home, body, new Map(), ctx.typeAliases)) {
|
|
365
365
|
const known = ctx.typeDecls.get(d.name);
|
|
366
366
|
if (known === undefined && ctx.typeStructs.has(d.name))
|
|
@@ -378,7 +378,8 @@ export function readTypeFile(file) {
|
|
|
378
378
|
ctx.typeFiles.set(d.name, rel);
|
|
379
379
|
}
|
|
380
380
|
for (const st of body) {
|
|
381
|
-
|
|
381
|
+
const forwards = (st.type === "ExportNamedDeclaration" && st.source) || st.type === "ExportAllDeclaration";
|
|
382
|
+
if ((st.type !== "ImportDeclaration" && !forwards) || st.source.value === TYPES_MODULE || st.source.value.endsWith(".vue"))
|
|
382
383
|
continue;
|
|
383
384
|
const imported = resolveImport(rel, st.source.value);
|
|
384
385
|
if (imported !== null)
|
package/package.json
CHANGED