tjs-lang 0.8.0 → 0.8.2
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/CLAUDE.md +15 -5
- package/CONTEXT.md +4 -0
- package/demo/docs.json +12 -690
- package/demo/examples.test.ts +6 -2
- package/demo/src/playground-shared.ts +48 -16
- package/demo/src/playground-test-results.test.ts +112 -0
- package/demo/src/tjs-playground.ts +11 -5
- package/dist/eslint.config.d.ts +2 -0
- package/dist/index.js +146 -141
- package/dist/index.js.map +4 -4
- package/dist/src/lang/core.d.ts +1 -1
- package/dist/src/lang/dialect.d.ts +35 -0
- package/dist/src/lang/emitters/ast.d.ts +1 -1
- package/dist/src/lang/emitters/js-tests.d.ts +7 -0
- package/dist/src/lang/emitters/js-wasm.d.ts +5 -1
- package/dist/src/lang/emitters/js.d.ts +21 -0
- package/dist/src/lang/index.d.ts +3 -1
- package/dist/src/lang/module-loader.d.ts +125 -0
- package/dist/src/lang/parser-transforms.d.ts +79 -0
- package/dist/src/lang/parser-types.d.ts +50 -0
- package/dist/src/lang/parser.d.ts +18 -0
- package/dist/src/lang/transpiler.d.ts +1 -0
- package/dist/src/lang/types.d.ts +18 -0
- package/dist/src/lang/wasm.d.ts +67 -1
- package/dist/src/vm/runtime.d.ts +18 -0
- package/dist/src/vm/vm.d.ts +15 -1
- package/dist/tjs-batteries.js +2 -2
- package/dist/tjs-batteries.js.map +2 -2
- package/dist/tjs-eval.js +43 -41
- package/dist/tjs-eval.js.map +3 -3
- package/dist/tjs-from-ts.js +2 -2
- package/dist/tjs-from-ts.js.map +2 -2
- package/dist/tjs-lang.js +107 -104
- package/dist/tjs-lang.js.map +4 -4
- package/dist/tjs-vm.js +53 -51
- package/dist/tjs-vm.js.map +3 -3
- package/docs/README.md +2 -0
- package/docs/lm-studio-setup.md +143 -0
- package/docs/universal-endpoint.md +122 -0
- package/llms.txt +9 -2
- package/package.json +24 -4
- package/src/batteries/audit.ts +6 -5
- package/src/batteries/llm.ts +8 -3
- package/src/builder.ts +0 -3
- package/src/cli/commands/check.ts +3 -2
- package/src/cli/commands/emit.ts +4 -2
- package/src/cli/commands/run.ts +6 -2
- package/src/cli/commands/test.ts +1 -1
- package/src/cli/commands/types.ts +2 -2
- package/src/lang/codegen.test.ts +4 -1
- package/src/lang/core.ts +6 -4
- package/src/lang/dialect.test.ts +63 -0
- package/src/lang/dialect.ts +50 -0
- package/src/lang/docs.test.ts +0 -1
- package/src/lang/emitters/ast.ts +145 -2
- package/src/lang/emitters/from-ts.ts +1 -1
- package/src/lang/emitters/js-tests.ts +46 -37
- package/src/lang/emitters/js.ts +19 -3
- package/src/lang/features.test.ts +6 -5
- package/src/lang/index.ts +18 -5
- package/src/lang/linter.ts +1 -1
- package/src/lang/module-loader.test.ts +9 -5
- package/src/lang/module-loader.ts +4 -5
- package/src/lang/parser-params.ts +1 -1
- package/src/lang/parser-transforms.ts +12 -18
- package/src/lang/parser-types.ts +17 -0
- package/src/lang/parser.test.ts +12 -6
- package/src/lang/parser.ts +113 -3
- package/src/lang/perf.test.ts +10 -4
- package/src/lang/runtime.ts +0 -1
- package/src/lang/subset-invariant.test.ts +90 -0
- package/src/lang/transpiler.ts +8 -0
- package/src/lang/types.ts +18 -0
- package/src/lang/wasm.test.ts +22 -16
- package/src/linalg/linalg.test.ts +16 -4
- package/src/linalg/vector-search.bench.test.ts +31 -10
- package/src/types/Type.ts +6 -6
- package/src/use-cases/asymmetric-client-server.test.ts +0 -2
- package/src/use-cases/batteries.test.ts +4 -0
- package/src/use-cases/client-server.test.ts +1 -1
- package/src/use-cases/local-helpers.test.ts +219 -0
- package/src/use-cases/timeout-overrides.test.ts +169 -0
- package/src/use-cases/unbundled-imports.test.ts +0 -1
- package/src/vm/atoms/batteries.ts +17 -3
- package/src/vm/runtime.ts +92 -7
- package/src/vm/vm.ts +50 -10
package/demo/examples.test.ts
CHANGED
|
@@ -448,9 +448,13 @@ describe('Playground Examples', () => {
|
|
|
448
448
|
args.imageUrl = '/test-shapes.jpg'
|
|
449
449
|
}
|
|
450
450
|
|
|
451
|
-
// Use the SAME capabilities as the playground
|
|
451
|
+
// Use the SAME capabilities as the playground.
|
|
452
|
+
// Vision inference on a local model can take a while; give it an
|
|
453
|
+
// explicit budget well above the slow-atom timeout so the test is
|
|
454
|
+
// deterministic regardless of the run-level default.
|
|
452
455
|
const runResult = await vm.run(result.ast, args, {
|
|
453
456
|
fuel: 100000,
|
|
457
|
+
timeoutMs: 180000,
|
|
454
458
|
capabilities: {
|
|
455
459
|
fetch: httpFetch,
|
|
456
460
|
llm: llmCapability || mockLLM,
|
|
@@ -466,7 +470,7 @@ describe('Playground Examples', () => {
|
|
|
466
470
|
} finally {
|
|
467
471
|
trackTestEnd()
|
|
468
472
|
}
|
|
469
|
-
},
|
|
473
|
+
}, 240000) // bun-test timeout above the vm timeoutMs (180s) + overhead
|
|
470
474
|
} else if (needsRetry) {
|
|
471
475
|
// Examples that use runCode need retry due to LLM variability
|
|
472
476
|
it(`${example.name} - runs successfully`, async () => {
|
|
@@ -255,28 +255,36 @@ export function renderConsoleMessages(
|
|
|
255
255
|
|
|
256
256
|
/**
|
|
257
257
|
* Render test results HTML with clickable error links and editor gutter markers.
|
|
258
|
-
* Returns pass/fail counts so callers can update tab indicators.
|
|
258
|
+
* Returns pass/fail/inconclusive counts so callers can update tab indicators.
|
|
259
259
|
*/
|
|
260
260
|
export function renderTestResults(
|
|
261
261
|
tests: any[],
|
|
262
262
|
outputEl: HTMLElement,
|
|
263
263
|
editor: CodeMirror,
|
|
264
264
|
goToLine: (line: number) => void
|
|
265
|
-
): { passed: number; failed: number } {
|
|
265
|
+
): { passed: number; failed: number; inconclusive: number } {
|
|
266
266
|
if (!tests || tests.length === 0) {
|
|
267
267
|
outputEl.textContent = 'No tests defined'
|
|
268
268
|
editor.clearMarkers()
|
|
269
|
-
return { passed: 0, failed: 0 }
|
|
269
|
+
return { passed: 0, failed: 0, inconclusive: 0 }
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
const passed = tests.filter((t: any) => t.passed).length
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
273
|
+
// Inconclusive = the test couldn't *run* (e.g. it calls an AJS atom that
|
|
274
|
+
// doesn't exist at build time). It is neither pass nor fail — surface it as
|
|
275
|
+
// a distinct warning state and keep it out of the failure count, so legal
|
|
276
|
+
// AJS doesn't read as broken in the playground. See PRINCIPLES.md.
|
|
277
|
+
const inconclusive = tests.filter(
|
|
278
|
+
(t: any) => !t.passed && t.inconclusive
|
|
279
|
+
).length
|
|
280
|
+
const failed = tests.filter((t: any) => !t.passed && !t.inconclusive).length
|
|
281
|
+
|
|
282
|
+
// Mark only genuine FAILURES in the editor. Passing tests don't need an
|
|
283
|
+
// underline (it reads as visual noise), and an inconclusive test isn't a
|
|
284
|
+
// failure — a red squiggle would wrongly imply the code is broken.
|
|
285
|
+
const failedWithLines = tests.filter(
|
|
286
|
+
(t: any) => t.line && !t.passed && !t.inconclusive
|
|
287
|
+
)
|
|
280
288
|
if (failedWithLines.length > 0) {
|
|
281
289
|
editor.setMarkers(
|
|
282
290
|
failedWithLines.map((t: any) => ({
|
|
@@ -294,19 +302,29 @@ export function renderTestResults(
|
|
|
294
302
|
if (failed > 0) {
|
|
295
303
|
html += `, <strong class="test-failed">${failed} failed</strong>`
|
|
296
304
|
}
|
|
305
|
+
if (inconclusive > 0) {
|
|
306
|
+
html += `, <strong class="test-inconclusive">${inconclusive} inconclusive</strong>`
|
|
307
|
+
}
|
|
297
308
|
html += `</div><ul class="test-list">`
|
|
298
309
|
|
|
299
310
|
for (const test of tests) {
|
|
300
|
-
const
|
|
301
|
-
|
|
311
|
+
const state = test.passed
|
|
312
|
+
? 'pass'
|
|
313
|
+
: test.inconclusive
|
|
314
|
+
? 'inconclusive'
|
|
315
|
+
: 'fail'
|
|
316
|
+
const icon = test.passed ? '✓' : test.inconclusive ? '—' : '✗'
|
|
302
317
|
const sigBadge = test.isSignatureTest
|
|
303
318
|
? ' <span class="sig-badge">signature</span>'
|
|
304
319
|
: ''
|
|
305
320
|
const dataLine = test.line ? ` data-line="${test.line}"` : ''
|
|
306
321
|
const clickable = test.line ? ' clickable-test' : ''
|
|
307
|
-
html += `<li class="
|
|
308
|
-
if (
|
|
309
|
-
|
|
322
|
+
html += `<li class="test-${state}${clickable}"${dataLine}>${icon} ${test.description}${sigBadge}`
|
|
323
|
+
if (test.error) {
|
|
324
|
+
// Inconclusive notes are warnings (couldn't run), not failures.
|
|
325
|
+
const noteCls = test.inconclusive ? 'test-note' : 'test-error'
|
|
326
|
+
const prefix = test.inconclusive ? 'could not run — ' : ''
|
|
327
|
+
html += `<div class="${noteCls}">${prefix}${test.error}</div>`
|
|
310
328
|
}
|
|
311
329
|
html += `</li>`
|
|
312
330
|
}
|
|
@@ -327,7 +345,7 @@ export function renderTestResults(
|
|
|
327
345
|
})
|
|
328
346
|
})
|
|
329
347
|
|
|
330
|
-
return { passed, failed }
|
|
348
|
+
return { passed, failed, inconclusive }
|
|
331
349
|
}
|
|
332
350
|
|
|
333
351
|
// ---------------------------------------------------------------------------
|
|
@@ -589,6 +607,20 @@ export const sharedPlaygroundStyles: Record<string, Record<string, string>> = {
|
|
|
589
607
|
fontFamily: 'var(--font-mono, monospace)',
|
|
590
608
|
},
|
|
591
609
|
|
|
610
|
+
':host .test-inconclusive': {
|
|
611
|
+
color: '#d97706',
|
|
612
|
+
},
|
|
613
|
+
|
|
614
|
+
':host .test-note': {
|
|
615
|
+
marginLeft: '20px',
|
|
616
|
+
marginTop: '4px',
|
|
617
|
+
padding: '8px',
|
|
618
|
+
background: 'rgba(217, 119, 6, 0.1)',
|
|
619
|
+
borderRadius: '4px',
|
|
620
|
+
fontSize: '13px',
|
|
621
|
+
fontFamily: 'var(--font-mono, monospace)',
|
|
622
|
+
},
|
|
623
|
+
|
|
592
624
|
':host .clickable-test': {
|
|
593
625
|
cursor: 'pointer',
|
|
594
626
|
},
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'bun:test'
|
|
2
|
+
import { GlobalRegistrator } from '@happy-dom/global-registrator'
|
|
3
|
+
|
|
4
|
+
beforeAll(() => GlobalRegistrator.register())
|
|
5
|
+
afterAll(() => GlobalRegistrator.unregister())
|
|
6
|
+
|
|
7
|
+
// Imported after DOM globals exist.
|
|
8
|
+
const { renderTestResults } = await import('./playground-shared')
|
|
9
|
+
|
|
10
|
+
function fakeEditor() {
|
|
11
|
+
const calls: any[] = []
|
|
12
|
+
return {
|
|
13
|
+
setMarkers: (m: any) => calls.push({ setMarkers: m }),
|
|
14
|
+
clearMarkers: () => calls.push({ clearMarkers: true }),
|
|
15
|
+
_calls: calls,
|
|
16
|
+
} as any
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('renderTestResults — inconclusive affordance', () => {
|
|
20
|
+
it('separates inconclusive from passed and failed', () => {
|
|
21
|
+
const out = document.createElement('div')
|
|
22
|
+
const editor = fakeEditor()
|
|
23
|
+
const counts = renderTestResults(
|
|
24
|
+
[
|
|
25
|
+
{ description: 'a', passed: true },
|
|
26
|
+
{ description: 'b', passed: false, error: 'Expected 5 got 6' },
|
|
27
|
+
{
|
|
28
|
+
description: 'c signature example',
|
|
29
|
+
passed: false,
|
|
30
|
+
inconclusive: true,
|
|
31
|
+
isSignatureTest: true,
|
|
32
|
+
error: 'httpFetch is not defined',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
out,
|
|
36
|
+
editor,
|
|
37
|
+
() => {}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
expect(counts).toEqual({ passed: 1, failed: 1, inconclusive: 1 })
|
|
41
|
+
|
|
42
|
+
// Summary shows all three states
|
|
43
|
+
expect(out.querySelector('.test-summary')?.textContent).toContain(
|
|
44
|
+
'1 passed'
|
|
45
|
+
)
|
|
46
|
+
expect(out.querySelector('.test-summary')?.textContent).toContain(
|
|
47
|
+
'1 failed'
|
|
48
|
+
)
|
|
49
|
+
expect(out.querySelector('.test-summary')?.textContent).toContain(
|
|
50
|
+
'1 inconclusive'
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
// The inconclusive test renders with its own class + a warning note (not
|
|
54
|
+
// a red error), with the "could not run" framing.
|
|
55
|
+
const incLi = out.querySelector('li.test-inconclusive')
|
|
56
|
+
expect(incLi).not.toBeNull()
|
|
57
|
+
expect(incLi?.textContent).toContain('—') // not the ✗ failure icon
|
|
58
|
+
expect(incLi?.querySelector('.test-note')).not.toBeNull()
|
|
59
|
+
expect(incLi?.querySelector('.test-error')).toBeNull()
|
|
60
|
+
expect(incLi?.querySelector('.test-note')?.textContent).toContain(
|
|
61
|
+
'could not run'
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
// The genuine failure keeps the red error box + ✗ icon.
|
|
65
|
+
const failLi = out.querySelector('li.test-fail')
|
|
66
|
+
expect(failLi?.textContent).toContain('✗')
|
|
67
|
+
expect(failLi?.querySelector('.test-error')).not.toBeNull()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('renders REAL transpiler output: atom-calling agent → inconclusive', async () => {
|
|
71
|
+
// End-to-end: the transpiler emits inconclusive for an un-runnable
|
|
72
|
+
// signature test; the playground must render it as such (not a failure).
|
|
73
|
+
const { tjs } = await import('../../src/lang')
|
|
74
|
+
const { testResults } = tjs(
|
|
75
|
+
`function main(url: ''): { x: '' } {\n const x = httpFetch({ url })\n return { x }\n}`
|
|
76
|
+
)
|
|
77
|
+
const out = document.createElement('div')
|
|
78
|
+
const counts = renderTestResults(
|
|
79
|
+
testResults as any,
|
|
80
|
+
out,
|
|
81
|
+
fakeEditor(),
|
|
82
|
+
() => {}
|
|
83
|
+
)
|
|
84
|
+
expect(counts.failed).toBe(0)
|
|
85
|
+
expect(counts.inconclusive).toBe(1)
|
|
86
|
+
expect(out.querySelector('li.test-inconclusive')).not.toBeNull()
|
|
87
|
+
expect(out.querySelector('li.test-fail')).toBeNull()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('does NOT put an error marker on an inconclusive test line', () => {
|
|
91
|
+
const out = document.createElement('div')
|
|
92
|
+
const editor = fakeEditor()
|
|
93
|
+
renderTestResults(
|
|
94
|
+
[
|
|
95
|
+
{
|
|
96
|
+
description: 'sig',
|
|
97
|
+
passed: false,
|
|
98
|
+
inconclusive: true,
|
|
99
|
+
line: 3,
|
|
100
|
+
error: 'atom not defined',
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
out,
|
|
104
|
+
editor,
|
|
105
|
+
() => {}
|
|
106
|
+
)
|
|
107
|
+
// No setMarkers with an error for the inconclusive line; clearMarkers instead.
|
|
108
|
+
const markerCalls = editor._calls.filter((c: any) => c.setMarkers)
|
|
109
|
+
expect(markerCalls).toHaveLength(0)
|
|
110
|
+
expect(editor._calls.some((c: any) => c.clearMarkers)).toBe(true)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
@@ -149,7 +149,7 @@ export class TJSPlayground extends Component<TJSPlaygroundParts> {
|
|
|
149
149
|
// Split mode
|
|
150
150
|
private _splitMode: null | 'code' | 'output' = null
|
|
151
151
|
private _splitChannel: BroadcastChannel | null = null
|
|
152
|
-
private _splitSessionId
|
|
152
|
+
private _splitSessionId = ''
|
|
153
153
|
|
|
154
154
|
// Build flags state
|
|
155
155
|
private buildFlags = {
|
|
@@ -860,21 +860,27 @@ export class TJSPlayground extends Component<TJSPlaygroundParts> {
|
|
|
860
860
|
|
|
861
861
|
private updateTestResults(result: any) {
|
|
862
862
|
const tests = result.testResults
|
|
863
|
-
const { passed, failed } = renderTestResults(
|
|
863
|
+
const { passed, failed, inconclusive } = renderTestResults(
|
|
864
864
|
tests,
|
|
865
865
|
this.parts.testsOutput,
|
|
866
866
|
this.parts.tjsEditor,
|
|
867
867
|
(line) => this.goToSourceLine(line)
|
|
868
868
|
)
|
|
869
|
-
this.updateTestsTabLabel(passed, failed)
|
|
869
|
+
this.updateTestsTabLabel(passed, failed, inconclusive)
|
|
870
870
|
}
|
|
871
871
|
|
|
872
|
-
private updateTestsTabLabel(
|
|
872
|
+
private updateTestsTabLabel(
|
|
873
|
+
passed: number,
|
|
874
|
+
failed: number,
|
|
875
|
+
inconclusive = 0
|
|
876
|
+
) {
|
|
873
877
|
const tabs = this.parts.outputTabs
|
|
874
878
|
if (!tabs) return
|
|
875
879
|
|
|
876
880
|
if (failed > 0) {
|
|
877
881
|
tabs.style.setProperty('--test-indicator-color', '#dc2626')
|
|
882
|
+
} else if (inconclusive > 0) {
|
|
883
|
+
tabs.style.setProperty('--test-indicator-color', '#d97706')
|
|
878
884
|
} else if (passed > 0) {
|
|
879
885
|
tabs.style.setProperty('--test-indicator-color', '#16a34a')
|
|
880
886
|
} else {
|
|
@@ -1394,7 +1400,7 @@ export class TJSPlayground extends Component<TJSPlaygroundParts> {
|
|
|
1394
1400
|
}
|
|
1395
1401
|
|
|
1396
1402
|
// Navigate to a specific line in the source editor
|
|
1397
|
-
goToSourceLine(line: number, column
|
|
1403
|
+
goToSourceLine(line: number, column = 1) {
|
|
1398
1404
|
this.parts.inputTabs.value = 0 // Switch to TJS tab (first tab)
|
|
1399
1405
|
// Wait for tab switch and editor resize before scrolling
|
|
1400
1406
|
setTimeout(() => {
|