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.
Files changed (86) hide show
  1. package/CLAUDE.md +85 -422
  2. package/README.md +15 -82
  3. package/bin/benchmarks.ts +7 -7
  4. package/bin/dev.ts +2 -1
  5. package/demo/autocomplete.test.ts +1 -1
  6. package/demo/docs.json +744 -48
  7. package/demo/src/demo-nav.ts +5 -5
  8. package/demo/src/index.ts +28 -36
  9. package/demo/src/module-sw.ts +1 -1
  10. package/demo/src/playground-shared.ts +17 -17
  11. package/demo/src/playground.ts +13 -1
  12. package/demo/src/style.ts +4 -1
  13. package/demo/src/tjs-playground.ts +5 -5
  14. package/demo/src/user-store.ts +2 -1
  15. package/demo/static/favicon.svg +17 -24
  16. package/demo/static/tosi-platform.json +9304 -0
  17. package/dist/index.js +158 -156
  18. package/dist/index.js.map +14 -13
  19. package/dist/scripts/compat-effect.d.ts +16 -0
  20. package/dist/scripts/compat-kysely.d.ts +13 -0
  21. package/dist/scripts/compat-radash.d.ts +13 -0
  22. package/dist/scripts/compat-superstruct.d.ts +13 -0
  23. package/dist/scripts/compat-ts-pattern.d.ts +13 -0
  24. package/dist/scripts/compat-zod.d.ts +12 -0
  25. package/dist/src/lang/emitters/from-ts.d.ts +1 -1
  26. package/dist/src/lang/emitters/js-tests.d.ts +4 -0
  27. package/dist/src/lang/emitters/js.d.ts +2 -2
  28. package/dist/src/lang/index.d.ts +1 -0
  29. package/dist/src/lang/json-schema.d.ts +40 -0
  30. package/dist/src/lang/parser-transforms.d.ts +14 -0
  31. package/dist/src/lang/runtime.d.ts +39 -6
  32. package/dist/src/types/Type.d.ts +5 -0
  33. package/dist/tjs-full.js +158 -156
  34. package/dist/tjs-full.js.map +14 -13
  35. package/dist/tjs-vm.js +44 -43
  36. package/dist/tjs-vm.js.map +5 -5
  37. package/docs/README.md +21 -20
  38. package/docs/WASM-QUICKSTART.md +283 -0
  39. package/docs/diagrams/architecture-shift.svg +117 -0
  40. package/docs/diagrams/compile-runtime.svg +130 -0
  41. package/docs/diagrams/icon-riff-1.svg +55 -0
  42. package/docs/diagrams/icon-riff-2.svg +62 -0
  43. package/docs/diagrams/icon-riff-3.svg +61 -0
  44. package/docs/diagrams/platform-overview.svg +114 -0
  45. package/docs/diagrams/safe-eval.svg +147 -0
  46. package/docs/eval-v4/arch-comparison.svg +277 -0
  47. package/docs/eval-v4/bundler-tree.svg +250 -0
  48. package/docs/eval-v4/http-lifecycle.svg +148 -0
  49. package/docs/function-predicate-design.md +8 -8
  50. package/docs/native-engine-integration.md +2 -2
  51. package/editors/codemirror/autocomplete.test.ts +29 -29
  52. package/package.json +10 -4
  53. package/src/cli/commands/convert.test.ts +11 -8
  54. package/src/cli/tjs.ts +1 -1
  55. package/src/lang/codegen.test.ts +117 -112
  56. package/src/lang/docs.test.ts +22 -22
  57. package/src/lang/docs.ts +5 -8
  58. package/src/lang/emitters/dts.test.ts +13 -13
  59. package/src/lang/emitters/from-ts.ts +36 -9
  60. package/src/lang/emitters/js-tests.ts +143 -28
  61. package/src/lang/emitters/js.ts +49 -28
  62. package/src/lang/features.test.ts +259 -43
  63. package/src/lang/from-ts.test.ts +3 -3
  64. package/src/lang/function-predicate.test.ts +1 -1
  65. package/src/lang/index.ts +8 -47
  66. package/src/lang/json-schema.test.ts +261 -0
  67. package/src/lang/json-schema.ts +167 -0
  68. package/src/lang/parser-params.ts +28 -44
  69. package/src/lang/parser-transforms.ts +255 -0
  70. package/src/lang/parser.test.ts +32 -13
  71. package/src/lang/parser.ts +49 -11
  72. package/src/lang/perf.test.ts +11 -11
  73. package/src/lang/roundtrip.test.ts +3 -3
  74. package/src/lang/runtime.test.ts +167 -0
  75. package/src/lang/runtime.ts +234 -46
  76. package/src/lang/transpiler.test.ts +21 -21
  77. package/src/lang/typescript-syntax.test.ts +11 -9
  78. package/src/types/Type.ts +38 -1
  79. package/src/use-cases/bootstrap.test.ts +7 -7
  80. package/src/use-cases/client-server.test.ts +1 -1
  81. package/src/use-cases/malicious-actor.test.ts +1 -1
  82. package/src/use-cases/rag-processor.test.ts +1 -1
  83. package/src/use-cases/sophisticated-agents.test.ts +2 -2
  84. package/src/use-cases/transpiler-llm.test.ts +1 -1
  85. package/src/use-cases/unbundled-imports.test.ts +9 -9
  86. package/tjs-lang.svg +17 -25
