tjs-lang 0.7.6 → 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.
Files changed (61) hide show
  1. package/CLAUDE.md +101 -26
  2. package/bin/docs.js +4 -1
  3. package/demo/docs.json +46 -12
  4. package/demo/src/examples.test.ts +1 -0
  5. package/demo/src/imports.test.ts +16 -4
  6. package/demo/src/imports.ts +60 -15
  7. package/demo/src/playground-shared.ts +9 -8
  8. package/demo/src/tfs-worker.js +205 -147
  9. package/demo/src/tjs-playground.ts +34 -10
  10. package/demo/src/ts-playground.ts +24 -8
  11. package/dist/index.js +140 -119
  12. package/dist/index.js.map +4 -4
  13. package/dist/src/lang/bool-coercion.d.ts +50 -0
  14. package/dist/src/lang/docs.d.ts +31 -6
  15. package/dist/src/lang/linter.d.ts +8 -0
  16. package/dist/src/lang/parser-transforms.d.ts +18 -0
  17. package/dist/src/lang/parser-types.d.ts +2 -0
  18. package/dist/src/lang/parser.d.ts +9 -0
  19. package/dist/src/lang/runtime.d.ts +34 -0
  20. package/dist/src/lang/types.d.ts +9 -1
  21. package/dist/src/rbac/index.d.ts +1 -1
  22. package/dist/src/vm/runtime.d.ts +1 -1
  23. package/dist/tjs-eval.js +44 -39
  24. package/dist/tjs-eval.js.map +4 -4
  25. package/dist/tjs-from-ts.js +20 -20
  26. package/dist/tjs-from-ts.js.map +3 -3
  27. package/dist/tjs-lang.js +86 -80
  28. package/dist/tjs-lang.js.map +4 -4
  29. package/dist/tjs-vm.js +50 -45
  30. package/dist/tjs-vm.js.map +4 -4
  31. package/llms.txt +79 -0
  32. package/package.json +3 -2
  33. package/src/cli/commands/convert.test.ts +16 -21
  34. package/src/lang/bool-coercion.test.ts +203 -0
  35. package/src/lang/bool-coercion.ts +314 -0
  36. package/src/lang/codegen.test.ts +177 -0
  37. package/src/lang/docs.test.ts +328 -1
  38. package/src/lang/docs.ts +424 -24
  39. package/src/lang/emitters/ast.ts +11 -12
  40. package/src/lang/emitters/dts.test.ts +41 -0
  41. package/src/lang/emitters/dts.ts +9 -0
  42. package/src/lang/emitters/js-tests.ts +16 -4
  43. package/src/lang/emitters/js.ts +208 -2
  44. package/src/lang/features.test.ts +22 -0
  45. package/src/lang/inference.ts +54 -0
  46. package/src/lang/linter.test.ts +104 -1
  47. package/src/lang/linter.ts +124 -1
  48. package/src/lang/parser-params.ts +31 -0
  49. package/src/lang/parser-transforms.ts +539 -6
  50. package/src/lang/parser-types.ts +2 -0
  51. package/src/lang/parser.test.ts +73 -1
  52. package/src/lang/parser.ts +85 -1
  53. package/src/lang/runtime.ts +98 -0
  54. package/src/lang/tests.ts +21 -8
  55. package/src/lang/types.ts +6 -0
  56. package/src/rbac/index.ts +2 -2
  57. package/src/rbac/rules.tjs.d.ts +9 -0
  58. package/src/vm/atoms/batteries.ts +2 -2
  59. package/src/vm/runtime.ts +10 -3
  60. package/dist/src/rbac/rules.d.ts +0 -184
  61. 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.6",
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(['bun', 'src/cli/tjs.ts', 'convert', inputPath], {
150
- cwd: '/Users/tonioloewald/tjs-lang',
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
- ['bun', 'src/cli/tjs.ts', 'convert', '--emit-tjs', inputPath],
169
- { cwd: '/Users/tonioloewald/tjs-lang', stdout: 'pipe', stderr: 'pipe' }
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
- ['bun', 'src/cli/tjs.ts', 'convert', '-V', inputPath],
185
- { cwd: '/Users/tonioloewald/tjs-lang', stdout: 'pipe', stderr: 'pipe' }
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(['bun', 'src/cli/tjs.ts', 'convert', inputPath], {
198
- cwd: '/Users/tonioloewald/tjs-lang',
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
- ['bun', 'src/cli/tjs.ts', 'convert', srcDir, '-o', outDir],
231
- { cwd: '/Users/tonioloewald/tjs-lang', stdout: 'pipe', stderr: 'pipe' }
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
- 'bun',
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
+ })