tjs-lang 0.6.12 → 0.6.13

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.12",
3
+ "version": "0.6.13",
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.12'
23
+ const VERSION = '0.6.13'
24
24
 
25
25
  const HELP = `
26
26
  tjs - Typed JavaScript CLI
@@ -157,6 +157,12 @@ function typeToExample(
157
157
  return 'any'
158
158
  case ts.SyntaxKind.NeverKeyword:
159
159
  return 'null'
160
+ case ts.SyntaxKind.SymbolKeyword:
161
+ return "Symbol('example')"
162
+ case ts.SyntaxKind.BigIntKeyword:
163
+ return '0n'
164
+ case ts.SyntaxKind.ObjectKeyword:
165
+ return '{}'
160
166
 
161
167
  case ts.SyntaxKind.ArrayType: {
162
168
  const arrayType = type as ts.ArrayTypeNode
@@ -202,11 +208,58 @@ function typeToExample(
202
208
  if (typeName === 'Record') {
203
209
  return '{}'
204
210
  }
205
- if (typeName === 'Map') {
206
- return 'new Map()'
211
+
212
+ // Built-in constructible types — valid JS expressions as examples
213
+ const builtinExamples: Record<string, string> = {
214
+ // Collections
215
+ Map: 'new Map()',
216
+ Set: 'new Set()',
217
+ WeakMap: 'new WeakMap()',
218
+ WeakSet: 'new WeakSet()',
219
+ WeakRef: 'new WeakRef({})',
220
+ // Errors
221
+ Error: "new Error('example')",
222
+ TypeError: "new TypeError('example')",
223
+ RangeError: "new RangeError('example')",
224
+ // Date/Regex
225
+ Date: 'new Date()',
226
+ RegExp: '/example/',
227
+ // Binary / WASM
228
+ ArrayBuffer: 'new ArrayBuffer(0)',
229
+ SharedArrayBuffer: 'new SharedArrayBuffer(0)',
230
+ DataView: 'new DataView(new ArrayBuffer(0))',
231
+ Float32Array: 'new Float32Array(0)',
232
+ Float64Array: 'new Float64Array(0)',
233
+ Int8Array: 'new Int8Array(0)',
234
+ Int16Array: 'new Int16Array(0)',
235
+ Int32Array: 'new Int32Array(0)',
236
+ Uint8Array: 'new Uint8Array(0)',
237
+ Uint16Array: 'new Uint16Array(0)',
238
+ Uint32Array: 'new Uint32Array(0)',
239
+ Uint8ClampedArray: 'new Uint8ClampedArray(0)',
240
+ BigInt64Array: 'new BigInt64Array(0)',
241
+ BigUint64Array: 'new BigUint64Array(0)',
242
+ // Web/DOM
243
+ URL: "new URL('https://example.com')",
244
+ URLSearchParams: 'new URLSearchParams()',
245
+ Headers: 'new Headers()',
246
+ FormData: 'new FormData()',
247
+ Blob: 'new Blob()',
248
+ File: "new File([], 'example')",
249
+ Response: 'new Response()',
250
+ Request: "new Request('https://example.com')",
251
+ AbortController: 'new AbortController()',
252
+ // Streams
253
+ ReadableStream: 'new ReadableStream()',
254
+ WritableStream: 'new WritableStream()',
255
+ TransformStream: 'new TransformStream()',
256
+ // Structured data
257
+ TextEncoder: 'new TextEncoder()',
258
+ TextDecoder: 'new TextDecoder()',
207
259
  }
208
- if (typeName === 'Set') {
209
- return 'new Set()'
260
+
261
+ if (typeName in builtinExamples) {
262
+ return builtinExamples[typeName]
210
263
  }
211
264
 
212
265
  // Resolve type aliases
@@ -320,6 +373,12 @@ function typeToExample(
320
373
  const hasNull = unionType.types.some(isNullType)
321
374
  const hasUndefined = unionType.types.some(isUndefinedType)
322
375
 
376
+ // All null/undefined — just return the simplest form
377
+ if (nonNullTypes.length === 0) {
378
+ if (hasNull) return 'null'
379
+ return 'undefined'
380
+ }
381
+
323
382
  if (nonNullTypes.length === 1 && (hasNull || hasUndefined)) {
324
383
  // Nullable type: T | null -> T | null
325
384
  const baseExample = typeToExample(nonNullTypes[0], checker)
@@ -327,10 +386,12 @@ function typeToExample(
327
386
  if (hasUndefined) return `${baseExample} | undefined`
328
387
  }
329
388
 
330
- // General union: use first type as example
331
- if (unionType.types.length > 0) {
332
- return typeToExample(unionType.types[0], checker)
333
- }
389
+ // General union: deduplicate and use first non-any type
390
+ const examples = unionType.types
391
+ .map((t) => typeToExample(t, checker))
392
+ .filter((e, i, arr) => arr.indexOf(e) === i) // deduplicate
393
+ if (examples.length === 1) return examples[0]
394
+ if (examples.length > 0) return examples[0]
334
395
  return 'undefined'
335
396
  }
336
397
 
@@ -366,10 +427,11 @@ function typeToExample(
366
427
  case ts.SyntaxKind.TupleType: {
367
428
  const tupleType = type as ts.TupleTypeNode
368
429
  const elements = tupleType.elements.map((e) => {
369
- if (ts.isNamedTupleMember(e)) {
370
- return typeToExample(e.type, checker)
371
- }
372
- return typeToExample(e as ts.TypeNode, checker)
430
+ const example = ts.isNamedTupleMember(e)
431
+ ? typeToExample(e.type, checker)
432
+ : typeToExample(e as ts.TypeNode, checker)
433
+ // 'any' is not a valid literal value
434
+ return example === 'any' ? 'null' : example
373
435
  })
374
436
  return `[${elements.join(', ')}]`
375
437
  }
@@ -746,13 +808,26 @@ function transformGenericInterfaceToGeneric(
746
808
  for (const member of node.members) {
747
809
  if (ts.isPropertySignature(member) && member.name) {
748
810
  const propName = member.name.getText(sourceFile)
749
- checks.push(`'${propName}' in x`)
811
+
812
+ // Computed property names: [SYMBOL] → use bracket access
813
+ const isComputed = propName.startsWith('[') && propName.endsWith(']')
814
+ const symbolName = isComputed ? propName.slice(1, -1) : null
815
+
816
+ if (isComputed) {
817
+ checks.push(`${symbolName} in x`)
818
+ } else {
819
+ checks.push(`'${propName}' in x`)
820
+ }
750
821
 
751
822
  // If property type is a type parameter, add check
752
823
  if (member.type && ts.isTypeReferenceNode(member.type)) {
753
824
  const refName = member.type.typeName.getText(sourceFile)
754
825
  if (typeParamNames.includes(refName)) {
755
- checks.push(`${refName}(x.${propName})`)
826
+ if (isComputed) {
827
+ checks.push(`${refName}(x[${symbolName}])`)
828
+ } else {
829
+ checks.push(`${refName}(x.${propName})`)
830
+ }
756
831
  }
757
832
  }
758
833
  }
@@ -905,6 +980,11 @@ function transformTypeAliasToType(
905
980
 
906
981
  const example = typeToExample(node.type, undefined, warnings)
907
982
 
983
+ // 'any' and 'undefined' — skip declaration (undeclared = any in TJS)
984
+ if (example === 'any' || example === 'undefined') {
985
+ return `Type ${typeName} {}`
986
+ }
987
+
908
988
  // For simple primitive types, use short form
909
989
  if (
910
990
  example === "''" ||
@@ -947,8 +1027,13 @@ function transformGenericTypeAliasToGeneric(
947
1027
  )
948
1028
  const predicateParams = ['x', ...typeParamNames].join(', ')
949
1029
 
950
- // Simple fallback - more sophisticated analysis could be added
951
- return `Generic ${typeName}<${typeParams.join(', ')}> {
1030
+ // Include original TS source as a block comment for manual enhancement
1031
+ // Use /* */ to avoid confusing the Generic block preprocessor
1032
+ const originalSource = node.getText(sourceFile).trim()
1033
+ const comment = `/* Original TS:\n${originalSource}\n*/`
1034
+
1035
+ return `${comment}
1036
+ Generic ${typeName}<${typeParams.join(', ')}> {
952
1037
  description: '${typeName}'
953
1038
  predicate(${predicateParams}) { return true }
954
1039
  }`
@@ -1966,7 +2051,10 @@ export function fromTS(
1966
2051
  warnings
1967
2052
  )
1968
2053
  if (typeDecl) {
1969
- tjsFunctions.push(typeDecl)
2054
+ const isExported = statement.modifiers?.some(
2055
+ (m) => m.kind === ts.SyntaxKind.ExportKeyword
2056
+ )
2057
+ tjsFunctions.push(isExported ? `export ${typeDecl}` : typeDecl)
1970
2058
  }
1971
2059
  }
