@swiftuijs/twill 0.3.1 → 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 +8 -3
- package/dist/branches.d.ts +7 -1
- package/dist/cli.js +7 -8
- package/dist/{compilation-cache-7SugqJ4n.js → compilation-cache-BrV71YKt.js} +2 -3
- package/dist/{compilation-cache-7SugqJ4n.js.map → compilation-cache-BrV71YKt.js.map} +1 -1
- package/dist/{compiler-BdkU4Ruh.js → compiler-DI0H4d1R.js} +147 -55
- package/dist/compiler-DI0H4d1R.js.map +1 -0
- package/dist/compiler.d.ts +2 -0
- package/dist/config.js +1 -2
- package/dist/{configuration-BVMf6Q_j.js → configuration-ppqhmuy4.js} +7 -5
- package/dist/configuration-ppqhmuy4.js.map +1 -0
- package/dist/configuration.d.ts +1 -1
- package/dist/{declarations-BTAS8Mb_.js → declarations-DXtb1AHP.js} +3 -4
- package/dist/{declarations-BTAS8Mb_.js.map → declarations-DXtb1AHP.js.map} +1 -1
- package/dist/declarations.js +1 -2
- package/dist/{doctor-Co_pdZtt.js → doctor-BHwU_YBw.js} +4 -6
- package/dist/{doctor-Co_pdZtt.js.map → doctor-BHwU_YBw.js.map} +1 -1
- package/dist/doctor.js +1 -2
- package/dist/editor.d.ts +1 -0
- package/dist/editor.js +31 -5
- package/dist/editor.js.map +1 -1
- package/dist/esbuild.js +2 -3
- package/dist/{extensions-B1xTkoYP.js → extensions-7ixttcoK.js} +2 -3
- package/dist/{extensions-B1xTkoYP.js.map → extensions-7ixttcoK.js.map} +1 -1
- package/dist/{files-BW9geU0_.js → files-CgwJJ992.js} +3 -4
- package/dist/{files-BW9geU0_.js.map → files-CgwJJ992.js.map} +1 -1
- package/dist/index.js +2 -2
- package/dist/loader-compiler.js +3 -4
- package/dist/{loader-registration-B1ST9h1e.js → loader-registration-BO43rvVU.js} +2 -3
- package/dist/{loader-registration-B1ST9h1e.js.map → loader-registration-BO43rvVU.js.map} +1 -1
- package/dist/loader-registration.js +1 -2
- package/dist/{loader-state-DE2ArGYl.js → loader-state-DB7sONR_.js} +5 -6
- package/dist/{loader-state-DE2ArGYl.js.map → loader-state-DB7sONR_.js.map} +1 -1
- package/dist/loader-sync.js +2 -3
- package/dist/loader.js +2 -3
- package/dist/{plugin-DdTVagIU.js → plugin-DDOHhv7Z.js} +6 -7
- package/dist/{plugin-DdTVagIU.js.map → plugin-DDOHhv7Z.js.map} +1 -1
- package/dist/{project-ZmlpFooa.js → project-DYXf2Kgt.js} +7 -9
- package/dist/{project-ZmlpFooa.js.map → project-DYXf2Kgt.js.map} +1 -1
- package/dist/project.js +2 -2
- package/dist/register.js +2 -3
- package/dist/rollup.js +2 -3
- package/dist/rspack.js +2 -3
- package/dist/syntax.js +2 -3
- package/dist/{transpile-CLNavDUr.js → transpile--6CFGUm0.js} +3 -4
- package/dist/{transpile-CLNavDUr.js.map → transpile--6CFGUm0.js.map} +1 -1
- package/dist/typescript-plugin.cjs +221 -117
- package/dist/typescript-plugin.cjs.map +1 -1
- package/dist/vite-D0Ct4uy4.js +6 -0
- package/dist/{vite-BI-5KRgr.js.map → vite-D0Ct4uy4.js.map} +1 -1
- package/dist/vite-react.js +2 -3
- package/dist/vite.js +1 -2
- package/dist/webpack.js +2 -3
- package/package.json +3 -3
- package/schemas/twill.schema.json +6 -0
- package/dist/compiler-BdkU4Ruh.js.map +0 -1
- package/dist/configuration-BVMf6Q_j.js.map +0 -1
- package/dist/vite-BI-5KRgr.js +0 -7
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# <img src="https://twill.evecalm.com/logo.png" alt="Twill hummingbird" align="right" width="40" height="40" /> Twill
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@swiftuijs/twill)
|
|
4
|
+
[](https://www.npmjs.com/package/@swiftuijs/twill)
|
|
5
|
+
[](https://github.com/swiftuijs/twill/actions/workflows/ci.yml)
|
|
6
|
+
[](https://github.com/swiftuijs/twill/blob/main/LICENSE)
|
|
7
|
+
|
|
3
8
|
**TypeScript. Clearer flow.**
|
|
4
9
|
|
|
5
10
|
Twill is a **TypeScript-based language with Swift-inspired syntax extensions**, built by [forth.ink](https://forth.ink). It extends TypeScript and TSX with guards, `defer`, trailing closures and checked switch expressions. Use TypeScript's type system and existing JavaScript libraries; compile to ordinary JavaScript.
|
|
@@ -73,8 +78,8 @@ The [playground](https://twill.evecalm.com/playground) runs the actual compiler
|
|
|
73
78
|
Install the compiler as a development dependency in your application. Use Node 24 LTS for a new setup; the package supports `^20.19.0 || >=22.12.0`. Run from the directory containing your `package.json` (run `npm init -y` first for a new project). Default inline emission introduces no language runtime dependency into your application bundle. The [optional runtime](https://twill.evecalm.com/runtime) in 0.2.0 shares dynamic synchronous cleanup when explicitly selected.
|
|
74
79
|
|
|
75
80
|
```sh
|
|
76
|
-
pnpm add -D @swiftuijs/twill@0.
|
|
77
|
-
# npm install --save-dev @swiftuijs/twill@0.
|
|
81
|
+
pnpm add -D @swiftuijs/twill@0.4.0
|
|
82
|
+
# npm install --save-dev @swiftuijs/twill@0.4.0
|
|
78
83
|
```
|
|
79
84
|
|
|
80
85
|
Add the Vite adapter to your existing configuration:
|
|
@@ -103,7 +108,7 @@ For a project-local command, install the compiler above, save this as `hello.twi
|
|
|
103
108
|
console.log(process.argv.slice(2));
|
|
104
109
|
```
|
|
105
110
|
|
|
106
|
-
Expected output is `[ 'hello world' ]`. On Linux/macOS, use `chmod +x hello.twill` then `npm exec --call './hello.twill "hello world"'` or `pnpm exec ./hello.twill 'hello world'` for a local shebang. For direct `./hello.twill` execution, install `@swiftuijs/twill` globally with `npm install --global @swiftuijs/twill@0.
|
|
111
|
+
Expected output is `[ 'hello world' ]`. On Linux/macOS, use `chmod +x hello.twill` then `npm exec --call './hello.twill "hello world"'` or `pnpm exec ./hello.twill 'hello world'` for a local shebang. For direct `./hello.twill` execution, install `@swiftuijs/twill` globally with `npm install --global @swiftuijs/twill@0.4.0` and keep `twill` on PATH. Windows uses the explicit CLI.
|
|
107
112
|
|
|
108
113
|
Install `@swiftuijs/twill-shell` in the script's project only when using its subprocess API; a global compiler does not install project imports. Follow [shell scripting](https://twill.evecalm.com/scripting) for complete installation, a portable subprocess example, Node type checking and troubleshooting. Native JS/TS consumers need only the SDK.
|
|
109
114
|
|
package/dist/branches.d.ts
CHANGED
|
@@ -6,10 +6,16 @@ type Node = {
|
|
|
6
6
|
end: number;
|
|
7
7
|
[key: string]: any;
|
|
8
8
|
};
|
|
9
|
+
/** Preserve the existing expression boundary for direct eval and suspension. */
|
|
10
|
+
export declare function canInlineSwitchExpression(node: Node): boolean;
|
|
11
|
+
/** Reuse authored blocks only when no direct eval (including a captured one)
|
|
12
|
+
* can observe the added temporary's broader lifetime/temporal dead zone.
|
|
13
|
+
* Memoize shared parser nodes so this analysis stays linear in the AST size. */
|
|
14
|
+
export declare function reusableStatements(ast: Node, usedNames: Set<string>): Set<Node>;
|
|
9
15
|
/** Native switches with per-arm scopes. Direct returns and standalone variable
|
|
10
16
|
* initializers need no extra function; other expressions keep a lexical IIFE. */
|
|
11
17
|
export declare function lowerSwitchExpressions(source: string, ast: Node, switches: Node[], code: MagicString, language: Language, usedNames: Set<string>, comments: {
|
|
12
18
|
start: number;
|
|
13
19
|
end: number;
|
|
14
|
-
}[], fail: (node: Node, message: string) => never): void;
|
|
20
|
+
}[], fail: (node: Node, message: string) => never, implicitReturns?: Node[], reusable?: Set<Node>): void;
|
|
15
21
|
export {};
|
package/dist/cli.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as transform } from "./compiler-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { t as
|
|
6
|
-
import { t as
|
|
7
|
-
import { t as
|
|
2
|
+
import { a as transform } from "./compiler-DI0H4d1R.js";
|
|
3
|
+
import { t as transpile } from "./transpile--6CFGUm0.js";
|
|
4
|
+
import { n as loadConfig } from "./configuration-ppqhmuy4.js";
|
|
5
|
+
import { t as TwillProject } from "./project-DYXf2Kgt.js";
|
|
6
|
+
import { t as inspectProject } from "./doctor-BHwU_YBw.js";
|
|
7
|
+
import { t as emitDeclarations } from "./declarations-DXtb1AHP.js";
|
|
8
8
|
import { createRequire } from "node:module";
|
|
9
9
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { dirname, resolve } from "node:path";
|
|
11
11
|
import { pathToFileURL } from "node:url";
|
|
12
12
|
import { parseArgs } from "node:util";
|
|
13
|
-
|
|
13
|
+
// src/cli.ts
|
|
14
14
|
async function main(args = process.argv.slice(2)) {
|
|
15
15
|
const { values, positionals, tokens } = parseArgs({
|
|
16
16
|
args,
|
|
@@ -129,7 +129,6 @@ async function main(args = process.argv.slice(2)) {
|
|
|
129
129
|
} else process.stdout.write(result.code + "\n");
|
|
130
130
|
return 0;
|
|
131
131
|
}
|
|
132
|
-
//#endregion
|
|
133
132
|
export { main };
|
|
134
133
|
|
|
135
134
|
//# sourceMappingURL=cli.js.map
|
|
@@ -4,7 +4,7 @@ import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
|
4
4
|
import { createHash, randomUUID } from "node:crypto";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
|
|
7
|
+
// src/compilation-cache.ts
|
|
8
8
|
var limit = 524288;
|
|
9
9
|
var packages = [
|
|
10
10
|
"typescript",
|
|
@@ -166,7 +166,6 @@ function openCompilationCache() {
|
|
|
166
166
|
if (identity) return new CompilationCache(resolve(directory), identity);
|
|
167
167
|
} catch {}
|
|
168
168
|
}
|
|
169
|
-
//#endregion
|
|
170
169
|
export { openCompilationCache as i, observationsMatch as n, observe as r, digest as t };
|
|
171
170
|
|
|
172
|
-
//# sourceMappingURL=compilation-cache-
|
|
171
|
+
//# sourceMappingURL=compilation-cache-BrV71YKt.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compilation-cache-7SugqJ4n.js","names":[],"sources":["../src/compilation-cache.ts"],"sourcesContent":["import {\n closeSync,\n constants,\n existsSync,\n fstatSync,\n lstatSync,\n mkdirSync,\n openSync,\n readFileSync,\n readdirSync,\n realpathSync,\n statSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n type Stats,\n} from 'node:fs';\nimport { createHash, randomUUID } from 'node:crypto';\nimport { createRequire } from 'node:module';\nimport { homedir } from 'node:os';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport type ConfigurationObservation = {\n path: string;\n kind: 'file' | 'directory';\n value: string;\n};\nconst limit = 512 * 1024;\nconst packages = [\n 'typescript',\n 'acorn',\n 'acorn-typescript',\n 'acorn-jsx',\n 'magic-string',\n '@ampproject/remapping',\n '@jridgewell/trace-mapping',\n];\nexport const digest = (source: string | Uint8Array) =>\n createHash('sha256').update(source).digest('hex');\n\nexport function observe(path: string, kind: ConfigurationObservation['kind']): string {\n try {\n if (kind === 'directory') return statSync(path).isDirectory() ? 'directory' : 'absent';\n return digest(readFileSync(path));\n } catch (error) {\n return ['ENOENT', 'ENOTDIR'].includes((error as NodeJS.ErrnoException).code ?? '')\n ? 'absent'\n : 'unavailable';\n }\n}\nexport function observationsMatch(observations: ConfigurationObservation[]): boolean {\n return observations.every(({ path, kind, value }) => {\n const current = observe(path, kind);\n return current !== 'unavailable' && current === value;\n });\n}\n\n/** Hash actual installed code without evaluating any compiler or dependency module. */\nexport function toolchainIdentity(\n location = import.meta.url,\n names = packages,\n): string | undefined {\n try {\n const hash = createHash('sha256').update('twill-loader-cache-v1\\0' + process.version);\n const add = (path: string, bytes: Uint8Array) =>\n hash.update(JSON.stringify([path, digest(bytes)]));\n const directory = dirname(fileURLToPath(location));\n for (const file of readdirSync(directory).sort())\n if (file.endsWith('.js')) add(file, readFileSync(join(directory, file)));\n const seen = new Set<string>();\n const visit = (name: string, from: string) => {\n const require = createRequire(from);\n let root = dirname(require.resolve(name));\n for (;;) {\n const metadata = join(root, 'package.json');\n if (existsSync(metadata) && JSON.parse(readFileSync(metadata, 'utf8')).name === name) break;\n const parent = dirname(root);\n if (parent === root) throw new Error('Package metadata unavailable');\n root = parent;\n }\n root = realpathSync(root);\n if (seen.has(root)) return;\n seen.add(root);\n const metadata = join(root, 'package.json');\n const value = JSON.parse(readFileSync(metadata, 'utf8'));\n add(name, readFileSync(metadata));\n const walk = (folder: string) => {\n for (const entry of readdirSync(folder, { withFileTypes: true }).sort((a, b) =>\n a.name.localeCompare(b.name),\n )) {\n const path = join(folder, entry.name);\n if (entry.isSymbolicLink() && statSync(path).isDirectory())\n throw new Error('Directory symlink requires uncached compilation');\n if (entry.isDirectory() && entry.name !== 'node_modules') walk(path);\n else if (/\\.(?:[cm]?js|json)$/.test(entry.name))\n add(path.slice(root.length), readFileSync(path));\n }\n };\n // TypeScript emission/configuration executes this single compiler bundle;\n // hashing its CLI/server copies would add unrelated cold-start work.\n if (name === 'typescript')\n add('typescript/lib/typescript.js', readFileSync(require.resolve(name)));\n else walk(root);\n for (const dependency of Object.keys(value.dependencies ?? {}).sort())\n visit(dependency, metadata);\n };\n for (const name of names) visit(name, location);\n return hash.digest('hex');\n } catch {\n return undefined;\n }\n}\n\nfunction safeDirectory(directory: string): boolean {\n const info = lstatSync(directory);\n if (!info.isDirectory() || info.isSymbolicLink()) return false;\n if (process.platform === 'win32') return true; // User-selected/profile ACL, not a POSIX mode claim.\n const uid = process.getuid!();\n if (info.uid !== uid || (info.mode & 0o077) !== 0) return false;\n let path = realpathSync(directory);\n for (;;) {\n const parent = lstatSync(path);\n if (parent.uid !== uid && parent.uid !== 0) return false;\n if ((parent.mode & 0o022) !== 0 && (parent.mode & 0o1000) === 0) return false;\n const next = dirname(path);\n if (next === path) return true;\n path = next;\n }\n}\nfunction safeFile(info: Stats): boolean {\n return (\n info.isFile() &&\n info.nlink === 1 &&\n info.size <= limit &&\n (process.platform === 'win32' || (info.uid === process.getuid!() && (info.mode & 0o077) === 0))\n );\n}\n\n/** Fixed slots bound completed storage; collisions are misses, never shared module results. */\nexport class CompilationCache {\n constructor(\n private readonly directory: string,\n private readonly identity: string,\n ) {}\n\n private key(url: string, source: string, dialect: boolean) {\n return digest(JSON.stringify([this.identity, url, digest(source), dialect, 'ES2022']));\n }\n private slot(url: string) {\n return join(this.directory, (parseInt(digest(url).slice(0, 2), 16) % 128) + '.json');\n }\n read(url: string, source: string, dialect: boolean): string | undefined {\n let fd: number | undefined;\n try {\n if (!safeDirectory(this.directory)) return;\n const file = this.slot(url);\n if (lstatSync(file).isSymbolicLink()) return;\n fd = openSync(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));\n if (!safeFile(fstatSync(fd))) return;\n const value = JSON.parse(readFileSync(fd, 'utf8'));\n if (\n value.key !== this.key(url, source, dialect) ||\n typeof value.source !== 'string' ||\n value.digest !== digest(value.source) ||\n !Array.isArray(value.observations) ||\n !value.observations.every(\n (entry: ConfigurationObservation) =>\n entry &&\n typeof entry.path === 'string' &&\n isAbsolute(entry.path) &&\n ['file', 'directory'].includes(entry.kind) &&\n typeof entry.value === 'string',\n ) ||\n !observationsMatch(value.observations)\n )\n return;\n return value.source;\n } catch {\n return;\n } finally {\n if (fd !== undefined) {\n try {\n closeSync(fd);\n } catch {\n /* Cache cleanup is optional too. */\n }\n }\n }\n }\n write(\n url: string,\n source: string,\n dialect: boolean,\n emitted: string,\n observations: ConfigurationObservation[],\n ): void {\n let temporary: string | undefined;\n try {\n const value = JSON.stringify({\n key: this.key(url, source, dialect),\n source: emitted,\n digest: digest(emitted),\n observations,\n });\n if (\n Buffer.byteLength(value) > limit ||\n !safeDirectory(this.directory) ||\n !observationsMatch(observations)\n )\n return;\n temporary = join(this.directory, randomUUID() + '.tmp');\n writeFileSync(temporary, value, { flag: 'wx', mode: 0o600 });\n renameSync(temporary, this.slot(url));\n } catch {\n // An executable cache is optional; its failure never changes script behavior.\n } finally {\n if (temporary !== undefined) {\n try {\n unlinkSync(temporary);\n } catch {\n // Renamed, concurrently removed, or inaccessible; never replace the program's result.\n }\n }\n }\n }\n}\n\nexport function openCompilationCache(): CompilationCache | undefined {\n try {\n const directory = process.env.TWILL_CACHE_DIR ?? join(homedir(), '.twill', 'script-cache-v1');\n if (!isAbsolute(directory)) return;\n mkdirSync(directory, { recursive: true, mode: 0o700 });\n if (!safeDirectory(directory)) return;\n const identity = toolchainIdentity();\n if (identity) return new CompilationCache(resolve(directory), identity);\n } catch {\n // Permissions, unsupported environment or unavailable identity: compile normally.\n }\n}\n"],"mappings":";;;;;;;AA4BA,IAAM,QAAQ;AACd,IAAM,WAAW;CACf;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AACA,IAAa,UAAU,WACrB,WAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO,KAAK;AAElD,SAAgB,QAAQ,MAAc,MAAgD;CACpF,IAAI;EACF,IAAI,SAAS,aAAa,OAAO,SAAS,IAAI,CAAC,CAAC,YAAY,IAAI,cAAc;EAC9E,OAAO,OAAO,aAAa,IAAI,CAAC;CAClC,SAAS,OAAO;EACd,OAAO,CAAC,UAAU,SAAS,CAAC,CAAC,SAAU,MAAgC,QAAQ,EAAE,IAC7E,WACA;CACN;AACF;AACA,SAAgB,kBAAkB,cAAmD;CACnF,OAAO,aAAa,OAAO,EAAE,MAAM,MAAM,YAAY;EACnD,MAAM,UAAU,QAAQ,MAAM,IAAI;EAClC,OAAO,YAAY,iBAAiB,YAAY;CAClD,CAAC;AACH;;AAGA,SAAgB,kBACd,WAAW,YAAY,KACvB,QAAQ,UACY;CACpB,IAAI;EACF,MAAM,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,4BAA4B,QAAQ,OAAO;EACpF,MAAM,OAAO,MAAc,UACzB,KAAK,OAAO,KAAK,UAAU,CAAC,MAAM,OAAO,KAAK,CAAC,CAAC,CAAC;EACnD,MAAM,YAAY,QAAQ,cAAc,QAAQ,CAAC;EACjD,KAAK,MAAM,QAAQ,YAAY,SAAS,CAAC,CAAC,KAAK,GAC7C,IAAI,KAAK,SAAS,KAAK,GAAG,IAAI,MAAM,aAAa,KAAK,WAAW,IAAI,CAAC,CAAC;EACzE,MAAM,uBAAO,IAAI,IAAY;EAC7B,MAAM,SAAS,MAAc,SAAiB;GAC5C,MAAM,UAAU,cAAc,IAAI;GAClC,IAAI,OAAO,QAAQ,QAAQ,QAAQ,IAAI,CAAC;GACxC,SAAS;IACP,MAAM,WAAW,KAAK,MAAM,cAAc;IAC1C,IAAI,WAAW,QAAQ,KAAK,KAAK,MAAM,aAAa,UAAU,MAAM,CAAC,CAAC,CAAC,SAAS,MAAM;IACtF,MAAM,SAAS,QAAQ,IAAI;IAC3B,IAAI,WAAW,MAAM,MAAM,IAAI,MAAM,8BAA8B;IACnE,OAAO;GACT;GACA,OAAO,aAAa,IAAI;GACxB,IAAI,KAAK,IAAI,IAAI,GAAG;GACpB,KAAK,IAAI,IAAI;GACb,MAAM,WAAW,KAAK,MAAM,cAAc;GAC1C,MAAM,QAAQ,KAAK,MAAM,aAAa,UAAU,MAAM,CAAC;GACvD,IAAI,MAAM,aAAa,QAAQ,CAAC;GAChC,MAAM,QAAQ,WAAmB;IAC/B,KAAK,MAAM,SAAS,YAAY,QAAQ,EAAE,eAAe,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,MACxE,EAAE,KAAK,cAAc,EAAE,IAAI,CAC7B,GAAG;KACD,MAAM,OAAO,KAAK,QAAQ,MAAM,IAAI;KACpC,IAAI,MAAM,eAAe,KAAK,SAAS,IAAI,CAAC,CAAC,YAAY,GACvD,MAAM,IAAI,MAAM,iDAAiD;KACnE,IAAI,MAAM,YAAY,KAAK,MAAM,SAAS,gBAAgB,KAAK,IAAI;UAC9D,IAAI,sBAAsB,KAAK,MAAM,IAAI,GAC5C,IAAI,KAAK,MAAM,KAAK,MAAM,GAAG,aAAa,IAAI,CAAC;IACnD;GACF;GAGA,IAAI,SAAS,cACX,IAAI,gCAAgC,aAAa,QAAQ,QAAQ,IAAI,CAAC,CAAC;QACpE,KAAK,IAAI;GACd,KAAK,MAAM,cAAc,OAAO,KAAK,MAAM,gBAAgB,CAAC,CAAC,CAAC,CAAC,KAAK,GAClE,MAAM,YAAY,QAAQ;EAC9B;EACA,KAAK,MAAM,QAAQ,OAAO,MAAM,MAAM,QAAQ;EAC9C,OAAO,KAAK,OAAO,KAAK;CAC1B,QAAQ;EACN;CACF;AACF;AAEA,SAAS,cAAc,WAA4B;CACjD,MAAM,OAAO,UAAU,SAAS;CAChC,IAAI,CAAC,KAAK,YAAY,KAAK,KAAK,eAAe,GAAG,OAAO;CACzD,IAAI,QAAQ,aAAa,SAAS,OAAO;CACzC,MAAM,MAAM,QAAQ,OAAQ;CAC5B,IAAI,KAAK,QAAQ,QAAQ,KAAK,OAAO,QAAW,GAAG,OAAO;CAC1D,IAAI,OAAO,aAAa,SAAS;CACjC,SAAS;EACP,MAAM,SAAS,UAAU,IAAI;EAC7B,IAAI,OAAO,QAAQ,OAAO,OAAO,QAAQ,GAAG,OAAO;EACnD,KAAK,OAAO,OAAO,QAAW,MAAM,OAAO,OAAO,SAAY,GAAG,OAAO;EACxE,MAAM,OAAO,QAAQ,IAAI;EACzB,IAAI,SAAS,MAAM,OAAO;EAC1B,OAAO;CACT;AACF;AACA,SAAS,SAAS,MAAsB;CACtC,OACE,KAAK,OAAO,KACZ,KAAK,UAAU,KACf,KAAK,QAAQ,UACZ,QAAQ,aAAa,WAAY,KAAK,QAAQ,QAAQ,OAAQ,MAAM,KAAK,OAAO,QAAW;AAEhG;;AAGA,IAAa,mBAAb,MAA8B;CAET;CACA;CAFnB,YACE,WACA,UACA;EAFiB,KAAA,YAAA;EACA,KAAA,WAAA;CAChB;CAEH,IAAY,KAAa,QAAgB,SAAkB;EACzD,OAAO,OAAO,KAAK,UAAU;GAAC,KAAK;GAAU;GAAK,OAAO,MAAM;GAAG;GAAS;EAAQ,CAAC,CAAC;CACvF;CACA,KAAa,KAAa;EACxB,OAAO,KAAK,KAAK,WAAY,SAAS,OAAO,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,EAAE,IAAI,MAAO,OAAO;CACrF;CACA,KAAK,KAAa,QAAgB,SAAsC;EACtE,IAAI;EACJ,IAAI;GACF,IAAI,CAAC,cAAc,KAAK,SAAS,GAAG;GACpC,MAAM,OAAO,KAAK,KAAK,GAAG;GAC1B,IAAI,UAAU,IAAI,CAAC,CAAC,eAAe,GAAG;GACtC,KAAK,SAAS,MAAM,UAAU,YAAY,UAAU,cAAc,EAAE;GACpE,IAAI,CAAC,SAAS,UAAU,EAAE,CAAC,GAAG;GAC9B,MAAM,QAAQ,KAAK,MAAM,aAAa,IAAI,MAAM,CAAC;GACjD,IACE,MAAM,QAAQ,KAAK,IAAI,KAAK,QAAQ,OAAO,KAC3C,OAAO,MAAM,WAAW,YACxB,MAAM,WAAW,OAAO,MAAM,MAAM,KACpC,CAAC,MAAM,QAAQ,MAAM,YAAY,KACjC,CAAC,MAAM,aAAa,OACjB,UACC,SACA,OAAO,MAAM,SAAS,YACtB,WAAW,MAAM,IAAI,KACrB,CAAC,QAAQ,WAAW,CAAC,CAAC,SAAS,MAAM,IAAI,KACzC,OAAO,MAAM,UAAU,QAC3B,KACA,CAAC,kBAAkB,MAAM,YAAY,GAErC;GACF,OAAO,MAAM;EACf,QAAQ;GACN;EACF,UAAU;GACR,IAAI,OAAO,KAAA,GACT,IAAI;IACF,UAAU,EAAE;GACd,QAAQ,CAER;EAEJ;CACF;CACA,MACE,KACA,QACA,SACA,SACA,cACM;EACN,IAAI;EACJ,IAAI;GACF,MAAM,QAAQ,KAAK,UAAU;IAC3B,KAAK,KAAK,IAAI,KAAK,QAAQ,OAAO;IAClC,QAAQ;IACR,QAAQ,OAAO,OAAO;IACtB;GACF,CAAC;GACD,IACE,OAAO,WAAW,KAAK,IAAI,SAC3B,CAAC,cAAc,KAAK,SAAS,KAC7B,CAAC,kBAAkB,YAAY,GAE/B;GACF,YAAY,KAAK,KAAK,WAAW,WAAW,IAAI,MAAM;GACtD,cAAc,WAAW,OAAO;IAAE,MAAM;IAAM,MAAM;GAAM,CAAC;GAC3D,WAAW,WAAW,KAAK,KAAK,GAAG,CAAC;EACtC,QAAQ,CAER,UAAU;GACR,IAAI,cAAc,KAAA,GAChB,IAAI;IACF,WAAW,SAAS;GACtB,QAAQ,CAER;EAEJ;CACF;AACF;AAEA,SAAgB,uBAAqD;CACnE,IAAI;EACF,MAAM,YAAY,QAAQ,IAAI,mBAAmB,KAAK,QAAQ,GAAG,UAAU,iBAAiB;EAC5F,IAAI,CAAC,WAAW,SAAS,GAAG;EAC5B,UAAU,WAAW;GAAE,WAAW;GAAM,MAAM;EAAM,CAAC;EACrD,IAAI,CAAC,cAAc,SAAS,GAAG;EAC/B,MAAM,WAAW,kBAAkB;EACnC,IAAI,UAAU,OAAO,IAAI,iBAAiB,QAAQ,SAAS,GAAG,QAAQ;CACxE,QAAQ,CAER;AACF"}
|
|
1
|
+
{"version":3,"file":"compilation-cache-BrV71YKt.js","names":[],"sources":["../src/compilation-cache.ts"],"sourcesContent":["import {\n closeSync,\n constants,\n existsSync,\n fstatSync,\n lstatSync,\n mkdirSync,\n openSync,\n readFileSync,\n readdirSync,\n realpathSync,\n statSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n type Stats,\n} from 'node:fs';\nimport { createHash, randomUUID } from 'node:crypto';\nimport { createRequire } from 'node:module';\nimport { homedir } from 'node:os';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport type ConfigurationObservation = {\n path: string;\n kind: 'file' | 'directory';\n value: string;\n};\nconst limit = 512 * 1024;\nconst packages = [\n 'typescript',\n 'acorn',\n 'acorn-typescript',\n 'acorn-jsx',\n 'magic-string',\n '@ampproject/remapping',\n '@jridgewell/trace-mapping',\n];\nexport const digest = (source: string | Uint8Array) =>\n createHash('sha256').update(source).digest('hex');\n\nexport function observe(path: string, kind: ConfigurationObservation['kind']): string {\n try {\n if (kind === 'directory') return statSync(path).isDirectory() ? 'directory' : 'absent';\n return digest(readFileSync(path));\n } catch (error) {\n return ['ENOENT', 'ENOTDIR'].includes((error as NodeJS.ErrnoException).code ?? '')\n ? 'absent'\n : 'unavailable';\n }\n}\nexport function observationsMatch(observations: ConfigurationObservation[]): boolean {\n return observations.every(({ path, kind, value }) => {\n const current = observe(path, kind);\n return current !== 'unavailable' && current === value;\n });\n}\n\n/** Hash actual installed code without evaluating any compiler or dependency module. */\nexport function toolchainIdentity(\n location = import.meta.url,\n names = packages,\n): string | undefined {\n try {\n const hash = createHash('sha256').update('twill-loader-cache-v1\\0' + process.version);\n const add = (path: string, bytes: Uint8Array) =>\n hash.update(JSON.stringify([path, digest(bytes)]));\n const directory = dirname(fileURLToPath(location));\n for (const file of readdirSync(directory).sort())\n if (file.endsWith('.js')) add(file, readFileSync(join(directory, file)));\n const seen = new Set<string>();\n const visit = (name: string, from: string) => {\n const require = createRequire(from);\n let root = dirname(require.resolve(name));\n for (;;) {\n const metadata = join(root, 'package.json');\n if (existsSync(metadata) && JSON.parse(readFileSync(metadata, 'utf8')).name === name) break;\n const parent = dirname(root);\n if (parent === root) throw new Error('Package metadata unavailable');\n root = parent;\n }\n root = realpathSync(root);\n if (seen.has(root)) return;\n seen.add(root);\n const metadata = join(root, 'package.json');\n const value = JSON.parse(readFileSync(metadata, 'utf8'));\n add(name, readFileSync(metadata));\n const walk = (folder: string) => {\n for (const entry of readdirSync(folder, { withFileTypes: true }).sort((a, b) =>\n a.name.localeCompare(b.name),\n )) {\n const path = join(folder, entry.name);\n if (entry.isSymbolicLink() && statSync(path).isDirectory())\n throw new Error('Directory symlink requires uncached compilation');\n if (entry.isDirectory() && entry.name !== 'node_modules') walk(path);\n else if (/\\.(?:[cm]?js|json)$/.test(entry.name))\n add(path.slice(root.length), readFileSync(path));\n }\n };\n // TypeScript emission/configuration executes this single compiler bundle;\n // hashing its CLI/server copies would add unrelated cold-start work.\n if (name === 'typescript')\n add('typescript/lib/typescript.js', readFileSync(require.resolve(name)));\n else walk(root);\n for (const dependency of Object.keys(value.dependencies ?? {}).sort())\n visit(dependency, metadata);\n };\n for (const name of names) visit(name, location);\n return hash.digest('hex');\n } catch {\n return undefined;\n }\n}\n\nfunction safeDirectory(directory: string): boolean {\n const info = lstatSync(directory);\n if (!info.isDirectory() || info.isSymbolicLink()) return false;\n if (process.platform === 'win32') return true; // User-selected/profile ACL, not a POSIX mode claim.\n const uid = process.getuid!();\n if (info.uid !== uid || (info.mode & 0o077) !== 0) return false;\n let path = realpathSync(directory);\n for (;;) {\n const parent = lstatSync(path);\n if (parent.uid !== uid && parent.uid !== 0) return false;\n if ((parent.mode & 0o022) !== 0 && (parent.mode & 0o1000) === 0) return false;\n const next = dirname(path);\n if (next === path) return true;\n path = next;\n }\n}\nfunction safeFile(info: Stats): boolean {\n return (\n info.isFile() &&\n info.nlink === 1 &&\n info.size <= limit &&\n (process.platform === 'win32' || (info.uid === process.getuid!() && (info.mode & 0o077) === 0))\n );\n}\n\n/** Fixed slots bound completed storage; collisions are misses, never shared module results. */\nexport class CompilationCache {\n constructor(\n private readonly directory: string,\n private readonly identity: string,\n ) {}\n\n private key(url: string, source: string, dialect: boolean) {\n return digest(JSON.stringify([this.identity, url, digest(source), dialect, 'ES2022']));\n }\n private slot(url: string) {\n return join(this.directory, (parseInt(digest(url).slice(0, 2), 16) % 128) + '.json');\n }\n read(url: string, source: string, dialect: boolean): string | undefined {\n let fd: number | undefined;\n try {\n if (!safeDirectory(this.directory)) return;\n const file = this.slot(url);\n if (lstatSync(file).isSymbolicLink()) return;\n fd = openSync(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));\n if (!safeFile(fstatSync(fd))) return;\n const value = JSON.parse(readFileSync(fd, 'utf8'));\n if (\n value.key !== this.key(url, source, dialect) ||\n typeof value.source !== 'string' ||\n value.digest !== digest(value.source) ||\n !Array.isArray(value.observations) ||\n !value.observations.every(\n (entry: ConfigurationObservation) =>\n entry &&\n typeof entry.path === 'string' &&\n isAbsolute(entry.path) &&\n ['file', 'directory'].includes(entry.kind) &&\n typeof entry.value === 'string',\n ) ||\n !observationsMatch(value.observations)\n )\n return;\n return value.source;\n } catch {\n return;\n } finally {\n if (fd !== undefined) {\n try {\n closeSync(fd);\n } catch {\n /* Cache cleanup is optional too. */\n }\n }\n }\n }\n write(\n url: string,\n source: string,\n dialect: boolean,\n emitted: string,\n observations: ConfigurationObservation[],\n ): void {\n let temporary: string | undefined;\n try {\n const value = JSON.stringify({\n key: this.key(url, source, dialect),\n source: emitted,\n digest: digest(emitted),\n observations,\n });\n if (\n Buffer.byteLength(value) > limit ||\n !safeDirectory(this.directory) ||\n !observationsMatch(observations)\n )\n return;\n temporary = join(this.directory, randomUUID() + '.tmp');\n writeFileSync(temporary, value, { flag: 'wx', mode: 0o600 });\n renameSync(temporary, this.slot(url));\n } catch {\n // An executable cache is optional; its failure never changes script behavior.\n } finally {\n if (temporary !== undefined) {\n try {\n unlinkSync(temporary);\n } catch {\n // Renamed, concurrently removed, or inaccessible; never replace the program's result.\n }\n }\n }\n }\n}\n\nexport function openCompilationCache(): CompilationCache | undefined {\n try {\n const directory = process.env.TWILL_CACHE_DIR ?? join(homedir(), '.twill', 'script-cache-v1');\n if (!isAbsolute(directory)) return;\n mkdirSync(directory, { recursive: true, mode: 0o700 });\n if (!safeDirectory(directory)) return;\n const identity = toolchainIdentity();\n if (identity) return new CompilationCache(resolve(directory), identity);\n } catch {\n // Permissions, unsupported environment or unavailable identity: compile normally.\n }\n}\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { Parser, tokTypes } from "acorn";
|
|
|
3
3
|
import { tsPlugin } from "acorn-typescript";
|
|
4
4
|
import jsx from "acorn-jsx";
|
|
5
5
|
import { GREATEST_LOWER_BOUND, TraceMap, decodedMappings, encodedMappings, generatedPositionFor, originalPositionFor } from "@jridgewell/trace-mapping";
|
|
6
|
-
|
|
6
|
+
// src/parser.js
|
|
7
7
|
var options = {
|
|
8
8
|
ecmaVersion: "latest",
|
|
9
9
|
sourceType: "module",
|
|
@@ -714,13 +714,11 @@ function parse(source, language = "ts", sourceType = "module") {
|
|
|
714
714
|
}))
|
|
715
715
|
};
|
|
716
716
|
}
|
|
717
|
-
|
|
718
|
-
//#region src/defer-helper.ts
|
|
717
|
+
// src/defer-helper.ts
|
|
719
718
|
function syncDeferBody(names) {
|
|
720
719
|
return "\n let " + names.failed + " = false,\n " + names.failure + ";\n while (" + names.stack + "?.length) {\n const " + names.cleanup + " = " + names.stack + ".pop();\n if (" + names.cleanup + ") {\n try {\n " + names.cleanup + "();\n } catch (" + names.error + ") {\n " + names.failed + " = true;\n " + names.failure + " = " + names.error + ";\n }\n }\n }\n if (" + names.failed + ") throw " + names.failure + ";\n";
|
|
721
720
|
}
|
|
722
|
-
|
|
723
|
-
//#region src/defer.ts
|
|
721
|
+
// src/defer.ts
|
|
724
722
|
var functions = /* @__PURE__ */ new Set([
|
|
725
723
|
"FunctionDeclaration",
|
|
726
724
|
"FunctionExpression",
|
|
@@ -896,8 +894,7 @@ function lowerDefers(source, ast, code, language, usedNames, contentStarts, comm
|
|
|
896
894
|
code.prependLeft(start, `\nimport { runDefers as ${sharedHelper} } from '@swiftuijs/twill-runtime/helpers/v1';\n`);
|
|
897
895
|
}
|
|
898
896
|
}
|
|
899
|
-
|
|
900
|
-
//#region src/branches.ts
|
|
897
|
+
// src/branches.ts
|
|
901
898
|
function suspension(node) {
|
|
902
899
|
if (["AwaitExpression", "YieldExpression"].includes(node.type)) return node;
|
|
903
900
|
if ([
|
|
@@ -917,10 +914,53 @@ function suspension(node) {
|
|
|
917
914
|
}
|
|
918
915
|
}
|
|
919
916
|
}
|
|
917
|
+
function directEval(node) {
|
|
918
|
+
if ([
|
|
919
|
+
"FunctionDeclaration",
|
|
920
|
+
"FunctionExpression",
|
|
921
|
+
"ArrowFunctionExpression"
|
|
922
|
+
].includes(node.type)) return false;
|
|
923
|
+
if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "eval" && !node.optional) return true;
|
|
924
|
+
return Object.entries(node).some(([key, value]) => ![
|
|
925
|
+
"loc",
|
|
926
|
+
"trailing",
|
|
927
|
+
"tag"
|
|
928
|
+
].includes(key) && (Array.isArray(value) ? value : [value]).some((child) => child?.type && directEval(child)));
|
|
929
|
+
}
|
|
930
|
+
/** Preserve the existing expression boundary for direct eval and suspension. */
|
|
931
|
+
function canInlineSwitchExpression(node) {
|
|
932
|
+
return !directEval(node) && !suspension(node);
|
|
933
|
+
}
|
|
934
|
+
/** Reuse authored blocks only when no direct eval (including a captured one)
|
|
935
|
+
* can observe the added temporary's broader lifetime/temporal dead zone.
|
|
936
|
+
* Memoize shared parser nodes so this analysis stays linear in the AST size. */
|
|
937
|
+
function reusableStatements(ast, usedNames) {
|
|
938
|
+
const reusable = /* @__PURE__ */ new Set();
|
|
939
|
+
const observed = /* @__PURE__ */ new Map();
|
|
940
|
+
const visit = (node) => {
|
|
941
|
+
const known = observed.get(node);
|
|
942
|
+
if (known !== void 0) return known;
|
|
943
|
+
if (node.type === "Identifier" || node.type === "JSXIdentifier") usedNames.add(node.name);
|
|
944
|
+
let observable = node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "eval";
|
|
945
|
+
for (const [key, value] of Object.entries(node)) {
|
|
946
|
+
if (["loc", "trailing"].includes(key)) continue;
|
|
947
|
+
for (const child of Array.isArray(value) ? value : [value]) if (child?.type && visit(child)) observable = true;
|
|
948
|
+
}
|
|
949
|
+
if (!observable && node.type === "BlockStatement") {
|
|
950
|
+
for (const statement of node.body) if (statement.type === "TwillIfBinding" || (statement.argument ?? statement.expression)?.type === "TwillSwitchExpression") reusable.add(statement);
|
|
951
|
+
}
|
|
952
|
+
observed.set(node, observable);
|
|
953
|
+
return observable;
|
|
954
|
+
};
|
|
955
|
+
visit(ast);
|
|
956
|
+
return reusable;
|
|
957
|
+
}
|
|
920
958
|
/** Native switches with per-arm scopes. Direct returns and standalone variable
|
|
921
959
|
* initializers need no extra function; other expressions keep a lexical IIFE. */
|
|
922
|
-
function lowerSwitchExpressions(source, ast, switches, code, language, usedNames, comments, fail) {
|
|
960
|
+
function lowerSwitchExpressions(source, ast, switches, code, language, usedNames, comments, fail, implicitReturns = [], reusable = /* @__PURE__ */ new Set()) {
|
|
923
961
|
const returns = /* @__PURE__ */ new Set();
|
|
962
|
+
const implicit = new Set(implicitReturns);
|
|
963
|
+
const sharedScopes = /* @__PURE__ */ new Set();
|
|
924
964
|
const initializers = /* @__PURE__ */ new Map();
|
|
925
965
|
const leadingDoc = (start) => {
|
|
926
966
|
let low = 0, high = comments.length;
|
|
@@ -932,19 +972,6 @@ function lowerSwitchExpressions(source, ast, switches, code, language, usedNames
|
|
|
932
972
|
const comment = comments[low - 1];
|
|
933
973
|
return comment && /^\s*$/.test(source.slice(comment.end, start)) ? comment : void 0;
|
|
934
974
|
};
|
|
935
|
-
const directEval = (node) => {
|
|
936
|
-
if ([
|
|
937
|
-
"FunctionDeclaration",
|
|
938
|
-
"FunctionExpression",
|
|
939
|
-
"ArrowFunctionExpression"
|
|
940
|
-
].includes(node.type)) return false;
|
|
941
|
-
if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "eval" && !node.optional) return true;
|
|
942
|
-
return Object.entries(node).some(([key, value]) => ![
|
|
943
|
-
"loc",
|
|
944
|
-
"trailing",
|
|
945
|
-
"tag"
|
|
946
|
-
].includes(key) && (Array.isArray(value) ? value : [value]).some((child) => child?.type && directEval(child)));
|
|
947
|
-
};
|
|
948
975
|
const grouping = (start, end, initializer = false) => {
|
|
949
976
|
const parentheses = [];
|
|
950
977
|
const tokens = Parser.tokenizer(source.slice(start, end), { ecmaVersion: "latest" });
|
|
@@ -957,6 +984,15 @@ function lowerSwitchExpressions(source, ast, switches, code, language, usedNames
|
|
|
957
984
|
for (const token of parentheses) code.remove(start + token.start, start + token.end);
|
|
958
985
|
};
|
|
959
986
|
const visit = (node, parent) => {
|
|
987
|
+
if (node.type === "BlockStatement") for (const statement of node.body) {
|
|
988
|
+
const value = statement.type === "ReturnStatement" ? statement.argument : statement.expression;
|
|
989
|
+
if (value?.type === "TwillSwitchExpression" && (node.body.length === 1 || reusable.has(statement)) && (statement.type === "ReturnStatement" || implicit.has(value))) sharedScopes.add(value);
|
|
990
|
+
}
|
|
991
|
+
if (node.type === "ExpressionStatement" && implicit.has(node.expression)) {
|
|
992
|
+
grouping(node.start, node.expression.start);
|
|
993
|
+
grouping(node.expression.end, node.end);
|
|
994
|
+
if (source[node.end - 1] === ";") code.remove(node.end - 1, node.end);
|
|
995
|
+
}
|
|
960
996
|
if (node.type === "VariableDeclaration" && node.declarations.length === 1) {
|
|
961
997
|
const declaration = node.declarations[0];
|
|
962
998
|
if (declaration.id.type === "Identifier" && declaration.init?.type === "TwillSwitchExpression" && [
|
|
@@ -978,6 +1014,7 @@ function lowerSwitchExpressions(source, ast, switches, code, language, usedNames
|
|
|
978
1014
|
if (node.type === "ReturnStatement" && node.argument?.type === "TwillSwitchExpression") {
|
|
979
1015
|
returns.add(node.argument);
|
|
980
1016
|
code.remove(node.start, node.start + 6);
|
|
1017
|
+
if (source[node.end - 1] === ";") code.remove(node.end - 1, node.end);
|
|
981
1018
|
for (const [start, end] of [[node.start + 6, node.argument.start], [node.argument.end, node.end]]) grouping(start, end);
|
|
982
1019
|
}
|
|
983
1020
|
for (const [key, value] of Object.entries(node)) {
|
|
@@ -1000,8 +1037,8 @@ function lowerSwitchExpressions(source, ast, switches, code, language, usedNames
|
|
|
1000
1037
|
return name;
|
|
1001
1038
|
};
|
|
1002
1039
|
for (const node of switches) {
|
|
1003
|
-
const direct = returns.has(node);
|
|
1004
|
-
const suspended = !
|
|
1040
|
+
const direct = returns.has(node) || implicit.has(node);
|
|
1041
|
+
const suspended = !returns.has(node) && suspension(node);
|
|
1005
1042
|
if (suspended) fail(suspended, `await/yield inside a ${node.keyword} expression requires a direct return. Bind the subject first, or await the whole result explicitly.`);
|
|
1006
1043
|
const initializer = initializers.get(node);
|
|
1007
1044
|
const result = initializer ? fresh("Result") : void 0;
|
|
@@ -1020,7 +1057,10 @@ function lowerSwitchExpressions(source, ast, switches, code, language, usedNames
|
|
|
1020
1057
|
const valuePrefix = result ? `${result} = (` : "return ";
|
|
1021
1058
|
const valueSuffix = result ? `); break ${exit};` : ";";
|
|
1022
1059
|
const kind = node.cases.some((branch) => branch.enumPattern) ? fresh("Kind") : void 0;
|
|
1023
|
-
if (!initializer)
|
|
1060
|
+
if (!initializer) {
|
|
1061
|
+
const opening = direct ? sharedScopes.has(node) ? "" : "{" : "(() => {";
|
|
1062
|
+
code.appendLeft(node.start, `${opening}const ${subject} = `);
|
|
1063
|
+
}
|
|
1024
1064
|
code.remove(node.parenStart, node.parenStart + 1);
|
|
1025
1065
|
if (node.keyword === "match") {
|
|
1026
1066
|
const tokens = Parser.tokenizer(source.slice(node.discriminant.end, node.parenEnd), { ecmaVersion: "latest" });
|
|
@@ -1077,13 +1117,13 @@ function lowerSwitchExpressions(source, ast, switches, code, language, usedNames
|
|
|
1077
1117
|
}
|
|
1078
1118
|
}
|
|
1079
1119
|
const hasDefault = node.cases.some((branch) => !branch.pattern && !branch.enumPattern && branch.test === null);
|
|
1080
|
-
const check = !hasDefault && language.startsWith("ts") ?
|
|
1081
|
-
const failure = hasDefault ? "" : `${check} throw new TypeError('Non-exhaustive switch expression');`;
|
|
1082
|
-
|
|
1120
|
+
const check = !hasDefault && language.startsWith("ts") ? fresh("Check") : void 0;
|
|
1121
|
+
const failure = hasDefault ? "" : `${check ? ` type ${check}<T extends never = typeof ${kind ?? subject}> = T & never;` : ""} throw new TypeError('Non-exhaustive switch expression'${check ? ` as ${check}` : ""});`;
|
|
1122
|
+
const closing = initializer || direct && !sharedScopes.has(node) ? "}" : direct ? "" : "})()";
|
|
1123
|
+
code.overwrite(node.end - 1, node.end, `}${failure}${closing}`, { contentOnly: true });
|
|
1083
1124
|
}
|
|
1084
1125
|
}
|
|
1085
|
-
|
|
1086
|
-
//#region src/enums.ts
|
|
1126
|
+
// src/enums.ts
|
|
1087
1127
|
/** Find free type references, respecting generic function/mapped/infer binders. */
|
|
1088
1128
|
function references(node, names, found = /* @__PURE__ */ new Set(), bound = /* @__PURE__ */ new Set()) {
|
|
1089
1129
|
const local = new Set(bound);
|
|
@@ -1262,8 +1302,7 @@ function mapEnumCopies(source, generated, map, insertions) {
|
|
|
1262
1302
|
}));
|
|
1263
1303
|
return copies;
|
|
1264
1304
|
}
|
|
1265
|
-
|
|
1266
|
-
//#region src/compiler.ts
|
|
1305
|
+
// src/compiler.ts
|
|
1267
1306
|
function inferLanguage(filename) {
|
|
1268
1307
|
if (/\.(?:twillx|tsx)$/.test(filename)) return "tsx";
|
|
1269
1308
|
if (/\.jsx$/.test(filename)) return "jsx";
|
|
@@ -1288,6 +1327,25 @@ var TwillSyntaxError = class extends SyntaxError {
|
|
|
1288
1327
|
this.frame = `${source.split(/\r\n|[\r\n\u2028\u2029]/)[line - 1] ?? ""}\n${" ".repeat(column)}^`;
|
|
1289
1328
|
}
|
|
1290
1329
|
};
|
|
1330
|
+
function closureExpression(body) {
|
|
1331
|
+
if (body.length !== 1) return;
|
|
1332
|
+
return body[0].type === "ReturnStatement" ? body[0].argument : body[0].expression;
|
|
1333
|
+
}
|
|
1334
|
+
function conciseExpression(body) {
|
|
1335
|
+
const expression = closureExpression(body);
|
|
1336
|
+
return !!expression && (expression.type !== "TwillSwitchExpression" || !canInlineSwitchExpression(expression));
|
|
1337
|
+
}
|
|
1338
|
+
function expressionGrouping(expression, source, start) {
|
|
1339
|
+
while ([
|
|
1340
|
+
"TSAsExpression",
|
|
1341
|
+
"TSSatisfiesExpression",
|
|
1342
|
+
"TSNonNullExpression"
|
|
1343
|
+
].includes(expression.type)) expression = expression.expression;
|
|
1344
|
+
if (!["ObjectExpression", "SequenceExpression"].includes(expression.type)) return "";
|
|
1345
|
+
const tokens = Parser.tokenizer(source.slice(start, expression.start), { ecmaVersion: "latest" });
|
|
1346
|
+
for (let token = tokens.getToken(); token.type !== tokTypes.eof; token = tokens.getToken()) if (token.type === tokTypes.parenL) return "";
|
|
1347
|
+
return "(";
|
|
1348
|
+
}
|
|
1291
1349
|
function walk(node, visit) {
|
|
1292
1350
|
visit(node);
|
|
1293
1351
|
for (const key of Object.keys(node)) {
|
|
@@ -1317,7 +1375,9 @@ function transform(source, options = {}) {
|
|
|
1317
1375
|
const code = new MagicString(source);
|
|
1318
1376
|
const enumInsertions = parsed.enums.length ? lowerEnums(source, parsed.ast, parsed.enums, code) : [];
|
|
1319
1377
|
const usedNames = /* @__PURE__ */ new Set();
|
|
1320
|
-
|
|
1378
|
+
const reusable = parsed.ifBindings.length || parsed.switches.length ? reusableStatements(parsed.ast, usedNames) : /* @__PURE__ */ new Set();
|
|
1379
|
+
const patternGuards = parsed.guards.filter((guard) => guard.binding && guard.binding.declarations[0].id.type !== "Identifier");
|
|
1380
|
+
if (!parsed.ifBindings.length && !parsed.switches.length && (patternGuards.length || parsed.defers.length || parsed.closures.some((closure) => closure.implicitMembers.length) || /\.twillx$/.test(filename.split(/[?#]/, 1)[0]))) walk(parsed.ast, (node) => {
|
|
1321
1381
|
if (node.type === "Identifier" || node.type === "JSXIdentifier") usedNames.add(node.name);
|
|
1322
1382
|
});
|
|
1323
1383
|
let guardCounter = 0;
|
|
@@ -1330,12 +1390,13 @@ function transform(source, options = {}) {
|
|
|
1330
1390
|
while (usedNames.has(temporary));
|
|
1331
1391
|
usedNames.add(temporary);
|
|
1332
1392
|
const patternEnd = binding.typeAnnotation?.start ?? binding.end;
|
|
1333
|
-
|
|
1393
|
+
const reuseScope = reusable.has(statement);
|
|
1394
|
+
code.overwrite(statement.start, binding.start, `${reuseScope ? "" : "{"}const ${temporary}`);
|
|
1334
1395
|
code.move(binding.start, patternEnd, statement.consequent.start + 1);
|
|
1335
1396
|
code.prependRight(binding.start, "const ");
|
|
1336
1397
|
code.appendLeft(patternEnd, ` = ${temporary};`);
|
|
1337
1398
|
code.prependLeft(statement.consequent.start, `; if (${temporary} !== null && ${temporary} !== void 0) `);
|
|
1338
|
-
code.appendLeft(statement.end, "}");
|
|
1399
|
+
if (!reuseScope) code.appendLeft(statement.end, "}");
|
|
1339
1400
|
}
|
|
1340
1401
|
for (const guard of parsed.guards) {
|
|
1341
1402
|
if (guard.singleStatement) {
|
|
@@ -1344,22 +1405,24 @@ function transform(source, options = {}) {
|
|
|
1344
1405
|
}
|
|
1345
1406
|
if (guard.binding) {
|
|
1346
1407
|
const binding = guard.binding.declarations[0].id;
|
|
1347
|
-
|
|
1348
|
-
|
|
1408
|
+
let name;
|
|
1409
|
+
if (binding.type === "Identifier") {
|
|
1410
|
+
name = binding.name;
|
|
1411
|
+
code.remove(guard.start, guard.start + 5);
|
|
1412
|
+
} else {
|
|
1349
1413
|
let temporary;
|
|
1350
1414
|
do
|
|
1351
1415
|
temporary = `__twillGuard${guardCounter++}`;
|
|
1352
1416
|
while (usedNames.has(temporary));
|
|
1353
1417
|
usedNames.add(temporary);
|
|
1418
|
+
name = temporary;
|
|
1354
1419
|
const patternEnd = binding.typeAnnotation?.start ?? binding.end;
|
|
1355
1420
|
code.overwrite(guard.start, binding.start, `const ${temporary}`);
|
|
1356
1421
|
code.move(binding.start, patternEnd, guard.end);
|
|
1357
1422
|
code.prependRight(binding.start, "; const ");
|
|
1358
1423
|
code.appendLeft(patternEnd, ` = ${temporary};`);
|
|
1359
|
-
code.overwrite(guard.elseStart, guard.elseStart + 4, `; if (${temporary} == null)`);
|
|
1360
|
-
continue;
|
|
1361
1424
|
}
|
|
1362
|
-
code.overwrite(guard.elseStart, guard.elseStart + 4, `; if (${
|
|
1425
|
+
code.overwrite(guard.elseStart, guard.elseStart + 4, `; if (${name} === null || ${name} === void 0)`);
|
|
1363
1426
|
} else {
|
|
1364
1427
|
code.overwrite(guard.start, guard.start + 5, "if (!(");
|
|
1365
1428
|
code.overwrite(guard.elseStart, guard.elseStart + 4, "))");
|
|
@@ -1390,7 +1453,9 @@ function transform(source, options = {}) {
|
|
|
1390
1453
|
name = `__twillArg${implicitCounter++}`;
|
|
1391
1454
|
while (usedNames.has(name));
|
|
1392
1455
|
usedNames.add(name);
|
|
1393
|
-
|
|
1456
|
+
const body = metadata.node.body.body;
|
|
1457
|
+
const opening = (options.implicitReturn !== false || body[0]?.type === "ReturnStatement") && conciseExpression(body) ? expressionGrouping(closureExpression(body), source, metadata.header?.end ?? metadata.node.start + 1) : "{";
|
|
1458
|
+
code.overwrite(metadata.node.start, metadata.node.start + 1, `(${name}) => ${opening}`);
|
|
1394
1459
|
for (const offset of metadata.implicitMembers) code.appendLeft(offset, name);
|
|
1395
1460
|
}
|
|
1396
1461
|
const pragma = [...parsed.comments.filter((comment) => comment.start < (parsed.ast.body[0]?.start ?? source.length)).map((comment) => comment.value).join("\n").matchAll(/@jsxImportSource\s+(\S+)/g)].at(-1)?.[1];
|
|
@@ -1418,9 +1483,13 @@ function transform(source, options = {}) {
|
|
|
1418
1483
|
return edits.toString();
|
|
1419
1484
|
};
|
|
1420
1485
|
let counter = 0;
|
|
1486
|
+
const directReturns = [];
|
|
1421
1487
|
for (const node of parsed.calls) {
|
|
1422
1488
|
const { callEnd, hadParens, originalArgs, closures } = node.trailing;
|
|
1423
1489
|
const component = componentCalls.has(node);
|
|
1490
|
+
const slotObject = vue && (options.vueDefaultSlot !== "function" || closures.length > 1);
|
|
1491
|
+
let nativeChild = false;
|
|
1492
|
+
let bareChild = false;
|
|
1424
1493
|
if (component) {
|
|
1425
1494
|
if (originalArgs.length > 1 || originalArgs[0]?.type === "SpreadElement" || node.optional || node.callee.optional) throw new TwillSyntaxError(source, filename, {
|
|
1426
1495
|
message: "Component syntax accepts one props object and children; optional calls use ordinary callbacks.",
|
|
@@ -1445,8 +1514,13 @@ function transform(source, options = {}) {
|
|
|
1445
1514
|
labels.add(name);
|
|
1446
1515
|
}
|
|
1447
1516
|
}
|
|
1448
|
-
const opening = vue ? ">{({default: " : ">{(";
|
|
1449
1517
|
const tagEnd = node.typeParameters?.end ?? node.callee.end;
|
|
1518
|
+
const child = closures[0];
|
|
1519
|
+
const expression = child.body.body[0]?.expression;
|
|
1520
|
+
nativeChild = !vue && !metadataByNode.get(child).header && child.body.body.length === 1 && (expression?.type === "JSXElement" || expression?.type === "JSXFragment") && source.slice(child.start + 1, expression.start).trim() === "" && /^\s*;?\s*$/.test(source.slice(expression.end, child.end - 1)) && source.slice(callEnd, child.start).trim() === "" && (!hadParens || originalArgs.length > 0 || /^\s*\(\s*\)$/.test(source.slice(tagEnd, callEnd)));
|
|
1521
|
+
bareChild = !vue && (!!metadataByNode.get(child).header || child.body.body.length === 1 && expression !== void 0 && expression.type !== "SequenceExpression");
|
|
1522
|
+
let opening = nativeChild ? ">" : bareChild ? ">{" : ">{(";
|
|
1523
|
+
if (vue) opening = slotObject ? ">{{default: " : ">{";
|
|
1450
1524
|
code.appendLeft(node.callee.start, "(<");
|
|
1451
1525
|
const props = originalArgs[0];
|
|
1452
1526
|
if (!vue && props?.type === "ObjectExpression" && props.properties.every((property) => property.type === "SpreadElement" || property.kind === "init" && !property.method && !property.computed && (property.key.name ?? property.key.value) !== "__proto__" && /^[A-Za-z_$][\w$.:-]*$/.test(String(property.key.name ?? property.key.value)))) {
|
|
@@ -1493,7 +1567,7 @@ function transform(source, options = {}) {
|
|
|
1493
1567
|
const tokens = Parser.tokenizer(gap, { ecmaVersion: "latest" });
|
|
1494
1568
|
const first = tokens.getToken();
|
|
1495
1569
|
const last = tokens.getToken();
|
|
1496
|
-
code.overwrite(tagEnd, callEnd, opening + gap.slice(first.end, last.start));
|
|
1570
|
+
code.overwrite(tagEnd, callEnd, opening + (nativeChild ? "" : gap.slice(first.end, last.start)));
|
|
1497
1571
|
} else code.appendLeft(callEnd, opening);
|
|
1498
1572
|
} else if (hadParens) {
|
|
1499
1573
|
const tailStart = originalArgs.at(-1)?.end ?? callEnd - 1;
|
|
@@ -1516,24 +1590,40 @@ function transform(source, options = {}) {
|
|
|
1516
1590
|
"DeferStatement",
|
|
1517
1591
|
"EmptyStatement"
|
|
1518
1592
|
].includes(statement.type)) && body.filter((statement) => statement.type !== "EmptyStatement").at(-1) === expressions[0];
|
|
1593
|
+
const explicitReturn = !content && body.length === 1 && body[0].type === "ReturnStatement" && body[0].argument;
|
|
1594
|
+
const returnsValue = explicitReturn || singleContent || options.implicitReturn !== false && body.length === 1 && body[0].type === "ExpressionStatement";
|
|
1595
|
+
const conciseBody = returnsValue && conciseExpression(body);
|
|
1596
|
+
const grouping = conciseBody ? expressionGrouping(closureExpression(body), source, metadata.header?.end ?? closure.start + 1) : "{";
|
|
1519
1597
|
if (singleContent && body.length === 1 && !vue) {
|
|
1520
1598
|
directChild = true;
|
|
1521
|
-
code.remove(closure.start, closure.start + 1);
|
|
1522
|
-
code.remove(closure.end - 1, closure.end);
|
|
1523
1599
|
const statement = expressions[0];
|
|
1524
|
-
if (
|
|
1600
|
+
if (nativeChild) {
|
|
1601
|
+
code.remove(callEnd, statement.expression.start);
|
|
1602
|
+
code.overwrite(statement.expression.end, closure.end, "", { contentOnly: true });
|
|
1603
|
+
} else {
|
|
1604
|
+
code.remove(closure.start, closure.start + 1);
|
|
1605
|
+
code.overwrite(closure.end - 1, closure.end, "", { contentOnly: true });
|
|
1606
|
+
if (source[statement.end - 1] === ";") code.remove(statement.end - 1, statement.end);
|
|
1607
|
+
}
|
|
1525
1608
|
continue;
|
|
1526
1609
|
}
|
|
1527
1610
|
if (metadata.label) {
|
|
1528
1611
|
const label = source.slice(metadata.label.start, metadata.label.end).replace(/\s*:\s*$/, "");
|
|
1529
|
-
code.overwrite(metadata.label.start, closure.start, component && vue ? `, [${JSON.stringify(label)}]: ` : ", ");
|
|
1612
|
+
code.overwrite(metadata.label.start, closure.start, component && vue ? `, ${label === "__proto__" ? `[${JSON.stringify(label)}]` : JSON.stringify(label)}: ` : ", ");
|
|
1530
1613
|
}
|
|
1531
1614
|
if (metadata.header) {
|
|
1532
1615
|
const header = metadata.header;
|
|
1533
1616
|
code.overwrite(closure.start, closure.start + 1, header.parenthesized || metadata.async ? "" : "(");
|
|
1534
1617
|
if (metadata.async && !header.parenthesized) code.appendLeft(header.asyncEnd, " (");
|
|
1535
|
-
code.overwrite(header.inStart, header.end, `${header.parenthesized ? "" : ") "}=> {`);
|
|
1536
|
-
} else if (!metadata.implicitMembers.length) code.overwrite(closure.start, closure.start + 1,
|
|
1618
|
+
code.overwrite(header.inStart, header.end, `${header.parenthesized ? "" : ") "}=> ${grouping}`);
|
|
1619
|
+
} else if (!metadata.implicitMembers.length) code.overwrite(closure.start, closure.start + 1, `() => ${grouping}`);
|
|
1620
|
+
if (conciseBody) {
|
|
1621
|
+
const statement = body[0];
|
|
1622
|
+
if (explicitReturn) code.remove(statement.start, statement.start + 6);
|
|
1623
|
+
if (source[statement.end - 1] === ";") code.remove(statement.end - 1, statement.end);
|
|
1624
|
+
code.overwrite(closure.end - 1, closure.end, grouping ? ")" : "", { contentOnly: true });
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1537
1627
|
if (content && !singleContent) {
|
|
1538
1628
|
let collector;
|
|
1539
1629
|
do
|
|
@@ -1586,13 +1676,16 @@ function transform(source, options = {}) {
|
|
|
1586
1676
|
body.forEach(collect);
|
|
1587
1677
|
code.prependLeft(bodyStart, `const ${collector} = [];`);
|
|
1588
1678
|
code.prependLeft(closure.end - 1, vue ? `;return ${collector};` : `;return ${collector}.length === 1 ? ${collector}[0] : ${collector};`);
|
|
1589
|
-
} else if (
|
|
1679
|
+
} else if (returnsValue && !explicitReturn) {
|
|
1590
1680
|
const statement = singleContent ? expressions[0] : body[0];
|
|
1591
|
-
|
|
1592
|
-
|
|
1681
|
+
if (statement.expression.type === "TwillSwitchExpression" && canInlineSwitchExpression(statement.expression)) directReturns.push(statement.expression);
|
|
1682
|
+
else {
|
|
1683
|
+
code.prependLeft(statement.start, "return (");
|
|
1684
|
+
code.appendLeft(source[statement.end - 1] === ";" ? statement.end - 1 : statement.end, ")");
|
|
1685
|
+
}
|
|
1593
1686
|
}
|
|
1594
1687
|
}
|
|
1595
|
-
if (component) code.prependLeft(closures.at(-1).end, (vue ? "}
|
|
1688
|
+
if (component) code.prependLeft(closures.at(-1).end, (vue ? slotObject ? "}}" : "}" : nativeChild ? "" : bareChild ? "}" : directChild ? ")}" : ")()}") + `</${calleeName(node.callee)}>)`);
|
|
1596
1689
|
else code.appendLeft(closures.at(-1).end, ")");
|
|
1597
1690
|
}
|
|
1598
1691
|
if (parsed.switches.length) lowerSwitchExpressions(source, parsed.ast, parsed.switches, code, options.language ?? inferLanguage(filename), usedNames, parsed.comments, (node, message) => {
|
|
@@ -1601,7 +1694,7 @@ function transform(source, options = {}) {
|
|
|
1601
1694
|
pos: node.start,
|
|
1602
1695
|
loc: node.loc.start
|
|
1603
1696
|
});
|
|
1604
|
-
});
|
|
1697
|
+
}, directReturns, reusable);
|
|
1605
1698
|
for (const end of attributeClosings) code.appendLeft(end, "}");
|
|
1606
1699
|
if (parsed.defers.length) {
|
|
1607
1700
|
const starts = /* @__PURE__ */ new Map();
|
|
@@ -1660,7 +1753,6 @@ function generatedPosition(result, line, column) {
|
|
|
1660
1753
|
bias: GREATEST_LOWER_BOUND
|
|
1661
1754
|
});
|
|
1662
1755
|
}
|
|
1663
|
-
//#endregion
|
|
1664
1756
|
export { transform as a, originalPosition as i, generatedPosition as n, parse as o, inferLanguage as r, TwillSyntaxError as t };
|
|
1665
1757
|
|
|
1666
|
-
//# sourceMappingURL=compiler-
|
|
1758
|
+
//# sourceMappingURL=compiler-DI0H4d1R.js.map
|