tjs-lang 0.7.7 → 0.7.8
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/CLAUDE.md +90 -33
- package/bin/docs.js +4 -1
- package/demo/docs.json +45 -11
- package/demo/src/examples.test.ts +1 -0
- package/demo/src/imports.test.ts +16 -4
- package/demo/src/imports.ts +60 -15
- package/demo/src/playground-shared.ts +9 -8
- package/demo/src/tfs-worker.js +205 -147
- package/demo/src/tjs-playground.ts +34 -10
- package/demo/src/ts-playground.ts +24 -8
- package/dist/index.js +118 -101
- package/dist/index.js.map +4 -4
- package/dist/src/lang/bool-coercion.d.ts +50 -0
- package/dist/src/lang/docs.d.ts +31 -6
- package/dist/src/lang/linter.d.ts +8 -0
- package/dist/src/lang/parser-transforms.d.ts +18 -0
- package/dist/src/lang/parser-types.d.ts +2 -0
- package/dist/src/lang/parser.d.ts +3 -0
- package/dist/src/lang/runtime.d.ts +34 -0
- package/dist/src/lang/types.d.ts +9 -1
- package/dist/src/rbac/index.d.ts +1 -1
- package/dist/src/vm/runtime.d.ts +1 -1
- package/dist/tjs-eval.js +38 -36
- package/dist/tjs-eval.js.map +4 -4
- package/dist/tjs-from-ts.js +20 -20
- package/dist/tjs-from-ts.js.map +3 -3
- package/dist/tjs-lang.js +85 -83
- package/dist/tjs-lang.js.map +4 -4
- package/dist/tjs-vm.js +47 -45
- package/dist/tjs-vm.js.map +4 -4
- package/llms.txt +79 -0
- package/package.json +3 -2
- package/src/cli/commands/convert.test.ts +16 -21
- package/src/lang/bool-coercion.test.ts +203 -0
- package/src/lang/bool-coercion.ts +314 -0
- package/src/lang/codegen.test.ts +137 -0
- package/src/lang/docs.test.ts +328 -1
- package/src/lang/docs.ts +424 -24
- package/src/lang/emitters/ast.ts +11 -12
- package/src/lang/emitters/dts.test.ts +41 -0
- package/src/lang/emitters/dts.ts +9 -0
- package/src/lang/emitters/js-tests.ts +9 -4
- package/src/lang/emitters/js.ts +182 -2
- package/src/lang/inference.ts +54 -0
- package/src/lang/linter.test.ts +104 -1
- package/src/lang/linter.ts +124 -1
- package/src/lang/parser-params.ts +31 -0
- package/src/lang/parser-transforms.ts +304 -0
- package/src/lang/parser-types.ts +2 -0
- package/src/lang/parser.test.ts +73 -1
- package/src/lang/parser.ts +34 -1
- package/src/lang/runtime.ts +98 -0
- package/src/lang/types.ts +6 -0
- package/src/rbac/index.ts +2 -2
- package/src/rbac/rules.tjs.d.ts +9 -0
- package/src/vm/atoms/batteries.ts +2 -2
- package/src/vm/runtime.ts +10 -3
- package/dist/src/rbac/rules.d.ts +0 -184
- package/src/rbac/rules.js +0 -338
package/llms.txt
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# tjs-lang
|
|
2
|
+
|
|
3
|
+
> A typed JavaScript platform: TJS (a TS-like language with runtime type validation), AJS (a gas-metered VM for executing untrusted agent code), and a single-pass toolchain (transpile + lint + test + docs). Types survive to runtime as contracts, examples, and tests. Inline WASM, monadic errors, safe eval, capability-based sandboxing.
|
|
4
|
+
|
|
5
|
+
This file is a navigation index for AI agents. It does not contain the docs themselves — it points to them. Read the linked files in the order suggested below.
|
|
6
|
+
|
|
7
|
+
## Read this first (avoids the #1 LLM mistake)
|
|
8
|
+
|
|
9
|
+
- [CLAUDE-TJS-SYNTAX.md](CLAUDE-TJS-SYNTAX.md) — TJS syntax reference. Critical: `function foo(x: 'default')` is NOT a TypeScript string-literal type. The colon value is an **example**; `'default'` widens to `string`. LLMs get this wrong constantly.
|
|
10
|
+
- [CLAUDE.md](CLAUDE.md) — project overview, common commands, architecture, security model, and key APIs.
|
|
11
|
+
|
|
12
|
+
## Language guides
|
|
13
|
+
|
|
14
|
+
- [DOCS-TJS.md](DOCS-TJS.md) — TJS language guide (the long form).
|
|
15
|
+
- [DOCS-AJS.md](DOCS-AJS.md) — AJS runtime guide (gas-metered sandboxed VM).
|
|
16
|
+
- [TJS-FOR-JS.md](TJS-FOR-JS.md) — for JavaScript developers: syntax differences, gotchas.
|
|
17
|
+
- [TJS-FOR-TS.md](TJS-FOR-TS.md) — for TypeScript developers: migration, interop, what changes.
|
|
18
|
+
- [guides/tjs.md](guides/tjs.md) and [guides/ajs.md](guides/ajs.md) — usage-oriented walkthroughs.
|
|
19
|
+
- [guides/patterns.md](guides/patterns.md) — common patterns and idioms.
|
|
20
|
+
- [guides/tjs-examples.md](guides/tjs-examples.md) — annotated example programs.
|
|
21
|
+
- [guides/footguns.md](guides/footguns.md) — JS footguns TJS quietly fixes (boxed-primitive coercion, ==/!=, typeof null, uninitialized let, etc.).
|
|
22
|
+
- [guides/playground-imports.md](guides/playground-imports.md) — how the playground resolves `import` statements: TFS service worker, default JSDelivr `/+esm`, esm.sh allowlist for React, CDN hints (`jsdelivr/`, `esmsh/`, `unpkg/`, `github/`), full-URL passthrough.
|
|
23
|
+
- [guides/ajs-llm-prompt.md](guides/ajs-llm-prompt.md) — prompt template for asking LLMs to write AJS.
|
|
24
|
+
|
|
25
|
+
## Architecture and internals
|
|
26
|
+
|
|
27
|
+
- [CONTEXT.md](CONTEXT.md) — architecture deep dive (two-layer design, atom system, value resolution).
|
|
28
|
+
- [docs/function-predicate-design.md](docs/function-predicate-design.md) — Type / Generic / FunctionPredicate design.
|
|
29
|
+
- [docs/generic-dts-design.md](docs/generic-dts-design.md) — `.d.ts` generation from TJS.
|
|
30
|
+
- [docs/WASM-QUICKSTART.md](docs/WASM-QUICKSTART.md) — inline WASM, SIMD, `wasmBuffer()`.
|
|
31
|
+
- [docs/native-engine-integration.md](docs/native-engine-integration.md) — embedding into a native runtime.
|
|
32
|
+
|
|
33
|
+
## Examples and live code
|
|
34
|
+
|
|
35
|
+
- [examples/](examples/) — standalone `.tjs` example files (`hello.tjs`, `datetime.tjs`, `generic-demo.tjs`, `js-footguns-fixed.tjs`).
|
|
36
|
+
- [tjs-src/](tjs-src/) — the TJS runtime, written in TJS itself (self-hosting reference).
|
|
37
|
+
- Playground: https://tjs-platform.web.app — try TJS in a browser.
|
|
38
|
+
|
|
39
|
+
## Package entry points (what to import)
|
|
40
|
+
|
|
41
|
+
- `tjs-lang` → `src/index.ts` — main entry: `Agent`, `AgentVM`, `ajs`, `tjs`.
|
|
42
|
+
- `tjs-lang/lang` → `src/lang/transpiler.ts` — language tools only: `tjs`, `transpile`.
|
|
43
|
+
- `tjs-lang/lang/from-ts` → `src/lang/emitters/from-ts.ts` — TypeScript → TJS/JS transpilation.
|
|
44
|
+
- `tjs-lang/vm` → `src/vm/index.ts` — VM and atoms.
|
|
45
|
+
- `tjs-lang/eval` → `src/lang/eval.ts` — `Eval`, `SafeFunction`.
|
|
46
|
+
- `tjs-lang/batteries` → `src/batteries/index.ts` — LM Studio integration (lazy, local-only).
|
|
47
|
+
|
|
48
|
+
## Source map (for code-reading agents)
|
|
49
|
+
|
|
50
|
+
- `src/lang/parser.ts` — TJS parser (colon shorthand, safety markers, return-type extraction).
|
|
51
|
+
- `src/lang/parser-transforms.ts` — Type / Generic / FunctionPredicate block transforms.
|
|
52
|
+
- `src/lang/emitters/js.ts` — emits JavaScript with `__tjs` metadata.
|
|
53
|
+
- `src/lang/emitters/from-ts.ts` — TypeScript → TJS/JS, including class metadata.
|
|
54
|
+
- `src/lang/emitters/dts.ts` — `.d.ts` generation.
|
|
55
|
+
- `src/lang/runtime.ts` — TJS runtime: monadic errors, `checkType`, `createRuntime`.
|
|
56
|
+
- `src/lang/inference.ts` — infer `TypeDescriptor` from example values.
|
|
57
|
+
- `src/lang/json-schema.ts` — JSON Schema from `TypeDescriptor`.
|
|
58
|
+
- `src/lang/wasm.ts` — WASM bytecode emission.
|
|
59
|
+
- `src/lang/linter.ts` — static analysis (unused vars, unreachable code).
|
|
60
|
+
- `src/vm/runtime.ts` — atom implementations, expression sandboxing, fuel charging (security-critical, ~3000 lines).
|
|
61
|
+
- `src/vm/vm.ts` — `AgentVM` class.
|
|
62
|
+
- `src/vm/atoms/` — individual atom definitions; `batteries.ts` has LLM/vector/store atoms.
|
|
63
|
+
- `src/builder.ts` — fluent `TypedBuilder` API for constructing AST without source.
|
|
64
|
+
- `src/cli/tjs.ts` — CLI: `check`, `run`, `types`, `emit`, `convert`, `test`.
|
|
65
|
+
|
|
66
|
+
## Common gotchas
|
|
67
|
+
|
|
68
|
+
- `tjs(source)` returns `{ code, types, metadata, testResults, ... }`. Use `.code` for the transpiled JS string.
|
|
69
|
+
- Errors are `MonadicError` (check with `isMonadicError()`), not native `Error`. Errors are returned, not thrown.
|
|
70
|
+
- AJS: `null.foo.bar` returns `undefined` (uses `?.` semantics) — does NOT throw like JS.
|
|
71
|
+
- AJS: computed member access with variables (`items[i]`) is a transpile-time error; use `.map`/`.reduce` atoms.
|
|
72
|
+
- Native TJS has all safety modes ON by default. `TjsCompat` disables them. `fromTS` output has them OFF unless `TjsStrict` is added.
|
|
73
|
+
|
|
74
|
+
## Tracking and process
|
|
75
|
+
|
|
76
|
+
- [TODO.md](TODO.md) — open work, organized by area. Move done items to the **Completed** section.
|
|
77
|
+
- [PLAN.md](PLAN.md) — roadmap.
|
|
78
|
+
- [AGENTS.md](AGENTS.md) — session-completion checklist (canonical). Hard rule: not done until `git push` succeeds.
|
|
79
|
+
- [README.md](README.md) — public-facing pitch.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tjs-lang",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
4
4
|
"description": "Type-safe JavaScript dialect with runtime validation, sandboxed VM execution, and AI agent orchestration. Transpiles TypeScript to validated JS with fuel-metered execution for untrusted code.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -76,7 +76,8 @@
|
|
|
76
76
|
"demo",
|
|
77
77
|
"tjs-lang.svg",
|
|
78
78
|
"CONTEXT.md",
|
|
79
|
-
"CLAUDE.md"
|
|
79
|
+
"CLAUDE.md",
|
|
80
|
+
"llms.txt"
|
|
80
81
|
],
|
|
81
82
|
"sideEffects": false,
|
|
82
83
|
"repository": {
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { describe, it, expect, beforeAll, afterAll } from 'bun:test'
|
|
2
2
|
import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'fs'
|
|
3
|
-
import { join } from 'path'
|
|
3
|
+
import { join, resolve } from 'path'
|
|
4
4
|
import { tmpdir } from 'os'
|
|
5
5
|
import { fromTS } from '../../lang/emitters/from-ts'
|
|
6
6
|
import { tjs } from '../../lang'
|
|
7
7
|
|
|
8
|
+
const REPO_ROOT = resolve(import.meta.dir, '../../..')
|
|
9
|
+
const BUN = process.execPath
|
|
10
|
+
|
|
8
11
|
const TS_SIMPLE = `
|
|
9
12
|
function greet(name: string): string {
|
|
10
13
|
return \`Hello, \${name}!\`
|
|
@@ -146,8 +149,8 @@ function getAge(): number { return 30 }
|
|
|
146
149
|
const inputPath = join(tmpDir, 'hello.ts')
|
|
147
150
|
writeFileSync(inputPath, TS_SIMPLE)
|
|
148
151
|
|
|
149
|
-
const proc = Bun.spawn([
|
|
150
|
-
cwd:
|
|
152
|
+
const proc = Bun.spawn([BUN, 'src/cli/tjs.ts', 'convert', inputPath], {
|
|
153
|
+
cwd: REPO_ROOT,
|
|
151
154
|
stdout: 'pipe',
|
|
152
155
|
stderr: 'pipe',
|
|
153
156
|
})
|
|
@@ -165,8 +168,8 @@ function getAge(): number { return 30 }
|
|
|
165
168
|
writeFileSync(inputPath, TS_SIMPLE)
|
|
166
169
|
|
|
167
170
|
const proc = Bun.spawn(
|
|
168
|
-
[
|
|
169
|
-
{ cwd:
|
|
171
|
+
[BUN, 'src/cli/tjs.ts', 'convert', '--emit-tjs', inputPath],
|
|
172
|
+
{ cwd: REPO_ROOT, stdout: 'pipe', stderr: 'pipe' }
|
|
170
173
|
)
|
|
171
174
|
const stdout = await new Response(proc.stdout).text()
|
|
172
175
|
await proc.exited
|
|
@@ -181,8 +184,8 @@ function getAge(): number { return 30 }
|
|
|
181
184
|
writeFileSync(inputPath, TS_WITH_TESTS)
|
|
182
185
|
|
|
183
186
|
const proc = Bun.spawn(
|
|
184
|
-
[
|
|
185
|
-
{ cwd:
|
|
187
|
+
[BUN, 'src/cli/tjs.ts', 'convert', '-V', inputPath],
|
|
188
|
+
{ cwd: REPO_ROOT, stdout: 'pipe', stderr: 'pipe' }
|
|
186
189
|
)
|
|
187
190
|
const stderr = await new Response(proc.stderr).text()
|
|
188
191
|
await proc.exited
|
|
@@ -194,8 +197,8 @@ function getAge(): number { return 30 }
|
|
|
194
197
|
const inputPath = join(tmpDir, 'failing.ts')
|
|
195
198
|
writeFileSync(inputPath, TS_WITH_FAILING_TEST)
|
|
196
199
|
|
|
197
|
-
const proc = Bun.spawn([
|
|
198
|
-
cwd:
|
|
200
|
+
const proc = Bun.spawn([BUN, 'src/cli/tjs.ts', 'convert', inputPath], {
|
|
201
|
+
cwd: REPO_ROOT,
|
|
199
202
|
stdout: 'pipe',
|
|
200
203
|
stderr: 'pipe',
|
|
201
204
|
})
|
|
@@ -227,8 +230,8 @@ function getAge(): number { return 30 }
|
|
|
227
230
|
writeFileSync(join(srcDir, 'types.d.ts'), `// declaration file`)
|
|
228
231
|
|
|
229
232
|
const proc = Bun.spawn(
|
|
230
|
-
[
|
|
231
|
-
{ cwd:
|
|
233
|
+
[BUN, 'src/cli/tjs.ts', 'convert', srcDir, '-o', outDir],
|
|
234
|
+
{ cwd: REPO_ROOT, stdout: 'pipe', stderr: 'pipe' }
|
|
232
235
|
)
|
|
233
236
|
await proc.exited
|
|
234
237
|
|
|
@@ -258,16 +261,8 @@ function getAge(): number { return 30 }
|
|
|
258
261
|
)
|
|
259
262
|
|
|
260
263
|
const proc = Bun.spawn(
|
|
261
|
-
[
|
|
262
|
-
|
|
263
|
-
'src/cli/tjs.ts',
|
|
264
|
-
'convert',
|
|
265
|
-
'--emit-tjs',
|
|
266
|
-
srcDir,
|
|
267
|
-
'-o',
|
|
268
|
-
outDir,
|
|
269
|
-
],
|
|
270
|
-
{ cwd: '/Users/tonioloewald/tjs-lang', stdout: 'pipe', stderr: 'pipe' }
|
|
264
|
+
[BUN, 'src/cli/tjs.ts', 'convert', '--emit-tjs', srcDir, '-o', outDir],
|
|
265
|
+
{ cwd: REPO_ROOT, stdout: 'pipe', stderr: 'pipe' }
|
|
271
266
|
)
|
|
272
267
|
await proc.exited
|
|
273
268
|
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boolean coercion rewriter tests.
|
|
3
|
+
*
|
|
4
|
+
* Verifies that `Boolean(new Boolean(false)) === true` and friends are
|
|
5
|
+
* fixed under TjsStandard mode.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach } from 'bun:test'
|
|
9
|
+
import { transpileToJS } from './emitters/js'
|
|
10
|
+
import { createRuntime } from './runtime'
|
|
11
|
+
|
|
12
|
+
function run(src: string): any {
|
|
13
|
+
const r = transpileToJS(src)
|
|
14
|
+
;(globalThis as any).__tjs = createRuntime()
|
|
15
|
+
try {
|
|
16
|
+
const fn = new Function(r.code + '\nreturn f')()
|
|
17
|
+
return fn()
|
|
18
|
+
} finally {
|
|
19
|
+
delete (globalThis as any).__tjs
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function emit(src: string): string {
|
|
24
|
+
return transpileToJS(src).code
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('Boolean coercion rewriter (TjsStandard)', () => {
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
delete (globalThis as any).__tjs
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('truthiness contexts unwrap boxed primitives', () => {
|
|
33
|
+
it('fixes `if (new Boolean(false))`', () => {
|
|
34
|
+
const out = run(
|
|
35
|
+
`function f(){ if (new Boolean(false)) return 'truthy'; return 'falsy' }`
|
|
36
|
+
)
|
|
37
|
+
expect(out).toBe('falsy')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('fixes `while (new Boolean(false))`', () => {
|
|
41
|
+
const out = run(
|
|
42
|
+
`function f(){ let n = 0; const g = new Boolean(false); while (g) { n++; if (n>2) break }; return n }`
|
|
43
|
+
)
|
|
44
|
+
expect(out).toBe(0)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('fixes `do {} while (new Boolean(false))`', () => {
|
|
48
|
+
const out = run(
|
|
49
|
+
`function f(){ let n = 0; const g = new Boolean(false); do { n++ } while (g && n < 3); return n }`
|
|
50
|
+
)
|
|
51
|
+
expect(out).toBe(1) // body runs once, then condition is checked & is false
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('fixes `for (_; new Boolean(false); _)`', () => {
|
|
55
|
+
const out = run(
|
|
56
|
+
`function f(){ let n = 0; const g = new Boolean(false); for (let i = 0; g; i++) { n++; if (n>2) break }; return n }`
|
|
57
|
+
)
|
|
58
|
+
expect(out).toBe(0)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('fixes `!new Boolean(false)`', () => {
|
|
62
|
+
const out = run(`function f(){ return !new Boolean(false) }`)
|
|
63
|
+
expect(out).toBe(true)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('fixes `new Boolean(false) ? a : b`', () => {
|
|
67
|
+
const out = run(`function f(){ return new Boolean(false) ? 'a' : 'b' }`)
|
|
68
|
+
expect(out).toBe('b')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('fixes `Boolean(new Boolean(false))`', () => {
|
|
72
|
+
const out = run(`function f(){ return Boolean(new Boolean(false)) }`)
|
|
73
|
+
expect(out).toBe(false)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('logical operators preserve value-returning semantics', () => {
|
|
78
|
+
it('`new Boolean(false) || x` returns x', () => {
|
|
79
|
+
const out = run(`function f(){ return (new Boolean(false)) || 'right' }`)
|
|
80
|
+
expect(out).toBe('right')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('`new Boolean(true) || x` returns the wrapper (truthy LHS wins)', () => {
|
|
84
|
+
const out = run(`function f(){ return (new Boolean(true)) || 'right' }`)
|
|
85
|
+
// LHS is truthy after unwrap, so JS-style && returns the original LHS
|
|
86
|
+
expect(typeof out).toBe('object')
|
|
87
|
+
expect(out.valueOf()).toBe(true)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('`new Boolean(false) && x` returns the wrapper (falsy LHS short-circuits)', () => {
|
|
91
|
+
const out = run(`function f(){ return (new Boolean(false)) && 'right' }`)
|
|
92
|
+
expect(typeof out).toBe('object')
|
|
93
|
+
expect(out.valueOf()).toBe(false)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('`new Boolean(true) && x` returns x', () => {
|
|
97
|
+
const out = run(`function f(){ return (new Boolean(true)) && 'right' }`)
|
|
98
|
+
expect(out).toBe('right')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it("`a || b` doesn't double-evaluate side effects", () => {
|
|
102
|
+
const out = run(
|
|
103
|
+
`function f(){ let n = 0; const inc = () => { n++; return new Boolean(false) }; const r = inc() || inc(); return n }`
|
|
104
|
+
)
|
|
105
|
+
expect(out).toBe(2) // both inc() called once each
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('nested coercion contexts', () => {
|
|
110
|
+
it('handles `if (a && b)`', () => {
|
|
111
|
+
const out = run(
|
|
112
|
+
`function f(){ const a = new Boolean(false); const b = true; if (a && b) return 'in'; return 'out' }`
|
|
113
|
+
)
|
|
114
|
+
expect(out).toBe('out')
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('handles `if (!(a && b))`', () => {
|
|
118
|
+
const out = run(
|
|
119
|
+
`function f(){ const a = new Boolean(false); const b = true; if (!(a && b)) return 'not'; return 'is' }`
|
|
120
|
+
)
|
|
121
|
+
expect(out).toBe('not')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('handles `f(a && b)` (LogicalExpression inside CallExpression)', () => {
|
|
125
|
+
const out = run(
|
|
126
|
+
`function f(){ const a = new Boolean(false); const id = (x) => x; const r = id(a && true); return Boolean(r) }`
|
|
127
|
+
)
|
|
128
|
+
expect(out).toBe(false)
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
describe('does not break normal JS', () => {
|
|
133
|
+
it('truthy primitives still truthy', () => {
|
|
134
|
+
const out = run(`function f(){ if (1) return 'in'; return 'out' }`)
|
|
135
|
+
expect(out).toBe('in')
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('falsy primitives still falsy', () => {
|
|
139
|
+
const out = run(`function f(){ if (0) return 'in'; return 'out' }`)
|
|
140
|
+
expect(out).toBe('out')
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('plain objects still truthy', () => {
|
|
144
|
+
const out = run(`function f(){ if ({}) return 'in'; return 'out' }`)
|
|
145
|
+
expect(out).toBe('in')
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('arrays still truthy', () => {
|
|
149
|
+
const out = run(`function f(){ if ([]) return 'in'; return 'out' }`)
|
|
150
|
+
expect(out).toBe('in')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('?? is unchanged (only checks null/undefined, not truthy)', () => {
|
|
154
|
+
const out = run(`function f(){ return null ?? 'fallback' }`)
|
|
155
|
+
expect(out).toBe('fallback')
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('`new Boolean(false) ?? x` returns the wrapper (it is not null/undef)', () => {
|
|
159
|
+
const out = run(`function f(){ return (new Boolean(false)) ?? 'x' }`)
|
|
160
|
+
expect(typeof out).toBe('object')
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('`Boolean(true)` (no boxed arg) still returns true', () => {
|
|
164
|
+
const out = run(`function f(){ return Boolean(1) }`)
|
|
165
|
+
expect(out).toBe(true)
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe('mode gating', () => {
|
|
170
|
+
it('TjsCompat disables the rewrite (preserves JS footgun)', () => {
|
|
171
|
+
const r = transpileToJS(
|
|
172
|
+
`TjsCompat\nfunction f(){ if (new Boolean(false)) return 'truthy'; return 'falsy' }`
|
|
173
|
+
)
|
|
174
|
+
;(globalThis as any).__tjs = createRuntime()
|
|
175
|
+
try {
|
|
176
|
+
const fn = new Function(r.code + '\nreturn f')()
|
|
177
|
+
// Native JS behavior: boxed Boolean(false) is truthy
|
|
178
|
+
expect(fn()).toBe('truthy')
|
|
179
|
+
} finally {
|
|
180
|
+
delete (globalThis as any).__tjs
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('emits __tjs.toBool calls under TjsStandard', () => {
|
|
185
|
+
const code = emit(`function f(){ if (x) return 1 }`)
|
|
186
|
+
expect(code).toContain('__tjs.toBool(')
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('does not emit __tjs.toBool calls under TjsCompat', () => {
|
|
190
|
+
const code = emit(`TjsCompat\nfunction f(){ if (x) return 1 }`)
|
|
191
|
+
expect(code).not.toContain('__tjs.toBool(')
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
describe('inline-runtime fallback includes toBool', () => {
|
|
196
|
+
it('emitted code includes toBool function when used', () => {
|
|
197
|
+
const code = emit(`function f(){ if (x) return 1 }`)
|
|
198
|
+
// Under TjsStandard, the rewrite is applied → __tjs.toBool is referenced
|
|
199
|
+
// → inline runtime includes the function definition
|
|
200
|
+
expect(code).toContain('function toBool')
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
})
|