tjs-lang 0.6.37 → 0.6.39
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 +107 -106
- package/dist/index.js.map +4 -4
- package/dist/tjs-full.js +107 -106
- 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 +14 -2
- package/src/lang/parser-params.ts +29 -1
- package/src/use-cases/tosijs-convert-issues.test.ts +67 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tjs-lang",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.39",
|
|
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
|
@@ -1712,6 +1712,10 @@ function transformClassToTJS(
|
|
|
1712
1712
|
// Getters
|
|
1713
1713
|
if (ts.isGetAccessorDeclaration(member) && member.name) {
|
|
1714
1714
|
const propName = member.name.getText(sourceFile)
|
|
1715
|
+
const isStatic = member.modifiers?.some(
|
|
1716
|
+
(m) => m.kind === ts.SyntaxKind.StaticKeyword
|
|
1717
|
+
)
|
|
1718
|
+
const staticPrefix = isStatic ? 'static ' : ''
|
|
1715
1719
|
const returnExample = member.type
|
|
1716
1720
|
? typeToExample(member.type, undefined, warnings, resolveCtx)
|
|
1717
1721
|
: ''
|
|
@@ -1735,12 +1739,18 @@ function transformClassToTJS(
|
|
|
1735
1739
|
body = replacePrivateRefs(transpiled.outputText.trim())
|
|
1736
1740
|
}
|
|
1737
1741
|
|
|
1738
|
-
members.push(
|
|
1742
|
+
members.push(
|
|
1743
|
+
` ${staticPrefix}get ${propName}()${returnAnnotation} ${body}`
|
|
1744
|
+
)
|
|
1739
1745
|
}
|
|
1740
1746
|
|
|
1741
1747
|
// Setters
|
|
1742
1748
|
if (ts.isSetAccessorDeclaration(member) && member.name) {
|
|
1743
1749
|
const propName = member.name.getText(sourceFile)
|
|
1750
|
+
const isStatic = member.modifiers?.some(
|
|
1751
|
+
(m) => m.kind === ts.SyntaxKind.StaticKeyword
|
|
1752
|
+
)
|
|
1753
|
+
const staticPrefix = isStatic ? 'static ' : ''
|
|
1744
1754
|
const params = transformParams(member.parameters, sourceFile, warnings)
|
|
1745
1755
|
|
|
1746
1756
|
let body = '{ }'
|
|
@@ -1755,7 +1765,9 @@ function transformClassToTJS(
|
|
|
1755
1765
|
body = replacePrivateRefs(transpiled.outputText.trim())
|
|
1756
1766
|
}
|
|
1757
1767
|
|
|
1758
|
-
members.push(
|
|
1768
|
+
members.push(
|
|
1769
|
+
` ${staticPrefix}set ${propName}(${params.join(', ')}) ${body}`
|
|
1770
|
+
)
|
|
1759
1771
|
}
|
|
1760
1772
|
|
|
1761
1773
|
// Properties with initializers (private fields, regular properties)
|
|
@@ -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
|
|
@@ -175,11 +175,75 @@ const tag = Component.getTag()
|
|
|
175
175
|
}).not.toThrow()
|
|
176
176
|
})
|
|
177
177
|
|
|
178
|
-
test('
|
|
179
|
-
// component.
|
|
178
|
+
test('static getter loses static keyword during conversion', () => {
|
|
179
|
+
// In tosijs component.ts:
|
|
180
|
+
// class Component extends HTMLElement {
|
|
181
|
+
// static _tagName: string | null = null
|
|
182
|
+
// static get tagName() { return this._tagName }
|
|
183
|
+
// }
|
|
184
|
+
//
|
|
185
|
+
// The converter emits `get tagName()` (instance getter) instead of
|
|
186
|
+
// `static get tagName()`. This overrides HTMLElement.tagName with a
|
|
187
|
+
// getter that returns null, crashing the constructor.
|
|
188
|
+
|
|
189
|
+
const source = `
|
|
190
|
+
class Foo {
|
|
191
|
+
static _label: string = ''
|
|
192
|
+
static get label() { return this._label }
|
|
193
|
+
static set label(v: string) { this._label = v }
|
|
194
|
+
}
|
|
195
|
+
`
|
|
196
|
+
// The bug is in TS→TJS: static is dropped from getters/setters
|
|
197
|
+
const tjsResult = fromTS(source, {
|
|
198
|
+
emitTJS: true,
|
|
199
|
+
filename: 'static-getter.ts',
|
|
200
|
+
})
|
|
201
|
+
expect(tjsResult.code).toContain('static get label')
|
|
202
|
+
expect(tjsResult.code).toContain('static set label')
|
|
203
|
+
})
|
|
204
|
+
|
|
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:
|
|
180
215
|
// "Shorthand property assignments are valid only in destructuring patterns"
|
|
181
216
|
//
|
|
182
|
-
//
|
|
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:
|
|
183
247
|
// const { mode = 'default' } = getConfig()
|
|
184
248
|
|
|
185
249
|
const source = `
|