tjs-lang 0.7.6 → 0.7.8
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 +101 -26
- package/bin/docs.js +4 -1
- package/demo/docs.json +46 -12
- package/demo/src/examples.test.ts +1 -0
- package/demo/src/imports.test.ts +16 -4
- package/demo/src/imports.ts +60 -15
- package/demo/src/playground-shared.ts +9 -8
- package/demo/src/tfs-worker.js +205 -147
- package/demo/src/tjs-playground.ts +34 -10
- package/demo/src/ts-playground.ts +24 -8
- package/dist/index.js +140 -119
- package/dist/index.js.map +4 -4
- package/dist/src/lang/bool-coercion.d.ts +50 -0
- package/dist/src/lang/docs.d.ts +31 -6
- package/dist/src/lang/linter.d.ts +8 -0
- package/dist/src/lang/parser-transforms.d.ts +18 -0
- package/dist/src/lang/parser-types.d.ts +2 -0
- package/dist/src/lang/parser.d.ts +9 -0
- package/dist/src/lang/runtime.d.ts +34 -0
- package/dist/src/lang/types.d.ts +9 -1
- package/dist/src/rbac/index.d.ts +1 -1
- package/dist/src/vm/runtime.d.ts +1 -1
- package/dist/tjs-eval.js +44 -39
- package/dist/tjs-eval.js.map +4 -4
- package/dist/tjs-from-ts.js +20 -20
- package/dist/tjs-from-ts.js.map +3 -3
- package/dist/tjs-lang.js +86 -80
- package/dist/tjs-lang.js.map +4 -4
- package/dist/tjs-vm.js +50 -45
- package/dist/tjs-vm.js.map +4 -4
- package/llms.txt +79 -0
- package/package.json +3 -2
- package/src/cli/commands/convert.test.ts +16 -21
- package/src/lang/bool-coercion.test.ts +203 -0
- package/src/lang/bool-coercion.ts +314 -0
- package/src/lang/codegen.test.ts +177 -0
- package/src/lang/docs.test.ts +328 -1
- package/src/lang/docs.ts +424 -24
- package/src/lang/emitters/ast.ts +11 -12
- package/src/lang/emitters/dts.test.ts +41 -0
- package/src/lang/emitters/dts.ts +9 -0
- package/src/lang/emitters/js-tests.ts +16 -4
- package/src/lang/emitters/js.ts +208 -2
- package/src/lang/features.test.ts +22 -0
- package/src/lang/inference.ts +54 -0
- package/src/lang/linter.test.ts +104 -1
- package/src/lang/linter.ts +124 -1
- package/src/lang/parser-params.ts +31 -0
- package/src/lang/parser-transforms.ts +539 -6
- package/src/lang/parser-types.ts +2 -0
- package/src/lang/parser.test.ts +73 -1
- package/src/lang/parser.ts +85 -1
- package/src/lang/runtime.ts +98 -0
- package/src/lang/tests.ts +21 -8
- package/src/lang/types.ts +6 -0
- package/src/rbac/index.ts +2 -2
- package/src/rbac/rules.tjs.d.ts +9 -0
- package/src/vm/atoms/batteries.ts +2 -2
- package/src/vm/runtime.ts +10 -3
- package/dist/src/rbac/rules.d.ts +0 -184
- package/src/rbac/rules.js +0 -338
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boolean coercion rewriter.
|
|
3
|
+
*
|
|
4
|
+
* Fixes the JS footgun `Boolean(new Boolean(false)) === true` (and friends)
|
|
5
|
+
* by rewriting every truthiness context to call `__tjs.toBool(x)`, which
|
|
6
|
+
* unwraps boxed primitives before coercing.
|
|
7
|
+
*
|
|
8
|
+
* Contexts rewritten:
|
|
9
|
+
* if (cond) → if (__tjs.toBool(cond))
|
|
10
|
+
* while (cond) → while (__tjs.toBool(cond))
|
|
11
|
+
* do {} while (cond) → do {} while (__tjs.toBool(cond))
|
|
12
|
+
* for (_; cond; _) → for (_; __tjs.toBool(cond); _)
|
|
13
|
+
* !x → !__tjs.toBool(x)
|
|
14
|
+
* a && b → ((__tjs__t)=>__tjs.toBool(__tjs__t)?(b):__tjs__t)(a)
|
|
15
|
+
* a || b → ((__tjs__t)=>__tjs.toBool(__tjs__t)?__tjs__t:(b))(a)
|
|
16
|
+
* a ? b : c → __tjs.toBool(a)?(b):(c)
|
|
17
|
+
* Boolean(x) → __tjs.toBool(x) (call form, not `new`)
|
|
18
|
+
*
|
|
19
|
+
* `??` (nullish coalescing) is intentionally NOT rewritten — its semantics
|
|
20
|
+
* are about null/undefined specifically, not truthiness, so boxed primitives
|
|
21
|
+
* behave correctly already.
|
|
22
|
+
*
|
|
23
|
+
* `===` / `!==` (identity) are also not touched — that's a separate
|
|
24
|
+
* footgun handled by the `Is` / `Eq` operators under TjsEquals.
|
|
25
|
+
*
|
|
26
|
+
* Always-on under TjsStandard.
|
|
27
|
+
*/
|
|
28
|
+
import type { Program } from 'acorn';
|
|
29
|
+
export interface BoolCoercionPatch {
|
|
30
|
+
start: number;
|
|
31
|
+
end: number;
|
|
32
|
+
newText: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Walk the AST and emit replacement patches for every truthiness context.
|
|
36
|
+
* Patches are pre-deduped: nested coercions inside an outer patch are
|
|
37
|
+
* folded into the outer patch's newText, so returned patches don't overlap.
|
|
38
|
+
*/
|
|
39
|
+
export declare function rewriteBoolCoercion(ast: Program, source: string): BoolCoercionPatch[];
|
|
40
|
+
/**
|
|
41
|
+
* Source-text wrapper: parse, rewrite, re-emit. Used for code that's
|
|
42
|
+
* extracted before the main parse (test/mock bodies) but still needs the
|
|
43
|
+
* coercion rewrite to behave consistently with the rest of the module.
|
|
44
|
+
*
|
|
45
|
+
* Wraps the body in a function so top-level statements like `expect(...)`
|
|
46
|
+
* parse as a Program. Returns the original source unchanged if parsing
|
|
47
|
+
* fails (rather than throwing — a bad test body would already have been
|
|
48
|
+
* caught by the main parse).
|
|
49
|
+
*/
|
|
50
|
+
export declare function rewriteBoolCoercionInSource(source: string): string;
|
package/dist/src/lang/docs.d.ts
CHANGED
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
* Dead simple: walk source in order, emit what you find.
|
|
5
5
|
* - Doc blocks render as markdown
|
|
6
6
|
* - Function signatures render as code blocks
|
|
7
|
+
* - Inline `test 'name' { ... }` blocks render as "Test Cases" section
|
|
8
|
+
* with `expect(...).toBe(...)` style assertions translated to comments
|
|
7
9
|
*
|
|
8
10
|
* No magic pairing. No attachment logic. The signature IS the docs.
|
|
9
11
|
* Doc blocks are just editorial commentary when you need it.
|
|
10
12
|
*/
|
|
13
|
+
import type { TypeDescriptor } from './types';
|
|
11
14
|
export interface DocResult {
|
|
12
15
|
/** Items in document order */
|
|
13
16
|
items: DocItem[];
|
|
@@ -21,6 +24,11 @@ export type DocItem = {
|
|
|
21
24
|
type: 'function';
|
|
22
25
|
name: string;
|
|
23
26
|
signature: string;
|
|
27
|
+
} | {
|
|
28
|
+
type: 'class';
|
|
29
|
+
name: string;
|
|
30
|
+
extendsName?: string;
|
|
31
|
+
members: string[];
|
|
24
32
|
};
|
|
25
33
|
/**
|
|
26
34
|
* Generate documentation from TJS source
|
|
@@ -33,9 +41,7 @@ export declare function generateDocs(source: string): DocResult;
|
|
|
33
41
|
* Type metadata for a function parameter
|
|
34
42
|
*/
|
|
35
43
|
export interface ParamTypeInfo {
|
|
36
|
-
type?:
|
|
37
|
-
kind: string;
|
|
38
|
-
};
|
|
44
|
+
type?: TypeDescriptor;
|
|
39
45
|
required?: boolean;
|
|
40
46
|
example?: any;
|
|
41
47
|
}
|
|
@@ -44,9 +50,7 @@ export interface ParamTypeInfo {
|
|
|
44
50
|
*/
|
|
45
51
|
export interface FunctionTypeInfo {
|
|
46
52
|
params?: Record<string, ParamTypeInfo>;
|
|
47
|
-
returns?:
|
|
48
|
-
kind: string;
|
|
49
|
-
};
|
|
53
|
+
returns?: TypeDescriptor;
|
|
50
54
|
}
|
|
51
55
|
/**
|
|
52
56
|
* Generate markdown documentation with type metadata
|
|
@@ -67,3 +71,24 @@ export interface FunctionTypeInfo {
|
|
|
67
71
|
* ```
|
|
68
72
|
*/
|
|
69
73
|
export declare function generateDocsMarkdown(source: string, types?: Record<string, FunctionTypeInfo>): string;
|
|
74
|
+
/**
|
|
75
|
+
* Translate `expect(actual).matcher(expected)` calls into inline comments
|
|
76
|
+
* for documentation rendering. Other lines (setup, console.log, etc.) are
|
|
77
|
+
* preserved as-is.
|
|
78
|
+
*
|
|
79
|
+
* expect(x).toBe(y) → x // → y
|
|
80
|
+
* expect(x).toEqual(y) → x // ≡ y (deep equality)
|
|
81
|
+
* expect(x).toBeTruthy() → x // → truthy
|
|
82
|
+
* expect(x).toBeFalsy() → x // → falsy
|
|
83
|
+
* expect(x).toBeNull() → x // → null
|
|
84
|
+
* expect(x).toBeUndefined() → x // → undefined
|
|
85
|
+
* expect(x).toContain(y) → x // → contains y
|
|
86
|
+
* expect(x).toThrow() → x // → throws
|
|
87
|
+
* expect(x).toBeGreaterThan(n) → x // → > n
|
|
88
|
+
* expect(x).toBeLessThan(n) → x // → < n
|
|
89
|
+
* expect(x).toBeNaN() → x // → NaN
|
|
90
|
+
*
|
|
91
|
+
* Uses balanced-paren scanning so nested calls (`expect(f(a, b)).toBe(c)`)
|
|
92
|
+
* work correctly.
|
|
93
|
+
*/
|
|
94
|
+
export declare function prettifyTestBody(body: string): string;
|
|
@@ -29,8 +29,16 @@ export interface LintOptions {
|
|
|
29
29
|
unreachableCode?: boolean;
|
|
30
30
|
/** Warn about explicit `new` keyword usage (TJS makes classes callable without new) */
|
|
31
31
|
noExplicitNew?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Check `let` declarations for missing type information and forbid literal
|
|
34
|
+
* undefined/null assignments to typed lets. If undefined, the parser's
|
|
35
|
+
* `TjsSafeAssign` mode controls whether the rule runs.
|
|
36
|
+
*/
|
|
37
|
+
safeAssign?: boolean;
|
|
32
38
|
/** Filename for error messages */
|
|
33
39
|
filename?: string;
|
|
40
|
+
/** Treat safeAssign violations as errors instead of warnings (TjsStrict semantics) */
|
|
41
|
+
strict?: boolean;
|
|
34
42
|
}
|
|
35
43
|
/**
|
|
36
44
|
* Lint TJS source code
|
|
@@ -309,3 +309,21 @@ export declare function validateNoEval(source: string): string;
|
|
|
309
309
|
* Otherwise, performs a bare property access (throws as usual).
|
|
310
310
|
*/
|
|
311
311
|
export declare function transformBangAccess(source: string): string;
|
|
312
|
+
/**
|
|
313
|
+
* Transform `let x: <example>` and `let x: <example> = value` declarations.
|
|
314
|
+
*
|
|
315
|
+
* Strips the `: <example>` annotation so Acorn can parse, and records the
|
|
316
|
+
* variable name + example text so the linter and (later) type inference can
|
|
317
|
+
* use the annotation. Acorn rejects the colon since it is not valid JS.
|
|
318
|
+
*
|
|
319
|
+
* let x: '' → let x (annotation: x → '')
|
|
320
|
+
* let x: 0 = 5 → let x = 5 (annotation: x → 0)
|
|
321
|
+
* let result: { ok: false } = ... (annotation: result → { ok: false })
|
|
322
|
+
*
|
|
323
|
+
* Only `let` is processed. `const` always has an initializer, so the type
|
|
324
|
+
* is always inferable. `var` is rejected by TjsNoVar mode.
|
|
325
|
+
*/
|
|
326
|
+
export declare function transformLetTypeAnnotations(source: string): {
|
|
327
|
+
source: string;
|
|
328
|
+
annotations: Map<string, string>;
|
|
329
|
+
};
|
|
@@ -124,6 +124,8 @@ export interface TjsModes {
|
|
|
124
124
|
tjsSafeEval: boolean;
|
|
125
125
|
/** TjsNoVar: var declarations are syntax errors */
|
|
126
126
|
tjsNoVar: boolean;
|
|
127
|
+
/** TjsSafeAssign: let declarations need an initializer or `: example` annotation; literal undefined/null/void 0 assigned to typed lets is flagged */
|
|
128
|
+
tjsSafeAssign: boolean;
|
|
127
129
|
}
|
|
128
130
|
/**
|
|
129
131
|
* Extension info for a single extend block
|
|
@@ -8,6 +8,12 @@ import type { Program, FunctionDeclaration } from 'acorn';
|
|
|
8
8
|
export type { ParseOptions, WasmBlock, TestBlock, PreprocessOptions, TjsModes, } from './parser-types';
|
|
9
9
|
import type { ParseOptions, WasmBlock, TestBlock, PreprocessOptions, TjsModes } from './parser-types';
|
|
10
10
|
export { transformExtensionCalls } from './parser-transforms';
|
|
11
|
+
/**
|
|
12
|
+
* Strip single-line comments (//) from source.
|
|
13
|
+
* Replaces comment content with spaces to preserve character offsets.
|
|
14
|
+
* Skips // inside strings and block comments.
|
|
15
|
+
*/
|
|
16
|
+
export declare function stripLineComments(source: string): string;
|
|
11
17
|
export declare function preprocess(source: string, options?: PreprocessOptions): {
|
|
12
18
|
source: string;
|
|
13
19
|
returnType?: string;
|
|
@@ -23,6 +29,7 @@ export declare function preprocess(source: string, options?: PreprocessOptions):
|
|
|
23
29
|
testErrors: string[];
|
|
24
30
|
polymorphicNames: Set<string>;
|
|
25
31
|
extensions: Map<string, Set<string>>;
|
|
32
|
+
letAnnotations: Map<string, string>;
|
|
26
33
|
};
|
|
27
34
|
/**
|
|
28
35
|
* Parse source code into an Acorn AST
|
|
@@ -39,6 +46,8 @@ export declare function parse(source: string, options?: ParseOptions): {
|
|
|
39
46
|
wasmBlocks: WasmBlock[];
|
|
40
47
|
tests: TestBlock[];
|
|
41
48
|
testErrors: string[];
|
|
49
|
+
letAnnotations: Map<string, string>;
|
|
50
|
+
tjsModes: TjsModes;
|
|
42
51
|
};
|
|
43
52
|
/**
|
|
44
53
|
* Validate that the source contains exactly one function declaration
|
|
@@ -226,6 +226,16 @@ export declare function IsNot(a: unknown, b: unknown): boolean;
|
|
|
226
226
|
* Usage: `typeof x` with TjsEquals transforms to `TypeOf(x)`
|
|
227
227
|
*/
|
|
228
228
|
export declare function TypeOf(value: unknown): string;
|
|
229
|
+
/**
|
|
230
|
+
* Honest boolean coercion. Like `Boolean(x)` but unwraps boxed primitives
|
|
231
|
+
* first, fixing the JS footgun `Boolean(new Boolean(false)) === true`.
|
|
232
|
+
*
|
|
233
|
+
* Under TjsStandard, the source rewriter wraps every truthiness context
|
|
234
|
+
* (if/while/for/do-while conditions, `!`, `&&`, `||`, ternary, and
|
|
235
|
+
* top-level `Boolean(x)` calls) with this function so a boxed `false`
|
|
236
|
+
* actually behaves as `false`.
|
|
237
|
+
*/
|
|
238
|
+
export declare function toBool(value: unknown): boolean;
|
|
229
239
|
export declare function Eq(a: unknown, b: unknown): boolean;
|
|
230
240
|
/**
|
|
231
241
|
* Honest inequality — what != should have been.
|
|
@@ -291,6 +301,26 @@ type TypeSpec = string | {
|
|
|
291
301
|
check: (v: unknown) => boolean | string;
|
|
292
302
|
description: string;
|
|
293
303
|
};
|
|
304
|
+
/**
|
|
305
|
+
* Check that a passed-in function's declared shape matches the expected
|
|
306
|
+
* shape. Returns the function unchanged on a match, or a MonadicError on
|
|
307
|
+
* mismatch. Untyped functions (no `__tjs` metadata — anonymous arrows
|
|
308
|
+
* like `x => false`) pass through unchanged on the assumption that the
|
|
309
|
+
* caller knows what they're doing; they accept any args and return
|
|
310
|
+
* whatever they return.
|
|
311
|
+
*
|
|
312
|
+
* This is a ONE-SHOT check at pass time, NOT a per-call wrapper. The TJS
|
|
313
|
+
* design call: a wrong-shape callback is ONE error at the boundary, not
|
|
314
|
+
* N errors when the receiving function invokes the callback N times.
|
|
315
|
+
*
|
|
316
|
+
* Compatibility rules (deliberately permissive — strict subtyping is a
|
|
317
|
+
* separate, larger feature):
|
|
318
|
+
* - For each expected param: the actual function may declare fewer
|
|
319
|
+
* params (extras simply not used). If both declare a kind, they
|
|
320
|
+
* must match exactly. Either side being `any` always matches.
|
|
321
|
+
* - For the return type: same exact-match rule when both are known.
|
|
322
|
+
*/
|
|
323
|
+
export declare function checkFnShape(fn: unknown, expectedParamKinds: string[], expectedReturnKind: string, path: string): unknown;
|
|
294
324
|
/** Parameter metadata with optional location */
|
|
295
325
|
interface ParamMeta {
|
|
296
326
|
type: TypeSpec;
|
|
@@ -376,6 +406,7 @@ export declare function createRuntime(): {
|
|
|
376
406
|
checkType: typeof checkType;
|
|
377
407
|
validateArgs: typeof validateArgs;
|
|
378
408
|
wrap: typeof wrap;
|
|
409
|
+
checkFnShape: typeof checkFnShape;
|
|
379
410
|
wrapClass: typeof wrapClass;
|
|
380
411
|
compareVersions: typeof compareVersions;
|
|
381
412
|
versionsCompatible: typeof versionsCompatible;
|
|
@@ -419,6 +450,7 @@ export declare function createRuntime(): {
|
|
|
419
450
|
Eq: typeof Eq;
|
|
420
451
|
NotEq: typeof NotEq;
|
|
421
452
|
TypeOf: typeof TypeOf;
|
|
453
|
+
toBool: typeof toBool;
|
|
422
454
|
tjsEquals: symbol;
|
|
423
455
|
registerExtension: (typeName: string, methodName: string, fn: (...args: any[]) => any) => void;
|
|
424
456
|
resolveExtension: (value: unknown, methodName: string) => ((...args: any[]) => any) | undefined;
|
|
@@ -444,6 +476,7 @@ export declare const runtime: {
|
|
|
444
476
|
checkType: typeof checkType;
|
|
445
477
|
validateArgs: typeof validateArgs;
|
|
446
478
|
wrap: typeof wrap;
|
|
479
|
+
checkFnShape: typeof checkFnShape;
|
|
447
480
|
wrapClass: typeof wrapClass;
|
|
448
481
|
compareVersions: typeof compareVersions;
|
|
449
482
|
versionsCompatible: typeof versionsCompatible;
|
|
@@ -489,6 +522,7 @@ export declare const runtime: {
|
|
|
489
522
|
Eq: typeof Eq;
|
|
490
523
|
NotEq: typeof NotEq;
|
|
491
524
|
TypeOf: typeof TypeOf;
|
|
525
|
+
toBool: typeof toBool;
|
|
492
526
|
};
|
|
493
527
|
/**
|
|
494
528
|
* Install runtime globally (idempotent, version-checked)
|
package/dist/src/lang/types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { Node } from 'acorn';
|
|
|
5
5
|
import type { SeqNode } from '../builder';
|
|
6
6
|
/** Represents a type extracted from value patterns */
|
|
7
7
|
export interface TypeDescriptor {
|
|
8
|
-
kind: 'string' | 'number' | 'integer' | 'non-negative-integer' | 'boolean' | 'null' | 'undefined' | 'array' | 'object' | 'union' | 'any';
|
|
8
|
+
kind: 'string' | 'number' | 'integer' | 'non-negative-integer' | 'boolean' | 'null' | 'undefined' | 'array' | 'object' | 'union' | 'function' | 'any';
|
|
9
9
|
nullable?: boolean;
|
|
10
10
|
/** For arrays: the element type */
|
|
11
11
|
items?: TypeDescriptor;
|
|
@@ -15,6 +15,14 @@ export interface TypeDescriptor {
|
|
|
15
15
|
members?: TypeDescriptor[];
|
|
16
16
|
/** For destructured parameters: full parameter descriptors */
|
|
17
17
|
destructuredParams?: Record<string, ParameterDescriptor>;
|
|
18
|
+
/** For functions: declared parameters with names and inferred types */
|
|
19
|
+
params?: Array<{
|
|
20
|
+
name: string;
|
|
21
|
+
type: TypeDescriptor;
|
|
22
|
+
}>;
|
|
23
|
+
/** For functions: inferred return type. Concise arrow bodies infer from
|
|
24
|
+
* the expression; block bodies and complex expressions stay `any`. */
|
|
25
|
+
returns?: TypeDescriptor;
|
|
18
26
|
}
|
|
19
27
|
/** Describes a function parameter */
|
|
20
28
|
export interface ParameterDescriptor {
|
package/dist/src/rbac/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Store } from '../store/interface';
|
|
2
|
-
export { evaluateAccessShortcut, selectAccessRule, validateSchema, interpretRuleResult, hasRoleLevel, buildRuleContext, } from './rules.
|
|
2
|
+
export { evaluateAccessShortcut, selectAccessRule, validateSchema, interpretRuleResult, hasRoleLevel, buildRuleContext, } from './rules.tjs';
|
|
3
3
|
/**
|
|
4
4
|
* Security rule definition
|
|
5
5
|
*/
|