tjs-lang 0.2.8 → 0.3.0

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.
@@ -1353,6 +1353,17 @@ function getPlaceholderForParam(name: string, info: any): string {
1353
1353
  return String(ex)
1354
1354
  }
1355
1355
 
1356
+ // Then check for examples from schema metadata (use first as placeholder)
1357
+ const examples = info.type?.examples || info.examples
1358
+ if (Array.isArray(examples) && examples.length > 0) {
1359
+ const ex = examples[0]
1360
+ if (typeof ex === 'string') return `'${ex}'`
1361
+ if (typeof ex === 'number' || typeof ex === 'boolean') return String(ex)
1362
+ if (Array.isArray(ex)) return JSON.stringify(ex)
1363
+ if (typeof ex === 'object') return JSON.stringify(ex)
1364
+ return String(ex)
1365
+ }
1366
+
1356
1367
  // Then check for explicit default value
1357
1368
  if (info.default !== undefined && info.default !== null) {
1358
1369
  const def = info.default
@@ -1481,12 +1492,27 @@ function tjsCompletionSource(config: AutocompleteConfig = {}) {
1481
1492
  // Handle both { type: 'string' } and { kind: 'string' } formats
1482
1493
  const returnType =
1483
1494
  meta.returns?.type || meta.returns?.kind || 'void'
1495
+
1496
+ // Build info string with description and parameter examples
1497
+ let infoText = meta.description || ''
1498
+ for (const [pName, pInfo] of paramEntries as [string, any][]) {
1499
+ const pExamples = pInfo.type?.examples || pInfo.examples
1500
+ if (Array.isArray(pExamples) && pExamples.length > 0) {
1501
+ const formatted = pExamples
1502
+ .map((ex: any) =>
1503
+ typeof ex === 'string' ? `'${ex}'` : String(ex)
1504
+ )
1505
+ .join(', ')
1506
+ infoText += `${infoText ? '\n' : ''}${pName}: e.g. ${formatted}`
1507
+ }
1508
+ }
1509
+
1484
1510
  options.push(
1485
1511
  snippetCompletion(`${name}(${snippetParams})`, {
1486
1512
  label: name,
1487
1513
  type: 'function',
1488
1514
  detail: `(${paramList}) -> ${returnType}`,
1489
- info: meta.description,
1515
+ info: infoText || undefined,
1490
1516
  boost: 2, // Boost user-defined functions above globals
1491
1517
  })
1492
1518
  )
@@ -432,8 +432,8 @@ const after = 2
432
432
 
433
433
  expect(metadata).toBeDefined()
434
434
  // metadata is now keyed by function name
435
- expect(metadata?.add?.params?.a?.type?.kind).toBe('number')
436
- expect(metadata?.add?.params?.b?.type?.kind).toBe('number')
435
+ expect(metadata?.add?.params?.a?.type?.kind).toBe('integer')
436
+ expect(metadata?.add?.params?.b?.type?.kind).toBe('integer')
437
437
  })
438
438
 
439
439
  it('extracts example-based types', () => {
@@ -445,7 +445,7 @@ const after = 2
445
445
  expect(metadata).toBeDefined()
446
446
  // metadata is now keyed by function name
447
447
  expect(metadata?.greet?.params?.name?.type?.kind).toBe('string')
448
- expect(metadata?.greet?.params?.times?.type?.kind).toBe('number')
448
+ expect(metadata?.greet?.params?.times?.type?.kind).toBe('integer')
449
449
  expect(metadata?.greet?.params?.times?.default).toBe(1)
450
450
  })
451
451
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tjs-lang",
3
- "version": "0.2.8",
3
+ "version": "0.3.0",
4
4
  "description": "Type-safe JavaScript dialect with runtime validation, sandboxed VM execution, and AI agent orchestration. Transpiles TypeScript to validated JS with fuel-metered execution for untrusted code.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -54,7 +54,7 @@ describe('TS → TJS conversion quality', () => {
54
54
  const ts = `function sum(nums: number[]): number { return nums.reduce((a, b) => a + b, 0) }`
55
55
  const { code } = fromTS(ts, { emitTJS: true })
56
56
 
57
- expect(code).toContain('nums: [0]')
57
+ expect(code).toContain('nums: [0.0]')
58
58
  })
59
59
 
