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
|
@@ -675,14 +675,14 @@ function greet(name: 'World') {
|
|
|
675
675
|
|
|
676
676
|
it('safety none should skip validation code in output', () => {
|
|
677
677
|
// With safety (default) - should have validation
|
|
678
|
-
const safe = tjs(`function add(a: 0, b: 0)
|
|
678
|
+
const safe = tjs(`function add(a: 0, b: 0): 0 { return a + b }`)
|
|
679
679
|
expect(safe.code).toContain('__tjs.typeError')
|
|
680
680
|
expect(safe.code).toContain('__tjs.pushStack')
|
|
681
681
|
expect(safe.code).toContain("typeof a !== 'number'")
|
|
682
682
|
|
|
683
683
|
// Without safety - should NOT have validation
|
|
684
684
|
const unsafe = tjs(`safety none
|
|
685
|
-
function add(a: 0, b: 0)
|
|
685
|
+
function add(a: 0, b: 0): 0 { return a + b }`)
|
|
686
686
|
expect(unsafe.code).not.toContain('__tjs.typeError')
|
|
687
687
|
expect(unsafe.code).not.toContain('__tjs.pushStack')
|
|
688
688
|
expect(unsafe.code).not.toContain("typeof a !== 'number'")
|
|
@@ -693,8 +693,8 @@ function add(a: 0, b: 0) -> 0 { return a + b }`)
|
|
|
693
693
|
|
|
694
694
|
it('safety none should work with multiple functions', () => {
|
|
695
695
|
const result = tjs(`safety none
|
|
696
|
-
function add(a: 0, b: 0)
|
|
697
|
-
function multiply(a: 0, b: 0)
|
|
696
|
+
function add(a: 0, b: 0): 0 { return a + b }
|
|
697
|
+
function multiply(a: 0, b: 0): 0 { return a * b }`)
|
|
698
698
|
|
|
699
699
|
// No validation for either function
|
|
700
700
|
expect(result.code).not.toContain('__tjs.typeError')
|
|
@@ -708,8 +708,8 @@ function multiply(a: 0, b: 0) -> 0 { return a * b }`)
|
|
|
708
708
|
describe('unsafe function marker (!)', () => {
|
|
709
709
|
it('(!) should skip validation for that function only', () => {
|
|
710
710
|
const result = tjs(`
|
|
711
|
-
function safeAdd(a: 0, b: 0)
|
|
712
|
-
function unsafeAdd(! a: 0, b: 0)
|
|
711
|
+
function safeAdd(a: 0, b: 0): 0 { return a + b }
|
|
712
|
+
function unsafeAdd(! a: 0, b: 0): 0 { return a + b }
|
|
713
713
|
`)
|
|
714
714
|
// Safe function has validation
|
|
715
715
|
expect(result.code).toContain('__tjs.pushStack')
|
|
@@ -730,7 +730,7 @@ function unsafeAdd(! a: 0, b: 0) -> 0 { return a + b }
|
|
|
730
730
|
})
|
|
731
731
|
|
|
732
732
|
it('(!) function metadata should have unsafe: true', () => {
|
|
733
|
-
const result = tjs(`function fast(! x: 0)
|
|
733
|
+
const result = tjs(`function fast(! x: 0): 0 { return x * 2 }`)
|
|
734
734
|
expect(result.code).toContain('"unsafe": true')
|
|
735
735
|
})
|
|
736
736
|
})
|
|
@@ -738,26 +738,26 @@ function unsafeAdd(! a: 0, b: 0) -> 0 { return a + b }
|
|
|
738
738
|
describe('safe vs unsafe comparison', () => {
|
|
739
739
|
it('safe function should have validation, unsafe should not', () => {
|
|
740
740
|
// Safe function (default)
|
|
741
|
-
const safe = tjs(`function double(x: 0)
|
|
741
|
+
const safe = tjs(`function double(x: 0): 0 { return x * 2 }`)
|
|
742
742
|
expect(safe.code).toContain("typeof x !== 'number'")
|
|
743
743
|
expect(safe.code).toContain('__tjs.typeError')
|
|
744
744
|
|
|
745
745
|
// Unsafe via (!) marker
|
|
746
|
-
const unsafeMarker = tjs(`function double(! x: 0)
|
|
746
|
+
const unsafeMarker = tjs(`function double(! x: 0): 0 { return x * 2 }`)
|
|
747
747
|
expect(unsafeMarker.code).not.toContain("typeof x !== 'number'")
|
|
748
748
|
expect(unsafeMarker.code).not.toContain('__tjs.typeError')
|
|
749
749
|
|
|
750
750
|
// Unsafe via safety none
|
|
751
751
|
const unsafeModule = tjs(`safety none
|
|
752
|
-
function double(x: 0)
|
|
752
|
+
function double(x: 0): 0 { return x * 2 }`)
|
|
753
753
|
expect(unsafeModule.code).not.toContain("typeof x !== 'number'")
|
|
754
754
|
expect(unsafeModule.code).not.toContain('__tjs.typeError')
|
|
755
755
|
})
|
|
756
756
|
|
|
757
757
|
it('both (!) and safety none should produce equivalent unsafe output', () => {
|
|
758
|
-
const viaMarker = tjs(`function add(! a: 0, b: 0)
|
|
758
|
+
const viaMarker = tjs(`function add(! a: 0, b: 0): 0 { return a + b }`)
|
|
759
759
|
const viaDirective = tjs(`safety none
|
|
760
|
-
function add(a: 0, b: 0)
|
|
760
|
+
function add(a: 0, b: 0): 0 { return a + b }`)
|
|
761
761
|
|
|
762
762
|
// Both should lack validation
|
|
763
763
|
expect(viaMarker.code).not.toContain('__tjs.pushStack')
|
|
@@ -772,7 +772,7 @@ function add(a: 0, b: 0) -> 0 { return a + b }`)
|
|
|
772
772
|
describe('safe function syntax (?)', () => {
|
|
773
773
|
it('should parse (?) function marker', () => {
|
|
774
774
|
const result = tjs(`
|
|
775
|
-
function validated(? x: 0)
|
|
775
|
+
function validated(? x: 0): 0 {
|
|
776
776
|
return x * 2
|
|
777
777
|
}
|
|
778
778
|
`)
|
|
@@ -871,7 +871,7 @@ function test() {
|
|
|
871
871
|
describe('return type safety arrows', () => {
|
|
872
872
|
it('should parse -> as normal return type', () => {
|
|
873
873
|
const result = tjs(`
|
|
874
|
-
function add(a: 0, b: 0)
|
|
874
|
+
function add(a: 0, b: 0): 0 {
|
|
875
875
|
return a + b
|
|
876
876
|
}
|
|
877
877
|
`)
|
|
@@ -883,7 +883,7 @@ describe('return type safety arrows', () => {
|
|
|
883
883
|
|
|
884
884
|
it('should parse -? as safe return (force output validation)', () => {
|
|
885
885
|
const result = tjs(`
|
|
886
|
-
function add(a: 0, b: 0)
|
|
886
|
+
function add(a: 0, b: 0):? 0 {
|
|
887
887
|
return a + b
|
|
888
888
|
}
|
|
889
889
|
`)
|
|
@@ -894,7 +894,7 @@ describe('return type safety arrows', () => {
|
|
|
894
894
|
|
|
895
895
|
it('should parse -! as unsafe return (skip output validation)', () => {
|
|
896
896
|
const result = tjs(`
|
|
897
|
-
function add(a: 0, b: 0)
|
|
897
|
+
function add(a: 0, b: 0):! 0 {
|
|
898
898
|
return a + b
|
|
899
899
|
}
|
|
900
900
|
`)
|
|
@@ -905,7 +905,7 @@ describe('return type safety arrows', () => {
|
|
|
905
905
|
|
|
906
906
|
it('should combine (?) with -? for fully safe function', () => {
|
|
907
907
|
const result = tjs(`
|
|
908
|
-
function critical(? x: 0)
|
|
908
|
+
function critical(? x: 0):? 0 {
|
|
909
909
|
return x * 2
|
|
910
910
|
}
|
|
911
911
|
`)
|
|
@@ -916,7 +916,7 @@ describe('return type safety arrows', () => {
|
|
|
916
916
|
|
|
917
917
|
it('should combine (!) with -! for fully unsafe function', () => {
|
|
918
918
|
const result = tjs(`
|
|
919
|
-
function fast(! x: 0)
|
|
919
|
+
function fast(! x: 0):! 0 {
|
|
920
920
|
return x * 2
|
|
921
921
|
}
|
|
922
922
|
`)
|
|
@@ -934,7 +934,7 @@ describe('signature tests (transpile-time)', () => {
|
|
|
934
934
|
|
|
935
935
|
it('-> should run signature test at transpile time', () => {
|
|
936
936
|
const result = tjs(`
|
|
937
|
-
function double(x: 5)
|
|
937
|
+
function double(x: 5): 10 {
|
|
938
938
|
return x * 2
|
|
939
939
|
}
|
|
940
940
|
`)
|
|
@@ -947,7 +947,7 @@ describe('signature tests (transpile-time)', () => {
|
|
|
947
947
|
// double(5) returns 10, but expected "" — value mismatch
|
|
948
948
|
expect(() =>
|
|
949
949
|
tjs(`
|
|
950
|
-
function double(x: 5)
|
|
950
|
+
function double(x: 5): "" {
|
|
951
951
|
return x * 2
|
|
952
952
|
}
|
|
953
953
|
`)
|
|
@@ -956,7 +956,7 @@ describe('signature tests (transpile-time)', () => {
|
|
|
956
956
|
|
|
957
957
|
it('-? should run signature test at transpile time', () => {
|
|
958
958
|
const result = tjs(`
|
|
959
|
-
function double(x: 5)
|
|
959
|
+
function double(x: 5):? 10 {
|
|
960
960
|
return x * 2
|
|
961
961
|
}
|
|
962
962
|
`)
|
|
@@ -967,7 +967,7 @@ describe('signature tests (transpile-time)', () => {
|
|
|
967
967
|
it('-? should pass when example is consistent', () => {
|
|
968
968
|
// double(5) returns 10, -> 10 is the exact expected result
|
|
969
969
|
const result = tjs(`
|
|
970
|
-
function double(x: 5)
|
|
970
|
+
function double(x: 5):? 10 {
|
|
971
971
|
return x * 2
|
|
972
972
|
}
|
|
973
973
|
`)
|
|
@@ -978,7 +978,7 @@ describe('signature tests (transpile-time)', () => {
|
|
|
978
978
|
// getString(5) returns 10, but expected "" — value mismatch
|
|
979
979
|
expect(() =>
|
|
980
980
|
tjs(`
|
|
981
|
-
function getString(x: 5)
|
|
981
|
+
function getString(x: 5):? "" {
|
|
982
982
|
return x * 2
|
|
983
983
|
}
|
|
984
984
|
`)
|
|
@@ -988,7 +988,7 @@ describe('signature tests (transpile-time)', () => {
|
|
|
988
988
|
it('-! should skip signature test entirely', () => {
|
|
989
989
|
// This would fail if tested, but -! skips the test
|
|
990
990
|
const result = tjs(`
|
|
991
|
-
function double(x: 5)
|
|
991
|
+
function double(x: 5):! 999 {
|
|
992
992
|
return x * 2
|
|
993
993
|
}
|
|
994
994
|
`)
|
|
@@ -997,7 +997,7 @@ describe('signature tests (transpile-time)', () => {
|
|
|
997
997
|
|
|
998
998
|
it('-> with object return should test structure', () => {
|
|
999
999
|
const result = tjs(`
|
|
1000
|
-
function getPoint(x: 3, y: 4)
|
|
1000
|
+
function getPoint(x: 3, y: 4): { x: 3, y: 4 } {
|
|
1001
1001
|
return { x, y }
|
|
1002
1002
|
}
|
|
1003
1003
|
`)
|
|
@@ -1007,7 +1007,7 @@ describe('signature tests (transpile-time)', () => {
|
|
|
1007
1007
|
it('-> with object return should pass when example is consistent', () => {
|
|
1008
1008
|
// getPoint(3, 4) returns {x: 3, y: 4}, -> matches exactly
|
|
1009
1009
|
const result = tjs(`
|
|
1010
|
-
function getPoint(x: 3, y: 4)
|
|
1010
|
+
function getPoint(x: 3, y: 4): { x: 3, y: 4 } {
|
|
1011
1011
|
return { x, y }
|
|
1012
1012
|
}
|
|
1013
1013
|
`)
|
|
@@ -1017,7 +1017,7 @@ describe('signature tests (transpile-time)', () => {
|
|
|
1017
1017
|
it('-> with object return should fail on value mismatch', () => {
|
|
1018
1018
|
expect(() =>
|
|
1019
1019
|
tjs(`
|
|
1020
|
-
function getPoint(x: 3, y: 4)
|
|
1020
|
+
function getPoint(x: 3, y: 4): { x: "", y: "" } {
|
|
1021
1021
|
return { x, y }
|
|
1022
1022
|
}
|
|
1023
1023
|
`)
|
|
@@ -1027,7 +1027,7 @@ describe('signature tests (transpile-time)', () => {
|
|
|
1027
1027
|
it('should skip signature tests for async functions', () => {
|
|
1028
1028
|
const result = tjs(
|
|
1029
1029
|
`
|
|
1030
|
-
async function fetchData(id: 'test-1')
|
|
1030
|
+
async function fetchData(id: 'test-1'): { name: '', id: '' } {
|
|
1031
1031
|
return { name: 'Test', id }
|
|
1032
1032
|
}
|
|
1033
1033
|
`,
|
|
@@ -1042,11 +1042,11 @@ describe('signature tests (transpile-time)', () => {
|
|
|
1042
1042
|
it('should handle top-level await in module code during tests', () => {
|
|
1043
1043
|
const result = tjs(
|
|
1044
1044
|
`
|
|
1045
|
-
function double(x: 5)
|
|
1045
|
+
function double(x: 5): 10 {
|
|
1046
1046
|
return x * 2
|
|
1047
1047
|
}
|
|
1048
1048
|
|
|
1049
|
-
async function fetchThing(id: '')
|
|
1049
|
+
async function fetchThing(id: ''): '' {
|
|
1050
1050
|
return id
|
|
1051
1051
|
}
|
|
1052
1052
|
|
|
@@ -1069,7 +1069,7 @@ describe('signature tests (transpile-time)', () => {
|
|
|
1069
1069
|
|
|
1070
1070
|
const UserSchema = Schema({ name: '', age: 0 })
|
|
1071
1071
|
|
|
1072
|
-
function validateUser(data: { name: '', age: 0 })
|
|
1072
|
+
function validateUser(data: { name: '', age: 0 }): { valid: true, errors: [''] } {
|
|
1073
1073
|
return { valid: true, errors: [] }
|
|
1074
1074
|
}
|
|
1075
1075
|
`,
|
|
@@ -1090,7 +1090,7 @@ describe('signature tests (transpile-time)', () => {
|
|
|
1090
1090
|
`
|
|
1091
1091
|
import { parseISO, format } from 'date-fns'
|
|
1092
1092
|
|
|
1093
|
-
function formatDate(date: '2024-01-15', pattern: 'yyyy-MM-dd')
|
|
1093
|
+
function formatDate(date: '2024-01-15', pattern: 'yyyy-MM-dd'): '' {
|
|
1094
1094
|
const parsed = parseISO(date)
|
|
1095
1095
|
return format(parsed, pattern)
|
|
1096
1096
|
}
|
|
@@ -1109,11 +1109,11 @@ describe('signature tests (transpile-time)', () => {
|
|
|
1109
1109
|
it('should test sync functions alongside async functions', () => {
|
|
1110
1110
|
const result = tjs(
|
|
1111
1111
|
`
|
|
1112
|
-
function add(a: 2, b: 3)
|
|
1112
|
+
function add(a: 2, b: 3): 5 {
|
|
1113
1113
|
return a + b
|
|
1114
1114
|
}
|
|
1115
1115
|
|
|
1116
|
-
async function fetchSum(a: 0, b: 0)
|
|
1116
|
+
async function fetchSum(a: 0, b: 0): 0 {
|
|
1117
1117
|
return a + b
|
|
1118
1118
|
}
|
|
1119
1119
|
`,
|
|
@@ -1126,6 +1126,82 @@ describe('signature tests (transpile-time)', () => {
|
|
|
1126
1126
|
)
|
|
1127
1127
|
expect(addTest?.passed).toBe(true)
|
|
1128
1128
|
})
|
|
1129
|
+
|
|
1130
|
+
it('should run signature tests for class methods using first constructor', () => {
|
|
1131
|
+
const result = tjs(
|
|
1132
|
+
`
|
|
1133
|
+
class Point {
|
|
1134
|
+
constructor(x: 0.0, y: 0.0) {
|
|
1135
|
+
this.x = x
|
|
1136
|
+
this.y = y
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
distanceTo(other: { x: 3.0, y: 4.0 }): 5.0 {
|
|
1140
|
+
const dx = this.x - other.x
|
|
1141
|
+
const dy = this.y - other.y
|
|
1142
|
+
return Math.sqrt(dx * dx + dy * dy)
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
`,
|
|
1146
|
+
{ runTests: 'report' }
|
|
1147
|
+
)
|
|
1148
|
+
expect(result.testResults).toBeDefined()
|
|
1149
|
+
const sigTest = result.testResults!.find((t: any) =>
|
|
1150
|
+
t.description.includes('Point.distanceTo')
|
|
1151
|
+
)
|
|
1152
|
+
expect(sigTest).toBeDefined()
|
|
1153
|
+
expect(sigTest?.passed).toBe(true)
|
|
1154
|
+
expect(sigTest?.isSignatureTest).toBe(true)
|
|
1155
|
+
})
|
|
1156
|
+
|
|
1157
|
+
it('should fail class method signature test when return value is wrong', () => {
|
|
1158
|
+
expect(() =>
|
|
1159
|
+
tjs(`
|
|
1160
|
+
class Adder {
|
|
1161
|
+
constructor(base: 10) {
|
|
1162
|
+
this.base = base
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
add(x: 5): 100 {
|
|
1166
|
+
return this.base + x
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
`)
|
|
1170
|
+
).toThrow(/Expected.*got/)
|
|
1171
|
+
})
|
|
1172
|
+
|
|
1173
|
+
it('should run signature tests for methods on classes with multiple constructors', () => {
|
|
1174
|
+
const result = tjs(
|
|
1175
|
+
`
|
|
1176
|
+
TjsClass
|
|
1177
|
+
|
|
1178
|
+
class Point {
|
|
1179
|
+
constructor(x: 0.0, y: 0.0) {
|
|
1180
|
+
this.x = x
|
|
1181
|
+
this.y = y
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
constructor(coords: { x: 0.0, y: 0.0 }) {
|
|
1185
|
+
this.x = coords.x
|
|
1186
|
+
this.y = coords.y
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
distanceTo(other: { x: 3.0, y: 4.0 }): 5.0 {
|
|
1190
|
+
const dx = this.x - other.x
|
|
1191
|
+
const dy = this.y - other.y
|
|
1192
|
+
return Math.sqrt(dx * dx + dy * dy)
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
`,
|
|
1196
|
+
{ runTests: 'report' }
|
|
1197
|
+
)
|
|
1198
|
+
expect(result.testResults).toBeDefined()
|
|
1199
|
+
const sigTest = result.testResults!.find((t: any) =>
|
|
1200
|
+
t.description.includes('Point.distanceTo')
|
|
1201
|
+
)
|
|
1202
|
+
expect(sigTest).toBeDefined()
|
|
1203
|
+
expect(sigTest?.passed).toBe(true)
|
|
1204
|
+
})
|
|
1129
1205
|
})
|
|
1130
1206
|
|
|
1131
1207
|
// =============================================================================
|
|
@@ -1143,7 +1219,7 @@ describe('signature test canaries — exact value matching', () => {
|
|
|
1143
1219
|
// add(2,3) returns 5, but -> says 0 — must fail
|
|
1144
1220
|
expect(() =>
|
|
1145
1221
|
tjs(`
|
|
1146
|
-
function add(a: 2, b: 3)
|
|
1222
|
+
function add(a: 2, b: 3): 0 {
|
|
1147
1223
|
return a + b
|
|
1148
1224
|
}
|
|
1149
1225
|
`)
|
|
@@ -1154,7 +1230,7 @@ describe('signature test canaries — exact value matching', () => {
|
|
|
1154
1230
|
// greet('World') returns 'Hello, World!', but -> says '' — must fail
|
|
1155
1231
|
expect(() =>
|
|
1156
1232
|
tjs(
|
|
1157
|
-
"function greet(name: 'World')
|
|
1233
|
+
"function greet(name: 'World'): '' {\n return 'Hello, ' + name + '!'\n}"
|
|
1158
1234
|
)
|
|
1159
1235
|
).toThrow(/Expected.*got/)
|
|
1160
1236
|
})
|
|
@@ -1163,7 +1239,7 @@ describe('signature test canaries — exact value matching', () => {
|
|
|
1163
1239
|
// getPoint(3,4) returns {x:3,y:4}, but -> says {x:0,y:0} — must fail
|
|
1164
1240
|
expect(() =>
|
|
1165
1241
|
tjs(`
|
|
1166
|
-
function getPoint(x: 3, y: 4)
|
|
1242
|
+
function getPoint(x: 3, y: 4): { x: 0, y: 0 } {
|
|
1167
1243
|
return { x, y }
|
|
1168
1244
|
}
|
|
1169
1245
|
`)
|
|
@@ -1174,7 +1250,7 @@ describe('signature test canaries — exact value matching', () => {
|
|
|
1174
1250
|
// -? runs signature test AND runtime validation
|
|
1175
1251
|
expect(() =>
|
|
1176
1252
|
tjs(`
|
|
1177
|
-
function double(x: 5)
|
|
1253
|
+
function double(x: 5):? 0 {
|
|
1178
1254
|
return x * 2
|
|
1179
1255
|
}
|
|
1180
1256
|
`)
|
|
@@ -1184,7 +1260,7 @@ describe('signature test canaries — exact value matching', () => {
|
|
|
1184
1260
|
it('CANARY: -! does NOT run signature test (wrong value is OK)', () => {
|
|
1185
1261
|
// -! skips the test — wrong return example is just metadata
|
|
1186
1262
|
const result = tjs(`
|
|
1187
|
-
function double(x: 5)
|
|
1263
|
+
function double(x: 5):! 999 {
|
|
1188
1264
|
return x * 2
|
|
1189
1265
|
}
|
|
1190
1266
|
`)
|
|
@@ -1193,7 +1269,7 @@ describe('signature test canaries — exact value matching', () => {
|
|
|
1193
1269
|
|
|
1194
1270
|
it('CANARY: -> passes with correct exact values', () => {
|
|
1195
1271
|
const result = tjs(`
|
|
1196
|
-
function add(a: 2, b: 3)
|
|
1272
|
+
function add(a: 2, b: 3): 5 {
|
|
1197
1273
|
return a + b
|
|
1198
1274
|
}
|
|
1199
1275
|
`)
|
|
@@ -1203,7 +1279,7 @@ describe('signature test canaries — exact value matching', () => {
|
|
|
1203
1279
|
|
|
1204
1280
|
it('CANARY: -> passes with correct string value', () => {
|
|
1205
1281
|
const result = tjs(
|
|
1206
|
-
"function greet(name: 'World')
|
|
1282
|
+
"function greet(name: 'World'): 'Hello, World!' {\n return 'Hello, ' + name + '!'\n}"
|
|
1207
1283
|
)
|
|
1208
1284
|
expect(result.testResults).toHaveLength(1)
|
|
1209
1285
|
expect(result.testResults![0].passed).toBe(true)
|
|
@@ -1211,7 +1287,7 @@ describe('signature test canaries — exact value matching', () => {
|
|
|
1211
1287
|
|
|
1212
1288
|
it('CANARY: -> passes with correct object values', () => {
|
|
1213
1289
|
const result = tjs(`
|
|
1214
|
-
function getPoint(x: 3, y: 4)
|
|
1290
|
+
function getPoint(x: 3, y: 4): { x: 3, y: 4 } {
|
|
1215
1291
|
return { x, y }
|
|
1216
1292
|
}
|
|
1217
1293
|
`)
|
|
@@ -1222,7 +1298,7 @@ describe('signature test canaries — exact value matching', () => {
|
|
|
1222
1298
|
it('CANARY: -? runtime validation checks type only (not value)', () => {
|
|
1223
1299
|
// -? validates at runtime that return TYPE matches (number, not 10)
|
|
1224
1300
|
// The runtime check should pass even if the value differs from annotation
|
|
1225
|
-
const result = tjs('function double(x: 5)
|
|
1301
|
+
const result = tjs('function double(x: 5):? 10 { return x * 2 }', {
|
|
1226
1302
|
runTests: false,
|
|
1227
1303
|
})
|
|
1228
1304
|
const savedTjs = globalThis.__tjs
|
|
@@ -1552,3 +1628,143 @@ describe('SyntaxError formatting', () => {
|
|
|
1552
1628
|
}
|
|
1553
1629
|
})
|
|
1554
1630
|
})
|
|
1631
|
+
|
|
1632
|
+
// =============================================================================
|
|
1633
|
+
// BANG ACCESS (!.) — asserted non-null member access
|
|
1634
|
+
// =============================================================================
|
|
1635
|
+
|
|
1636
|
+
describe('bang access (!.)', () => {
|
|
1637
|
+
// Helper to run TJS code and return the result of calling test()
|
|
1638
|
+
function runBang(source: string): unknown {
|
|
1639
|
+
const result = tjs(source, { runTests: false })
|
|
1640
|
+
const fn = new Function(result.code + '\nreturn test')()
|
|
1641
|
+
return fn()
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
it('should access property on non-null object', () => {
|
|
1645
|
+
expect(
|
|
1646
|
+
runBang(`
|
|
1647
|
+
function test() {
|
|
1648
|
+
const x = { foo: 42 }
|
|
1649
|
+
return x!.foo
|
|
1650
|
+
}
|
|
1651
|
+
`)
|
|
1652
|
+
).toBe(42)
|
|
1653
|
+
})
|
|
1654
|
+
|
|
1655
|
+
it('should return MonadicError on null', () => {
|
|
1656
|
+
const result = runBang(`
|
|
1657
|
+
function test() {
|
|
1658
|
+
const x = null
|
|
1659
|
+
return x!.foo
|
|
1660
|
+
}
|
|
1661
|
+
`)
|
|
1662
|
+
expect(isMonadicError(result)).toBe(true)
|
|
1663
|
+
})
|
|
1664
|
+
|
|
1665
|
+
it('should return MonadicError on undefined', () => {
|
|
1666
|
+
const result = runBang(`
|
|
1667
|
+
function test() {
|
|
1668
|
+
let x
|
|
1669
|
+
return x!.foo
|
|
1670
|
+
}
|
|
1671
|
+
`)
|
|
1672
|
+
expect(isMonadicError(result)).toBe(true)
|
|
1673
|
+
})
|
|
1674
|
+
|
|
1675
|
+
it('should propagate MonadicError through chains', () => {
|
|
1676
|
+
const result = runBang(`
|
|
1677
|
+
function test() {
|
|
1678
|
+
const x = null
|
|
1679
|
+
return x!.foo!.bar
|
|
1680
|
+
}
|
|
1681
|
+
`)
|
|
1682
|
+
expect(isMonadicError(result)).toBe(true)
|
|
1683
|
+
})
|
|
1684
|
+
|
|
1685
|
+
it('should handle member chains before !.', () => {
|
|
1686
|
+
expect(
|
|
1687
|
+
runBang(`
|
|
1688
|
+
function test() {
|
|
1689
|
+
const x = { y: { foo: 99 } }
|
|
1690
|
+
return x.y!.foo
|
|
1691
|
+
}
|
|
1692
|
+
`)
|
|
1693
|
+
).toBe(99)
|
|
1694
|
+
})
|
|
1695
|
+
|
|
1696
|
+
it('should handle function call before !.', () => {
|
|
1697
|
+
expect(
|
|
1698
|
+
runBang(`
|
|
1699
|
+
function getObj() { return { val: 7 } }
|
|
1700
|
+
function test() {
|
|
1701
|
+
return getObj()!.val
|
|
1702
|
+
}
|
|
1703
|
+
`)
|
|
1704
|
+
).toBe(7)
|
|
1705
|
+
})
|
|
1706
|
+
|
|
1707
|
+
it('should return MonadicError when function returns null', () => {
|
|
1708
|
+
const result = runBang(`
|
|
1709
|
+
function getNull() { return null }
|
|
1710
|
+
function test() {
|
|
1711
|
+
return getNull()!.val
|
|
1712
|
+
}
|
|
1713
|
+
`)
|
|
1714
|
+
expect(isMonadicError(result)).toBe(true)
|
|
1715
|
+
})
|
|
1716
|
+
|
|
1717
|
+
it('should handle bracket access before !.', () => {
|
|
1718
|
+
expect(
|
|
1719
|
+
runBang(`
|
|
1720
|
+
function test() {
|
|
1721
|
+
const arr = [{ name: 'first' }]
|
|
1722
|
+
return arr[0]!.name
|
|
1723
|
+
}
|
|
1724
|
+
`)
|
|
1725
|
+
).toBe('first')
|
|
1726
|
+
})
|
|
1727
|
+
|
|
1728
|
+
it('should not transform !. inside strings', () => {
|
|
1729
|
+
const result = tjs(
|
|
1730
|
+
`
|
|
1731
|
+
function test() { return "x!.foo" }
|
|
1732
|
+
`,
|
|
1733
|
+
{ runTests: false }
|
|
1734
|
+
)
|
|
1735
|
+
expect(result.code).toContain('"x!.foo"')
|
|
1736
|
+
expect(result.code).not.toContain('__tjs.bang')
|
|
1737
|
+
})
|
|
1738
|
+
|
|
1739
|
+
it('should not transform !. inside comments', () => {
|
|
1740
|
+
const result = tjs(
|
|
1741
|
+
`
|
|
1742
|
+
// x!.foo should not be transformed
|
|
1743
|
+
function test() { return 1 }
|
|
1744
|
+
`,
|
|
1745
|
+
{ runTests: false }
|
|
1746
|
+
)
|
|
1747
|
+
expect(result.code).not.toContain('__tjs.bang')
|
|
1748
|
+
})
|
|
1749
|
+
|
|
1750
|
+
it('should work alongside ?. optional chaining', () => {
|
|
1751
|
+
expect(
|
|
1752
|
+
runBang(`
|
|
1753
|
+
function test() {
|
|
1754
|
+
const x = { y: { z: 5 } }
|
|
1755
|
+
return x?.y!.z
|
|
1756
|
+
}
|
|
1757
|
+
`)
|
|
1758
|
+
).toBe(5)
|
|
1759
|
+
})
|
|
1760
|
+
|
|
1761
|
+
it('emitted standalone JS includes bang inline stub', () => {
|
|
1762
|
+
const result = tjs(
|
|
1763
|
+
`
|
|
1764
|
+
function test() { const x = {}; return x!.foo }
|
|
1765
|
+
`,
|
|
1766
|
+
{ runTests: false }
|
|
1767
|
+
)
|
|
1768
|
+
expect(result.code).toContain('function bang(')
|
|
1769
|
+
})
|
|
1770
|
+
})
|
package/src/lang/from-ts.test.ts
CHANGED
|
@@ -33,7 +33,7 @@ describe('TypeScript to TJS Transpiler', () => {
|
|
|
33
33
|
`function greet(name: string): string { return name }`,
|
|
34
34
|
{ emitTJS: true }
|
|
35
35
|
)
|
|
36
|
-
expect(result.code).toContain("
|
|
36
|
+
expect(result.code).toContain(":! ''") // :! skips signature test for TS-transpiled code
|
|
37
37
|
})
|
|
38
38
|
|
|
39
39
|
it('should handle array types', () => {
|
|
@@ -49,7 +49,7 @@ describe('TypeScript to TJS Transpiler', () => {
|
|
|
49
49
|
`function getUser(): { name: string, age: number } { return { name: '', age: 0 } }`,
|
|
50
50
|
{ emitTJS: true }
|
|
51
51
|
)
|
|
52
|
-
expect(result.code).toContain("
|
|
52
|
+
expect(result.code).toContain(":! { name: '', age: 0.0 }") // :! for TS-transpiled
|
|
53
53
|
})
|
|
54
54
|
|
|
55
55
|
it('should handle nullable types', () => {
|
|
@@ -57,7 +57,7 @@ describe('TypeScript to TJS Transpiler', () => {
|
|
|
57
57
|
`function find(id: string): string | null { return null }`,
|
|
58
58
|
{ emitTJS: true }
|
|
59
59
|
)
|
|
60
|
-
expect(result.code).toContain("
|
|
60
|
+
expect(result.code).toContain(":! '' | null") // :! for TS-transpiled
|
|
61
61
|
})
|
|
62
62
|
|
|
63
63
|
it('should preserve default values', () => {
|
|
@@ -200,7 +200,7 @@ describe('FunctionPredicate regression fixes', () => {
|
|
|
200
200
|
|
|
201
201
|
it('should handle FunctionPredicate call in return type position', () => {
|
|
202
202
|
const result = tjs(
|
|
203
|
-
"function makeStyle(spec: {})
|
|
203
|
+
"function makeStyle(spec: {}):! FunctionPredicate('function', { params: { el: {} } }) {\n return (el) => el\n}",
|
|
204
204
|
{ runTests: false }
|
|
205
205
|
)
|
|
206
206
|
// Should not error — FunctionPredicate(...) is a valid return type
|
package/src/lang/index.ts
CHANGED
|
@@ -53,6 +53,12 @@ export {
|
|
|
53
53
|
} from './emitters/from-ts'
|
|
54
54
|
export * from './inference'
|
|
55
55
|
export { Schema } from './schema'
|
|
56
|
+
export {
|
|
57
|
+
typeDescriptorToJSONSchema,
|
|
58
|
+
exampleToJSONSchema,
|
|
59
|
+
functionMetaToJSONSchema,
|
|
60
|
+
type JSONSchemaObject,
|
|
61
|
+
} from './json-schema'
|
|
56
62
|
export { MetadataCache, getGlobalCache, setGlobalCache } from './metadata-cache'
|
|
57
63
|
export {
|
|
58
64
|
lint,
|
|
@@ -116,6 +122,7 @@ export {
|
|
|
116
122
|
|
|
117
123
|
// Re-import for local use in this file
|
|
118
124
|
import { MetadataCache, getGlobalCache } from './metadata-cache'
|
|
125
|
+
import { typeDescriptorToJSONSchema } from './json-schema'
|
|
119
126
|
|
|
120
127
|
/**
|
|
121
128
|
* Transpile JavaScript source code to Agent99 AST
|
|
@@ -464,7 +471,7 @@ export function getToolDefinitions(
|
|
|
464
471
|
const required: string[] = []
|
|
465
472
|
|
|
466
473
|
for (const [paramName, param] of Object.entries(sig.parameters)) {
|
|
467
|
-
properties[paramName] =
|
|
474
|
+
properties[paramName] = typeDescriptorToJSONSchema(param.type)
|
|
468
475
|
if (param.description) {
|
|
469
476
|
properties[paramName].description = param.description
|
|
470
477
|
}
|
|
@@ -487,49 +494,3 @@ export function getToolDefinitions(
|
|
|
487
494
|
}
|
|
488
495
|
})
|
|
489
496
|
}
|
|
490
|
-
|
|
491
|
-
/**
|
|
492
|
-
* Convert TypeDescriptor to JSON Schema
|
|
493
|
-
*/
|
|
494
|
-
function typeDescriptorToJsonSchema(
|
|
495
|
-
type: import('./types').TypeDescriptor
|
|
496
|
-
): any {
|
|
497
|
-
switch (type.kind) {
|
|
498
|
-
case 'string':
|
|
499
|
-
return { type: 'string' }
|
|
500
|
-
case 'number':
|
|
501
|
-
return { type: 'number' }
|
|
502
|
-
case 'boolean':
|
|
503
|
-
return { type: 'boolean' }
|
|
504
|
-
case 'null':
|
|
505
|
-
return { type: 'null' }
|
|
506
|
-
case 'array':
|
|
507
|
-
return {
|
|
508
|
-
type: 'array',
|
|
509
|
-
items: type.items ? typeDescriptorToJsonSchema(type.items) : {},
|
|
510
|
-
}
|
|
511
|
-
case 'object':
|
|
512
|
-
if (!type.shape) {
|
|
513
|
-
return { type: 'object' }
|
|
514
|
-
}
|
|
515
|
-
return {
|
|
516
|
-
type: 'object',
|
|
517
|
-
properties: Object.fromEntries(
|
|
518
|
-
Object.entries(type.shape).map(([k, v]) => [
|
|
519
|
-
k,
|
|
520
|
-
typeDescriptorToJsonSchema(v),
|
|
521
|
-
])
|
|
522
|
-
),
|
|
523
|
-
}
|
|
524
|
-
case 'union':
|
|
525
|
-
if (!type.members) {
|
|
526
|
-
return {}
|
|
527
|
-
}
|
|
528
|
-
return {
|
|
529
|
-
anyOf: type.members.map(typeDescriptorToJsonSchema),
|
|
530
|
-
}
|
|
531
|
-
case 'any':
|
|
532
|
-
default:
|
|
533
|
-
return {}
|
|
534
|
-
}
|
|
535
|
-
}
|