@@ -539,11 +539,31 @@ export function insertAsiProtection(source: string): string {
539
539
 
540
540
  const lines = source.split('\n')
541
541
  const result: string[] = []
542
+ let inBlockComment = false
542
543
 
543
544
  for (let i = 0; i < lines.length; i++) {
544
545
  const line = lines[i]
545
546
  const prevLine = i > 0 ? lines[i - 1] : ''
546
547
 
548
+ // Track block comment state
549
+ if (inBlockComment) {
550
+ result.push(line)
551
+ if (line.includes('*/')) inBlockComment = false
552
+ continue
553
+ }
554
+
555
+ // Check if this line opens a block comment
556
+ const commentOpen = line.indexOf('/*')
557
+ const commentClose = line.indexOf('*/')
558
+ if (
559
+ commentOpen !== -1 &&
560
+ (commentClose === -1 || commentClose < commentOpen)
561
+ ) {
562
+ inBlockComment = true
563
+ result.push(line)
564
+ continue
565
+ }
566
+
547
567
  // Check if this line starts with a problematic character
548
568
  if (i > 0 && continuationStarts.test(line)) {
549
569
  // Get the previous line without trailing comment
@@ -3109,3 +3129,238 @@ export function validateNoEval(source: string): string {
3109
3129
 
3110
3130
  return source
3111
3131
  }
3132
+
3133
+ /**
3134
+ * Transform bang access (!.) to __tjs.bang() calls.
3135
+ *
3136
+ * x!.foo → __tjs.bang(x,'foo')
3137
+ * x.y!.foo → __tjs.bang(x.y,'foo')
3138
+ * fn()!.foo → __tjs.bang(fn(),'foo')
3139
+ * arr[0]!.foo → __tjs.bang(arr[0],'foo')
3140
+ * x!.foo!.bar → __tjs.bang(__tjs.bang(x,'foo'),'bar')
3141
+ *
3142
+ * If the source is null/undefined, returns MonadicError.
3143
+ * If the source is a MonadicError, propagates it.
3144
+ * Otherwise, performs a bare property access (throws as usual).
3145
+ */
3146
+ export function transformBangAccess(source: string): string {
3147
+ // Quick bail — no !. in source at all
3148
+ if (!source.includes('!.')) return source
3149
+
3150
+ let result = ''
3151
+ let i = 0
3152
+
3153
+ // State tracking for strings/comments
3154
+ type State =
3155
+ | 'normal'
3156
+ | 'string-single'
3157
+ | 'string-double'
3158
+ | 'string-template'
3159
+ | 'line-comment'
3160
+ | 'block-comment'
3161
+ let state: State = 'normal'
3162
+ let templateDepth = 0
3163
+
3164
+ while (i < source.length) {
3165
+ const ch = source[i]
3166
+ const next = source[i + 1]
3167
+
3168
+ // State transitions
3169
+ if (state === 'normal') {
3170
+ if (ch === '/' && next === '/') {
3171
+ state = 'line-comment'
3172
+ result += ch
3173
+ i++
3174
+ continue
3175
+ }
3176
+ if (ch === '/' && next === '*') {
3177
+ state = 'block-comment'
3178
+ result += ch
3179
+ i++
3180
+ continue
3181
+ }
3182
+ if (ch === "'") {
3183
+ state = 'string-single'
3184
+ result += ch
3185
+ i++
3186
+ continue
3187
+ }
3188
+ if (ch === '"') {
3189
+ state = 'string-double'
3190
+ result += ch
3191
+ i++
3192
+ continue
3193
+ }
3194
+ if (ch === '`') {
3195
+ state = 'string-template'
3196
+ templateDepth++
3197
+ result += ch
3198
+ i++
3199
+ continue
3200
+ }
3201
+
3202
+ // Detect bang access: ! followed by . followed by a word char (not digit)
3203
+ if (
3204
+ ch === '!' &&
3205
+ next === '.' &&
3206
+ i + 2 < source.length &&
3207
+ /[a-zA-Z_$]/.test(source[i + 2])
3208
+ ) {
3209
+ // Scan backward in `result` to find the expression start
3210
+ const exprEnd = result.length
3211
+ const exprStart = findExprStartBackward(result)
3212
+
3213
+ if (exprStart < exprEnd) {
3214
+ const expr = result.slice(exprStart)
3215
+ result = result.slice(0, exprStart)
3216
+
3217
+ // Scan forward to capture the property name after !.
3218
+ let j = i + 2
3219
+ while (j < source.length && /[\w$]/.test(source[j])) j++
3220
+ const prop = source.slice(i + 2, j)
3221
+
3222
+ result += `__tjs.bang(${expr},'${prop}')`
3223
+ i = j
3224
+ continue
3225
+ }
3226
+ }
3227
+
3228
+ result += ch
3229
+ i++
3230
+ } else if (state === 'line-comment') {
3231
+ result += ch
3232
+ if (ch === '\n') state = 'normal'
3233
+ i++
3234
+ } else if (state === 'block-comment') {
3235
+ result += ch
3236
+ if (ch === '*' && next === '/') {
3237
+ result += next
3238
+ state = 'normal'
3239
+ i += 2
3240
+ } else {
3241
+ i++
3242
+ }
3243
+ } else if (state === 'string-single') {
3244
+ result += ch
3245
+ if (ch === '\\') {
3246
+ result += next || ''
3247
+ i += 2
3248
+ } else if (ch === "'") {
3249
+ state = 'normal'
3250
+ i++
3251
+ } else {
3252
+ i++
3253
+ }
3254
+ } else if (state === 'string-double') {
3255
+ result += ch
3256
+ if (ch === '\\') {
3257
+ result += next || ''
3258
+ i += 2
3259
+ } else if (ch === '"') {
3260
+ state = 'normal'
3261
+ i++
3262
+ } else {
3263
+ i++
3264
+ }
3265
+ } else if (state === 'string-template') {
3266
+ result += ch
3267
+ if (ch === '\\') {
3268
+ result += next || ''
3269
+ i += 2
3270
+ } else if (ch === '`') {
3271
+ templateDepth--
3272
+ state = templateDepth > 0 ? 'string-template' : 'normal'
3273
+ i++
3274
+ } else if (ch === '$' && next === '{') {
3275
+ result += next
3276
+ i += 2
3277
+ state = 'normal'
3278
+ } else {
3279
+ i++
3280
+ }
3281
+ } else {
3282
+ result += ch
3283
+ i++
3284
+ }
3285
+ }
3286
+
3287
+ return result
3288
+ }
3289
+
3290
+ /**
3291
+ * Scan backward through `text` to find the start of the expression
3292
+ * that ends at the last character of `text`.
3293
+ *
3294
+ * Handles: identifiers, member chains (. and ?.), function calls (),
3295
+ * computed access [], and nested combinations.
3296
+ */
3297
+ function findExprStartBackward(text: string): number {
3298
+ let pos = text.length - 1
3299
+
3300
+ // Skip trailing whitespace
3301
+ while (pos >= 0 && /\s/.test(text[pos])) pos--
3302
+ if (pos < 0) return text.length
3303
+
3304
+ // Walk backward consuming expression parts
3305
+ while (pos >= 0) {
3306
+ const ch = text[pos]
3307
+
3308
+ if (/[\w$]/.test(ch)) {
3309
+ // Identifier — consume word chars
3310
+ while (pos >= 0 && /[\w$]/.test(text[pos])) pos--
3311
+ // Check if preceded by . or ?. (member chain continues)
3312
+ if (pos >= 0 && text[pos] === '.') {
3313
+ if (pos >= 1 && text[pos - 1] === '?') {
3314
+ pos -= 2
3315
+ } else {
3316
+ pos--
3317
+ }
3318
+ continue
3319
+ }
3320
+ return pos + 1
3321
+ } else if (ch === ')') {
3322
+ pos = findMatchingOpen(text, pos, '(', ')')
3323
+ if (pos < 0) return 0
3324
+ pos--
3325
+ if (pos >= 0 && /[\w$]/.test(text[pos])) continue
3326
+ if (pos >= 0 && text[pos] === '.') {
3327
+ if (pos >= 1 && text[pos - 1] === '?') pos -= 2
3328
+ else pos--
3329
+ continue
3330
+ }
3331
+ return pos + 1
3332
+ } else if (ch === ']') {
3333
+ pos = findMatchingOpen(text, pos, '[', ']')
3334
+ if (pos < 0) return 0
3335
+ pos--
3336
+ if (pos >= 0 && /[\w$]/.test(text[pos])) continue
3337
+ if (pos >= 0 && text[pos] === '.') {
3338
+ if (pos >= 1 && text[pos - 1] === '?') pos -= 2
3339
+ else pos--
3340
+ continue
3341
+ }
3342
+ return pos + 1
3343
+ } else {
3344
+ return pos + 1
3345
+ }
3346
+ }
3347
+
3348
+ return 0
3349
+ }
3350
+
3351
+ /** Find the matching opening bracket/paren scanning backward from `pos`. */
3352
+ function findMatchingOpen(
3353
+ text: string,
3354
+ pos: number,
3355
+ open: string,
3356
+ close: string
3357
+ ): number {
3358
+ let depth = 1
3359
+ pos--
3360
+ while (pos >= 0 && depth > 0) {
3361
+ if (text[pos] === close) depth++
3362
+ else if (text[pos] === open) depth--
3363
+ if (depth > 0) pos--
3364
+ }
3365
+ return pos
3366
+ }
@@ -13,12 +13,30 @@ describe('Transpiler', () => {
13
13
 
14
14
  it('should extract return type annotation', () => {
15
15
  const result = preprocess(
16
- `function foo(x: 'string') -> { result: 'string' } { }`
16
+ `function foo(x: 'string'): { result: 'string' } { }`
17
17
  )
18
18
  expect(result.returnType).toBe(`{ result: 'string' }`)
19
19
  expect(result.source).not.toContain('->')
20
20
  })
21
21
 
22
+ it('should extract colon-style return type annotation', () => {
23
+ const result = preprocess(
24
+ `function foo(x: 'string'): { result: 'string' } { }`
25
+ )
26
+ expect(result.returnType).toBe(`{ result: 'string' }`)
27
+ expect(result.source).not.toContain('): ')
28
+ })
29
+
30
+ it('should extract colon-style return type with safety markers', () => {
31
+ const safe = preprocess(`function foo(x: 0):? 0 { }`)
32
+ expect(safe.returnType).toBe('0')
33
+ expect(safe.returnSafety).toBe('safe')
34
+
35
+ const unsafe = preprocess(`function foo(x: 0):! 0 { }`)
36
+ expect(unsafe.returnType).toBe('0')
37
+ expect(unsafe.returnSafety).toBe('unsafe')
38
+ })
39
+
22
40
  it('should handle multiple parameters', () => {
23
41
  const result = preprocess(`function foo(a: 'string', b: 0, c = 10) { }`)
24
42
  expect(result.source).toContain(`a = 'string'`)
@@ -44,7 +62,7 @@ describe('Transpiler', () => {
44
62
 
45
63
  it('should handle rest param with array type example', () => {
46
64
  const result = preprocess(
47
- `function mean(...values: [1.0, 2.0, 3.0]) -> 2.0 { return 0 }`
65
+ `function mean(...values: [1.0, 2.0, 3.0]): 2.0 { return 0 }`
48
66
  )
49
67
  expect(result.source).toContain('...values)')
50
68
  expect(result.source).not.toContain('[1.0')
@@ -417,20 +435,20 @@ test 'always fails' { throw new Error('intentional') }
417
435
  })
418
436
 
419
437
  it('should generate correct runtime validation for integer', () => {
420
- const result = tjs(`function test(n: 1) -> 1 { return n }`)
438
+ const result = tjs(`function test(n: 1): 1 { return n }`)
421
439
  // Should check Number.isInteger
422
440
  expect(result.code).toContain('Number.isInteger')
423
441
  })
424
442
 
425
443
  it('should generate correct runtime validation for non-negative-integer', () => {
426
- const result = tjs(`function test(n: +1) -> 1 { return n }`)
444
+ const result = tjs(`function test(n: +1): 1 { return n }`)
427
445
  // Should check Number.isInteger AND >= 0
428
446
  expect(result.code).toContain('Number.isInteger')
429
447
  expect(result.code).toContain('< 0')
430
448
  })
431
449
 
432
450
  it('should validate integer at runtime', () => {
433
- const result = tjs(`function check(n: 1) -> 1 { return n }`)
451
+ const result = tjs(`function check(n: 1): 1 { return n }`)
434
452
  const savedTjs = globalThis.__tjs
435
453
  globalThis.__tjs = createRuntime()
436
454
  try {
@@ -446,7 +464,7 @@ test 'always fails' { throw new Error('intentional') }
446
464
  })
447
465
 
448
466
  it('should validate non-negative-integer at runtime', () => {
449
- const result = tjs(`function check(n: +1) -> 1 { return n }`)
467
+ const result = tjs(`function check(n: +1): 1 { return n }`)
450
468
  const savedTjs = globalThis.__tjs
451
469
  globalThis.__tjs = createRuntime()
452
470
  try {
@@ -466,7 +484,7 @@ test 'always fails' { throw new Error('intentional') }
466
484
  })
467
485
 
468
486
  it('should validate float (number) accepts all numbers at runtime', () => {
469
- const result = tjs(`function check(n: 0.0) -> 0.0 { return n }`)
487
+ const result = tjs(`function check(n: 0.0): 0.0 { return n }`)
470
488
  const savedTjs = globalThis.__tjs
471
489
  globalThis.__tjs = createRuntime()
472
490
  try {
@@ -695,7 +713,7 @@ test 'always fails' { throw new Error('intentional') }
695
713
 
696
714
  This demonstrates TJS doc comments.
697
715
  */
698
- function greet(name: 'World') -> '' {
716
+ function greet(name: 'World'): '' {
699
717
  return 'Hello, ' + name + '!'
700
718
  }
701
719
  `)
@@ -715,7 +733,7 @@ test 'always fails' { throw new Error('intentional') }
715
733
 
716
734
  \`code example\`
717
735
  */
718
- function test(x: 0) -> 0 {
736
+ function test(x: 0): 0 {
719
737
  return x
720
738
  }
721
739
  `)
@@ -730,7 +748,7 @@ test 'always fails' { throw new Error('intentional') }
730
748
  Indented content here.
731
749
  More indented content.
732
750
  */
733
- function test(x: 0) -> 0 {
751
+ function test(x: 0): 0 {
734
752
  return x
735
753
  }
736
754
  `)
@@ -748,7 +766,7 @@ test 'always fails' { throw new Error('intentional') }
748
766
  /*#
749
767
  TJS doc description
750
768
  */
751
- function test(x: 0) -> 0 {
769
+ function test(x: 0): 0 {
752
770
  return x
753
771
  }
754
772
  `)
@@ -768,7 +786,7 @@ test 'always fails' { throw new Error('intentional') }
768
786
  /*#
769
787
  Function-specific documentation.
770
788
  */
771
- function test(x: 0) -> 0 {
789
+ function test(x: 0): 0 {
772
790
  return x
773
791
  }
774
792
  `)
@@ -786,7 +804,7 @@ test 'always fails' { throw new Error('intentional') }
786
804
 
787
805
  const someVar = 123
788
806
 
789
- function test(x: 0) -> 0 {
807
+ function test(x: 0): 0 {
790
808
  return x
791
809
  }
792
810
  `)
@@ -810,6 +828,7 @@ test 'always fails' { throw new Error('intentional') }
810
828
  // Classes are not supported in AJS/VM - they're a TJS-only feature
811
829
  expect(() =>
812
830
  transpile(`
831
+ TjsCompat
813
832
  class Foo {}
814
833
  `)
815
834
  ).toThrow('Classes are not supported')
@@ -49,6 +49,7 @@ import {
49
49
  validateNoEval,
50
50
  validateNoVar,
51
51
  transformConstBang,
52
+ transformBangAccess,
52
53
  transformExtensionCalls,
53
54
  } from './parser-transforms'
54
55
 
@@ -80,19 +81,42 @@ export function preprocess(
80
81
  const unsafeFunctions = new Set<string>()
81
82
  const safeFunctions = new Set<string>()
82
83
 
83
- // TJS modes - all default to false (JS-compatible by default)
84
- const tjsModes: TjsModes = {
85
- tjsEquals: false,
86
- tjsClass: false,
87
- tjsDate: false,
88
- tjsNoeval: false,
89
- tjsStandard: false,
90
- tjsSafeEval: false,
91
- tjsNoVar: false,
84
+ // Detect whether this source was emitted by fromTS (TS-originated)
85
+ // The /* tjs <- filename */ annotation is the signal
86
+ const isFromTS = /\/\*\s*tjs\s*<-\s*\S+\s*\*\//.test(source)
87
+
88
+ // Native TJS: all modes ON by default (TJS is its own language)
89
+ // TS-originated or VM target (AJS): all modes OFF, safety none (JS-compatible)
90
+ const isCompat = isFromTS || options.vmTarget
91
+ const tjsModes: TjsModes = isCompat
92
+ ? {
93
+ tjsEquals: false,
94
+ tjsClass: false,
95
+ tjsDate: false,
96
+ tjsNoeval: false,
97
+ tjsStandard: false,
98
+ tjsSafeEval: false,
99
+ tjsNoVar: false,
100
+ }
101
+ : {
102
+ tjsEquals: true,
103
+ tjsClass: true,
104
+ tjsDate: true,
105
+ tjsNoeval: true,
106
+ tjsStandard: true,
107
+ tjsSafeEval: false, // opt-in only (adds import)
108
+ tjsNoVar: true,
109
+ }
110
+
111
+ // Safety: native TJS defaults to 'inputs' (runtime default),
112
+ // TS-originated and VM targets default to 'none'
113
+ if (isCompat) {
114
+ moduleSafety = 'none'
92
115
  }
93
116
 
94
117
  // Handle module-level safety directive: safety none | safety inputs | safety all
95
118
  // Must be at the start of the file (possibly after comments/whitespace)
119
+ // Explicit directive always overrides the default
96
120
  const safetyMatch = source.match(
97
121
  /^(\s*(?:\/\/[^\n]*\n|\/\*[\s\S]*?\*\/\s*)*)\s*safety\s+(none|inputs|all)\b/
98
122
  )
@@ -106,10 +130,11 @@ export function preprocess(
106
130
  }
107
131
 
108
132
  // Handle TJS mode directives (can appear in any order after safety)
109
- // TjsStrict enables all TJS modes
133
+ // TjsStrict enables all TJS modes (useful for TS-originated code opting in)
134
+ // TjsCompat disables all TJS modes (useful for native TJS opting out)
110
135
  // Individual modes: TjsEquals, TjsClass, TjsDate, TjsNoeval, TjsStandard, TjsSafeEval
111
136
  const directivePattern =
112
- /^(\s*(?:\/\/[^\n]*\n|\/\*[\s\S]*?\*\/\s*)*)\s*(TjsStrict|TjsEquals|TjsClass|TjsDate|TjsNoeval|TjsNoVar|TjsStandard|TjsSafeEval)\b/
137
+ /^(\s*(?:\/\/[^\n]*\n|\/\*[\s\S]*?\*\/\s*)*)\s*(TjsStrict|TjsCompat|TjsEquals|TjsClass|TjsDate|TjsNoeval|TjsNoVar|TjsStandard|TjsSafeEval)\b/
113
138
 
114
139
  let match
115
140
  while ((match = source.match(directivePattern))) {
@@ -123,6 +148,15 @@ export function preprocess(
123
148
  tjsModes.tjsNoeval = true
124
149
  tjsModes.tjsNoVar = true
125
150
  tjsModes.tjsStandard = true
151
+ } else if (directive === 'TjsCompat') {
152
+ // Disable all TJS modes (JS-compatible)
153
+ tjsModes.tjsEquals = false
154
+ tjsModes.tjsClass = false
155
+ tjsModes.tjsDate = false
156
+ tjsModes.tjsNoeval = false
157
+ tjsModes.tjsNoVar = false
158
+ tjsModes.tjsStandard = false
159
+ tjsModes.tjsSafeEval = false
126
160
  } else if (directive === 'TjsEquals') {
127
161
  tjsModes.tjsEquals = true
128
162
  } else if (directive === 'TjsClass') {
@@ -158,6 +192,10 @@ export function preprocess(
158
192
  // Must happen before acorn parsing since const! is not valid JS
159
193
  source = transformConstBang(source)
160
194
 
195
+ // Transform !. bang access to __tjs.bang() calls
196
+ // Must happen before acorn parsing since !. is not valid JS
197
+ source = transformBangAccess(source)
198
+
161
199
  // Transform Is/IsNot infix operators to function calls
162
200
  // a Is b -> Is(a, b)
163
201
  // a IsNot b -> IsNot(a, b)
@@ -38,7 +38,7 @@ describe('TJS Performance', () => {
38
38
  // Create a simple test file
39
39
  const testFile = '/tmp/perf-test.tjs'
40
40
  const { writeFileSync } = await import('fs')
41
- writeFileSync(testFile, `function add(a: 1, b: 2) -> 3 { return a + b }`)
41
+ writeFileSync(testFile, `function add(a: 1, b: 2): 3 { return a + b }`)
42
42
 
43
43
  const cliPath = path.join(import.meta.dir, '../cli/tjsx.ts')
44
44
 
@@ -120,7 +120,7 @@ describe('TJS Performance', () => {
120
120
 
121
121
  // TJS transpiled
122
122
  const tjsResult = tjs(`
123
- function tjsDouble(x: 0) -> 0 {
123
+ function tjsDouble(x: 0): 0 {
124
124
  return x * 2
125
125
  }
126
126
  `)
@@ -128,7 +128,7 @@ describe('TJS Performance', () => {
128
128
 
129
129
  // TJS with unsafe (!) - no validation wrapper
130
130
  const unsafeResult = tjs(`
131
- function unsafeDouble(! x: 0) -> 0 {
131
+ function unsafeDouble(! x: 0): 0 {
132
132
  return x * 2
133
133
  }
134
134
  `)
@@ -172,7 +172,7 @@ describe('TJS Performance', () => {
172
172
 
173
173
  // TJS transpiled
174
174
  const tjsResult = tjs(`
175
- function tjsTransform(x: 0, y: 0) -> { sum: 0, product: 0 } {
175
+ function tjsTransform(x: 0, y: 0): { sum: 0, product: 0 } {
176
176
  return { sum: x + y, product: x * y }
177
177
  }
178
178
  `)
@@ -182,7 +182,7 @@ describe('TJS Performance', () => {
182
182
 
183
183
  // TJS with unsafe (!) - no validation wrapper
184
184
  const unsafeResult = tjs(`
185
- function unsafeTransform(! x: 0, y: 0) -> { sum: 0, product: 0 } {
185
+ function unsafeTransform(! x: 0, y: 0): { sum: 0, product: 0 } {
186
186
  return { sum: x + y, product: x * y }
187
187
  }
188
188
  `)
@@ -225,7 +225,7 @@ describe('TJS Performance', () => {
225
225
 
226
226
  // TJS transpiled
227
227
  const tjsResult = tjs(`
228
- function tjsSum(arr: [0]) -> 0 {
228
+ function tjsSum(arr: [0]): 0 {
229
229
  let sum = 0
230
230
  for (const n of arr) sum += n
231
231
  return sum
@@ -235,7 +235,7 @@ describe('TJS Performance', () => {
235
235
 
236
236
  // TJS with unsafe (!) - no validation wrapper
237
237
  const unsafeResult = tjs(`
238
- function unsafeSum(! arr: [0]) -> 0 {
238
+ function unsafeSum(! arr: [0]): 0 {
239
239
  let sum = 0
240
240
  for (const n of arr) sum += n
241
241
  return sum
@@ -281,7 +281,7 @@ describe('TJS Performance', () => {
281
281
 
282
282
  // TJS - loop inside function body
283
283
  const tjsResult = tjs(`
284
- function tjsIntensive(n: 0) -> 0 {
284
+ function tjsIntensive(n: 0): 0 {
285
285
  let sum = 0
286
286
  for (let i = 0; i < n; i++) {
287
287
  sum += i * i
@@ -295,7 +295,7 @@ describe('TJS Performance', () => {
295
295
 
296
296
  // TJS unsafe (!) - no validation wrapper
297
297
  const unsafeResult = tjs(`
298
- function unsafeIntensive(! n: 0) -> 0 {
298
+ function unsafeIntensive(! n: 0): 0 {
299
299
  let sum = 0
300
300
  for (let i = 0; i < n; i++) {
301
301
  sum += i * i
@@ -338,7 +338,7 @@ describe('TJS Performance', () => {
338
338
  it('should measure error path overhead for try block', () => {
339
339
  // TJS try-without-catch converts exceptions to monadic errors
340
340
  const tryResult = tjs(`
341
- function tryThrow(x: 0) -> 0 {
341
+ function tryThrow(x: 0): 0 {
342
342
  try {
343
343
  if (x < 0) throw new Error('negative')
344
344
  return x
@@ -456,7 +456,7 @@ describe('TJS Performance', () => {
456
456
 
457
457
  // TJS unsafe (!) version - no wrapper at all
458
458
  const unsafeResult = tjs(`
459
- function unsafeAdd(! a: 0, b: 0) -> 0 {
459
+ function unsafeAdd(! a: 0, b: 0): 0 {
460
460
  return a + b
461
461
  }
462
462
  `)
@@ -47,7 +47,7 @@ function execCode(code: string): any[] {
47
47
  describe('TJS roundtrip - code should just work', () => {
48
48
  test('basic function with type annotations', () => {
49
49
  const source = `
50
- function add(a: 0, b: 0) -> 0 {
50
+ function add(a: 0, b: 0): 0 {
51
51
  return a + b
52
52
  }
53
53
  console.log(add(2, 3))
@@ -61,7 +61,7 @@ console.log(add(2, 3))
61
61
 
62
62
  test('template literals (backticks)', () => {
63
63
  const source = `
64
- function greet(name: 'World') -> '' {
64
+ function greet(name: 'World'): '' {
65
65
  return \`Hello, \${name}!\`
66
66
  }
67
67
  console.log(greet('TJS'))
@@ -75,7 +75,7 @@ console.log(greet('TJS'))
75
75
 
76
76
  test('inline tests execute at transpile time', () => {
77
77
  const source = `
78
- function double(x: 0) -> 0 {
78
+ function double(x: 0): 0 {
79
79
  return x * 2
80
80
  }
81
81