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/dist/index.js +60 -57
- package/dist/index.js.map +4 -4
- package/dist/tjs-full.js +60 -57
- package/dist/tjs-full.js.map +4 -4
- package/dist/tjs-vm.js +2 -2
- package/dist/tjs-vm.js.map +3 -3
- package/package.json +1 -1
- package/src/cli/tjs.ts +1 -1
- package/src/lang/emitters/from-ts.ts +109 -18
- package/src/lang/parser-transforms.ts +7 -2
- package/src/lang/typescript-syntax.test.ts +47 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tjs-lang",
|
|
3
|
-
"version": "0.6.
|
|
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
|
@@ -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
|
-
|
|
206
|
-
|
|
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
|
-
|
|
209
|
-
|
|
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
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
951
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|