tjs-lang 0.6.44 → 0.7.3
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 +85 -422
- package/README.md +15 -82
- package/bin/benchmarks.ts +7 -7
- package/bin/dev.ts +2 -1
- package/demo/autocomplete.test.ts +1 -1
- package/demo/docs.json +744 -48
- package/demo/src/demo-nav.ts +5 -5
- package/demo/src/index.ts +28 -36
- package/demo/src/module-sw.ts +1 -1
- package/demo/src/playground-shared.ts +17 -17
- package/demo/src/playground.ts +13 -1
- package/demo/src/style.ts +4 -1
- package/demo/src/tjs-playground.ts +5 -5
- package/demo/src/user-store.ts +2 -1
- package/demo/static/favicon.svg +17 -24
- package/demo/static/tosi-platform.json +9304 -0
- package/dist/index.js +158 -156
- package/dist/index.js.map +14 -13
- package/dist/scripts/compat-effect.d.ts +16 -0
- package/dist/scripts/compat-kysely.d.ts +13 -0
- package/dist/scripts/compat-radash.d.ts +13 -0
- package/dist/scripts/compat-superstruct.d.ts +13 -0
- package/dist/scripts/compat-ts-pattern.d.ts +13 -0
- package/dist/scripts/compat-zod.d.ts +12 -0
- package/dist/src/lang/emitters/from-ts.d.ts +1 -1
- package/dist/src/lang/emitters/js-tests.d.ts +4 -0
- package/dist/src/lang/emitters/js.d.ts +2 -2
- package/dist/src/lang/index.d.ts +1 -0
- package/dist/src/lang/json-schema.d.ts +40 -0
- package/dist/src/lang/parser-transforms.d.ts +14 -0
- package/dist/src/lang/runtime.d.ts +39 -6
- package/dist/src/types/Type.d.ts +5 -0
- package/dist/tjs-full.js +158 -156
- package/dist/tjs-full.js.map +14 -13
- package/dist/tjs-vm.js +44 -43
- package/dist/tjs-vm.js.map +5 -5
- package/docs/README.md +21 -20
- package/docs/WASM-QUICKSTART.md +283 -0
- package/docs/diagrams/architecture-shift.svg +117 -0
- package/docs/diagrams/compile-runtime.svg +130 -0
- package/docs/diagrams/icon-riff-1.svg +55 -0
- package/docs/diagrams/icon-riff-2.svg +62 -0
- package/docs/diagrams/icon-riff-3.svg +61 -0
- package/docs/diagrams/platform-overview.svg +114 -0
- package/docs/diagrams/safe-eval.svg +147 -0
- package/docs/eval-v4/arch-comparison.svg +277 -0
- package/docs/eval-v4/bundler-tree.svg +250 -0
- package/docs/eval-v4/http-lifecycle.svg +148 -0
- package/docs/function-predicate-design.md +8 -8
- package/docs/native-engine-integration.md +2 -2
- package/editors/codemirror/autocomplete.test.ts +29 -29
- package/package.json +10 -4
- package/src/cli/commands/convert.test.ts +11 -8
- package/src/cli/tjs.ts +1 -1
- package/src/lang/codegen.test.ts +117 -112
- package/src/lang/docs.test.ts +22 -22
- package/src/lang/docs.ts +5 -8
- package/src/lang/emitters/dts.test.ts +13 -13
- package/src/lang/emitters/from-ts.ts +36 -9
- package/src/lang/emitters/js-tests.ts +143 -28
- package/src/lang/emitters/js.ts +49 -28
- package/src/lang/features.test.ts +259 -43
- package/src/lang/from-ts.test.ts +3 -3
- package/src/lang/function-predicate.test.ts +1 -1
- package/src/lang/index.ts +8 -47
- package/src/lang/json-schema.test.ts +261 -0
- package/src/lang/json-schema.ts +167 -0
- package/src/lang/parser-params.ts +28 -44
- package/src/lang/parser-transforms.ts +255 -0
- package/src/lang/parser.test.ts +32 -13
- package/src/lang/parser.ts +49 -11
- package/src/lang/perf.test.ts +11 -11
- package/src/lang/roundtrip.test.ts +3 -3
- package/src/lang/runtime.test.ts +167 -0
- package/src/lang/runtime.ts +234 -46
- package/src/lang/transpiler.test.ts +21 -21
- package/src/lang/typescript-syntax.test.ts +11 -9
- package/src/types/Type.ts +38 -1
- package/src/use-cases/bootstrap.test.ts +7 -7
- package/src/use-cases/client-server.test.ts +1 -1
- package/src/use-cases/malicious-actor.test.ts +1 -1
- package/src/use-cases/rag-processor.test.ts +1 -1
- package/src/use-cases/sophisticated-agents.test.ts +2 -2
- package/src/use-cases/transpiler-llm.test.ts +1 -1
- package/src/use-cases/unbundled-imports.test.ts +9 -9
- package/tjs-lang.svg +17 -25
package/src/lang/codegen.test.ts
CHANGED
|
@@ -94,28 +94,28 @@ describe('TS → TJS conversion quality', () => {
|
|
|
94
94
|
const ts = `function getName(): string { return 'test' }`
|
|
95
95
|
const { code } = fromTS(ts, { emitTJS: true })
|
|
96
96
|
|
|
97
|
-
expect(code).toContain("
|
|
97
|
+
expect(code).toContain(":! ''")
|
|
98
98
|
})
|
|
99
99
|
|
|
100
100
|
it('converts number return type to -! syntax', () => {
|
|
101
101
|
const ts = `function getCount(): number { return 42 }`
|
|
102
102
|
const { code } = fromTS(ts, { emitTJS: true })
|
|
103
103
|
|
|
104
|
-
expect(code).toContain('
|
|
104
|
+
expect(code).toContain(':! 0')
|
|
105
105
|
})
|
|
106
106
|
|
|
107
107
|
it('converts boolean return type to -! syntax', () => {
|
|
108
108
|
const ts = `function isValid(): boolean { return true }`
|
|
109
109
|
const { code } = fromTS(ts, { emitTJS: true })
|
|
110
110
|
|
|
111
|
-
expect(code).toContain('
|
|
111
|
+
expect(code).toContain(':! false')
|
|
112
112
|
})
|
|
113
113
|
|
|
114
114
|
it('converts object return type to -! syntax', () => {
|
|
115
115
|
const ts = `function getUser(): { name: string; age: number } { return { name: '', age: 0 } }`
|
|
116
116
|
const { code } = fromTS(ts, { emitTJS: true })
|
|
117
117
|
|
|
118
|
-
expect(code).toContain('
|
|
118
|
+
expect(code).toContain(':!')
|
|
119
119
|
expect(code).toContain("name: ''")
|
|
120
120
|
expect(code).toContain('age: 0')
|
|
121
121
|
})
|
|
@@ -124,22 +124,22 @@ describe('TS → TJS conversion quality', () => {
|
|
|
124
124
|
const ts = `function getItems(): string[] { return [] }`
|
|
125
125
|
const { code } = fromTS(ts, { emitTJS: true })
|
|
126
126
|
|
|
127
|
-
expect(code).toContain("
|
|
127
|
+
expect(code).toContain(":! ['']")
|
|
128
128
|
})
|
|
129
129
|
|
|
130
130
|
it('omits void return type', () => {
|
|
131
131
|
const ts = `function doSomething(): void { console.log('done') }`
|
|
132
132
|
const { code } = fromTS(ts, { emitTJS: true })
|
|
133
133
|
|
|
134
|
-
expect(code).not.toContain('
|
|
135
|
-
expect(code).not.
|
|
134
|
+
expect(code).not.toContain(':!')
|
|
135
|
+
expect(code).not.toMatch(/\)\s*:/)
|
|
136
136
|
})
|
|
137
137
|
|
|
138
138
|
it('handles Promise return types by unwrapping', () => {
|
|
139
139
|
const ts = `async function fetchData(): Promise<string> { return 'data' }`
|
|
140
140
|
const { code } = fromTS(ts, { emitTJS: true })
|
|
141
141
|
|
|
142
|
-
expect(code).toContain("
|
|
142
|
+
expect(code).toContain(":! ''")
|
|
143
143
|
expect(code).not.toContain('Promise')
|
|
144
144
|
})
|
|
145
145
|
})
|
|
@@ -236,7 +236,7 @@ class Calculator {
|
|
|
236
236
|
`
|
|
237
237
|
const { code } = fromTS(ts, { emitTJS: true })
|
|
238
238
|
|
|
239
|
-
expect(code).toContain('add(a: 0.0, b: 0.0)
|
|
239
|
+
expect(code).toContain('add(a: 0.0, b: 0.0):! 0.0')
|
|
240
240
|
})
|
|
241
241
|
|
|
242
242
|
it('converts getters and setters', () => {
|
|
@@ -277,7 +277,7 @@ class MathUtils {
|
|
|
277
277
|
`
|
|
278
278
|
const { code } = fromTS(ts, { emitTJS: true })
|
|
279
279
|
|
|
280
|
-
expect(code).toContain('static double(x: 0.0)
|
|
280
|
+
expect(code).toContain('static double(x: 0.0):! 0.0')
|
|
281
281
|
})
|
|
282
282
|
|
|
283
283
|
it('converts async methods', () => {
|
|
@@ -291,7 +291,7 @@ class Api {
|
|
|
291
291
|
const { code } = fromTS(ts, { emitTJS: true })
|
|
292
292
|
|
|
293
293
|
expect(code).toContain('async fetch')
|
|
294
|
-
expect(code).toContain("
|
|
294
|
+
expect(code).toContain(":! ''")
|
|
295
295
|
})
|
|
296
296
|
})
|
|
297
297
|
|
|
@@ -344,7 +344,7 @@ class Api {
|
|
|
344
344
|
|
|
345
345
|
expect(code).toContain('function double')
|
|
346
346
|
expect(code).toContain('x: 0')
|
|
347
|
-
expect(code).toContain('
|
|
347
|
+
expect(code).toContain(':! 0')
|
|
348
348
|
})
|
|
349
349
|
|
|
350
350
|
it('converts arrow function with block body', () => {
|
|
@@ -399,7 +399,7 @@ describe('TJS → JS transpilation quality', () => {
|
|
|
399
399
|
|
|
400
400
|
describe('__tjs metadata', () => {
|
|
401
401
|
it('includes param types in metadata', () => {
|
|
402
|
-
const source = `function greet(name: 'World')
|
|
402
|
+
const source = `function greet(name: 'World'): 'World' { return name }`
|
|
403
403
|
const { code, types } = tjs(source)
|
|
404
404
|
|
|
405
405
|
expect(code).toContain('__tjs')
|
|
@@ -408,7 +408,7 @@ describe('TJS → JS transpilation quality', () => {
|
|
|
408
408
|
})
|
|
409
409
|
|
|
410
410
|
it('includes return type in metadata', () => {
|
|
411
|
-
const source = `function double(x: 0)
|
|
411
|
+
const source = `function double(x: 0): 0 { return x * 2 }`
|
|
412
412
|
const { code, types } = tjs(source)
|
|
413
413
|
|
|
414
414
|
expect(code).toContain('__tjs')
|
|
@@ -430,15 +430,15 @@ describe('TJS → JS transpilation quality', () => {
|
|
|
430
430
|
describe('documentation generation quality', () => {
|
|
431
431
|
describe('function signatures', () => {
|
|
432
432
|
it('preserves original signature in markdown', () => {
|
|
433
|
-
const source = `function greet(name: 'World')
|
|
433
|
+
const source = `function greet(name: 'World'): '' { return name }`
|
|
434
434
|
const { markdown } = generateDocs(source)
|
|
435
435
|
|
|
436
436
|
// Signature is preserved as-is - the types ARE the docs
|
|
437
|
-
expect(markdown).toContain("function greet(name: 'World')
|
|
437
|
+
expect(markdown).toContain("function greet(name: 'World'): ''")
|
|
438
438
|
})
|
|
439
439
|
|
|
440
440
|
it('preserves optional params with defaults', () => {
|
|
441
|
-
const source = `function greet(name = 'World')
|
|
441
|
+
const source = `function greet(name = 'World'): '' { return name }`
|
|
442
442
|
const { markdown } = generateDocs(source)
|
|
443
443
|
|
|
444
444
|
expect(markdown).toContain("name = 'World'")
|
|
@@ -447,7 +447,7 @@ describe('documentation generation quality', () => {
|
|
|
447
447
|
|
|
448
448
|
describe('signature as documentation', () => {
|
|
449
449
|
it('shows params in signature', () => {
|
|
450
|
-
const source = `function add(a: 0, b: 0)
|
|
450
|
+
const source = `function add(a: 0, b: 0): 0 { return a + b }`
|
|
451
451
|
const { markdown } = generateDocs(source)
|
|
452
452
|
|
|
453
453
|
expect(markdown).toContain('a: 0')
|
|
@@ -455,10 +455,10 @@ describe('documentation generation quality', () => {
|
|
|
455
455
|
})
|
|
456
456
|
|
|
457
457
|
it('shows return type in signature', () => {
|
|
458
|
-
const source = `function double(x: 0)
|
|
458
|
+
const source = `function double(x: 0): 0 { return x * 2 }`
|
|
459
459
|
const { markdown } = generateDocs(source)
|
|
460
460
|
|
|
461
|
-
expect(markdown).toContain('
|
|
461
|
+
expect(markdown).toContain(': 0')
|
|
462
462
|
})
|
|
463
463
|
})
|
|
464
464
|
})
|
|
@@ -577,10 +577,10 @@ function greet(name: string): string {
|
|
|
577
577
|
`
|
|
578
578
|
const { code } = fromTS(ts, { emitTJS: true })
|
|
579
579
|
|
|
580
|
-
// All functions should be present (TS transpiler uses
|
|
581
|
-
expect(code).toContain('function add(a: 0.0, b: 0.0)
|
|
582
|
-
expect(code).toContain('function multiply(a: 0.0, b: 0.0)
|
|
583
|
-
expect(code).toContain("function greet(name: '')
|
|
580
|
+
// All functions should be present (TS transpiler uses :! to skip signature tests)
|
|
581
|
+
expect(code).toContain('function add(a: 0.0, b: 0.0):! 0.0')
|
|
582
|
+
expect(code).toContain('function multiply(a: 0.0, b: 0.0):! 0.0')
|
|
583
|
+
expect(code).toContain("function greet(name: ''):! ''")
|
|
584
584
|
|
|
585
585
|
// Should be valid TJS (no TypeScript syntax remaining)
|
|
586
586
|
expect(code).not.toContain(': number')
|
|
@@ -653,7 +653,7 @@ console.log(second())
|
|
|
653
653
|
const ts = `function test(): number { return 42 }`
|
|
654
654
|
const { code } = fromTS(ts, { emitTJS: true })
|
|
655
655
|
|
|
656
|
-
expect(code).toContain('
|
|
656
|
+
expect(code).toContain(':! 0')
|
|
657
657
|
expect(code).not.toContain(': number')
|
|
658
658
|
})
|
|
659
659
|
|
|
@@ -709,15 +709,15 @@ describe('Pipeline Step 2: TJS → JS', () => {
|
|
|
709
709
|
it('transpiles multiple functions correctly', () => {
|
|
710
710
|
// Use -! to skip signature tests (we're testing transpilation, not return values)
|
|
711
711
|
const tjsSource = `
|
|
712
|
-
function add(a: 0, b: 0)
|
|
712
|
+
function add(a: 0, b: 0):! 0 {
|
|
713
713
|
return a + b
|
|
714
714
|
}
|
|
715
715
|
|
|
716
|
-
function multiply(a: 0, b: 0)
|
|
716
|
+
function multiply(a: 0, b: 0):! 0 {
|
|
717
717
|
return a * b
|
|
718
718
|
}
|
|
719
719
|
|
|
720
|
-
function greet(name: '')
|
|
720
|
+
function greet(name: ''):! '' {
|
|
721
721
|
return 'Hello, ' + name
|
|
722
722
|
}
|
|
723
723
|
`
|
|
@@ -742,11 +742,11 @@ function greet(name: '') -! '' {
|
|
|
742
742
|
it('each function has correct metadata', () => {
|
|
743
743
|
// Use -! to skip signature tests
|
|
744
744
|
const tjsSource = `
|
|
745
|
-
function add(a: 0, b: 0)
|
|
745
|
+
function add(a: 0, b: 0):! 0 {
|
|
746
746
|
return a + b
|
|
747
747
|
}
|
|
748
748
|
|
|
749
|
-
function greet(name: '', excited = false)
|
|
749
|
+
function greet(name: '', excited = false):! '' {
|
|
750
750
|
return excited ? name + '!' : name
|
|
751
751
|
}
|
|
752
752
|
`
|
|
@@ -768,7 +768,7 @@ function greet(name: '', excited = false) -! '' {
|
|
|
768
768
|
it('produces executable JavaScript', () => {
|
|
769
769
|
// Use -! to skip signature test
|
|
770
770
|
const tjsSource = `
|
|
771
|
-
function double(x: 0)
|
|
771
|
+
function double(x: 0):! 0 {
|
|
772
772
|
return x * 2
|
|
773
773
|
}
|
|
774
774
|
`
|
|
@@ -782,7 +782,7 @@ function double(x: 0) -! 0 {
|
|
|
782
782
|
it('includes runtime validation', () => {
|
|
783
783
|
// Use -! to skip signature test
|
|
784
784
|
const tjsSource = `
|
|
785
|
-
function greet(name: '')
|
|
785
|
+
function greet(name: ''):! '' {
|
|
786
786
|
return 'Hello, ' + name
|
|
787
787
|
}
|
|
788
788
|
`
|
|
@@ -799,7 +799,7 @@ function greet(name: '') -! '' {
|
|
|
799
799
|
it('__tjs metadata is valid JSON structure', () => {
|
|
800
800
|
// Use -! to skip signature test
|
|
801
801
|
const tjsSource = `
|
|
802
|
-
function test(a: 0, b: '')
|
|
802
|
+
function test(a: 0, b: ''):! true {
|
|
803
803
|
return a > 0
|
|
804
804
|
}
|
|
805
805
|
`
|
|
@@ -819,7 +819,7 @@ function test(a: 0, b: '') -! true {
|
|
|
819
819
|
it('runs inline tests during transpilation', () => {
|
|
820
820
|
// Use -! to skip signature test - we only want to count explicit tests
|
|
821
821
|
const tjsSource = `
|
|
822
|
-
function add(a: 0, b: 0)
|
|
822
|
+
function add(a: 0, b: 0):! 0 {
|
|
823
823
|
return a + b
|
|
824
824
|
}
|
|
825
825
|
|
|
@@ -841,7 +841,7 @@ test 'add works' {
|
|
|
841
841
|
// TjsEquals directive enables structural equality transformation.
|
|
842
842
|
|
|
843
843
|
it('preserves == as-is by default (JS semantics)', () => {
|
|
844
|
-
const tjsSource = `function isEqual(a: {x: 0}, b: {x: 0})
|
|
844
|
+
const tjsSource = `function isEqual(a: {x: 0}, b: {x: 0}):! true { return a == b }`
|
|
845
845
|
const { code } = tjs(tjsSource)
|
|
846
846
|
|
|
847
847
|
// Without TjsEquals, == is NOT transformed
|
|
@@ -851,7 +851,7 @@ test 'add works' {
|
|
|
851
851
|
|
|
852
852
|
it('transforms == to Eq() with TjsEquals directive', () => {
|
|
853
853
|
const tjsSource = `TjsEquals
|
|
854
|
-
function isEqual(a: {x: 0}, b: {x: 0})
|
|
854
|
+
function isEqual(a: {x: 0}, b: {x: 0}):! true { return a == b }`
|
|
855
855
|
const { code } = tjs(tjsSource)
|
|
856
856
|
|
|
857
857
|
// Should transform == to Eq()
|
|
@@ -862,7 +862,7 @@ function isEqual(a: {x: 0}, b: {x: 0}) -! true { return a == b }`
|
|
|
862
862
|
|
|
863
863
|
it('transforms != to NotEq() with TjsEquals directive', () => {
|
|
864
864
|
const tjsSource = `TjsEquals
|
|
865
|
-
function notEqual(a: {x: 0}, b: {x: 0})
|
|
865
|
+
function notEqual(a: {x: 0}, b: {x: 0}):! true { return a != b }`
|
|
866
866
|
const { code } = tjs(tjsSource)
|
|
867
867
|
|
|
868
868
|
// Should transform != to NotEq()
|
|
@@ -872,7 +872,7 @@ function notEqual(a: {x: 0}, b: {x: 0}) -! true { return a != b }`
|
|
|
872
872
|
|
|
873
873
|
it('preserves === for identity comparison', () => {
|
|
874
874
|
const tjsSource = `TjsEquals
|
|
875
|
-
function isSame(a: {x: 0}, b: {x: 0})
|
|
875
|
+
function isSame(a: {x: 0}, b: {x: 0}):! true { return a === b }`
|
|
876
876
|
const { code } = tjs(tjsSource)
|
|
877
877
|
|
|
878
878
|
// Should preserve === unchanged
|
|
@@ -883,7 +883,7 @@ function isSame(a: {x: 0}, b: {x: 0}) -! true { return a === b }`
|
|
|
883
883
|
|
|
884
884
|
it('does NOT add Is/IsNot imports when not needed', () => {
|
|
885
885
|
// Use unsafe (!) to skip all validation, then no __tjs needed
|
|
886
|
-
const tjsSource = `function add(! a: 0, b: 0)
|
|
886
|
+
const tjsSource = `function add(! a: 0, b: 0):! 0 { return a + b }`
|
|
887
887
|
const { code } = tjs(tjsSource)
|
|
888
888
|
|
|
889
889
|
// No equality ops and fully unsafe = no __tjs reference
|
|
@@ -895,7 +895,7 @@ function isSame(a: {x: 0}, b: {x: 0}) -! true { return a === b }`
|
|
|
895
895
|
|
|
896
896
|
it('adds only Eq when only == is used with TjsEquals', () => {
|
|
897
897
|
const tjsSource = `TjsEquals
|
|
898
|
-
function eq(a: 0, b: 0)
|
|
898
|
+
function eq(a: 0, b: 0):! true { return a == b }`
|
|
899
899
|
const { code } = tjs(tjsSource)
|
|
900
900
|
|
|
901
901
|
expect(code).toContain('Eq(')
|
|
@@ -905,7 +905,7 @@ function eq(a: 0, b: 0) -! true { return a == b }`
|
|
|
905
905
|
|
|
906
906
|
it('adds only NotEq when only != is used with TjsEquals', () => {
|
|
907
907
|
const tjsSource = `TjsEquals
|
|
908
|
-
function neq(a: 0, b: 0)
|
|
908
|
+
function neq(a: 0, b: 0):! true { return a != b }`
|
|
909
909
|
const { code } = tjs(tjsSource)
|
|
910
910
|
|
|
911
911
|
expect(code).toContain('NotEq(')
|
|
@@ -914,7 +914,7 @@ function neq(a: 0, b: 0) -! true { return a != b }`
|
|
|
914
914
|
|
|
915
915
|
it('adds both Eq and NotEq when both == and != are used with TjsEquals', () => {
|
|
916
916
|
const tjsSource = `TjsEquals
|
|
917
|
-
function test(a: 0, b: 0)
|
|
917
|
+
function test(a: 0, b: 0):! true { return a == b || a != b }`
|
|
918
918
|
const { code } = tjs(tjsSource)
|
|
919
919
|
|
|
920
920
|
expect(code).toContain('Eq(')
|
|
@@ -923,7 +923,7 @@ function test(a: 0, b: 0) -! true { return a == b || a != b }`
|
|
|
923
923
|
|
|
924
924
|
it('does NOT add imports for === only even with TjsEquals', () => {
|
|
925
925
|
const tjsSource = `TjsEquals
|
|
926
|
-
function strict(a: 0, b: 0)
|
|
926
|
+
function strict(a: 0, b: 0):! true { return a === b }`
|
|
927
927
|
const { code } = tjs(tjsSource)
|
|
928
928
|
|
|
929
929
|
expect(code).not.toContain('const { Is')
|
|
@@ -935,7 +935,7 @@ function strict(a: 0, b: 0) -! true { return a === b }`
|
|
|
935
935
|
installRuntime()
|
|
936
936
|
|
|
937
937
|
const tjsSource = `TjsEquals
|
|
938
|
-
function isEqual(! a: null, b: null)
|
|
938
|
+
function isEqual(! a: null, b: null):! true { return a == b }`
|
|
939
939
|
const { code } = tjs(tjsSource)
|
|
940
940
|
|
|
941
941
|
const isEqual = new Function(code + '; return isEqual')()
|
|
@@ -952,7 +952,7 @@ function isEqual(! a: null, b: null) -! true { return a == b }`
|
|
|
952
952
|
describe('TjsStandard (ASI protection)', () => {
|
|
953
953
|
it('inserts semicolon before IIFE to prevent footgun', () => {
|
|
954
954
|
const tjsSource = `TjsStandard
|
|
955
|
-
function test()
|
|
955
|
+
function test():! 0 {
|
|
956
956
|
const x = 1
|
|
957
957
|
(() => console.log('iife'))()
|
|
958
958
|
return x
|
|
@@ -965,7 +965,7 @@ function test() -! 0 {
|
|
|
965
965
|
|
|
966
966
|
it('inserts semicolon before array literal on new line', () => {
|
|
967
967
|
const tjsSource = `TjsStandard
|
|
968
|
-
function test()
|
|
968
|
+
function test():! 0 {
|
|
969
969
|
const x = 1
|
|
970
970
|
[1, 2, 3].forEach(console.log)
|
|
971
971
|
return x
|
|
@@ -978,7 +978,7 @@ function test() -! 0 {
|
|
|
978
978
|
|
|
979
979
|
it('does NOT insert semicolon when previous line has operator', () => {
|
|
980
980
|
const tjsSource = `TjsStandard
|
|
981
|
-
function test()
|
|
981
|
+
function test():! 0 {
|
|
982
982
|
const result = 1 +
|
|
983
983
|
(2 + 3)
|
|
984
984
|
return result
|
|
@@ -994,7 +994,7 @@ function test() -! 0 {
|
|
|
994
994
|
it('does NOT insert semicolon after opening brace', () => {
|
|
995
995
|
// Test that we don't add ; after [ or {
|
|
996
996
|
const tjsSource = `TjsStandard
|
|
997
|
-
function test()
|
|
997
|
+
function test():! 0 {
|
|
998
998
|
const arr = [
|
|
999
999
|
(x => x + 1)
|
|
1000
1000
|
]
|
|
@@ -1009,7 +1009,7 @@ function test() -! 0 {
|
|
|
1009
1009
|
|
|
1010
1010
|
it('does NOT insert semicolon after return keyword', () => {
|
|
1011
1011
|
const tjsSource = `TjsStandard
|
|
1012
|
-
function test()
|
|
1012
|
+
function test():! 0 {
|
|
1013
1013
|
return (
|
|
1014
1014
|
1 + 2
|
|
1015
1015
|
)
|
|
@@ -1023,7 +1023,7 @@ function test() -! 0 {
|
|
|
1023
1023
|
|
|
1024
1024
|
it('does NOT insert semicolon after comma (multi-line array)', () => {
|
|
1025
1025
|
const tjsSource = `TjsStandard
|
|
1026
|
-
function test()
|
|
1026
|
+
function test():! [] {
|
|
1027
1027
|
return [
|
|
1028
1028
|
1,
|
|
1029
1029
|
(2 + 3),
|
|
@@ -1038,7 +1038,7 @@ function test() -! [] {
|
|
|
1038
1038
|
|
|
1039
1039
|
it('TjsStrict enables TjsStandard', () => {
|
|
1040
1040
|
const tjsSource = `TjsStrict
|
|
1041
|
-
function test()
|
|
1041
|
+
function test():! 0 {
|
|
1042
1042
|
const x = 1
|
|
1043
1043
|
(() => console.log('iife'))()
|
|
1044
1044
|
return x
|
|
@@ -1051,7 +1051,7 @@ function test() -! 0 {
|
|
|
1051
1051
|
|
|
1052
1052
|
it('works correctly at runtime', () => {
|
|
1053
1053
|
const tjsSource = `TjsStandard
|
|
1054
|
-
function test()
|
|
1054
|
+
function test():! 0 {
|
|
1055
1055
|
let result = 42
|
|
1056
1056
|
(() => { result = result + 1 })()
|
|
1057
1057
|
return result
|
|
@@ -1066,9 +1066,9 @@ function test() -! 0 {
|
|
|
1066
1066
|
it('without TjsStandard, IIFE would be footgun (JS behavior)', () => {
|
|
1067
1067
|
// This demonstrates the footgun that TjsStandard prevents
|
|
1068
1068
|
// Without the directive, the code passes through unchanged
|
|
1069
|
-
const tjsSource = `
|
|
1070
|
-
function getNumber()
|
|
1071
|
-
function test()
|
|
1069
|
+
const tjsSource = `TjsCompat
|
|
1070
|
+
function getNumber():! 0 { return 42 }
|
|
1071
|
+
function test():! 0 {
|
|
1072
1072
|
const x = getNumber
|
|
1073
1073
|
(() => {})()
|
|
1074
1074
|
return 1
|
|
@@ -1098,7 +1098,7 @@ function calculate(a: number, b: number, operation: string): number {
|
|
|
1098
1098
|
expect(tjsCode).toContain('a: 0')
|
|
1099
1099
|
expect(tjsCode).toContain('b: 0')
|
|
1100
1100
|
expect(tjsCode).toContain("operation: ''")
|
|
1101
|
-
expect(tjsCode).toContain('
|
|
1101
|
+
expect(tjsCode).toContain(':! 0') // TS transpiler uses :! to skip signature tests
|
|
1102
1102
|
|
|
1103
1103
|
// Step 3: TJS → JS (already has -! from TS transpiler)
|
|
1104
1104
|
const { code: jsCode, types } = tjs(tjsCode)
|
|
@@ -1195,7 +1195,7 @@ function greet(user: { name: string; age: number }): string {
|
|
|
1195
1195
|
`
|
|
1196
1196
|
const { code: tjsCode } = fromTS(ts, { emitTJS: true })
|
|
1197
1197
|
// Already has -! from TS transpiler
|
|
1198
|
-
const { code: jsCode } = tjs(tjsCode)
|
|
1198
|
+
const { code: jsCode } = tjs('safety inputs\n' + tjsCode)
|
|
1199
1199
|
|
|
1200
1200
|
const greet = new Function(jsCode + '; return greet')()
|
|
1201
1201
|
|
|
@@ -1221,7 +1221,7 @@ function add(a: number, b: number): number {
|
|
|
1221
1221
|
`
|
|
1222
1222
|
const { code: tjsCode } = fromTS(ts, { emitTJS: true })
|
|
1223
1223
|
// Already has -! from TS transpiler
|
|
1224
|
-
const { code: jsCode } = tjs(tjsCode)
|
|
1224
|
+
const { code: jsCode } = tjs('safety inputs\n' + tjsCode)
|
|
1225
1225
|
|
|
1226
1226
|
const add = new Function(jsCode + '; return add')()
|
|
1227
1227
|
|
|
@@ -1287,7 +1287,7 @@ describe('Monadic error handling', () => {
|
|
|
1287
1287
|
describe('error pass-through (monadic propagation)', () => {
|
|
1288
1288
|
it('passes through Error input without processing', () => {
|
|
1289
1289
|
const tjsSource = `
|
|
1290
|
-
function double(x: 0)
|
|
1290
|
+
function double(x: 0):! 0 {
|
|
1291
1291
|
return x * 2
|
|
1292
1292
|
}
|
|
1293
1293
|
`
|
|
@@ -1305,7 +1305,7 @@ function double(x: 0) -! 0 {
|
|
|
1305
1305
|
|
|
1306
1306
|
it('passes through error in multi-param function', () => {
|
|
1307
1307
|
const tjsSource = `
|
|
1308
|
-
function add(a: 0, b: 0)
|
|
1308
|
+
function add(a: 0, b: 0):! 0 {
|
|
1309
1309
|
return a + b
|
|
1310
1310
|
}
|
|
1311
1311
|
`
|
|
@@ -1323,15 +1323,15 @@ function add(a: 0, b: 0) -! 0 {
|
|
|
1323
1323
|
|
|
1324
1324
|
it('propagates error through function chain', () => {
|
|
1325
1325
|
const tjsSource = `
|
|
1326
|
-
function step1(x: 0)
|
|
1326
|
+
function step1(x: 0):! 0 {
|
|
1327
1327
|
return x * 2
|
|
1328
1328
|
}
|
|
1329
1329
|
|
|
1330
|
-
function step2(x: 0)
|
|
1330
|
+
function step2(x: 0):! 0 {
|
|
1331
1331
|
return x + 10
|
|
1332
1332
|
}
|
|
1333
1333
|
|
|
1334
|
-
function step3(x: 0)
|
|
1334
|
+
function step3(x: 0):! 0 {
|
|
1335
1335
|
return x / 2
|
|
1336
1336
|
}
|
|
1337
1337
|
`
|
|
@@ -1350,7 +1350,7 @@ function step3(x: 0) -! 0 {
|
|
|
1350
1350
|
describe('type error emission', () => {
|
|
1351
1351
|
it('returns MonadicError on type mismatch', () => {
|
|
1352
1352
|
const tjsSource = `
|
|
1353
|
-
function greet(name: '')
|
|
1353
|
+
function greet(name: ''):! '' {
|
|
1354
1354
|
return 'Hello, ' + name
|
|
1355
1355
|
}
|
|
1356
1356
|
`
|
|
@@ -1370,7 +1370,7 @@ function greet(name: '') -! '' {
|
|
|
1370
1370
|
|
|
1371
1371
|
it('includes path for nested params', () => {
|
|
1372
1372
|
const tjsSource = `
|
|
1373
|
-
function process({ name: '', age: 0 })
|
|
1373
|
+
function process({ name: '', age: 0 }):! '' {
|
|
1374
1374
|
return name + ' is ' + age
|
|
1375
1375
|
}
|
|
1376
1376
|
`
|
|
@@ -1387,7 +1387,7 @@ function process({ name: '', age: 0 }) -! '' {
|
|
|
1387
1387
|
|
|
1388
1388
|
it('user code cannot accidentally process error as data', () => {
|
|
1389
1389
|
const tjsSource = `
|
|
1390
|
-
function getData(id: 0)
|
|
1390
|
+
function getData(id: 0):! { value: 0 } {
|
|
1391
1391
|
return { value: id * 10 }
|
|
1392
1392
|
}
|
|
1393
1393
|
`
|
|
@@ -1411,7 +1411,7 @@ function getData(id: 0) -! { value: 0 } {
|
|
|
1411
1411
|
describe('error vs valid value distinction', () => {
|
|
1412
1412
|
it('valid values pass through normally', () => {
|
|
1413
1413
|
const tjsSource = `
|
|
1414
|
-
function double(x: 0)
|
|
1414
|
+
function double(x: 0):! 0 {
|
|
1415
1415
|
return x * 2
|
|
1416
1416
|
}
|
|
1417
1417
|
`
|
|
@@ -1425,7 +1425,7 @@ function double(x: 0) -! 0 {
|
|
|
1425
1425
|
|
|
1426
1426
|
it('distinguishes Error from error-like objects', () => {
|
|
1427
1427
|
const tjsSource = `
|
|
1428
|
-
function process(data: { error: false })
|
|
1428
|
+
function process(data: { error: false }):! { error: false } {
|
|
1429
1429
|
return data
|
|
1430
1430
|
}
|
|
1431
1431
|
`
|
|
@@ -1451,7 +1451,7 @@ function process(data: { error: false }) -! { error: false } {
|
|
|
1451
1451
|
describe('unsafe functions skip validation entirely', () => {
|
|
1452
1452
|
it('unsafe function skips all validation including error pass-through', () => {
|
|
1453
1453
|
const tjsSource = `
|
|
1454
|
-
function fastDouble(! x: 0)
|
|
1454
|
+
function fastDouble(! x: 0):! 0 {
|
|
1455
1455
|
return x * 2
|
|
1456
1456
|
}
|
|
1457
1457
|
`
|
|
@@ -1473,7 +1473,7 @@ function fastDouble(! x: 0) -! 0 {
|
|
|
1473
1473
|
describe('source location tracking', () => {
|
|
1474
1474
|
it('error includes source file and line (no debug mode)', () => {
|
|
1475
1475
|
const tjsSource = `
|
|
1476
|
-
function greet(name: '')
|
|
1476
|
+
function greet(name: ''):! '' {
|
|
1477
1477
|
return 'Hello, ' + name
|
|
1478
1478
|
}
|
|
1479
1479
|
`
|
|
@@ -1510,8 +1510,10 @@ function transform(value: string): string {
|
|
|
1510
1510
|
filename: 'src/processors/data.ts',
|
|
1511
1511
|
})
|
|
1512
1512
|
|
|
1513
|
-
// TJS → JS
|
|
1514
|
-
const { code: jsCode } = tjs(
|
|
1513
|
+
// TJS → JS — inject safety directive after the tjs annotation line
|
|
1514
|
+
const { code: jsCode } = tjs(
|
|
1515
|
+
tjsCode.replace(/(\/\* tjs <- [^*]+ \*\/)/, '$1\nsafety inputs')
|
|
1516
|
+
)
|
|
1515
1517
|
|
|
1516
1518
|
// Execute and trigger errors
|
|
1517
1519
|
const fns = new Function(
|
|
@@ -1538,9 +1540,10 @@ function transform(value: string): string {
|
|
|
1538
1540
|
it('preserves line annotations through TJS intermediate', () => {
|
|
1539
1541
|
// TJS with explicit line annotations (as if from TS transpilation)
|
|
1540
1542
|
const tjsSource = `/* tjs <- lib/utils.ts */
|
|
1543
|
+
safety inputs
|
|
1541
1544
|
|
|
1542
1545
|
/* line 15 */
|
|
1543
|
-
function helper(x: 0)
|
|
1546
|
+
function helper(x: 0):! 0 {
|
|
1544
1547
|
return x + 1
|
|
1545
1548
|
}
|
|
1546
1549
|
`
|
|
@@ -1555,34 +1558,35 @@ function helper(x: 0) -! 0 {
|
|
|
1555
1558
|
expect(err.path).toBe('lib/utils.ts:15:helper.x')
|
|
1556
1559
|
})
|
|
1557
1560
|
|
|
1558
|
-
it('captures TJS call stack
|
|
1561
|
+
it('captures TJS call stack with callStacks enabled', () => {
|
|
1559
1562
|
const { configure } = require('./runtime')
|
|
1560
1563
|
|
|
1561
|
-
// Enable
|
|
1562
|
-
configure({
|
|
1564
|
+
// Enable call stack tracking
|
|
1565
|
+
configure({ callStacks: true })
|
|
1563
1566
|
|
|
1564
1567
|
try {
|
|
1565
1568
|
const tjsSource = `/* tjs <- src/chain.ts */
|
|
1569
|
+
safety inputs
|
|
1566
1570
|
|
|
1567
1571
|
/* line 10 */
|
|
1568
|
-
function outer(x: 0)
|
|
1572
|
+
function outer(x: 0):! 0 {
|
|
1569
1573
|
return middle(x * 2)
|
|
1570
1574
|
}
|
|
1571
1575
|
|
|
1572
1576
|
/* line 20 */
|
|
1573
|
-
function middle(x: 0)
|
|
1577
|
+
function middle(x: 0):! 0 {
|
|
1574
1578
|
return inner(x + 10)
|
|
1575
1579
|
}
|
|
1576
1580
|
|
|
1577
1581
|
/* line 30 */
|
|
1578
|
-
function inner(x: '')
|
|
1582
|
+
function inner(x: ''):! '' {
|
|
1579
1583
|
return x.toUpperCase()
|
|
1580
1584
|
}
|
|
1581
1585
|
`
|
|
1582
1586
|
const { code } = tjs(tjsSource)
|
|
1583
1587
|
const fns = new Function(code + '; return { outer, middle, inner }')()
|
|
1584
1588
|
|
|
1585
|
-
// outer(5)
|
|
1589
|
+
// outer(5): middle(10): inner(20) fails (20 is not a string)
|
|
1586
1590
|
const err = fns.outer(5)
|
|
1587
1591
|
|
|
1588
1592
|
expect(err).toBeInstanceOf(MonadicError)
|
|
@@ -1594,8 +1598,7 @@ function inner(x: '') -! '' {
|
|
|
1594
1598
|
expect(err.callStack).toContain('src/chain.ts:20:middle')
|
|
1595
1599
|
expect(err.callStack).toContain('src/chain.ts:30:inner')
|
|
1596
1600
|
} finally {
|
|
1597
|
-
|
|
1598
|
-
configure({ debug: false })
|
|
1601
|
+
configure({ debug: false, callStacks: false })
|
|
1599
1602
|
}
|
|
1600
1603
|
})
|
|
1601
1604
|
|
|
@@ -1606,7 +1609,7 @@ function inner(x: '') -! '' {
|
|
|
1606
1609
|
configure({ debug: false })
|
|
1607
1610
|
|
|
1608
1611
|
const tjsSource = `
|
|
1609
|
-
function test(x: 0)
|
|
1612
|
+
function test(x: 0):! 0 {
|
|
1610
1613
|
return x * 2
|
|
1611
1614
|
}
|
|
1612
1615
|
`
|
|
@@ -1628,7 +1631,7 @@ function test(x: 0) -! 0 {
|
|
|
1628
1631
|
|
|
1629
1632
|
try {
|
|
1630
1633
|
const { code } = tjs(`
|
|
1631
|
-
function add(a: 0, b: 0)
|
|
1634
|
+
function add(a: 0, b: 0):! 0 {
|
|
1632
1635
|
return a + b
|
|
1633
1636
|
}
|
|
1634
1637
|
`)
|
|
@@ -1652,12 +1655,13 @@ function add(a: 0, b: 0) -! 0 {
|
|
|
1652
1655
|
|
|
1653
1656
|
try {
|
|
1654
1657
|
const { code } = tjs(`/* tjs <- src/app.ts */
|
|
1658
|
+
safety inputs
|
|
1655
1659
|
/* line 1 */
|
|
1656
|
-
function outer(x: 0)
|
|
1660
|
+
function outer(x: 0):! 0 {
|
|
1657
1661
|
return inner(x)
|
|
1658
1662
|
}
|
|
1659
1663
|
/* line 5 */
|
|
1660
|
-
function inner(x: '')
|
|
1664
|
+
function inner(x: ''):! '' {
|
|
1661
1665
|
return x.toUpperCase()
|
|
1662
1666
|
}
|
|
1663
1667
|
`)
|
|
@@ -1679,7 +1683,7 @@ function inner(x: '') -! '' {
|
|
|
1679
1683
|
|
|
1680
1684
|
try {
|
|
1681
1685
|
const { code } = tjs(`
|
|
1682
|
-
function greet(name: '')
|
|
1686
|
+
function greet(name: ''):! '' {
|
|
1683
1687
|
return 'Hello, ' + name
|
|
1684
1688
|
}
|
|
1685
1689
|
`)
|
|
@@ -1710,14 +1714,15 @@ function greet(name: '') -! '' {
|
|
|
1710
1714
|
|
|
1711
1715
|
try {
|
|
1712
1716
|
const { code } = tjs(`/* tjs <- src/pipeline.ts */
|
|
1717
|
+
safety inputs
|
|
1713
1718
|
/* line 1 */
|
|
1714
|
-
function a(x: 0)
|
|
1719
|
+
function a(x: 0):! 0 { return b(x) }
|
|
1715
1720
|
/* line 3 */
|
|
1716
|
-
function b(x: 0)
|
|
1721
|
+
function b(x: 0):! 0 { return c(x) }
|
|
1717
1722
|
/* line 5 */
|
|
1718
|
-
function c(x: 0)
|
|
1723
|
+
function c(x: 0):! 0 { return d(x) }
|
|
1719
1724
|
/* line 7 */
|
|
1720
|
-
function d(x: '')
|
|
1725
|
+
function d(x: ''):! '' { return x.toUpperCase() }
|
|
1721
1726
|
`)
|
|
1722
1727
|
const fns = new Function(code + '; return { a, b, c, d }')()
|
|
1723
1728
|
const err = fns.a(99)
|
|
@@ -1741,11 +1746,11 @@ function d(x: '') -! '' { return x.toUpperCase() }
|
|
|
1741
1746
|
describe('input-side error propagation', () => {
|
|
1742
1747
|
it('error from inner function caught by outer input check', () => {
|
|
1743
1748
|
const { code } = tjs(`
|
|
1744
|
-
function step1(x: '')
|
|
1749
|
+
function step1(x: ''):! '' {
|
|
1745
1750
|
return x.toUpperCase()
|
|
1746
1751
|
}
|
|
1747
1752
|
|
|
1748
|
-
function step2(x: '')
|
|
1753
|
+
function step2(x: ''):! '' {
|
|
1749
1754
|
return x + '!'
|
|
1750
1755
|
}
|
|
1751
1756
|
`)
|
|
@@ -1761,9 +1766,9 @@ function step2(x: '') -! '' {
|
|
|
1761
1766
|
|
|
1762
1767
|
it('error identity preserved through chain', () => {
|
|
1763
1768
|
const { code } = tjs(`
|
|
1764
|
-
function a(x: '')
|
|
1765
|
-
function b(x: '')
|
|
1766
|
-
function c(x: '')
|
|
1769
|
+
function a(x: ''):! '' { return x }
|
|
1770
|
+
function b(x: ''):! '' { return x }
|
|
1771
|
+
function c(x: ''):! '' { return x }
|
|
1767
1772
|
`)
|
|
1768
1773
|
const fns = new Function(code + '; return { a, b, c }')()
|
|
1769
1774
|
|
|
@@ -1781,9 +1786,9 @@ function c(x: '') -! '' { return x }
|
|
|
1781
1786
|
|
|
1782
1787
|
it('multi-level nested call propagation', () => {
|
|
1783
1788
|
const { code } = tjs(`
|
|
1784
|
-
function validate(x: '')
|
|
1785
|
-
function transform(x: '')
|
|
1786
|
-
function format(x: '')
|
|
1789
|
+
function validate(x: ''):! '' { return x }
|
|
1790
|
+
function transform(x: ''):! '' { return x.toUpperCase() }
|
|
1791
|
+
function format(x: ''):! '' { return x + '!' }
|
|
1787
1792
|
`)
|
|
1788
1793
|
const fns = new Function(
|
|
1789
1794
|
code + '; return { validate, transform, format }'
|
|
@@ -1801,7 +1806,7 @@ function format(x: '') -! '' { return x + '!' }
|
|
|
1801
1806
|
const bodyExecuted = false
|
|
1802
1807
|
|
|
1803
1808
|
const { code } = tjs(`
|
|
1804
|
-
function process(x: '')
|
|
1809
|
+
function process(x: ''):! '' {
|
|
1805
1810
|
globalThis.__test_body_ran = true
|
|
1806
1811
|
return x.toUpperCase()
|
|
1807
1812
|
}
|
|
@@ -1828,7 +1833,7 @@ function process(x: '') -! '' {
|
|
|
1828
1833
|
describe('return type default keys', () => {
|
|
1829
1834
|
it('signature test passes when optional key is absent', () => {
|
|
1830
1835
|
const result = tjs(`
|
|
1831
|
-
function divide(a: 10, b: 2)
|
|
1836
|
+
function divide(a: 10, b: 2): { value: 5, error = '' } {
|
|
1832
1837
|
return { value: a / b }
|
|
1833
1838
|
}
|
|
1834
1839
|
`)
|
|
@@ -1839,7 +1844,7 @@ function divide(a: 10, b: 2) -> { value: 5, error = '' } {
|
|
|
1839
1844
|
|
|
1840
1845
|
it('signature test passes when optional key is present', () => {
|
|
1841
1846
|
const result = tjs(`
|
|
1842
|
-
function divide(a: 10, b: 0)
|
|
1847
|
+
function divide(a: 10, b: 0): { value: 0, error = 'Division by zero' } {
|
|
1843
1848
|
if (b === 0) return { value: 0, error: 'Division by zero' }
|
|
1844
1849
|
return { value: a / b }
|
|
1845
1850
|
}
|
|
@@ -1851,7 +1856,7 @@ function divide(a: 10, b: 0) -> { value: 0, error = 'Division by zero' } {
|
|
|
1851
1856
|
|
|
1852
1857
|
it('works with non-string defaults', () => {
|
|
1853
1858
|
const result = tjs(`
|
|
1854
|
-
function lookup(key: 'x')
|
|
1859
|
+
function lookup(key: 'x'): { value: 'found', count = 0 } {
|
|
1855
1860
|
return { value: 'found' }
|
|
1856
1861
|
}
|
|
1857
1862
|
`)
|
|
@@ -1864,7 +1869,7 @@ function lookup(key: 'x') -> { value: 'found', count = 0 } {
|
|
|
1864
1869
|
// Transpiler throws on signature test failure, so we catch it
|
|
1865
1870
|
expect(() =>
|
|
1866
1871
|
tjs(`
|
|
1867
|
-
function broken(x: 0)
|
|
1872
|
+
function broken(x: 0): { value: 0, error = '' } {
|
|
1868
1873
|
return { error: 'oops' }
|
|
1869
1874
|
}
|
|
1870
1875
|
`)
|
|
@@ -1873,7 +1878,7 @@ function broken(x: 0) -> { value: 0, error = '' } {
|
|
|
1873
1878
|
|
|
1874
1879
|
it('inline tests can check default keys', () => {
|
|
1875
1880
|
const result = tjs(`
|
|
1876
|
-
function divide(a: 10, b: 2)
|
|
1881
|
+
function divide(a: 10, b: 2): { value: 5, error = '' } {
|
|
1877
1882
|
if (b === 0) return { value: NaN, error: 'Division by zero' }
|
|
1878
1883
|
return { value: a / b }
|
|
1879
1884
|
}
|
|
@@ -1895,7 +1900,7 @@ test 'normal division works' {
|
|
|
1895
1900
|
|
|
1896
1901
|
it('type metadata parses return type with defaults', () => {
|
|
1897
1902
|
const result = tjs(`
|
|
1898
|
-
function divide(a: 10, b: 2)
|
|
1903
|
+
function divide(a: 10, b: 2): { value: 5, error = '' } {
|
|
1899
1904
|
return { value: a / b }
|
|
1900
1905
|
}
|
|
1901
1906
|
`)
|
|
@@ -1908,7 +1913,7 @@ function divide(a: 10, b: 2) -> { value: 5, error = '' } {
|
|
|
1908
1913
|
|
|
1909
1914
|
it('-? runtime validation passes when optional key is absent', () => {
|
|
1910
1915
|
const result = tjs(`
|
|
1911
|
-
function divide(a: 10, b: 2)
|
|
1916
|
+
function divide(a: 10, b: 2):? { value: 5, error = '' } {
|
|
1912
1917
|
return { value: a / b }
|
|
1913
1918
|
}
|
|
1914
1919
|
`)
|
|
@@ -1923,7 +1928,7 @@ function divide(a: 10, b: 2) -? { value: 5, error = '' } {
|
|
|
1923
1928
|
it('-? with simple return type rejects wrong type at runtime', () => {
|
|
1924
1929
|
// Use a simple return type (string) where checkType works
|
|
1925
1930
|
const result = tjs(`
|
|
1926
|
-
function greet(name: 'World')
|
|
1931
|
+
function greet(name: 'World'):? 'Hello, World' {
|
|
1927
1932
|
return 'Hello, ' + name
|
|
1928
1933
|
}
|
|
1929
1934
|
`)
|
|
@@ -1937,7 +1942,7 @@ function greet(name: 'World') -? 'Hello, World' {
|
|
|
1937
1942
|
|
|
1938
1943
|
it('__tjs metadata includes return defaults', () => {
|
|
1939
1944
|
const result = tjs(`
|
|
1940
|
-
function divide(a: 10, b: 2)
|
|
1945
|
+
function divide(a: 10, b: 2):? { value: 5, error = '' } {
|
|
1941
1946
|
return { value: a / b }
|
|
1942
1947
|
}
|
|
1943
1948
|
`)
|
|
@@ -2046,7 +2051,7 @@ describe('TS overloads → TJS → JS full pipeline', () => {
|
|
|
2046
2051
|
|
|
2047
2052
|
describe('rest parameter metadata', () => {
|
|
2048
2053
|
it('should capture typed rest param in metadata', () => {
|
|
2049
|
-
const result = tjs(`function sum(...nums: [0])
|
|
2054
|
+
const result = tjs(`function sum(...nums: [0]): 0 { return 0 }`, {
|
|
2050
2055
|
runTests: false,
|
|
2051
2056
|
})
|
|
2052
2057
|
const info = result.types.sum
|
|
@@ -2058,7 +2063,7 @@ describe('rest parameter metadata', () => {
|
|
|
2058
2063
|
|
|
2059
2064
|
it('should capture rest param with float array type', () => {
|
|
2060
2065
|
const result = tjs(
|
|
2061
|
-
`function mean(...values: [1.0, 2.0])
|
|
2066
|
+
`function mean(...values: [1.0, 2.0]): 0.0 { return 0 }`,
|
|
2062
2067
|
{ runTests: false }
|
|
2063
2068
|
)
|
|
2064
2069
|
const info = result.types.mean
|
|
@@ -2069,7 +2074,7 @@ describe('rest parameter metadata', () => {
|
|
|
2069
2074
|
|
|
2070
2075
|
it('should capture heterogeneous rest param as union', () => {
|
|
2071
2076
|
const result = tjs(
|
|
2072
|
-
`function log(...args: ['hello', 42, true])
|
|
2077
|
+
`function log(...args: ['hello', 42, true]): 0 { return 0 }`,
|
|
2073
2078
|
{ runTests: false }
|
|
2074
2079
|
)
|
|
2075
2080
|
const info = result.types.log
|