tjs-lang 0.8.7 → 0.9.0

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.
Files changed (75) hide show
  1. package/CLAUDE.md +12 -4
  2. package/dist/experiments/ambient/probe.d.ts +52 -0
  3. package/dist/index.js +115 -171
  4. package/dist/index.js.map +4 -4
  5. package/dist/scripts/build-editors.d.ts +19 -0
  6. package/dist/src/css/dimensions.d.ts +23 -0
  7. package/dist/src/css/index.d.ts +85 -0
  8. package/dist/src/css/predicates.d.ts +38 -0
  9. package/dist/src/css/shorthands.d.ts +31 -0
  10. package/dist/src/css/style.d.ts +48 -0
  11. package/dist/src/lang/emitters/js.d.ts +9 -1
  12. package/dist/src/lang/index.d.ts +2 -2
  13. package/dist/src/lang/parser-transforms.d.ts +9 -5
  14. package/dist/src/lang/parser.d.ts +2 -0
  15. package/dist/src/lang/predicate.d.ts +60 -0
  16. package/dist/src/lang/transpiler.d.ts +3 -2
  17. package/dist/src/lang/types.d.ts +16 -0
  18. package/dist/src/schema/index.d.ts +12 -0
  19. package/dist/tjs-browser-from-ts.js +20 -20
  20. package/dist/tjs-browser-from-ts.js.map +3 -3
  21. package/dist/tjs-browser.js +100 -90
  22. package/dist/tjs-browser.js.map +4 -4
  23. package/dist/tjs-css.js +193 -0
  24. package/dist/tjs-css.js.map +7 -0
  25. package/dist/tjs-eval.js +43 -42
  26. package/dist/tjs-eval.js.map +4 -4
  27. package/dist/tjs-from-ts.js +13 -13
  28. package/dist/tjs-from-ts.js.map +2 -2
  29. package/dist/tjs-lang.js +102 -92
  30. package/dist/tjs-lang.js.map +4 -4
  31. package/dist/tjs-runtime.js +10 -0
  32. package/dist/tjs-runtime.js.map +7 -0
  33. package/dist/tjs-schema.js +6 -0
  34. package/dist/tjs-schema.js.map +7 -0
  35. package/dist/tjs-vm.js +55 -54
  36. package/dist/tjs-vm.js.map +4 -4
  37. package/docs/ambient-contracts.md +261 -0
  38. package/editors/ace/ajs-mode.js +214 -233
  39. package/editors/codemirror/ajs-language.js +1570 -233
  40. package/editors/editors-build.test.ts +29 -0
  41. package/editors/monaco/ajs-monarch.js +239 -195
  42. package/llms.txt +4 -0
  43. package/package.json +35 -4
  44. package/src/css/css.test.ts +122 -0
  45. package/src/css/dimensions.test.ts +112 -0
  46. package/src/css/dimensions.ts +146 -0
  47. package/src/css/index.ts +243 -0
  48. package/src/css/perf.bench.test.ts +109 -0
  49. package/src/css/predicates.ts +232 -0
  50. package/src/css/property-aware.test.ts +84 -0
  51. package/src/css/shorthands.test.ts +90 -0
  52. package/src/css/shorthands.ts +113 -0
  53. package/src/css/style.test.ts +125 -0
  54. package/src/css/style.ts +134 -0
  55. package/src/index-tsfree.test.ts +58 -0
  56. package/src/lang/emit-verified-predicate.test.ts +95 -0
  57. package/src/lang/emitters/dts.test.ts +31 -0
  58. package/src/lang/emitters/dts.ts +23 -4
  59. package/src/lang/emitters/js.ts +33 -1
  60. package/src/lang/from-ts.test.ts +1 -1
  61. package/src/lang/generic-verified-predicate.test.ts +39 -0
  62. package/src/lang/index.ts +14 -5
  63. package/src/lang/parser-transforms.ts +109 -14
  64. package/src/lang/parser.ts +9 -2
  65. package/src/lang/predicate-evaluator.test.ts +49 -0
  66. package/src/lang/predicate-report.test.ts +54 -0
  67. package/src/lang/predicate.ts +268 -0
  68. package/src/lang/redos-lint.test.ts +81 -0
  69. package/src/lang/transpiler.ts +13 -0
  70. package/src/lang/type-verified-predicate.test.ts +83 -0
  71. package/src/lang/types.ts +17 -0
  72. package/src/lang/typescript-syntax.test.ts +2 -1
  73. package/src/schema/index.ts +62 -0
  74. package/src/schema/schema.test.ts +66 -0
  75. package/src/use-cases/bootstrap.test.ts +14 -4
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Regex ReDoS linting in the predicate verifier. A regex match is opaque to the
3
+ * fuel counter (a single `.test`/`.match` can't be interrupted mid-backtrack),
4
+ * so a catastrophic-backtracking pattern can't be certified predicate-safe. The
5
+ * verifier flags the exponential star-height≥2 class (`(a+)+`, `(a*)*`, …) and
6
+ * fails closed: over-flagging only costs the "verified" badge (the predicate
7
+ * still runs), whereas certifying a dangerous one would be a broken promise.
8
+ */
9
+ import { describe, it, expect } from 'bun:test'
10
+ import { verifyPredicate, emitVerifiedPredicate } from './predicate'
11
+ import { preprocess } from './parser'
12
+
13
+ /** Verify a single predicate whose body uses the given regex literal. */
14
+ const verifyRe = (re: string) =>
15
+ verifyPredicate(`function p(s) { return ${re}.test(s) }`)
16
+
17
+ describe('ReDoS linting: dangerous patterns are not predicate-safe', () => {
18
+ const dangerous: Array<[string, string]> = [
19
+ ['nested +', '/(a+)+$/'],
20
+ ['nested *', '/(a*)*/'],
21
+ ['plus-in-star', '/([a-z]+)*/'],
22
+ ['dot-star star', '/(.*)*/'],
23
+ ['digit nest', '/(\\d+)+/'],
24
+ ['unbounded brace nest', '/(a{2,})+/'],
25
+ ['doubly nested group', '/((a+))+/'],
26
+ ]
27
+ for (const [label, re] of dangerous) {
28
+ it(`flags ${label}: ${re}`, () => {
29
+ const r = verifyRe(re)
30
+ expect(r.safe).toBe(false)
31
+ expect(
32
+ r.diagnostics.some((d) => /redos|backtrack/i.test(d.message))
33
+ ).toBe(true)
34
+ })
35
+ }
36
+ })
37
+
38
+ describe('ReDoS linting: safe patterns still verify', () => {
39
+ const safe: Array<[string, string]> = [
40
+ ['char class +', '/[a-z]+/'],
41
+ ['bounded braces', '/\\d{3}-\\d{4}/'],
42
+ ['group repeated, no inner quantifier', '/(abc)+/'],
43
+ ['alternation repeated', '/(foo|bar)+/'],
44
+ ['anchored hex', '/^#[0-9a-f]{6}$/'],
45
+ ['single star', '/foo.*bar/'],
46
+ ['optional group', '/(ab)?c+/'],
47
+ ]
48
+ for (const [label, re] of safe) {
49
+ it(`accepts ${label}: ${re}`, () => {
50
+ const r = verifyRe(re)
51
+ expect(r.safe).toBe(true)
52
+ expect(r.diagnostics).toEqual([])
53
+ })
54
+ }
55
+ })
56
+
57
+ describe('ReDoS linting: end-to-end', () => {
58
+ it('emitVerifiedPredicate refuses a ReDoS regex (no code)', () => {
59
+ const r = emitVerifiedPredicate(
60
+ 'function p(s) { return /(a+)+$/.test(s) }',
61
+ 'p'
62
+ )
63
+ expect(r.safe).toBe(false)
64
+ expect(r.code).toBeUndefined()
65
+ })
66
+
67
+ it('a Type predicate with a ReDoS regex falls back (no verified guard)', () => {
68
+ const out = preprocess(
69
+ `Type Bad 'bad' { predicate(s) { return /(a+)+$/.test(s) } }`
70
+ ).source
71
+ expect(out).not.toContain('__fuel') // not certified → raw fallback
72
+ expect(out).toContain('(a+)+') // raw body preserved
73
+ })
74
+
75
+ it('a Type predicate with a safe regex compiles to a verified guard', () => {
76
+ const out = preprocess(
77
+ `Type Hex 'hex' { predicate(s) { return /^#[0-9a-f]{6}$/.test(s) } }`
78
+ ).source
79
+ expect(out).toContain('__fuel') // certified safe → fuel-bounded guard
80
+ })
81
+ })
@@ -28,6 +28,8 @@ export {
28
28
  export {
29
29
  verifyPredicate,
30
30
  compilePredicate,
31
+ emitVerifiedPredicate,
32
+ createPredicateEvaluator,
31
33
  suggest,
32
34
  effectfulFromAtoms,
33
35
  formatPredicateDiagnostics,
@@ -36,6 +38,8 @@ export {
36
38
  type PredicateVerifyResult,
37
39
  type VerifyPredicateOptions,
38
40
  type CompilePredicateOptions,
41
+ type EmitPredicateResult,
42
+ type PredicateEvaluatorOptions,
39
43
  type Suggestion,
40
44
  type SuggestOptions,
41
45
  } from './predicate'
@@ -53,6 +57,14 @@ export {
53
57
  // AST emitter
54
58
  export { transformFunction } from './emitters/ast'
55
59
 
60
+ // .d.ts emitter — the express-controlled migration bridge for a TS importer
61
+ // graph (see ../tosijs/TJS-PORT-DX.md). Needs to be reachable from `tjs-lang/lang`.
62
+ export {
63
+ generateDTS,
64
+ typeDescriptorToTS,
65
+ type GenerateDTSOptions,
66
+ } from './emitters/dts'
67
+
56
68
  // JS emitter (TJS -> JS)
57
69
  export { transpileToJS } from './emitters/js'
58
70
  export type {
@@ -107,4 +119,5 @@ export type {
107
119
  TranspileOptions,
108
120
  TranspileResult,
109
121
  TranspileWarning,
122
+ PredicateVerification,
110
123
  } from './types'
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Integration: `Type … { predicate(x){…} }` bodies that verify as predicate-safe
3
+ * compile to a self-contained, fuel-bounded native guard (the `emitVerifiedPredicate`
4
+ * path); unverifiable ones fall back to the raw arrow (never rejected — TJS ⊇ JS).
5
+ *
6
+ * We assert against the preprocessed source: `__fuel` present ⇔ the verified
7
+ * guard was emitted. Runtime guard behavior is covered by
8
+ * `emit-verified-predicate.test.ts`.
9
+ */
10
+ import { describe, it, expect } from 'bun:test'
11
+ import { preprocess } from './parser'
12
+ import { tjs } from './index'
13
+
14
+ const src = (s: string) => preprocess(s).source
15
+
16
+ describe('multiple verified predicates in one module (no name collision)', () => {
17
+ // Regression: each verified guard used a fixed `function __pred`, so two in
18
+ // one module collided — the polymorphic-merge transform saw duplicate names
19
+ // and either errored ("ambiguous signatures") or renamed to `__pred$1`,
20
+ // breaking the guard IIFE at runtime. Names are now per-declaration.
21
+ it('transpiles two Type predicates without a `__pred` clash', () => {
22
+ const out = tjs(
23
+ `Type A 'a' { predicate(x) { return x > 0 } }\n` +
24
+ `Type B 'b' { predicate(x) { return x < 0 } }`
25
+ )
26
+ expect(out.code).not.toContain('__pred$')
27
+ expect(out.code).toContain('__pred_A')
28
+ expect(out.code).toContain('__pred_B')
29
+ })
30
+
31
+ it('transpiles a Type + Generic predicate together', () => {
32
+ const out = tjs(
33
+ `Type Pos 'positive' { predicate(x) { return x > 0 } }\n` +
34
+ `Generic Box<T> { description: 'b', predicate(x, T) { return T(x.value) } }`
35
+ )
36
+ expect(out.code).not.toContain('__pred$')
37
+ expect(out.code).toContain('__pred_Pos')
38
+ expect(out.code).toContain('__pred_Box')
39
+ })
40
+ })
41
+
42
+ describe('Type predicate → verified fuel-bounded guard', () => {
43
+ it('compiles a safe predicate-only Type to a fuel-bounded guard', () => {
44
+ const out = src(`Type Pos 'positive' { predicate(x) { return x > 0 } }`)
45
+ expect(out).toContain(`const Pos = Type('positive'`)
46
+ expect(out).toContain('__fuel') // verified + fuel-injected
47
+ expect(out).toContain('x > 0') // original body preserved inside the guard
48
+ })
49
+
50
+ it('compiles a safe predicate+example Type, keeping the example schema gate', () => {
51
+ const out = src(
52
+ `Type EvenNum 'even' { example: 2, predicate(x) { return x % 2 === 0 } }`
53
+ )
54
+ expect(out).toContain(`const EvenNum = Type('even'`)
55
+ expect(out).toContain('__fuel') // verified
56
+ expect(out).toContain('__tjs?.validate') // example schema still gates
57
+ expect(out).toContain('x % 2 === 0')
58
+ })
59
+
60
+ it('verifies a native-TJS predicate using == (rewritten to Eq)', () => {
61
+ // `==` → `Eq(...)`; Eq is whitelisted pure, so the predicate still verifies.
62
+ const out = src(`Type Five 'five' { predicate(x) { return x == 5 } }`)
63
+ expect(out).toContain('Eq(') // proves the equality rewrite ran
64
+ expect(out).toContain('__fuel') // ...and it still verified as safe
65
+ })
66
+
67
+ it('falls back to the raw arrow for an unverifiable predicate (loop)', () => {
68
+ const out = src(
69
+ `Type AllPos 'all positive' { example: [1], predicate(xs) { for (const x of xs) { if (x <= 0) return false } return true } }`
70
+ )
71
+ expect(out).not.toContain('__fuel') // NOT verified → no fuel guard
72
+ expect(out).toContain('for (const x of xs)') // raw body preserved
73
+ expect(out).toContain('__tjs?.validate') // example gate still present
74
+ })
75
+
76
+ it('falls back for an impure predicate (effectful call)', () => {
77
+ const out = src(
78
+ `Type Reachable 'reachable' { predicate(url) { return fetch(url) } }`
79
+ )
80
+ expect(out).not.toContain('__fuel')
81
+ expect(out).toContain('fetch(url)') // raw body preserved
82
+ })
83
+ })
package/src/lang/types.ts CHANGED
@@ -97,6 +97,23 @@ export interface TranspileWarning {
97
97
  source?: string
98
98
  }
99
99
 
100
+ /**
101
+ * Per-declaration report of whether a `Type`/`Generic` predicate body verified
102
+ * as predicate-safe (→ compiled to a fuel-bounded, DoS-safe native guard) or
103
+ * fell back to a plain unverified function (still valid — TJS ⊇ JS — but not
104
+ * fuel-bounded or safe to run on untrusted data). Surfaced on the transpile
105
+ * result so tools can flag unverifiable predicates.
106
+ */
107
+ export interface PredicateVerification {
108
+ /** The `Type` / `Generic` declaration name. */
109
+ name: string
110
+ kind: 'Type' | 'Generic'
111
+ /** True → verified + compiled to a native guard; false → raw-function fallback. */
112
+ verified: boolean
113
+ /** When `!verified`, the verifier's reason(s). */
114
+ reason?: string
115
+ }
116
+
100
117
  /** Source map for debugging */
101
118
  export interface SourceMap {
102
119
  version: 3
@@ -13,7 +13,8 @@
13
13
  */
14
14
 
15
15
  import { describe, test, expect } from 'bun:test'
16
- import { transpileToJS, fromTS, tjs } from './index'
16
+ import { transpileToJS, tjs } from './index'
17
+ import { fromTS } from './emitters/from-ts'
17
18
 
18
19
  // Helper to get the first function's metadata from the Record
19
20
  function getFirstFunc(metadata: Record<string, any>) {
@@ -0,0 +1,62 @@
1
+ /**
2
+ * `tjs-lang/schema` — tosijs-schema, pre-wired with predicate support.
3
+ *
4
+ * A drop-in for `tosijs-schema` that additionally evaluates the `$predicate`
5
+ * keyword: importing this module registers tjs-lang's verified predicate engine
6
+ * as the evaluator, so JSON-Schema validation is predicate-aware **out of the
7
+ * box** — no manual `setPredicateEvaluator` call. Everything tosijs-schema
8
+ * exports is re-exported here, so this is the only import a consumer needs:
9
+ *
10
+ * ```ts
11
+ * import { s, validate } from 'tjs-lang/schema'
12
+ * // a schema whose node carries `$predicate` now validates computationally
13
+ * ```
14
+ *
15
+ * Why it lives here and not in tosijs-schema: tjs-lang depends on tosijs-schema,
16
+ * so the reverse would be a circular dependency. This entry sits on the side of
17
+ * the graph that may know about both — it's the "batteries-included" packaging
18
+ * of the two.
19
+ *
20
+ * Registration is a global (tosijs-schema keeps one evaluator), so importing
21
+ * this module makes `$predicate` work for tosijs-schema usage app-wide, even in
22
+ * code that imports `tosijs-schema` directly. Call `setPredicateEvaluator(null)`
23
+ * to opt back out, or `installPredicateSupport(opts)` to re-install with custom
24
+ * options (fuel budget, atom-aware effects, etc.).
25
+ */
26
+ import { setPredicateEvaluator, getPredicateEvaluator } from 'tosijs-schema'
27
+ import {
28
+ createPredicateEvaluator,
29
+ type PredicateEvaluatorOptions,
30
+ } from '../lang/predicate'
31
+
32
+ // The full tosijs-schema surface (s, validate, filter, diff, types,
33
+ // setPredicateEvaluator, getPredicateEvaluator, PredicateEvaluator, …).
34
+ export * from 'tosijs-schema'
35
+ // The evaluator factory, for advanced/custom wiring.
36
+ export {
37
+ createPredicateEvaluator,
38
+ type PredicateEvaluatorOptions,
39
+ } from '../lang/predicate'
40
+
41
+ let installed = false
42
+
43
+ /**
44
+ * Register tjs-lang's predicate engine as tosijs-schema's `$predicate` evaluator.
45
+ * Called automatically when this module is imported; exposed so callers can
46
+ * re-install with custom {@link PredicateEvaluatorOptions} (e.g. a smaller fuel
47
+ * budget, or an atom-aware effectful set) after opting out.
48
+ */
49
+ export function installPredicateSupport(
50
+ opts?: PredicateEvaluatorOptions
51
+ ): void {
52
+ setPredicateEvaluator(createPredicateEvaluator(opts))
53
+ installed = true
54
+ }
55
+
56
+ /** True once predicate support has been installed (and not since cleared). */
57
+ export function predicateSupportInstalled(): boolean {
58
+ return installed && getPredicateEvaluator() !== null
59
+ }
60
+
61
+ // Batteries included: wire the engine up on import.
62
+ installPredicateSupport()
@@ -0,0 +1,66 @@
1
+ /**
2
+ * `tjs-lang/schema` — tosijs-schema pre-wired with predicate support. Importing
3
+ * the module should make `$predicate` validation work with no manual wiring, and
4
+ * re-export the whole tosijs-schema surface. Runs against the real published
5
+ * tosijs-schema (node_modules), so it also guards the version/API contract.
6
+ */
7
+ import { describe, it, expect, afterAll } from 'bun:test'
8
+ import {
9
+ validate,
10
+ s,
11
+ setPredicateEvaluator,
12
+ getPredicateEvaluator,
13
+ installPredicateSupport,
14
+ predicateSupportInstalled,
15
+ createPredicateEvaluator,
16
+ } from './index'
17
+ import { cssStyleSchema, cssColorSchema } from '../css'
18
+
19
+ // Leave the global evaluator registered for other suites (import order is
20
+ // nondeterministic); this module's contract is "installed after import".
21
+ afterAll(() => installPredicateSupport())
22
+
23
+ describe('tjs-lang/schema is batteries-included', () => {
24
+ it('registers the predicate evaluator on import', () => {
25
+ expect(predicateSupportInstalled()).toBe(true)
26
+ expect(typeof getPredicateEvaluator()).toBe('function')
27
+ })
28
+
29
+ it('re-exports the tosijs-schema surface', () => {
30
+ expect(typeof validate).toBe('function')
31
+ expect(typeof s).toBe('object') // the builder proxy
32
+ expect(typeof setPredicateEvaluator).toBe('function')
33
+ expect(typeof createPredicateEvaluator).toBe('function')
34
+ })
35
+
36
+ it('validates a $predicate node out of the box (CSS color)', () => {
37
+ const color = cssColorSchema()
38
+ expect(validate('#3a3', color)).toBe(true)
39
+ expect(validate('notacolor', color)).toBe(false) // predicate ran, no manual wiring
40
+ })
41
+
42
+ it('validates the recursive CSS style structure', () => {
43
+ const style = cssStyleSchema()
44
+ expect(
45
+ validate({ color: 'red', '&:hover': { color: 'var(--accent)' } }, style)
46
+ ).toBe(true)
47
+ expect(validate({ ' bad key ': 'red' }, style)).toBe(false)
48
+ expect(validate('not-an-object', style)).toBe(false)
49
+ })
50
+
51
+ it('opting out (setPredicateEvaluator(null)) falls back to structural only', () => {
52
+ setPredicateEvaluator(null)
53
+ const color = cssColorSchema()
54
+ // naive: only `type: string` is checked, so a non-color string passes
55
+ expect(validate('notacolor', color)).toBe(true)
56
+ // re-install for the remaining assertions
57
+ installPredicateSupport()
58
+ expect(validate('notacolor', color)).toBe(false)
59
+ })
60
+
61
+ it('installPredicateSupport accepts custom options (fuel budget)', () => {
62
+ installPredicateSupport({ fuel: 500_000 })
63
+ expect(predicateSupportInstalled()).toBe(true)
64
+ expect(validate('#fff', cssColorSchema())).toBe(true)
65
+ })
66
+ })
@@ -14,6 +14,7 @@
14
14
  import { describe, it, expect } from 'bun:test'
15
15
  import { fromTS } from '../lang/emitters/from-ts'
16
16
  import { tjs } from '../lang'
17
+ import { emitVerifiedPredicate } from '../lang/predicate'
17
18
  import * as fs from 'fs'
18
19
  import * as path from 'path'
19
20
 
@@ -528,10 +529,16 @@ describe('Bootstrap Canary', () => {
528
529
  // Step 2: Execute the combined transpiled parser modules
529
530
  const execStart = performance.now()
530
531
 
531
- const parserModule = new Function(`
532
+ // parser-transforms.ts calls emitVerifiedPredicate (from predicate.ts, an
533
+ // acorn-backed engine module not in this parser-only bundle) — inject the
534
+ // native one so the self-hosted preprocess can verify Type/Generic predicates.
535
+ const parserModule = new Function(
536
+ 'emitVerifiedPredicate',
537
+ `
532
538
  ${combinedCode}
533
539
  return { preprocess };
534
- `)()
540
+ `
541
+ )(emitVerifiedPredicate)
535
542
  const execTime = performance.now() - execStart
536
543
 
537
544
  expect(typeof parserModule.preprocess).toBe('function')
@@ -639,10 +646,13 @@ describe('Bootstrap Canary', () => {
639
646
  combinedCode += stripped + '\n'
640
647
  }
641
648
 
642
- const bootstrappedParser = new Function(`
649
+ const bootstrappedParser = new Function(
650
+ 'emitVerifiedPredicate',
651
+ `
643
652
  ${combinedCode}
644
653
  return { preprocess };
645
- `)()
654
+ `
655
+ )(emitVerifiedPredicate)
646
656
 
647
657
  // Import native parser
648
658
  const nativeParser = require('../lang/parser')