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.
- package/CLAUDE.md +12 -4
- package/dist/experiments/ambient/probe.d.ts +52 -0
- package/dist/index.js +115 -171
- package/dist/index.js.map +4 -4
- package/dist/scripts/build-editors.d.ts +19 -0
- package/dist/src/css/dimensions.d.ts +23 -0
- package/dist/src/css/index.d.ts +85 -0
- package/dist/src/css/predicates.d.ts +38 -0
- package/dist/src/css/shorthands.d.ts +31 -0
- package/dist/src/css/style.d.ts +48 -0
- package/dist/src/lang/emitters/js.d.ts +9 -1
- package/dist/src/lang/index.d.ts +2 -2
- package/dist/src/lang/parser-transforms.d.ts +9 -5
- package/dist/src/lang/parser.d.ts +2 -0
- package/dist/src/lang/predicate.d.ts +60 -0
- package/dist/src/lang/transpiler.d.ts +3 -2
- package/dist/src/lang/types.d.ts +16 -0
- package/dist/src/schema/index.d.ts +12 -0
- package/dist/tjs-browser-from-ts.js +20 -20
- package/dist/tjs-browser-from-ts.js.map +3 -3
- package/dist/tjs-browser.js +100 -90
- package/dist/tjs-browser.js.map +4 -4
- package/dist/tjs-css.js +193 -0
- package/dist/tjs-css.js.map +7 -0
- package/dist/tjs-eval.js +43 -42
- package/dist/tjs-eval.js.map +4 -4
- package/dist/tjs-from-ts.js +13 -13
- package/dist/tjs-from-ts.js.map +2 -2
- package/dist/tjs-lang.js +102 -92
- package/dist/tjs-lang.js.map +4 -4
- package/dist/tjs-runtime.js +10 -0
- package/dist/tjs-runtime.js.map +7 -0
- package/dist/tjs-schema.js +6 -0
- package/dist/tjs-schema.js.map +7 -0
- package/dist/tjs-vm.js +55 -54
- package/dist/tjs-vm.js.map +4 -4
- package/docs/ambient-contracts.md +261 -0
- package/editors/ace/ajs-mode.js +214 -233
- package/editors/codemirror/ajs-language.js +1570 -233
- package/editors/editors-build.test.ts +29 -0
- package/editors/monaco/ajs-monarch.js +239 -195
- package/llms.txt +4 -0
- package/package.json +35 -4
- package/src/css/css.test.ts +122 -0
- package/src/css/dimensions.test.ts +112 -0
- package/src/css/dimensions.ts +146 -0
- package/src/css/index.ts +243 -0
- package/src/css/perf.bench.test.ts +109 -0
- package/src/css/predicates.ts +232 -0
- package/src/css/property-aware.test.ts +84 -0
- package/src/css/shorthands.test.ts +90 -0
- package/src/css/shorthands.ts +113 -0
- package/src/css/style.test.ts +125 -0
- package/src/css/style.ts +134 -0
- package/src/index-tsfree.test.ts +58 -0
- package/src/lang/emit-verified-predicate.test.ts +95 -0
- package/src/lang/emitters/dts.test.ts +31 -0
- package/src/lang/emitters/dts.ts +23 -4
- package/src/lang/emitters/js.ts +33 -1
- package/src/lang/from-ts.test.ts +1 -1
- package/src/lang/generic-verified-predicate.test.ts +39 -0
- package/src/lang/index.ts +14 -5
- package/src/lang/parser-transforms.ts +109 -14
- package/src/lang/parser.ts +9 -2
- package/src/lang/predicate-evaluator.test.ts +49 -0
- package/src/lang/predicate-report.test.ts +54 -0
- package/src/lang/predicate.ts +268 -0
- package/src/lang/redos-lint.test.ts +81 -0
- package/src/lang/transpiler.ts +13 -0
- package/src/lang/type-verified-predicate.test.ts +83 -0
- package/src/lang/types.ts +17 -0
- package/src/lang/typescript-syntax.test.ts +2 -1
- package/src/schema/index.ts +62 -0
- package/src/schema/schema.test.ts +66 -0
- 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
|
+
})
|
package/src/lang/transpiler.ts
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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')
|