1972
2060
  }
@@ -1985,7 +2073,10 @@ export function fromTS(
1985
2073
  warnings
1986
2074
  )
1987
2075
  if (typeDecl) {
1988
- tjsFunctions.push(typeDecl)
2076
+ const isExported = statement.modifiers?.some(
2077
+ (m) => m.kind === ts.SyntaxKind.ExportKeyword
2078
+ )
2079
+ tjsFunctions.push(isExported ? `export ${typeDecl}` : typeDecl)
1989
2080
  }
1990
2081
  }
1991
2082
  }
@@ -1024,7 +1024,9 @@ export function transformTypeDeclarations(source: string): string {
1024
1024
 
1025
1025
  while (i < source.length) {
1026
1026
  // Look for 'Type' keyword followed by identifier
1027
- const typeMatch = source.slice(i).match(/^\bType\s+([A-Z][a-zA-Z0-9_]*)\s*/)
1027
+ const typeMatch = source
1028
+ .slice(i)
1029
+ .match(/^\bType\s+([A-Z_][a-zA-Z0-9_]*)\s*/)
1028
1030
  if (typeMatch) {
1029
1031
  const typeName = typeMatch[1]
1030
1032
  let j = i + typeMatch[0].length
@@ -1239,7 +1241,10 @@ export function transformGenericDeclarations(source: string): string {
1239
1241
  .split('=')
1240
1242
  .map((s) => s.trim())
1241
1243
  if (parts.length === 2) {
1242
- return `['${parts[0]}', ${parts[1]}]`
1244
+ // 'any' and 'undefined' aren't valid JS values — use null
1245
+ const defaultVal =
1246
+ parts[1] === 'any' || parts[1] === 'undefined' ? 'null' : parts[1]
1247
+ return `['${parts[0]}', ${defaultVal}]`
1243
1248
  }
1244
1249
  return `'${parts[0]}'`
1245
1250
  })
@@ -1524,6 +1524,53 @@ describe('fromTS — tosijs conversion edge cases', () => {
1524
1524
  expect(result.code).toContain('{ value:')
1525
1525
  })
1526
1526
 
1527
+ test('symbol type produces Symbol example', () => {
1528
+ const result = fromTS(`function f(x: symbol): void {}`, { emitTJS: true })
1529
+ expect(result.code).toContain("Symbol('example')")
1530
+ })
1531
+
1532
+ test('bigint type produces 0n example', () => {
1533
+ const result = fromTS(`function f(x: bigint): void {}`, { emitTJS: true })
1534
+ expect(result.code).toContain('0n')
1535
+ })
1536
+
1537
+ test('built-in reference types produce valid JS examples', () => {
1538
+ const cases: [string, string][] = [
1539
+ ['RegExp', '/example/'],
1540
+ ['Date', 'new Date()'],
1541
+ ['Map<string, number>', 'new Map()'],
1542
+ ['Set<number>', 'new Set()'],
1543
+ ['WeakMap<object, number>', 'new WeakMap()'],
1544
+ ['Float32Array', 'new Float32Array(0)'],
1545
+ ['Uint8Array', 'new Uint8Array(0)'],
1546
+ ['ArrayBuffer', 'new ArrayBuffer(0)'],
1547
+ ['URL', "new URL('https://example.com')"],
1548
+ ['AbortController', 'new AbortController()'],
1549
+ ['ReadableStream', 'new ReadableStream()'],
1550
+ ['TextEncoder', 'new TextEncoder()'],
1551
+ ['Error', "new Error('example')"],
1552
+ ]
1553
+ for (const [tsType, expected] of cases) {
1554
+ const result = fromTS(`function f(x: ${tsType}): void {}`, {
1555
+ emitTJS: true,
1556
+ })
1557
+ expect(result.code).toContain(expected)
1558
+ }
1559
+ })
1560
+
1561
+ test('export keyword preserved on Type declarations', () => {
1562
+ const result = fromTS(
1563
+ `export interface User { name: string; age: number }`,
1564
+ { emitTJS: true }
1565
+ )
1566
+ expect(result.code).toContain('export Type User')
1567
+ })
1568
+
1569
+ test('export keyword preserved on type alias declarations', () => {
1570
+ const result = fromTS(`export type Name = string`, { emitTJS: true })
1571
+ expect(result.code).toContain('export Type Name')
1572
+ })
1573
+
1527
1574
  test('non-exported functions have no export keyword', () => {
1528
1575
  const result = fromTS(`function internal(x: number): number { return x }`, {
1529
1576
  emitTJS: true,