tjs-lang 0.6.39 → 0.6.42

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tjs-lang",
3
- "version": "0.6.39",
3
+ "version": "0.6.42",
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",
package/src/cli/tjs.ts CHANGED
@@ -20,7 +20,7 @@ import { emit } from './commands/emit'
20
20
  import { convert } from './commands/convert'
21
21
  import { test } from './commands/test'
22
22
 
23
- const VERSION = '0.6.39'
23
+ const VERSION = '0.6.42'
24
24
 
25
25
  const HELP = `
26
26
  tjs - Typed JavaScript CLI
@@ -453,6 +453,14 @@ function detectTypeDeclarations(source: string): Map<string, string> {
453
453
  result.set(m[1], m[2].trim())
454
454
  }
455
455
 
456
+ // Empty block: Type Name {} (no example — degraded type, emit as any)
457
+ const emptyBlockRe = /^[ \t]*(?:export\s+)?Type\s+(\w+)\s*\{\s*\}/gm
458
+ while ((m = emptyBlockRe.exec(source)) !== null) {
459
+ if (!result.has(m[1])) {
460
+ result.set(m[1], '') // empty string signals "any"
461
+ }
462
+ }
463
+
456
464
  return result
457
465
  }
458
466
 
@@ -505,6 +513,57 @@ function detectGenerics(source: string): Map<string, GenericInfo> {
505
513
  return result
506
514
  }
507
515
 
516
+ /** Info about a const/let/var declaration */
517
+ interface VarDeclInfo {
518
+ name: string
519
+ value: string
520
+ kind: 'const' | 'let' | 'var'
521
+ }
522
+
523
+ /** Detect exported const/let/var declarations and their initializer values */
524
+ function detectVarDeclarations(source: string): VarDeclInfo[] {
525
+ const result: VarDeclInfo[] = []
526
+ const re =
527
+ /^[ \t]*export\s+(?:default\s+)?(const|let|var)\s+(\w+)\s*(?::\s*\w+\s*)?=\s*(.+)/gm
528
+ let m
529
+ while ((m = re.exec(source)) !== null) {
530
+ // Get the value — may span multiple lines for objects/arrays
531
+ let value = m[3].trim()
532
+ // Strip trailing semicolons
533
+ if (value.endsWith(';')) value = value.slice(0, -1).trim()
534
+ result.push({ name: m[2], value, kind: m[1] as 'const' | 'let' | 'var' })
535
+ }
536
+ return result
537
+ }
538
+
539
+ /** Infer a TS type from a const initializer value */
540
+ function inferConstType(value: string): string {
541
+ // String literal
542
+ if (/^['"]/.test(value)) return 'string'
543
+ // Template literal
544
+ if (value.startsWith('`')) return 'string'
545
+ // Boolean
546
+ if (value === 'true' || value === 'false') return 'boolean'
547
+ // Number
548
+ if (/^[+-]?\d+(\.\d+)?$/.test(value)) return 'number'
549
+ // Symbol
550
+ if (value.startsWith('Symbol(') || value.startsWith('Symbol.'))
551
+ return 'symbol'
552
+ // Array
553
+ if (value.startsWith('[')) return 'any[]'
554
+ // new Map/Set/WeakMap etc.
555
+ if (value.startsWith('new WeakMap')) return 'WeakMap<any, any>'
556
+ if (value.startsWith('new Map')) return 'Map<any, any>'
557
+ if (value.startsWith('new Set')) return 'Set<any>'
558
+ if (value.startsWith('new ')) return 'any'
559
+ // Object/Record
560
+ if (value.startsWith('{')) return 'Record<string, any>'
561
+ // null/undefined
562
+ if (value === 'null') return 'null'
563
+ if (value === 'undefined') return 'undefined'
564
+ return 'any'
565
+ }
566
+
508
567
  /**
509
568
  * Generate a .d.ts string from TJS transpilation output.
510
569
  *
@@ -594,14 +653,19 @@ export function generateDTS(
594
653
  const isExported = hasAnyExport ? !!exportInfo?.exported : true
595
654
  if (!isExported) continue
596
655
 
597
- const tsType = inferTSTypeFromExample(exampleStr)
598
- lines.push(
599
- `export declare const ${name}: {` +
600
- ` check(value: any): boolean;` +
601
- ` default: ${tsType};` +
602
- ` (value: any): boolean;` +
603
- ` };`
604
- )
656
+ if (exampleStr === '') {
657
+ // Empty Type {} — degraded from TS type alias, emit as type = any
658
+ lines.push(`export type ${name} = any;`)
659
+ } else {
660
+ const tsType = inferTSTypeFromExample(exampleStr)
661
+ lines.push(
662
+ `export declare const ${name}: {` +
663
+ ` check(value: any): boolean;` +
664
+ ` default: ${tsType};` +
665
+ ` (value: any): boolean;` +
666
+ ` };`
667
+ )
668
+ }
605
669
  emitted.add(name)
606
670
  }
607
671
 
@@ -619,14 +683,23 @@ export function generateDTS(
619
683
  info.typeParams.length > 0 ? `<${info.typeParams.join(', ')}>` : ''
620
684
 
621
685
  if (info.declaration) {
622
- // Emit a proper TypeScript interface from the declaration block
623
- const declLines = info.declaration
624
- .split('\n')
625
- .map((l) => l.trim())
626
- .filter((l) => l.length > 0)
627
- .map((l) => ` ${l}`)
628
- .join('\n')
629
- lines.push(`export interface ${name}${typeParamStr} {\n${declLines}\n}`)
686
+ const declContent = info.declaration.trim()
687
+
688
+ // Check if this is a verbatim TS type (conditional, mapped, etc.)
689
+ // These start with "// TS:" and should be emitted as `export type`
690
+ const tsMatch = declContent.match(/^\/\/\s*TS:\s*(.+)$/s)
691
+ if (tsMatch) {
692
+ lines.push(`export type ${name}${typeParamStr} = ${tsMatch[1].trim()};`)
693
+ } else {
694
+ // Structured declaration — emit as interface
695
+ const declLines = declContent
696
+ .split('\n')
697
+ .map((l) => l.trim())
698
+ .filter((l) => l.length > 0)
699
+ .map((l) => ` ${l}`)
700
+ .join('\n')
701
+ lines.push(`export interface ${name}${typeParamStr} {\n${declLines}\n}`)
702
+ }
630
703
  } else {
631
704
  // No declaration block — emit any-based factory stub
632
705
  lines.push(
@@ -659,15 +732,31 @@ export function generateDTS(
659
732
 
660
733
  const tsParams = fpInfo.params
661
734
  .map((p) => {
662
- const tsType = tpNames.has(p.example)
663
- ? p.example
664
- : inferTSTypeFromExample(p.example)
735
+ // Array example [X] → rest param ...name: X[]
736
+ if (p.example.startsWith('[') && p.example.endsWith(']')) {
737
+ const inner = p.example.slice(1, -1).trim()
738
+ const innerType = inner
739
+ ? tpNames.has(inner)
740
+ ? inner
741
+ : inferTSTypeFromExample(inner)
742
+ : 'any'
743
+ return `...${p.name}: ${innerType}[]`
744
+ }
745
+ // In FunctionPredicate params, null means "any" (not literal null)
746
+ const tsType =
747
+ p.example === 'null'
748
+ ? 'any'
749
+ : tpNames.has(p.example)
750
+ ? p.example
751
+ : inferTSTypeFromExample(p.example)
665
752
  return `${p.name}: ${tsType}`
666
753
  })
667
754
  .join(', ')
668
755
  const tsReturn =
669
756
  fpInfo.returns !== undefined
670
- ? tpNames.has(fpInfo.returns)
757
+ ? fpInfo.returns === 'null'
758
+ ? 'any'
759
+ : tpNames.has(fpInfo.returns)
671
760
  ? fpInfo.returns
672
761
  : inferTSTypeFromExample(fpInfo.returns)
673
762
  : 'void'
@@ -677,6 +766,20 @@ export function generateDTS(
677
766
  emitted.add(name)
678
767
  }
679
768
 
769
+ // Emit exported const/let/var declarations
770
+ const varDecls = detectVarDeclarations(source)
771
+ for (const decl of varDecls) {
772
+ if (emitted.has(decl.name)) continue
773
+
774
+ const exportInfo = exports.get(decl.name)
775
+ const isExported = hasAnyExport ? !!exportInfo?.exported : true
776
+ if (!isExported) continue
777
+
778
+ const tsType = inferConstType(decl.value)
779
+ lines.push(`export declare const ${decl.name}: ${tsType};`)
780
+ emitted.add(decl.name)
781
+ }
782
+
680
783
  if (options.moduleName) {
681
784
  const indented = lines.map((l) => ` ${l}`).join('\n')
682
785
  return `declare module '${options.moduleName}' {\n${indented}\n}\n`
@@ -1065,8 +1065,28 @@ function transformGenericInterfaceToGeneric(
1065
1065
  }
1066
1066
 
1067
1067
  const parts = [`description: '${typeName}'`, predicateLine]
1068
+
1068
1069
  if (declarationAnnotation?.text) {
1069
1070
  parts.push(`declaration ${declarationAnnotation.text}`)
1071
+ } else {
1072
+ // Auto-generate declaration block from interface members
1073
+ const declMembers: string[] = []
1074
+ for (const member of node.members) {
1075
+ if (ts.isPropertySignature(member) && member.name) {
1076
+ const propName = member.name.getText(sourceFile)
1077
+ const optional = member.questionToken ? '?' : ''
1078
+ const typeText = member.type ? member.type.getText(sourceFile) : 'any'
1079
+ declMembers.push(`${propName}${optional}: ${typeText}`)
1080
+ } else if (ts.isMethodSignature(member) && member.name) {
1081
+ // Method: name(params): returnType
1082
+ const methodText = member.getText(sourceFile).trim()
1083
+ // Remove trailing semicolon if present
1084
+ declMembers.push(methodText.replace(/;$/, ''))
1085
+ }
1086
+ }
1087
+ if (declMembers.length > 0) {
1088
+ parts.push(`declaration {\n ${declMembers.join('\n ')}\n }`)
1089
+ }
1070
1090
  }
1071
1091
 
1072
1092
  return `Generic ${typeName}<${typeParams.join(', ')}> {\n ${parts.join(
@@ -1366,18 +1386,41 @@ function transformGenericTypeAliasToGeneric(
1366
1386
  predicateLine = `predicate(${predicateParams}) { return true }`
1367
1387
  }
1368
1388
 
1369
- // Include original TS source as a block comment for manual enhancement
1370
- const originalSource = node.getText(sourceFile).trim()
1371
- const comment = `/* Original TS:\n${originalSource}\n*/`
1372
-
1373
1389
  const parts = [`description: '${typeName}'`, predicateLine]
1390
+
1374
1391
  if (declarationAnnotation?.text) {
1375
1392
  parts.push(`declaration ${declarationAnnotation.text}`)
1393
+ } else {
1394
+ // Auto-generate declaration block from the type body
1395
+ const typeBody = node.type
1396
+
1397
+ if (typeBody && ts.isTypeLiteralNode(typeBody)) {
1398
+ // Object type literal: { item: T; count: number }
1399
+ const declMembers: string[] = []
1400
+ for (const member of typeBody.members) {
1401
+ if (ts.isPropertySignature(member) && member.name) {
1402
+ const propName = member.name.getText(sourceFile)
1403
+ const optional = member.questionToken ? '?' : ''
1404
+ const typeText = member.type ? member.type.getText(sourceFile) : 'any'
1405
+ declMembers.push(`${propName}${optional}: ${typeText}`)
1406
+ } else if (ts.isMethodSignature(member) && member.name) {
1407
+ declMembers.push(member.getText(sourceFile).trim().replace(/;$/, ''))
1408
+ }
1409
+ }
1410
+ if (declMembers.length > 0) {
1411
+ parts.push(`declaration {\n ${declMembers.join('\n ')}\n }`)
1412
+ }
1413
+ } else if (typeBody) {
1414
+ // Complex type (conditional, mapped, intersection, etc.)
1415
+ // Pass through the TS type body verbatim
1416
+ const typeText = typeBody.getText(sourceFile).trim()
1417
+ parts.push(`declaration {\n // TS: ${typeText}\n }`)
1418
+ }
1376
1419
  }
1377
1420
 
1378
- return `${comment}\nGeneric ${typeName}<${typeParams.join(
1379
- ', '
1380
- )}> {\n ${parts.join('\n ')}\n}`
1421
+ return `Generic ${typeName}<${typeParams.join(', ')}> {\n ${parts.join(
1422
+ '\n '
1423
+ )}\n}`
1381
1424
  }
1382
1425
 
1383
1426
  function transformFunctionToTJS(