@swiftuijs/twill 0.2.0 → 0.3.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 +22 -7
- package/bin/run.mjs +2 -2
- package/dist/cli.js +6 -6
- package/dist/{compilation-cache-CuCGz02_.js → compilation-cache-7SugqJ4n.js} +2 -2
- package/dist/{compilation-cache-CuCGz02_.js.map → compilation-cache-7SugqJ4n.js.map} +1 -1
- package/dist/{compiler-DY1N07np.js → compiler-BdkU4Ruh.js} +85 -26
- package/dist/compiler-BdkU4Ruh.js.map +1 -0
- package/dist/config-la_KkjCS.js +1 -0
- package/dist/config.js +2 -1
- package/dist/{configuration-CTJjSu_h.js → configuration-BVMf6Q_j.js} +17 -7
- package/dist/configuration-BVMf6Q_j.js.map +1 -0
- package/dist/declarations-BTAS8Mb_.js +132 -0
- package/dist/declarations-BTAS8Mb_.js.map +1 -0
- package/dist/declarations.js +1 -131
- package/dist/defer.d.ts +1 -1
- package/dist/doctor-Co_pdZtt.js +202 -0
- package/dist/doctor-Co_pdZtt.js.map +1 -0
- package/dist/doctor.js +1 -201
- package/dist/editor.js +1 -1
- package/dist/esbuild.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{loader-compiler-DIw9aw5n.js → loader-compiler.js} +3 -3
- package/dist/loader-compiler.js.map +1 -0
- package/dist/loader-registration-B1ST9h1e.js +15 -0
- package/dist/loader-registration-B1ST9h1e.js.map +1 -0
- package/dist/loader-registration.d.ts +4 -0
- package/dist/loader-registration.js +2 -0
- package/dist/loader-state-DE2ArGYl.js +61 -0
- package/dist/loader-state-DE2ArGYl.js.map +1 -0
- package/dist/loader-state.d.ts +34 -0
- package/dist/loader-sync.d.ts +3 -0
- package/dist/loader-sync.js +28 -0
- package/dist/loader-sync.js.map +1 -0
- package/dist/loader.d.ts +2 -4
- package/dist/loader.js +9 -46
- package/dist/loader.js.map +1 -1
- package/dist/{plugin-Bks2hxCJ.js → plugin-DdTVagIU.js} +3 -3
- package/dist/{plugin-Bks2hxCJ.js.map → plugin-DdTVagIU.js.map} +1 -1
- package/dist/project-ZmlpFooa.js +444 -0
- package/dist/project-ZmlpFooa.js.map +1 -0
- package/dist/project.js +1 -438
- package/dist/recovery.d.ts +2 -2
- package/dist/register.js +2 -2
- package/dist/register.js.map +1 -1
- package/dist/rollup.js +1 -1
- package/dist/rspack.js +1 -1
- package/dist/syntax.js +1 -1
- package/dist/{transpile-DG81yfVs.js → transpile-CLNavDUr.js} +2 -2
- package/dist/{transpile-DG81yfVs.js.map → transpile-CLNavDUr.js.map} +1 -1
- package/dist/typescript-plugin.cjs +96 -33
- package/dist/typescript-plugin.cjs.map +1 -1
- package/dist/vite-BI-5KRgr.js +7 -0
- package/dist/vite-BI-5KRgr.js.map +1 -0
- package/dist/vite-react.js +1 -1
- package/dist/vite.js +1 -6
- package/dist/webpack.js +1 -1
- package/package.json +2 -2
- package/dist/compiler-DY1N07np.js.map +0 -1
- package/dist/configuration-CTJjSu_h.js.map +0 -1
- package/dist/declarations.js.map +0 -1
- package/dist/doctor.js.map +0 -1
- package/dist/loader-compiler-DIw9aw5n.js.map +0 -1
- package/dist/project.js.map +0 -1
- package/dist/vite.js.map +0 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ async function readOwned(acquire: () => Promise<TextDocument>) {
|
|
|
22
22
|
|
|
23
23
|
The successful path stays in view; release sits next to acquisition. Registration is block-scoped, cleanup is awaited explicitly, and `return await` keeps an owned resource alive until its work finishes. The output uses ordinary JS control flow.
|
|
24
24
|
|
|
25
|
-
**Release line:** 0.
|
|
25
|
+
**Release line:** 0.2. Twill is an experimental language with a tested compiler, editor and build workflow. Start with one module and evaluate it against your application’s requirements. See [support and compatibility](https://twill.evecalm.com/readiness).
|
|
26
26
|
|
|
27
27
|
## Why use Twill?
|
|
28
28
|
|
|
@@ -52,7 +52,9 @@ function describe(outcome: Outcome): string {
|
|
|
52
52
|
}
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
Ordinary closures become arrows; guards become branches. Direct-return switch expressions become native switches. Twill 0.2.0 also removes the IIFE from standalone identifier initializers; other expression contexts retain it. Inline emission introduces no runtime library. `defer`
|
|
55
|
+
Ordinary closures become arrows; guards become branches. Direct-return switch expressions become native switches. Twill 0.2.0 also removes the IIFE from standalone identifier initializers; other expression contexts retain it. Inline emission introduces no runtime library. Dynamic/async `defer` and scope-sensitive registrations retain callbacks; dynamic registrations use a local stack; general component child collection uses arrays. These costs are [documented and measured](https://twill.evecalm.com/performance).
|
|
56
|
+
|
|
57
|
+
Twill 0.3.0 emits native `try/finally` for eligible direct synchronous cleanup and reduces repeated editor/configuration work. The older 0.2.0 retains callback-based cleanup. Dynamic registration, general expression lowering and cold initialization still have measured costs; use the [performance guide](https://twill.evecalm.com/performance) to choose and profile hot paths.
|
|
56
58
|
|
|
57
59
|
| File | Use |
|
|
58
60
|
| ---------------------------- | ----------------------------------------------------------- |
|
|
@@ -68,11 +70,11 @@ Twill and native files can import each other through the build adapters, virtual
|
|
|
68
70
|
|
|
69
71
|
The [playground](https://twill.evecalm.com/playground) runs the actual compiler locally, with live highlighting and generated-source inspection. It does not execute your code or type-check project imports.
|
|
70
72
|
|
|
71
|
-
Install the compiler as a development dependency in your application.
|
|
73
|
+
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.
|
|
72
74
|
|
|
73
75
|
```sh
|
|
74
|
-
pnpm add -D @swiftuijs/twill
|
|
75
|
-
# npm install --save-dev @swiftuijs/twill
|
|
76
|
+
pnpm add -D @swiftuijs/twill@0.3.1
|
|
77
|
+
# npm install --save-dev @swiftuijs/twill@0.3.1
|
|
76
78
|
```
|
|
77
79
|
|
|
78
80
|
Add the Vite adapter to your existing configuration:
|
|
@@ -92,6 +94,19 @@ pnpm exec twill check -p tsconfig.json
|
|
|
92
94
|
|
|
93
95
|
Follow [getting started](https://twill.evecalm.com/getting-started) to complete the editor, formatter and linter setup. For React, use the [React Vite adapter](https://twill.evecalm.com/frameworks#react-with-vite-8) to keep Fast Refresh working.
|
|
94
96
|
|
|
97
|
+
## Run a script
|
|
98
|
+
|
|
99
|
+
For a project-local command, install the compiler above, save this as `hello.twill` and run `npm exec -- twill hello.twill 'hello world'` (or `pnpm exec twill hello.twill 'hello world'`):
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
#!/usr/bin/env twill
|
|
103
|
+
console.log(process.argv.slice(2));
|
|
104
|
+
```
|
|
105
|
+
|
|
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.3.1` and keep `twill` on PATH. Windows uses the explicit CLI.
|
|
107
|
+
|
|
108
|
+
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
|
+
|
|
95
110
|
## A complete working loop
|
|
96
111
|
|
|
97
112
|
| Tool | What it provides |
|
|
@@ -136,7 +151,7 @@ Install only the tools your project needs. Keep Twill packages on the same relea
|
|
|
136
151
|
| `@swiftuijs/twill-shell` | Native argv execution, bounded output and owned process teardown |
|
|
137
152
|
| `@swiftuijs/twill-runtime` | Optional, versioned synchronous cleanup helpers |
|
|
138
153
|
|
|
139
|
-
The [shell scripting](https://twill.evecalm.com/scripting) toolkit adds `#!/usr/bin/env twill` executable scripts and content-validated startup caching in 0.2.0. Install/import `@swiftuijs/twill-shell` for subprocesses;
|
|
154
|
+
The [shell scripting](https://twill.evecalm.com/scripting) toolkit adds `#!/usr/bin/env twill` executable scripts and content-validated startup caching in 0.2.0. Install/import `@swiftuijs/twill-shell` for subprocesses; native JS/TS consumers need no compiler. npm 0.2.0 still uses the retired Rust addon. Twill 0.3.0 simplifies the SDK to Node spawn/streams with zero production dependencies; cancel and await work before worker/process exit. See the [version and ownership boundary](https://twill.evecalm.com/scripting#execution-and-version-boundary).
|
|
140
155
|
|
|
141
156
|
Install the [Twill VS Code extension](https://marketplace.visualstudio.com/items?itemName=forth-ink.twill) from Marketplace, or run `code --install-extension forth-ink.twill`. The extension bundles its editing tools. Your application installs the compiler for builds and whole-project checks. See the [editor guide](https://twill.evecalm.com/tooling) for configuration.
|
|
142
157
|
|
|
@@ -152,7 +167,7 @@ Install the [official Twill AI skill](https://twill.evecalm.com/ai) with `npx sk
|
|
|
152
167
|
|
|
153
168
|
## Contributing
|
|
154
169
|
|
|
155
|
-
The repository is a pnpm workspace with Vite builds. See the [development guide](https://github.com/swiftuijs/twill/blob/main/docs/contributing/tooling.md) for checkout, dependency and build commands. Compiler, formatter, linter, export, highlight, optional runtime and shell packages, editor packages, examples and the English documentation site own their code and tests.
|
|
170
|
+
The repository is a pnpm workspace with Vite builds. Tooling packages and editor integrations are developed in Twill, exercising the same public adapters, checker and declaration emitter as applications. The compiler remains bootstrapped from JS/TS; see [developing tooling in Twill](https://github.com/swiftuijs/twill/blob/main/docs/contributing/dogfooding.md). See the [development guide](https://github.com/swiftuijs/twill/blob/main/docs/contributing/tooling.md) for checkout, dependency and build commands. Compiler, formatter, linter, export, highlight, optional runtime and shell packages, editor packages, examples and the English documentation site own their code and tests.
|
|
156
171
|
|
|
157
172
|
```sh
|
|
158
173
|
pnpm check
|
package/bin/run.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolve, extname } from 'node:path';
|
|
2
2
|
import { pathToFileURL } from 'node:url';
|
|
3
|
-
import {
|
|
3
|
+
import { registerLoader } from '../dist/loader-registration.js';
|
|
4
4
|
import { realpathSync } from 'node:fs';
|
|
5
5
|
|
|
6
6
|
export async function run(filename, args) {
|
|
@@ -10,6 +10,6 @@ export async function run(filename, args) {
|
|
|
10
10
|
const entries = native ? [] : [url, pathToFileURL(realpathSync(script)).href];
|
|
11
11
|
process.argv = [process.execPath, script, ...args];
|
|
12
12
|
process.setSourceMapsEnabled(true);
|
|
13
|
-
|
|
13
|
+
await registerLoader({ entries, cache: true });
|
|
14
14
|
await import(url);
|
|
15
15
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as transform } from "./compiler-
|
|
3
|
-
import { n as loadConfig } from "./configuration-
|
|
4
|
-
import { TwillProject } from "./project.js";
|
|
5
|
-
import { inspectProject } from "./doctor.js";
|
|
6
|
-
import { emitDeclarations } from "./declarations.js";
|
|
7
|
-
import { t as transpile } from "./transpile-
|
|
2
|
+
import { a as transform } from "./compiler-BdkU4Ruh.js";
|
|
3
|
+
import { n as loadConfig } from "./configuration-BVMf6Q_j.js";
|
|
4
|
+
import { t as TwillProject } from "./project-ZmlpFooa.js";
|
|
5
|
+
import { t as inspectProject } from "./doctor-Co_pdZtt.js";
|
|
6
|
+
import { t as emitDeclarations } from "./declarations-BTAS8Mb_.js";
|
|
7
|
+
import { t as transpile } from "./transpile-CLNavDUr.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";
|
|
@@ -167,6 +167,6 @@ function openCompilationCache() {
|
|
|
167
167
|
} catch {}
|
|
168
168
|
}
|
|
169
169
|
//#endregion
|
|
170
|
-
export {
|
|
170
|
+
export { openCompilationCache as i, observationsMatch as n, observe as r, digest as t };
|
|
171
171
|
|
|
172
|
-
//# sourceMappingURL=compilation-cache-
|
|
172
|
+
//# sourceMappingURL=compilation-cache-7SugqJ4n.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compilation-cache-CuCGz02_.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-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":"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"}
|
|
@@ -19,9 +19,12 @@ function guardAt(input, offset, implicitMember = false) {
|
|
|
19
19
|
const lexer = Parser.tokenizer(input.slice(offset), { ecmaVersion: "latest" });
|
|
20
20
|
const stack = [];
|
|
21
21
|
let first = true;
|
|
22
|
+
let missingElse = false;
|
|
23
|
+
let afterNonNull = false;
|
|
22
24
|
try {
|
|
23
25
|
for (;;) {
|
|
24
|
-
const
|
|
26
|
+
const token = lexer.getToken();
|
|
27
|
+
const label = token.type.label;
|
|
25
28
|
if (first && [
|
|
26
29
|
"=",
|
|
27
30
|
".",
|
|
@@ -35,6 +38,22 @@ function guardAt(input, offset, implicitMember = false) {
|
|
|
35
38
|
"in",
|
|
36
39
|
"instanceof"
|
|
37
40
|
].includes(label) && !(implicitMember && label === ".")) return false;
|
|
41
|
+
if (first || afterNonNull) {
|
|
42
|
+
missingElse = !/[\r\n\u2028\u2029]/.test(input.slice(offset, offset + token.start)) && (token.type === tokTypes.name && !["as", "satisfies"].includes(token.value) || [
|
|
43
|
+
"const",
|
|
44
|
+
"num",
|
|
45
|
+
"string",
|
|
46
|
+
"true",
|
|
47
|
+
"false",
|
|
48
|
+
"null",
|
|
49
|
+
"this",
|
|
50
|
+
"new",
|
|
51
|
+
"typeof",
|
|
52
|
+
"void",
|
|
53
|
+
"delete"
|
|
54
|
+
].includes(label) || token.type === tokTypes.prefix && token.value === "~" || implicitMember && first && label === ".");
|
|
55
|
+
afterNonNull = token.type === tokTypes.prefix && token.value === "!";
|
|
56
|
+
}
|
|
38
57
|
if (!stack.length && [
|
|
39
58
|
"if",
|
|
40
59
|
"for",
|
|
@@ -45,15 +64,12 @@ function guardAt(input, offset, implicitMember = false) {
|
|
|
45
64
|
"throw",
|
|
46
65
|
"var",
|
|
47
66
|
"export"
|
|
48
|
-
].includes(label)) return false;
|
|
49
|
-
if (!first && !stack.length && label === "const") return false;
|
|
67
|
+
].includes(label)) return missingElse ? "missingElse" : false;
|
|
68
|
+
if (!first && !stack.length && label === "const") return missingElse ? "missingElse" : false;
|
|
50
69
|
first = false;
|
|
51
70
|
if (!stack.length && label === "else") return true;
|
|
52
|
-
if (!stack.length &&
|
|
53
|
-
|
|
54
|
-
";",
|
|
55
|
-
"}"
|
|
56
|
-
].includes(label)) return false;
|
|
71
|
+
if (label === "eof") return !stack.length && missingElse ? "missingElse" : false;
|
|
72
|
+
if (!stack.length && [";", "}"].includes(label)) return missingElse ? "missingElse" : false;
|
|
57
73
|
if ([
|
|
58
74
|
"(",
|
|
59
75
|
"[",
|
|
@@ -189,6 +205,15 @@ function parserFor(language) {
|
|
|
189
205
|
if (parsers.has(language)) return parsers.get(language);
|
|
190
206
|
const Base = bases[language];
|
|
191
207
|
class TwillParser extends Base {
|
|
208
|
+
declareName(name, bindingType, position) {
|
|
209
|
+
if (bindingType === 6 || bindingType === 7) {
|
|
210
|
+
const scope = this.currentScope();
|
|
211
|
+
(scope.types ??= []).push(name);
|
|
212
|
+
if (this.inModule && scope.flags & 1) delete this.undefinedExports[name];
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
super.declareName(name, bindingType, position);
|
|
216
|
+
}
|
|
192
217
|
skipHeader = null;
|
|
193
218
|
closures = [];
|
|
194
219
|
lineStarts = null;
|
|
@@ -281,7 +306,9 @@ function parserFor(language) {
|
|
|
281
306
|
node.cleanup = cleanup;
|
|
282
307
|
return this.finishNode(node, "DeferStatement");
|
|
283
308
|
}
|
|
284
|
-
|
|
309
|
+
const guard = this.isContextual("guard") && guardAt(this.input, this.end, !!this.implicitClosure);
|
|
310
|
+
if (guard === "missingElse") this.raise(this.start, "Missing 'else' in guard statement. Put 'else' before the failure block: 'guard condition else { return ...; }'.");
|
|
311
|
+
if (!guard) {
|
|
285
312
|
const statement = super.parseStatement(context, ...args);
|
|
286
313
|
if (context && statement.type === "TwillEnumDeclaration") this.raise(statement.start, "An associated-value enum requires a block; add braces around this body.");
|
|
287
314
|
return statement;
|
|
@@ -290,6 +317,7 @@ function parserFor(language) {
|
|
|
290
317
|
node.singleStatement = !!context;
|
|
291
318
|
this.next();
|
|
292
319
|
node.binding = null;
|
|
320
|
+
if (this.type === tokTypes._else) this.raise(this.start, "Guard needs a condition or 'const' binding before 'else'.");
|
|
293
321
|
if (this.type === tokTypes._const) {
|
|
294
322
|
if (context) this.raise(node.start, "A guard binding requires a block; add braces around this body.");
|
|
295
323
|
const declaration = this.startNode();
|
|
@@ -299,8 +327,9 @@ function parserFor(language) {
|
|
|
299
327
|
if (node.binding.declarations.length !== 1) this.raise(node.start, "guard const requires one binding declaration.");
|
|
300
328
|
} else node.test = this.parseExpression();
|
|
301
329
|
node.elseStart = this.start;
|
|
302
|
-
this.
|
|
303
|
-
|
|
330
|
+
if (this.type !== tokTypes._else) this.raise(this.start, "Expected 'else' after the guard condition or binding.");
|
|
331
|
+
this.next();
|
|
332
|
+
if (this.type !== tokTypes.braceL) this.raise(this.start, "Guard 'else' must be followed by a block, for example 'else { return ...; }'.");
|
|
304
333
|
node.failure = this.parseBlock();
|
|
305
334
|
if (!exits(node.failure)) this.raise(node.failure.start, "Every guard else path must exit with return, throw, break or continue.");
|
|
306
335
|
return this.finishNode(node, "GuardStatement");
|
|
@@ -662,9 +691,10 @@ function parse(source, language = "ts", sourceType = "module") {
|
|
|
662
691
|
if (node.type === "DeferStatement") defers.push(node);
|
|
663
692
|
if (node.type === "TwillSwitchExpression") switches.push(node);
|
|
664
693
|
if (node.type === "TwillEnumDeclaration") enums.push(node);
|
|
665
|
-
for (const
|
|
694
|
+
for (const key of Object.keys(node)) {
|
|
666
695
|
if (key === "trailing" || key === "loc") continue;
|
|
667
|
-
|
|
696
|
+
const value = node[key];
|
|
697
|
+
if (Array.isArray(value)) for (const child of value) visit(child);
|
|
668
698
|
else if (value?.type) visit(value);
|
|
669
699
|
}
|
|
670
700
|
};
|
|
@@ -697,15 +727,15 @@ var functions = /* @__PURE__ */ new Set([
|
|
|
697
727
|
"ArrowFunctionExpression"
|
|
698
728
|
]);
|
|
699
729
|
function children(node, visit) {
|
|
700
|
-
for (const
|
|
730
|
+
for (const key of Object.keys(node)) {
|
|
701
731
|
if (key === "loc" || key === "trailing") continue;
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
else if (value?.type) visit(value);
|
|
732
|
+
const value = node[key];
|
|
733
|
+
if (Array.isArray(value)) {
|
|
734
|
+
for (const child of value) if (child?.type) visit(child);
|
|
735
|
+
} else if (value?.type) visit(value);
|
|
706
736
|
}
|
|
707
737
|
}
|
|
708
|
-
/**
|
|
738
|
+
/** Native finally where scope permits; callbacks retain dynamic registration semantics. */
|
|
709
739
|
function lowerDefers(source, ast, code, language, usedNames, contentStarts, comments, runtime, sourceType, fail) {
|
|
710
740
|
const scopes = [];
|
|
711
741
|
const visit = (node, scope, parent) => {
|
|
@@ -760,6 +790,35 @@ function lowerDefers(source, ast, code, language, usedNames, contentStarts, comm
|
|
|
760
790
|
if (!scope.defers.length) continue;
|
|
761
791
|
const { block } = scope;
|
|
762
792
|
const statements = block.body;
|
|
793
|
+
const first = statements.indexOf(scope.defers[0]);
|
|
794
|
+
let native = first >= 0 && scope.defers.every((node) => !node.awaited && statements.includes(node));
|
|
795
|
+
if (native) {
|
|
796
|
+
native = !statements.some((node, index) => node.type === "FunctionDeclaration" || node.type === "TSDeclareFunction" || node.type === "VariableDeclaration" && node.kind.endsWith("using") || index > first && (node.type === "VariableDeclaration" && node.kind !== "var" || node.type.endsWith("Declaration") && node.type !== "VariableDeclaration" || node.type === "GuardStatement" && node.binding));
|
|
797
|
+
const check = (node) => {
|
|
798
|
+
if (node.type === "WithStatement" || node.type === "ThisExpression" || node.type === "VariableDeclarator" && !node.init || node.type === "VariableDeclaration" && node.kind === "var" && (!statements.includes(node) || node.start > scope.defers[0].start) || node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "eval") native = false;
|
|
799
|
+
children(node, check);
|
|
800
|
+
};
|
|
801
|
+
if (native) check(block);
|
|
802
|
+
const cleanupScope = (node) => {
|
|
803
|
+
if (node.type === "FunctionDeclaration" || node.directive || node.type === "VariableDeclaration" && node.kind === "var") native = false;
|
|
804
|
+
if (!functions.has(node.type)) children(node, cleanupScope);
|
|
805
|
+
};
|
|
806
|
+
if (native) for (const node of scope.defers) cleanupScope(node.cleanup.body);
|
|
807
|
+
}
|
|
808
|
+
if (native) {
|
|
809
|
+
for (const node of [...scope.defers].reverse()) {
|
|
810
|
+
code.overwrite(node.start, node.cleanup.body.start, ";try {");
|
|
811
|
+
if (!node.cleanup.body.body.length) {
|
|
812
|
+
code.remove(node.cleanup.body.start, node.cleanup.body.end);
|
|
813
|
+
code.appendLeft(block.end - 1, "\n} finally " + source.slice(node.cleanup.body.start, node.cleanup.body.end) + "\n");
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
code.prependRight(node.cleanup.body.start, "\n} finally ");
|
|
817
|
+
code.appendLeft(node.cleanup.body.end, "\n");
|
|
818
|
+
if (node.cleanup.body.end !== block.end - 1) code.move(node.cleanup.body.start, node.cleanup.body.end, block.end - 1);
|
|
819
|
+
}
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
763
822
|
const single = scope.defers.length === 1 && statements.includes(scope.defers[0]);
|
|
764
823
|
const stack = fresh(single ? "Cleanup" : "Defers");
|
|
765
824
|
const failure = single ? "" : fresh("Failure");
|
|
@@ -1231,12 +1290,12 @@ var TwillSyntaxError = class extends SyntaxError {
|
|
|
1231
1290
|
};
|
|
1232
1291
|
function walk(node, visit) {
|
|
1233
1292
|
visit(node);
|
|
1234
|
-
for (const
|
|
1235
|
-
if (
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
else if (value?.type) walk(value, visit);
|
|
1293
|
+
for (const key of Object.keys(node)) {
|
|
1294
|
+
if (key === "loc" || key === "trailing") continue;
|
|
1295
|
+
const value = node[key];
|
|
1296
|
+
if (Array.isArray(value)) {
|
|
1297
|
+
for (const child of value) if (child?.type) walk(child, visit);
|
|
1298
|
+
} else if (value?.type) walk(value, visit);
|
|
1240
1299
|
}
|
|
1241
1300
|
}
|
|
1242
1301
|
function calleeName(node) {
|
|
@@ -1604,4 +1663,4 @@ function generatedPosition(result, line, column) {
|
|
|
1604
1663
|
//#endregion
|
|
1605
1664
|
export { transform as a, originalPosition as i, generatedPosition as n, parse as o, inferLanguage as r, TwillSyntaxError as t };
|
|
1606
1665
|
|
|
1607
|
-
//# sourceMappingURL=compiler-
|
|
1666
|
+
//# sourceMappingURL=compiler-BdkU4Ruh.js.map
|