60
60
  it('converts object param correctly', () => {
@@ -62,7 +62,7 @@ describe('TS → TJS conversion quality', () => {
62
62
  const { code } = fromTS(ts, { emitTJS: true })
63
63
 
64
64
  expect(code).toContain("name: ''")
65
- expect(code).toContain('age: 0')
65
+ expect(code).toContain('age: 0.0')
66
66
  })
67
67
 
68
68
  it('handles multiple params in order', () => {
@@ -70,7 +70,7 @@ describe('TS → TJS conversion quality', () => {
70
70
  const { code } = fromTS(ts, { emitTJS: true })
71
71
 
72
72
  // Should have both params with colon syntax
73
- expect(code).toMatch(/add\(a: 0, b: 0\)/)
73
+ expect(code).toMatch(/add\(a: 0\.0, b: 0\.0\)/)
74
74
  })
75
75
 
76
76
  it('handles mixed required and optional params', () => {
@@ -228,7 +228,7 @@ class Calculator {
228
228
  `
229
229
  const { code } = fromTS(ts, { emitTJS: true })
230
230
 
231
- expect(code).toContain('add(a: 0, b: 0) -! 0')
231
+ expect(code).toContain('add(a: 0.0, b: 0.0) -! 0.0')
232
232
  })
233
233
 
234
234
  it('converts getters and setters', () => {
@@ -269,7 +269,7 @@ class MathUtils {
269
269
  `
270
270
  const { code } = fromTS(ts, { emitTJS: true })
271
271
 
272
- expect(code).toContain('static double(x: 0) -! 0')
272
+ expect(code).toContain('static double(x: 0.0) -! 0.0')
273
273
  })
274
274
 
275
275
  it('converts async methods', () => {
@@ -396,7 +396,7 @@ describe('TJS → JS transpilation quality', () => {
396
396
 
397
397
  expect(code).toContain('__tjs')
398
398
  // types is now keyed by function name
399
- expect(types?.double?.returns?.kind).toBe('number')
399
+ expect(types?.double?.returns?.kind).toBe('integer')
400
400
  })
401
401
 
402
402
  it('marks required params correctly', () => {
@@ -561,8 +561,8 @@ function greet(name: string): string {
561
561
  const { code } = fromTS(ts, { emitTJS: true })
562
562
 
563
563
  // All functions should be present (TS transpiler uses -! to skip signature tests)
564
- expect(code).toContain('function add(a: 0, b: 0) -! 0')
565
- expect(code).toContain('function multiply(a: 0, b: 0) -! 0')
564
+ expect(code).toContain('function add(a: 0.0, b: 0.0) -! 0.0')
565
+ expect(code).toContain('function multiply(a: 0.0, b: 0.0) -! 0.0')
566
566
  expect(code).toContain("function greet(name: '') -! ''")
567
567
 
568
568
  // Should be valid TJS (no TypeScript syntax remaining)
@@ -738,7 +738,7 @@ function greet(name: '', excited = false) -! '' {
738
738
  // add function
739
739
  expect(types?.add?.params?.a?.required).toBe(true)
740
740
  expect(types?.add?.params?.b?.required).toBe(true)
741
- expect(types?.add?.returns?.kind).toBe('number')
741
+ expect(types?.add?.returns?.kind).toBe('integer')
742
742
 
743
743
  // greet function
744
744
  expect(types?.greet?.params?.name?.required).toBe(true)
@@ -1146,7 +1146,7 @@ function processUser(user: { name: string; age: number }): string {
1146
1146
  // TS → TJS
1147
1147
  const { code: tjsCode } = fromTS(ts, { emitTJS: true })
1148
1148
  expect(tjsCode).toContain("name: ''")
1149
- expect(tjsCode).toContain('age: 0')
1149
+ expect(tjsCode).toContain('age: 0.0')
1150
1150
 
1151
1151
  // TJS → JS (already has -! from TS transpiler)
1152
1152
  const { code: jsCode, types } = tjs(tjsCode)
@@ -1386,7 +1386,7 @@ function getData(id: 0) -! { value: 0 } {
1386
1386
  expect(result.value).toBeUndefined()
1387
1387
 
1388
1388
  // But error properties are accessible
1389
- expect(result.message).toContain('Expected number')
1389
+ expect(result.message).toContain('Expected integer')
1390
1390
  })
1391
1391
  })
1392
1392
 
@@ -137,7 +137,7 @@ function typeToExample(
137
137
  case ts.SyntaxKind.StringKeyword:
138
138
  return "''"
139
139
  case ts.SyntaxKind.NumberKeyword:
140
- return '0'
140
+ return '0.0'
141
141
  case ts.SyntaxKind.BooleanKeyword:
142
142
  return 'true'
143
143
  case ts.SyntaxKind.NullKeyword:
@@ -868,6 +868,10 @@ function generateTypeCheckExpr(
868
868
  return `typeof ${fieldPath} !== 'string'`
869
869
  case 'number':
870
870
  return `typeof ${fieldPath} !== 'number'`
871
+ case 'integer':
872
+ return `(typeof ${fieldPath} !== 'number' || !Number.isInteger(${fieldPath}))`
873
+ case 'non-negative-integer':
874
+ return `(typeof ${fieldPath} !== 'number' || !Number.isInteger(${fieldPath}) || ${fieldPath} < 0)`
871
875
  case 'boolean':
872
876
  return `typeof ${fieldPath} !== 'boolean'`
873
877
  case 'null':
@@ -1493,6 +1497,41 @@ function runAllTests(
1493
1497
  if (!__deepEqual(actual, expected)) {
1494
1498
  throw new Error('Expected ' + __format(expected) + ' but got ' + __format(actual))
1495
1499
  }
1500
+ },
1501
+ toContain(item) {
1502
+ if (!Array.isArray(actual) || !actual.some(function(v) { return __deepEqual(v, item) })) {
1503
+ throw new Error('Expected ' + __format(actual) + ' to contain ' + __format(item))
1504
+ }
1505
+ },
1506
+ toBeTruthy() {
1507
+ if (!actual) {
1508
+ throw new Error('Expected ' + __format(actual) + ' to be truthy')
1509
+ }
1510
+ },
1511
+ toBeFalsy() {
1512
+ if (actual) {
1513
+ throw new Error('Expected ' + __format(actual) + ' to be falsy')
1514
+ }
1515
+ },
1516
+ toBeNull() {
1517
+ if (actual !== null) {
1518
+ throw new Error('Expected null but got ' + __format(actual))
1519
+ }
1520
+ },
1521
+ toBeUndefined() {
1522
+ if (actual !== undefined) {
1523
+ throw new Error('Expected undefined but got ' + __format(actual))
1524
+ }
1525
+ },
1526
+ toBeGreaterThan(n) {
1527
+ if (!(actual > n)) {
1528
+ throw new Error('Expected ' + __format(actual) + ' to be greater than ' + n)
1529
+ }
1530
+ },
1531
+ toBeLessThan(n) {
1532
+ if (!(actual < n)) {
1533
+ throw new Error('Expected ' + __format(actual) + ' to be less than ' + n)
1534
+ }
1496
1535
  }
1497
1536
  }
1498
1537
  }
@@ -1634,6 +1673,41 @@ function runTestBlocks(
1634
1673
  if (!__deepEqual(actual, expected)) {
1635
1674
  throw new Error('Expected ' + __format(expected) + ' but got ' + __format(actual))
1636
1675
  }
1676
+ },
1677
+ toContain(item) {
1678
+ if (!Array.isArray(actual) || !actual.some(function(v) { return __deepEqual(v, item) })) {
1679
+ throw new Error('Expected ' + __format(actual) + ' to contain ' + __format(item))
1680
+ }
1681
+ },
1682
+ toBeTruthy() {
1683
+ if (!actual) {
1684
+ throw new Error('Expected ' + __format(actual) + ' to be truthy')
1685
+ }
1686
+ },
1687
+ toBeFalsy() {
1688
+ if (actual) {
1689
+ throw new Error('Expected ' + __format(actual) + ' to be falsy')
1690
+ }
1691
+ },
1692
+ toBeNull() {
1693
+ if (actual !== null) {
1694
+ throw new Error('Expected null but got ' + __format(actual))
1695
+ }
1696
+ },
1697
+ toBeUndefined() {
1698
+ if (actual !== undefined) {
1699
+ throw new Error('Expected undefined but got ' + __format(actual))
1700
+ }
1701
+ },
1702
+ toBeGreaterThan(n) {
1703
+ if (!(actual > n)) {
1704
+ throw new Error('Expected ' + __format(actual) + ' to be greater than ' + n)
1705
+ }
1706
+ },
1707
+ toBeLessThan(n) {
1708
+ if (!(actual < n)) {
1709
+ throw new Error('Expected ' + __format(actual) + ' to be less than ' + n)
1710
+ }
1637
1711
  }
1638
1712
  }
1639
1713
  }
@@ -29,7 +29,13 @@ export function inferTypeFromValue(node: Expression): TypeDescriptor {
29
29
  return { kind: 'string' }
30
30
  }
31
31
  if (typeof value === 'number') {
32
- return { kind: 'number' }
32
+ // Distinguish float vs integer by checking if source contains '.'
33
+ // 2.0 -> number (float), 42 -> integer
34
+ const raw = (node as any).raw as string | undefined
35
+ if (raw && raw.includes('.')) {
36
+ return { kind: 'number' }
37
+ }
38
+ return { kind: 'integer' }
33
39
  }
34
40
  if (typeof value === 'boolean') {
35
41
  return { kind: 'boolean' }
@@ -110,14 +116,26 @@ export function inferTypeFromValue(node: Expression): TypeDescriptor {
110
116
  }
111
117
 
112
118
  case 'UnaryExpression': {
113
- // Handle negative numbers: -1
114
- if (
115
- (node as any).operator === '-' &&
116
- (node as any).argument.type === 'Literal'
117
- ) {
118
- const value = (node as any).argument.value
119
+ const op = (node as any).operator
120
+ const arg = (node as any).argument
121
+
122
+ // +N means non-negative integer (e.g., +1, +3)
123
+ if (op === '+' && arg.type === 'Literal') {
124
+ const value = arg.value
119
125
  if (typeof value === 'number') {
120
- return { kind: 'number' }
126
+ return { kind: 'non-negative-integer' }
127
+ }
128
+ }
129
+
130
+ // -N means integer or float depending on source
131
+ if (op === '-' && arg.type === 'Literal') {
132
+ const value = arg.value
133
+ if (typeof value === 'number') {
134
+ const raw = arg.raw as string | undefined
135
+ if (raw && raw.includes('.')) {
136
+ return { kind: 'number' }
137
+ }
138
+ return { kind: 'integer' }
121
139
  }
122
140
  }
123
141
  return { kind: 'any' }
@@ -258,6 +276,10 @@ export function extractLiteralValue(node: Expression): any {
258
276
  const arg = extractLiteralValue((node as any).argument)
259
277
  return typeof arg === 'number' ? -arg : undefined
260
278
  }
279
+ if ((node as any).operator === '+') {
280
+ const arg = extractLiteralValue((node as any).argument)
281
+ return typeof arg === 'number' ? +arg : undefined
282
+ }
261
283
  return undefined
262
284
 
263
285
  case 'LogicalExpression': {
@@ -310,6 +332,12 @@ export function typeToString(type: TypeDescriptor): string {
310
332
  return type.nullable ? 'string | null' : 'string'
311
333
  case 'number':
312
334
  return type.nullable ? 'number | null' : 'number'
335
+ case 'integer':
336
+ return type.nullable ? 'integer | null' : 'integer'
337
+ case 'non-negative-integer':
338
+ return type.nullable
339
+ ? 'non-negative integer | null'
340
+ : 'non-negative integer'
313
341
  case 'boolean':
314
342
  return type.nullable ? 'boolean | null' : 'boolean'
315
343
  case 'null':
@@ -354,6 +382,10 @@ export function checkType(value: any, type: TypeDescriptor): boolean {
354
382
  return typeof value === 'string'
355
383
  case 'number':
356
384
  return typeof value === 'number'
385
+ case 'integer':
386
+ return typeof value === 'number' && Number.isInteger(value)
387
+ case 'non-negative-integer':
388
+ return typeof value === 'number' && Number.isInteger(value) && value >= 0
357
389
  case 'boolean':
358
390
  return typeof value === 'boolean'
359
391
  case 'array':
@@ -15,6 +15,7 @@ import {
15
15
  wrap,
16
16
  } from './index'
17
17
  import { preprocess } from './parser'
18
+ import { createRuntime, isMonadicError } from './runtime'
18
19
  import { Schema } from './schema'
19
20
 
20
21
  describe('Transpiler', () => {
@@ -293,7 +294,7 @@ test 'always fails' { throw new Error('intentional') }
293
294
  return { count }
294
295
  }
295
296
  `)
296
- expect(signature.parameters.count.type.kind).toBe('number')
297
+ expect(signature.parameters.count.type.kind).toBe('integer')
297
298
  expect(signature.parameters.count.required).toBe(true)
298
299
  })
299
300
 
@@ -303,7 +304,7 @@ test 'always fails' { throw new Error('intentional') }
303
304
  return { limit }
304
305
  }
305
306
  `)
306
- expect(signature.parameters.limit.type.kind).toBe('number')
307
+ expect(signature.parameters.limit.type.kind).toBe('integer')
307
308
  expect(signature.parameters.limit.required).toBe(false)
308
309
  expect(signature.parameters.limit.default).toBe(10)
309
310
  })
@@ -336,7 +337,7 @@ test 'always fails' { throw new Error('intentional') }
336
337
  `)
337
338
  expect(signature.parameters.user.type.kind).toBe('object')
338
339
  expect(signature.parameters.user.type.shape?.name.kind).toBe('string')
339
- expect(signature.parameters.user.type.shape?.age.kind).toBe('number')
340
+ expect(signature.parameters.user.type.shape?.age.kind).toBe('integer')
340
341
  })
341
342
 
342
343
  it('should handle array types with colon syntax', () => {
@@ -357,13 +358,150 @@ test 'always fails' { throw new Error('intentional') }
357
358
  `)
358
359
  expect(signature.parameters.name.type.kind).toBe('string')
359
360
  expect(signature.parameters.name.required).toBe(true)
360
- expect(signature.parameters.count.type.kind).toBe('number')
361
+ expect(signature.parameters.count.type.kind).toBe('integer')
361
362
  expect(signature.parameters.count.required).toBe(true)
362
- expect(signature.parameters.limit.type.kind).toBe('number')
363
+ expect(signature.parameters.limit.type.kind).toBe('integer')
363
364
  expect(signature.parameters.limit.required).toBe(false)
364
365
  })
365
366
  })
366
367
 
368
+ describe('Numeric type narrowing', () => {
369
+ it('should infer integer from whole number literal', () => {
370
+ const { signature } = transpile(`
371
+ function test(count: 42) { return { count } }
372
+ `)
373
+ expect(signature.parameters.count.type.kind).toBe('integer')
374
+ })
375
+
376
+ it('should infer float (number) from decimal literal', () => {
377
+ const { signature } = transpile(`
378
+ function test(rate: 3.14) { return { rate } }
379
+ `)
380
+ expect(signature.parameters.rate.type.kind).toBe('number')
381
+ })
382
+
383
+ it('should infer float from 0.0', () => {
384
+ const { signature } = transpile(`
385
+ function test(value: 0.0) { return { value } }
386
+ `)
387
+ expect(signature.parameters.value.type.kind).toBe('number')
388
+ })
389
+
390
+ it('should infer non-negative-integer from +N syntax', () => {
391
+ const { signature } = transpile(`
392
+ function test(age: +20) { return { age } }
393
+ `)
394
+ expect(signature.parameters.age.type.kind).toBe('non-negative-integer')
395
+ })
396
+
397
+ it('should infer non-negative-integer from +0', () => {
398
+ const { signature } = transpile(`
399
+ function test(index: +0) { return { index } }
400
+ `)
401
+ expect(signature.parameters.index.type.kind).toBe('non-negative-integer')
402
+ })
403
+
404
+ it('should infer integer from negative literal', () => {
405
+ const { signature } = transpile(`
406
+ function test(offset: -5) { return { offset } }
407
+ `)
408
+ expect(signature.parameters.offset.type.kind).toBe('integer')
409
+ })
410
+
411
+ it('should infer number from negative decimal', () => {
412
+ const { signature } = transpile(`
413
+ function test(temp: -3.5) { return { temp } }
414
+ `)
415
+ expect(signature.parameters.temp.type.kind).toBe('number')
416
+ })
417
+
418
+ it('should generate correct runtime validation for integer', () => {
419
+ const result = tjs(`function test(n: 1) -> 0 { return n }`)
420
+ // Should check Number.isInteger
421
+ expect(result.code).toContain('Number.isInteger')
422
+ })
423
+
424
+ it('should generate correct runtime validation for non-negative-integer', () => {
425
+ const result = tjs(`function test(n: +1) -> 0 { return n }`)
426
+ // Should check Number.isInteger AND >= 0
427
+ expect(result.code).toContain('Number.isInteger')
428
+ expect(result.code).toContain('< 0')
429
+ })
430
+
431
+ it('should validate integer at runtime', () => {
432
+ const result = tjs(`function check(n: 1) -> 0 { return n }`)
433
+ const savedTjs = globalThis.__tjs
434
+ globalThis.__tjs = createRuntime()
435
+ try {
436
+ const fn = new Function(result.code + '\nreturn check')()
437
+ // Valid integer
438
+ expect(fn(42)).toBe(42)
439
+ // Float should fail
440
+ const bad = fn(3.14)
441
+ expect(isMonadicError(bad)).toBe(true)
442
+ } finally {
443
+ globalThis.__tjs = savedTjs
444
+ }
445
+ })
446
+
447
+ it('should validate non-negative-integer at runtime', () => {
448
+ const result = tjs(`function check(n: +1) -> 0 { return n }`)
449
+ const savedTjs = globalThis.__tjs
450
+ globalThis.__tjs = createRuntime()
451
+ try {
452
+ const fn = new Function(result.code + '\nreturn check')()
453
+ // Valid non-negative integer
454
+ expect(fn(0)).toBe(0)
455
+ expect(fn(42)).toBe(42)
456
+ // Negative integer should fail
457
+ const negResult = fn(-1)
458
+ expect(isMonadicError(negResult)).toBe(true)
459
+ // Float should fail
460
+ const floatResult = fn(3.14)
461
+ expect(isMonadicError(floatResult)).toBe(true)
462
+ } finally {
463
+ globalThis.__tjs = savedTjs
464
+ }
465
+ })
466
+
467
+ it('should validate float (number) accepts all numbers at runtime', () => {
468
+ const result = tjs(`function check(n: 0.0) -> 0.0 { return n }`)
469
+ const savedTjs = globalThis.__tjs
470
+ globalThis.__tjs = createRuntime()
471
+ try {
472
+ const fn = new Function(result.code + '\nreturn check')()
473
+ // All numbers should pass for float
474
+ expect(fn(42)).toBe(42)
475
+ expect(fn(3.14)).toBe(3.14)
476
+ expect(fn(-5)).toBe(-5)
477
+ expect(fn(0)).toBe(0)
478
+ } finally {
479
+ globalThis.__tjs = savedTjs
480
+ }
481
+ })
482
+
483
+ it('should handle numeric types in object shapes', () => {
484
+ const { signature } = transpile(`
485
+ function test(point: { x: 0.0, y: 0.0, index: 0 }) { return point }
486
+ `)
487
+ expect(signature.parameters.point.type.shape?.x.kind).toBe('number')
488
+ expect(signature.parameters.point.type.shape?.y.kind).toBe('number')
489
+ expect(signature.parameters.point.type.shape?.index.kind).toBe('integer')
490
+ })
491
+
492
+ it('should handle numeric types in array items', () => {
493
+ const { signature } = transpile(`
494
+ function test(counts: [0]) { return counts }
495
+ `)
496
+ expect(signature.parameters.counts.type.items?.kind).toBe('integer')
497
+
498
+ const { signature: sig2 } = transpile(`
499
+ function test(values: [0.0]) { return values }
500
+ `)
501
+ expect(sig2.parameters.values.type.items?.kind).toBe('number')
502
+ })
503
+ })
504
+
367
505
  describe('Basic transpilation', () => {
368
506
  it('should transpile a simple function', () => {
369
507
  const { ast } = transpile(`
@@ -934,7 +1072,7 @@ describe('TJS Emitter', () => {
934
1072
  `)
935
1073
  expect(result.code).not.toContain('->')
936
1074
  expect(result.types.add.returns).toBeDefined()
937
- expect(result.types.add.returns?.kind).toBe('number')
1075
+ expect(result.types.add.returns?.kind).toBe('integer')
938
1076
  })
939
1077
 
940
1078
  it('should mark parameters as required when using colon syntax', () => {
@@ -968,7 +1106,7 @@ describe('TJS Emitter', () => {
968
1106
  'string'
969
1107
  )
970
1108
  expect(result.types.process.params.user.type.shape?.age.kind).toBe(
971
- 'number'
1109
+ 'integer'
972
1110
  )
973
1111
  })
974
1112
 
@@ -979,7 +1117,7 @@ describe('TJS Emitter', () => {
979
1117
  }
980
1118
  `)
981
1119
  expect(result.types.sum.params.numbers.type.kind).toBe('array')
982
- expect(result.types.sum.params.numbers.type.items?.kind).toBe('number')
1120
+ expect(result.types.sum.params.numbers.type.items?.kind).toBe('integer')
983
1121
  })
984
1122
 
985
1123
  it('should generate __tjs metadata object', () => {
@@ -1052,7 +1190,7 @@ function greet(name: 'world') {
1052
1190
  }
1053
1191
  `
1054
1192
  expect(result.code).toContain('function double')
1055
- expect(result.types.double.params.n.type.kind).toBe('number')
1193
+ expect(result.types.double.params.n.type.kind).toBe('integer')
1056
1194
  })
1057
1195
 
1058
1196
  it('should handle interpolation in tagged template', () => {
@@ -1106,7 +1244,7 @@ function greet(name: 'world') {
1106
1244
  `)
1107
1245
  expect(result.types.test.returns).toBeDefined()
1108
1246
  expect(result.types.test.returns?.kind).toBe('object')
1109
- expect(result.types.test.returns?.shape?.result.kind).toBe('number')
1247
+ expect(result.types.test.returns?.shape?.result.kind).toBe('integer')
1110
1248
  })
1111
1249
  })
1112
1250
 
@@ -1155,9 +1293,9 @@ function greet(name: 'world') {
1155
1293
  return x
1156
1294
  }
1157
1295
  `)
1158
- expect(result.types.compute.params.x.type.kind).toBe('number')
1296
+ expect(result.types.compute.params.x.type.kind).toBe('integer')
1159
1297
  expect(result.types.compute.params.y.type.kind).toBe('string')
1160
- expect(result.types.compute.returns?.kind).toBe('number')
1298
+ expect(result.types.compute.returns?.kind).toBe('integer')
1161
1299
  })
1162
1300
 
1163
1301
  // === NEW TESTS: Multi-function and no-function support ===
@@ -1350,8 +1488,8 @@ function greet(name: 'world') {
1350
1488
  `,
1351
1489
  { runTests: false }
1352
1490
  )
1353
- // Should have inline validation
1354
- expect(result.code).toContain("if (typeof a !== 'number')")
1491
+ // Should have inline validation (integer check for integer examples)
1492
+ expect(result.code).toContain('Number.isInteger(a)')
1355
1493
  expect(result.code).toContain('__tjs.typeError')
1356
1494
  })
1357
1495
 
@@ -1482,7 +1620,7 @@ describe('TypeScript to TJS Transpiler', () => {
1482
1620
  `function sum(nums: number[]): number { return 0 }`,
1483
1621
  { emitTJS: true }
1484
1622
  )
1485
- expect(result.code).toContain('nums: [0]')
1623
+ expect(result.code).toContain('nums: [0.0]')
1486
1624
  })
1487
1625
 
1488
1626
  it('should handle object literal types', () => {
@@ -1490,7 +1628,7 @@ describe('TypeScript to TJS Transpiler', () => {
1490
1628
  `function getUser(): { name: string, age: number } { return { name: '', age: 0 } }`,
1491
1629
  { emitTJS: true }
1492
1630
  )
1493
- expect(result.code).toContain("-! { name: '', age: 0 }") // -! for TS-transpiled
1631
+ expect(result.code).toContain("-! { name: '', age: 0.0 }") // -! for TS-transpiled
1494
1632
  })
1495
1633
 
1496
1634
  it('should handle nullable types', () => {
@@ -563,6 +563,13 @@ export function checkType(
563
563
  if (expected === 'number' && actual === 'number') return null
564
564
  if (expected === 'integer' && actual === 'number' && Number.isInteger(value))
565
565
  return null
566
+ if (
567
+ expected === 'non-negative-integer' &&
568
+ actual === 'number' &&
569
+ Number.isInteger(value) &&
570
+ (value as number) >= 0
571
+ )
572
+ return null
566
573
 
567
574
  // Object matching (basic)
568
575
  if (expected === 'object' && actual === 'object') return null
package/src/lang/types.ts CHANGED
@@ -14,6 +14,8 @@ export interface TypeDescriptor {
14
14
  kind:
15
15
  | 'string'
16
16
  | 'number'
17
+ | 'integer'
18
+ | 'non-negative-integer'
17
19
  | 'boolean'
18
20
  | 'null'
19
21
  | 'undefined'