tjs-lang 0.6.38 → 0.6.40
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 +122 -116
- package/dist/index.js.map +4 -4
- package/dist/tjs-full.js +122 -116
- package/dist/tjs-full.js.map +4 -4
- package/dist/tjs-vm.js +46 -45
- 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 +50 -7
- package/src/lang/parser-params.ts +29 -1
- package/src/use-cases/tosijs-convert-issues.test.ts +40 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tjs-lang",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.40",
|
|
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
|
@@ -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
|
|
1379
|
-
'
|
|
1380
|
-
)}
|
|
1421
|
+
return `Generic ${typeName}<${typeParams.join(', ')}> {\n ${parts.join(
|
|
1422
|
+
'\n '
|
|
1423
|
+
)}\n}`
|
|
1381
1424
|
}
|
|
1382
1425
|
|
|
1383
1426
|
function transformFunctionToTJS(
|
|
@@ -343,10 +343,38 @@ export function transformParenExpressions(
|
|
|
343
343
|
// Look for class method syntax: constructor(, methodName(, get name(, set name(
|
|
344
344
|
// These appear inside class bodies and need param transformation
|
|
345
345
|
// Only match if we're actually in a class body (proper context tracking)
|
|
346
|
+
// Must NOT match function calls in expressions (div(), span(), etc.)
|
|
346
347
|
const methodMatch = source
|
|
347
348
|
.slice(i)
|
|
348
349
|
.match(/^(constructor|(?:get|set)\s+\w+|async\s+\w+|\w+)\s*\(/)
|
|
349
|
-
|
|
350
|
+
// Check that the preceding non-whitespace character indicates this is a
|
|
351
|
+
// declaration, not a function call in an expression.
|
|
352
|
+
// Method declarations follow: newline, {, ;, or start of file
|
|
353
|
+
// Function calls follow: = => , [ ( . operators etc.
|
|
354
|
+
const prevNonWs = (() => {
|
|
355
|
+
for (let k = result.length - 1; k >= 0; k--) {
|
|
356
|
+
if (!/\s/.test(result[k])) return result[k]
|
|
357
|
+
}
|
|
358
|
+
return '\n' // start of input
|
|
359
|
+
})()
|
|
360
|
+
// Method declarations can follow almost anything (property, }, ;, etc.)
|
|
361
|
+
// Function CALLS in expressions specifically follow: = => , [ (
|
|
362
|
+
const isMethodDecl =
|
|
363
|
+
prevNonWs !== '=' &&
|
|
364
|
+
prevNonWs !== ',' &&
|
|
365
|
+
prevNonWs !== '(' &&
|
|
366
|
+
prevNonWs !== '[' &&
|
|
367
|
+
prevNonWs !== '>' // catches =>
|
|
368
|
+
if (methodMatch && isInClassBody() && !isMethodDecl) {
|
|
369
|
+
// Not a method declaration (it's a function call in an expression).
|
|
370
|
+
// Skip past the identifier to prevent re-matching a suffix
|
|
371
|
+
// (e.g. 'div(' → skip 'div', don't let 'iv(' match next).
|
|
372
|
+
const skipLen = methodMatch[1].length
|
|
373
|
+
result += source.slice(i, i + skipLen)
|
|
374
|
+
i += skipLen
|
|
375
|
+
continue
|
|
376
|
+
}
|
|
377
|
+
if (methodMatch && isInClassBody() && isMethodDecl) {
|
|
350
378
|
// We're actually in a class body - this is a method definition
|
|
351
379
|
const methodPart = methodMatch[1]
|
|
352
380
|
const matchLen = methodMatch[0].length
|
|
@@ -202,11 +202,48 @@ class Foo {
|
|
|
202
202
|
expect(tjsResult.code).toContain('static set label')
|
|
203
203
|
})
|
|
204
204
|
|
|
205
|
-
test('
|
|
206
|
-
// component.test.ts
|
|
205
|
+
test('destructured arrow param in class property fails TJS parse', () => {
|
|
206
|
+
// In tosijs component.test.ts, a class property is an arrow function
|
|
207
|
+
// with a destructured parameter:
|
|
208
|
+
//
|
|
209
|
+
// content = ({ div, span }: typeof elements) => [...]
|
|
210
|
+
//
|
|
211
|
+
// fromTS (TS→TJS) handles this fine, stripping the type annotation:
|
|
212
|
+
// content = ({ div, span }) => [...]
|
|
213
|
+
//
|
|
214
|
+
// But the TJS parser then chokes on this with:
|
|
207
215
|
// "Shorthand property assignments are valid only in destructuring patterns"
|
|
208
216
|
//
|
|
209
|
-
//
|
|
217
|
+
// The bug is in the TJS→JS step (tjs parser), not the TS→TJS step.
|
|
218
|
+
|
|
219
|
+
const source = `
|
|
220
|
+
class TestComponent {
|
|
221
|
+
content = ({ div, span }: { div: Function, span: Function }) => [
|
|
222
|
+
div({ part: 'container' }, span({ part: 'label' }, 'Test')),
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
render() {}
|
|
226
|
+
}
|
|
227
|
+
`
|
|
228
|
+
const tjsResult = fromTS(source, {
|
|
229
|
+
emitTJS: true,
|
|
230
|
+
filename: 'destructured-param.ts',
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
// TS→TJS works fine
|
|
234
|
+
expect(tjsResult.code).toContain('content')
|
|
235
|
+
|
|
236
|
+
// TJS→JS fails on the destructured arrow param in class property
|
|
237
|
+
expect(() => {
|
|
238
|
+
tjs(tjsResult.code, {
|
|
239
|
+
filename: 'destructured-param.ts',
|
|
240
|
+
runTests: false,
|
|
241
|
+
})
|
|
242
|
+
}).not.toThrow()
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
test('shorthand property assignment in destructuring converts', () => {
|
|
246
|
+
// Also from component.test.ts — default values in destructuring:
|
|
210
247
|
// const { mode = 'default' } = getConfig()
|
|
211
248
|
|
|
212
249
|
const source = `
|