rip-lang 3.15.4 → 3.16.1
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/README.md +6 -4
- package/bin/rip +167 -12
- package/docs/AGENTS.md +1 -1
- package/docs/RIP-APP.md +808 -0
- package/docs/RIP-DUCKDB.md +477 -0
- package/docs/RIP-INTRO.md +396 -0
- package/docs/RIP-LANG.md +59 -5
- package/docs/RIP-SCHEMA.md +191 -8
- package/docs/RIP-TYPES.md +74 -103
- package/docs/demo/README.md +4 -3
- package/docs/dist/rip.js +3627 -1470
- package/docs/dist/rip.min.js +671 -244
- package/docs/dist/rip.min.js.br +0 -0
- package/docs/example/index.json +7 -7
- package/docs/example/index.json.br +0 -0
- package/docs/extensions/duckdb/manifest.json +1 -1
- package/docs/extensions/duckdb/v1.5.2/linux_amd64/ripdb.duckdb_extension.gz +0 -0
- package/docs/extensions/duckdb/v1.5.2/osx_arm64/ripdb.duckdb_extension.gz +0 -0
- package/docs/extensions/vscode/print/index.html +2 -1
- package/docs/extensions/vscode/print/print-1.0.13.vsix +0 -0
- package/docs/extensions/vscode/print/print-1.0.14.vsix +0 -0
- package/docs/extensions/vscode/print/print-latest.vsix +0 -0
- package/docs/extensions/vscode/rip/rip-0.5.15.vsix +0 -0
- package/docs/extensions/vscode/rip/rip-latest.vsix +0 -0
- package/docs/ui/bundle.json +61 -0
- package/docs/ui/bundle.json.br +0 -0
- package/docs/ui/hljs-rip.js +0 -7
- package/docs/ui/index.css +66 -23
- package/docs/ui/index.html +6 -6
- package/package.json +9 -3
- package/rip-loader.js +64 -2
- package/src/AGENTS.md +63 -36
- package/src/browser.js +96 -14
- package/src/compiler.js +960 -143
- package/src/components.js +794 -88
- package/src/{types-emit.js → dts.js} +181 -71
- package/src/grammar/README.md +1 -1
- package/src/grammar/grammar.rip +111 -97
- package/src/lexer.js +132 -18
- package/src/parser.js +203 -205
- package/src/repl.js +74 -6
- package/src/schema/runtime-orm.js +168 -4
- package/src/schema/runtime-validate.js +146 -2
- package/src/schema/runtime.generated.js +314 -6
- package/src/schema/schema.js +5 -5
- package/src/sourcemaps.js +277 -1
- package/src/stdlib.js +253 -0
- package/src/typecheck.js +2023 -106
- package/src/types.js +127 -7
- package/docs/ui/accordion.rip +0 -103
- package/docs/ui/alert-dialog.rip +0 -53
- package/docs/ui/autocomplete.rip +0 -115
- package/docs/ui/avatar.rip +0 -37
- package/docs/ui/badge.rip +0 -15
- package/docs/ui/breadcrumb.rip +0 -47
- package/docs/ui/button-group.rip +0 -26
- package/docs/ui/button.rip +0 -23
- package/docs/ui/card.rip +0 -25
- package/docs/ui/carousel.rip +0 -110
- package/docs/ui/checkbox-group.rip +0 -61
- package/docs/ui/checkbox.rip +0 -33
- package/docs/ui/collapsible.rip +0 -50
- package/docs/ui/combobox.rip +0 -130
- package/docs/ui/context-menu.rip +0 -88
- package/docs/ui/date-picker.rip +0 -206
- package/docs/ui/dialog.rip +0 -60
- package/docs/ui/drawer.rip +0 -58
- package/docs/ui/editable-value.rip +0 -82
- package/docs/ui/field.rip +0 -53
- package/docs/ui/fieldset.rip +0 -22
- package/docs/ui/form.rip +0 -39
- package/docs/ui/grid.rip +0 -901
- package/docs/ui/input-group.rip +0 -28
- package/docs/ui/input.rip +0 -36
- package/docs/ui/label.rip +0 -16
- package/docs/ui/menu.rip +0 -134
- package/docs/ui/menubar.rip +0 -151
- package/docs/ui/meter.rip +0 -36
- package/docs/ui/multi-select.rip +0 -203
- package/docs/ui/native-select.rip +0 -33
- package/docs/ui/nav-menu.rip +0 -126
- package/docs/ui/number-field.rip +0 -162
- package/docs/ui/otp-field.rip +0 -89
- package/docs/ui/pagination.rip +0 -123
- package/docs/ui/popover.rip +0 -93
- package/docs/ui/preview-card.rip +0 -75
- package/docs/ui/progress.rip +0 -25
- package/docs/ui/radio-group.rip +0 -57
- package/docs/ui/resizable.rip +0 -123
- package/docs/ui/scroll-area.rip +0 -145
- package/docs/ui/select.rip +0 -151
- package/docs/ui/separator.rip +0 -17
- package/docs/ui/skeleton.rip +0 -22
- package/docs/ui/slider.rip +0 -165
- package/docs/ui/spinner.rip +0 -17
- package/docs/ui/table.rip +0 -27
- package/docs/ui/tabs.rip +0 -113
- package/docs/ui/textarea.rip +0 -48
- package/docs/ui/toast.rip +0 -87
- package/docs/ui/toggle-group.rip +0 -71
- package/docs/ui/toggle.rip +0 -24
- package/docs/ui/toolbar.rip +0 -38
- package/docs/ui/tooltip.rip +0 -85
- package/src/app.rip +0 -1571
- package/src/sourcemap-merge.js +0 -287
- /package/docs/demo/{components → routes}/_layout.rip +0 -0
- /package/docs/demo/{components → routes}/about.rip +0 -0
- /package/docs/demo/{components → routes}/card.rip +0 -0
- /package/docs/demo/{components → routes}/counter.rip +0 -0
- /package/docs/demo/{components → routes}/index.rip +0 -0
- /package/docs/demo/{components → routes}/todos.rip +0 -0
- /package/src/schema/{dts-emit.js → dts.js} +0 -0
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { SCHEMA_INTRINSIC_DECLS, emitSchemaTypes } from "./schema/dts
|
|
1
|
+
import { SCHEMA_INTRINSIC_DECLS, emitSchemaTypes } from "./schema/dts.js";
|
|
2
2
|
import { setTypesEmitter } from "./compiler.js";
|
|
3
3
|
|
|
4
4
|
// Type System — .d.ts emission for Rip (CLI / typecheck only).
|
|
5
5
|
//
|
|
6
|
-
// This module is
|
|
6
|
+
// This module is the type system's sibling to src/schema/dts.js —
|
|
7
|
+
// both are CLI/editor-only `.d.ts` emitters for their respective
|
|
8
|
+
// subsystem. The `dts` name signals "compile-time .d.ts emitter,
|
|
9
|
+
// never reachable from the browser bundle."
|
|
7
10
|
//
|
|
8
11
|
// emitTypes(tokens, sexpr, source) — generates .d.ts from annotated
|
|
9
12
|
// tokens and the parsed s-expression tree.
|
|
@@ -31,12 +34,13 @@ export const INTRINSIC_TYPE_DECLS = [
|
|
|
31
34
|
"type __RipBrowserElement = Omit<HTMLElement, 'querySelector' | 'querySelectorAll' | 'closest' | 'setAttribute' | 'hidden'> & { hidden: boolean | 'until-found'; setAttribute(qualifiedName: string, value: any): void; querySelector(selectors: string): __RipBrowserElement | null; querySelectorAll(selectors: string): NodeListOf<__RipBrowserElement>; closest(selectors: string): __RipBrowserElement | null; };",
|
|
32
35
|
"type __RipDomEl<K extends __RipTag> = Omit<__RipElementMap[K], 'querySelector' | 'querySelectorAll' | 'closest' | 'setAttribute' | 'hidden'> & __RipBrowserElement;",
|
|
33
36
|
"type __RipAttrKeys<T> = { [K in keyof T]-?: K extends 'style' | 'classList' | 'className' | 'nodeValue' | 'textContent' | 'innerHTML' | 'innerText' | 'outerHTML' | 'outerText' | 'scrollLeft' | 'scrollTop' ? never : K extends `on${string}` | `aria${string}Element` | `aria${string}Elements` ? never : T[K] extends (...args: any[]) => any ? never : (<V>() => V extends Pick<T, K> ? 1 : 2) extends (<V>() => V extends { -readonly [P in K]: T[P] } ? 1 : 2) ? K : never }[keyof T] & string;",
|
|
34
|
-
|
|
35
|
-
'type
|
|
36
|
-
'type
|
|
37
|
+
"type __RipEvents<K extends __RipTag> = { [E in keyof HTMLElementEventMap as `@${E}`]?: ((event: RipEvent<HTMLElementEventMap[E], __RipElementMap[K]>) => void) | null };",
|
|
38
|
+
'type RipEvent<E extends Event, T extends EventTarget> = E & { readonly target: T; readonly currentTarget: T };',
|
|
39
|
+
'type __RipClassValue = string | boolean | null | undefined | Record<string, boolean | null | undefined> | __RipClassValue[];',
|
|
40
|
+
"type __RipProps<K extends __RipTag> = { [P in __RipAttrKeys<__RipElementMap[K]>]?: __RipElementMap[K][P] } & __RipEvents<K> & { ref?: string; class?: __RipClassValue | __RipClassValue[]; style?: string; [k: `data-${string}`]: any; [k: `aria-${string}`]: any };",
|
|
37
41
|
];
|
|
38
42
|
|
|
39
|
-
export const INTRINSIC_FN_DECL = 'declare function __ripEl<K extends __RipTag>(tag: K, props?: __RipProps<K>): void;';
|
|
43
|
+
export const INTRINSIC_FN_DECL = 'declare function __ripEl<K extends __RipTag>(tag: K, props?: __RipProps<K>): void;\ndeclare function __ripRoute<const T extends string>(s: T): T;';
|
|
40
44
|
|
|
41
45
|
export const ARIA_TYPE_DECLS = [
|
|
42
46
|
'type __RipAriaNavHandlers = { next?: () => void; prev?: () => void; first?: () => void; last?: () => void; select?: () => void; dismiss?: () => void; tab?: () => void; char?: () => void; };',
|
|
@@ -63,6 +67,17 @@ export const SIGNAL_FN = 'declare function __state<T>(value: T | Signal<T>): Sig
|
|
|
63
67
|
export const COMPUTED_INTERFACE = 'interface Computed<T> { readonly value: T; read(): T; lock(): Computed<T>; free(): Computed<T>; kill(): T; }';
|
|
64
68
|
export const COMPUTED_FN = 'declare function __computed<T>(fn: () => T): Computed<T>;';
|
|
65
69
|
export const EFFECT_FN = 'declare function __effect(fn: () => void | (() => void)): () => void;';
|
|
70
|
+
export const BATCH_FN = 'declare function __batch<T>(fn: () => T): T;';
|
|
71
|
+
|
|
72
|
+
// Names destructured from `globalThis.__rip` in the source. The DTS
|
|
73
|
+
// preamble and the post-compile `declare function` injection both need
|
|
74
|
+
// to skip auto-declaring these names — the explicit binding shadows the
|
|
75
|
+
// global and would otherwise trip TS2630.
|
|
76
|
+
export function ripDestructuredNames(source) {
|
|
77
|
+
if (typeof source !== 'string') return new Set();
|
|
78
|
+
const inside = (source.match(/\{\s*([^}]*?)\s*\}\s*=\s*globalThis\.__rip\b/) || [])[1] || '';
|
|
79
|
+
return new Set(inside.split(',').map(s => s.trim().split(/[:\s]/)[0]).filter(Boolean));
|
|
80
|
+
}
|
|
66
81
|
|
|
67
82
|
// ============================================================================
|
|
68
83
|
// emitTypes — generate .d.ts from annotated tokens + s-expression tree
|
|
@@ -74,10 +89,13 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
74
89
|
let indentStr = ' ';
|
|
75
90
|
let indent = () => indentStr.repeat(indentLevel);
|
|
76
91
|
let inClass = false;
|
|
92
|
+
let inSubclass = false; // Tracks whether the active class extends another
|
|
77
93
|
let classFields = new Set(); // Track emitted field names to avoid duplicates
|
|
78
94
|
let usesSignal = false;
|
|
79
95
|
let usesComputed = false;
|
|
96
|
+
let usesBatch = false;
|
|
80
97
|
let usesRipIntrinsicProps = false;
|
|
98
|
+
const explicitlyBound = ripDestructuredNames(source);
|
|
81
99
|
const sourceLines = typeof source === 'string' ? source.split('\n') : [];
|
|
82
100
|
|
|
83
101
|
// Pre-scan: detect reactive operators regardless of type annotations.
|
|
@@ -87,6 +105,7 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
87
105
|
const tag = tokens[i][0];
|
|
88
106
|
if (tag === 'REACTIVE_ASSIGN') usesSignal = true;
|
|
89
107
|
else if (tag === 'COMPUTED_ASSIGN') usesComputed = true;
|
|
108
|
+
else if (tag === 'IDENTIFIER' && tokens[i][1] === '__batch') usesBatch = true;
|
|
90
109
|
}
|
|
91
110
|
|
|
92
111
|
// Format { prop; prop } into multi-line block. Only applies when the
|
|
@@ -190,7 +209,7 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
190
209
|
let type = tokens[j].data?.type;
|
|
191
210
|
if (type) hasAnyType = true;
|
|
192
211
|
let hasDefault = tokens[j + 1]?.[0] === '=';
|
|
193
|
-
props.push({ kind: 'rename', propName, localName, type: type ?
|
|
212
|
+
props.push({ kind: 'rename', propName, localName, type: type ? tsType(type) : null, hasDefault });
|
|
194
213
|
j++;
|
|
195
214
|
if (hasDefault) j = skipDefault(tokens, j);
|
|
196
215
|
}
|
|
@@ -203,7 +222,7 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
203
222
|
let type = tokens[j].data?.type;
|
|
204
223
|
if (type) hasAnyType = true;
|
|
205
224
|
let hasDefault = tokens[j + 1]?.[0] === '=';
|
|
206
|
-
props.push({ kind: 'simple', propName: name, type: type ?
|
|
225
|
+
props.push({ kind: 'simple', propName: name, type: type ? tsType(type) : null, hasDefault });
|
|
207
226
|
j++;
|
|
208
227
|
if (hasDefault) j = skipDefault(tokens, j);
|
|
209
228
|
continue;
|
|
@@ -254,7 +273,7 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
254
273
|
let name = tokens[j][1];
|
|
255
274
|
let type = tokens[j].data?.type;
|
|
256
275
|
names.push(name);
|
|
257
|
-
elemTypes.push(type ?
|
|
276
|
+
elemTypes.push(type ? tsType(type) : null);
|
|
258
277
|
if (type) hasAnyType = true;
|
|
259
278
|
}
|
|
260
279
|
j++;
|
|
@@ -268,7 +287,13 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
268
287
|
};
|
|
269
288
|
|
|
270
289
|
// Collect function parameters (handles simple, destructured, rest, defaults)
|
|
271
|
-
|
|
290
|
+
//
|
|
291
|
+
// `subclassConstructor` mirrors the compiler's @-shorthand renaming:
|
|
292
|
+
// when a constructor lives inside `class Foo extends Bar`, the compiler
|
|
293
|
+
// rewrites `@name` params to `_name` so the assignment `this.name =
|
|
294
|
+
// _name` can run AFTER `super(...)`. The shadow TS must use the same
|
|
295
|
+
// `_name` binding or the body's `_name` references will be unresolved.
|
|
296
|
+
let collectParams = (tokens, startIdx, subclassConstructor = false) => {
|
|
272
297
|
let params = [];
|
|
273
298
|
let fields = []; // Track @param:: type for class field emission
|
|
274
299
|
let j = startIdx;
|
|
@@ -296,8 +321,9 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
296
321
|
if (tokens[j]?.[0] === 'PROPERTY' || tokens[j]?.[0] === 'IDENTIFIER') {
|
|
297
322
|
let name = tokens[j][1];
|
|
298
323
|
let type = tokens[j].data?.type;
|
|
299
|
-
|
|
300
|
-
|
|
324
|
+
let paramName = subclassConstructor ? `_${name}` : name;
|
|
325
|
+
params.push(type ? `${paramName}: ${tsType(type)}` : paramName);
|
|
326
|
+
if (type) fields.push({ name, type: tsType(type) });
|
|
301
327
|
j++;
|
|
302
328
|
}
|
|
303
329
|
continue;
|
|
@@ -309,7 +335,7 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
309
335
|
if (tokens[j]?.[0] === 'IDENTIFIER') {
|
|
310
336
|
let name = tokens[j][1];
|
|
311
337
|
let type = tokens[j].data?.type;
|
|
312
|
-
params.push(type ? `...${name}: ${
|
|
338
|
+
params.push(type ? `...${name}: ${tsType(type)}` : `...${name}: any[]`);
|
|
313
339
|
j++;
|
|
314
340
|
}
|
|
315
341
|
continue;
|
|
@@ -352,15 +378,19 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
352
378
|
hasDefault = true;
|
|
353
379
|
}
|
|
354
380
|
|
|
355
|
-
let isOptional = hasDefault || tok.data?.
|
|
381
|
+
let isOptional = hasDefault || tok.data?.optional;
|
|
356
382
|
if (paramType) {
|
|
357
|
-
params.push(`${paramName}${isOptional ? '?' : ''}: ${
|
|
383
|
+
params.push(`${paramName}${isOptional ? '?' : ''}: ${tsType(paramType)}`);
|
|
358
384
|
} else {
|
|
359
385
|
params.push(paramName);
|
|
360
386
|
}
|
|
361
387
|
j++;
|
|
362
388
|
|
|
363
|
-
// Skip past default value expression
|
|
389
|
+
// Skip past default value expression. Stops at the next top-level
|
|
390
|
+
// comma, the matching CALL_END, or the matching PARAM_END — the
|
|
391
|
+
// last is critical for arrow-function param lists wrapped in
|
|
392
|
+
// PARAM_START/PARAM_END (e.g. `(input, opts = {}) -> ...`),
|
|
393
|
+
// because PARAM_END is the only sentinel that closes the list.
|
|
364
394
|
if (hasDefault) {
|
|
365
395
|
j++; // skip =
|
|
366
396
|
let dd = 0;
|
|
@@ -368,7 +398,7 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
368
398
|
let dt = tokens[j];
|
|
369
399
|
if (dt[0] === '(' || dt[0] === '[' || dt[0] === '{') dd++;
|
|
370
400
|
if (dt[0] === ')' || dt[0] === ']' || dt[0] === '}') dd--;
|
|
371
|
-
if (dd === 0 && (dt[1] === ',' || dt[0] === 'CALL_END')) break;
|
|
401
|
+
if (dd === 0 && (dt[1] === ',' || dt[0] === 'CALL_END' || dt[0] === 'PARAM_END')) break;
|
|
372
402
|
j++;
|
|
373
403
|
}
|
|
374
404
|
}
|
|
@@ -381,10 +411,26 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
381
411
|
return { params, fields, endIdx: j };
|
|
382
412
|
};
|
|
383
413
|
|
|
414
|
+
let paramDepth = 0;
|
|
415
|
+
// bodyDepth counts how deep we are inside `def`/`->` bodies. At
|
|
416
|
+
// bodyDepth === 0 we're at module scope and dts.js may emit
|
|
417
|
+
// `declare function NAME(...)` for arrow assignments. Inside a
|
|
418
|
+
// function body, those assignments would be locals (e.g.
|
|
419
|
+
// `inst = (input, opts) -> ...` inside `def makeInstance`) and
|
|
420
|
+
// emitting a module-scope declare for them is wrong — TypeScript
|
|
421
|
+
// would treat the local and the (synthetic) global as separate
|
|
422
|
+
// bindings, causing the local to lose its type info.
|
|
423
|
+
let bodyDepth = 0;
|
|
384
424
|
for (let i = 0; i < tokens.length; i++) {
|
|
385
425
|
let t = tokens[i];
|
|
386
426
|
let tag = t[0];
|
|
387
427
|
|
|
428
|
+
// Skip tokens inside parameter lists — annotations there describe
|
|
429
|
+
// the params of an enclosing function/method, not a top-level binding.
|
|
430
|
+
if (tag === 'PARAM_START') { paramDepth++; continue; }
|
|
431
|
+
if (tag === 'PARAM_END') { paramDepth--; continue; }
|
|
432
|
+
if (paramDepth > 0) continue;
|
|
433
|
+
|
|
388
434
|
// Track export flag
|
|
389
435
|
let exported = false;
|
|
390
436
|
if (tag === 'EXPORT') {
|
|
@@ -394,18 +440,17 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
394
440
|
t = tokens[i];
|
|
395
441
|
tag = t[0];
|
|
396
442
|
|
|
397
|
-
// Export default
|
|
443
|
+
// Export default — runtime statement, not a type declaration.
|
|
444
|
+
// The compiled JS body already carries `export default x`; emitting
|
|
445
|
+
// a duplicate here puts two `export default` declarations in front
|
|
446
|
+
// of the TypeScript language service and triggers TS2528. Skip the
|
|
447
|
+
// entire `export default ...` clause and let the body provide it.
|
|
398
448
|
if (tag === 'DEFAULT') {
|
|
399
449
|
i++;
|
|
400
|
-
if (i
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
// export default IDENTIFIER (re-export)
|
|
405
|
-
if (tag === 'IDENTIFIER') {
|
|
406
|
-
lines.push(`${indent()}export default ${t[1]};`);
|
|
450
|
+
if (i < tokens.length) {
|
|
451
|
+
t = tokens[i];
|
|
452
|
+
tag = t[0];
|
|
407
453
|
}
|
|
408
|
-
// export default { ... } or other expressions — skip for now
|
|
409
454
|
continue;
|
|
410
455
|
}
|
|
411
456
|
}
|
|
@@ -414,8 +459,21 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
414
459
|
if (tag === 'IMPORT') {
|
|
415
460
|
let importTokens = [];
|
|
416
461
|
let j = i + 1;
|
|
417
|
-
|
|
418
|
-
|
|
462
|
+
let depth = 0;
|
|
463
|
+
while (j < tokens.length) {
|
|
464
|
+
const tk = tokens[j];
|
|
465
|
+
const tg = tk[0];
|
|
466
|
+
if (tg === '{' || tg === '[' || tg === '(') depth++;
|
|
467
|
+
else if (tg === '}' || tg === ']' || tg === ')') depth--;
|
|
468
|
+
// Stop at top-level TERMINATOR; skip inner TERMINATORs and the
|
|
469
|
+
// synthetic INDENT/OUTDENT pair the lexer inserts inside multi-line
|
|
470
|
+
// braced import lists.
|
|
471
|
+
if (tg === 'TERMINATOR') {
|
|
472
|
+
if (depth <= 0) break;
|
|
473
|
+
j++; continue;
|
|
474
|
+
}
|
|
475
|
+
if (tg === 'INDENT' || tg === 'OUTDENT') { j++; continue; }
|
|
476
|
+
importTokens.push(tk);
|
|
419
477
|
j++;
|
|
420
478
|
}
|
|
421
479
|
// Reconstruct: join with spaces, then clean up spacing
|
|
@@ -442,7 +500,7 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
442
500
|
let nameToken = ot[1]; // DEF is [0], name is [1]
|
|
443
501
|
let { params: paramList } = collectParams(ot, 2);
|
|
444
502
|
let returnType = nameToken.data?.returnType;
|
|
445
|
-
let ret = returnType ? `: ${
|
|
503
|
+
let ret = returnType ? `: ${tsType(returnType)}` : '';
|
|
446
504
|
let declare = inClass ? '' : (exp ? '' : 'declare ');
|
|
447
505
|
let typeParams = data.typeParams || '';
|
|
448
506
|
if (inClass) {
|
|
@@ -454,7 +512,7 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
454
512
|
let ext = data.extends ? ` extends ${data.extends}` : '';
|
|
455
513
|
emitBlock(`${exp}interface ${data.name}${params}${ext} `, data.typeText || '{}', '');
|
|
456
514
|
} else {
|
|
457
|
-
let typeText =
|
|
515
|
+
let typeText = tsType(data.typeText || '');
|
|
458
516
|
emitBlock(`${exp}type ${data.name}${params} = `, typeText, ';');
|
|
459
517
|
}
|
|
460
518
|
continue;
|
|
@@ -514,9 +572,11 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
514
572
|
|
|
515
573
|
// Check for extends
|
|
516
574
|
let ext = '';
|
|
575
|
+
let isSubclass = false;
|
|
517
576
|
let j = i + 2;
|
|
518
577
|
if (tokens[j]?.[0] === 'EXTENDS') {
|
|
519
578
|
ext = ` extends ${tokens[j + 1]?.[1] || ''}`;
|
|
579
|
+
isSubclass = true;
|
|
520
580
|
j += 2;
|
|
521
581
|
}
|
|
522
582
|
|
|
@@ -534,6 +594,7 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
534
594
|
if (hasTypedMembers) {
|
|
535
595
|
lines.push(`${indent()}${exp}declare class ${className}${ext} {`);
|
|
536
596
|
inClass = true;
|
|
597
|
+
inSubclass = isSubclass;
|
|
537
598
|
classFields.clear();
|
|
538
599
|
indentLevel++;
|
|
539
600
|
}
|
|
@@ -547,7 +608,7 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
547
608
|
if (!nameToken) continue;
|
|
548
609
|
let fnName = nameToken[1];
|
|
549
610
|
let returnType = nameToken.data?.returnType;
|
|
550
|
-
if (!returnType && nameToken.data?.
|
|
611
|
+
if (!returnType && nameToken.data?.bang === true) returnType = 'void';
|
|
551
612
|
let typeParams = nameToken.data?.typeParams || '';
|
|
552
613
|
|
|
553
614
|
let { params, endIdx } = collectParams(tokens, i + 2);
|
|
@@ -556,7 +617,7 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
556
617
|
if (returnType || params.some(p => p.includes(':'))) {
|
|
557
618
|
let exp = exported ? 'export ' : '';
|
|
558
619
|
let declare = inClass ? '' : (exported ? '' : 'declare ');
|
|
559
|
-
let ret = returnType ? `: ${
|
|
620
|
+
let ret = returnType ? `: ${tsType(returnType)}` : '';
|
|
560
621
|
let paramStr = params.join(', ');
|
|
561
622
|
if (inClass) {
|
|
562
623
|
lines.push(`${indent()}${fnName}${typeParams}(${paramStr})${ret};`);
|
|
@@ -593,7 +654,12 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
593
654
|
let params = [];
|
|
594
655
|
let fields = [];
|
|
595
656
|
if (tokens[j]?.[0] === 'PARAM_START') {
|
|
596
|
-
|
|
657
|
+
// The compiler renames `@name` → `_name` on subclass
|
|
658
|
+
// constructors so `this.name = _name` runs after super();
|
|
659
|
+
// mirror that here so the shadow TS body's `_name`
|
|
660
|
+
// references resolve to a real param binding.
|
|
661
|
+
let isSubCtor = inSubclass && methodName === 'constructor';
|
|
662
|
+
let result = collectParams(tokens, j, isSubCtor);
|
|
597
663
|
params = result.params;
|
|
598
664
|
fields = result.fields;
|
|
599
665
|
j = result.endIdx + 1;
|
|
@@ -612,7 +678,7 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
612
678
|
}
|
|
613
679
|
|
|
614
680
|
if (returnType || params.some(p => p.includes(':'))) {
|
|
615
|
-
let ret = returnType ? `: ${
|
|
681
|
+
let ret = returnType ? `: ${tsType(returnType)}` : '';
|
|
616
682
|
let paramStr = params.join(', ');
|
|
617
683
|
// Emit field declarations for constructor @param:: type shorthand
|
|
618
684
|
if (methodName === 'constructor' && fields.length) {
|
|
@@ -637,19 +703,41 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
637
703
|
|
|
638
704
|
// Track INDENT/OUTDENT for class body
|
|
639
705
|
if (tag === 'INDENT') {
|
|
706
|
+
bodyDepth++;
|
|
640
707
|
continue;
|
|
641
708
|
}
|
|
642
709
|
if (tag === 'OUTDENT') {
|
|
710
|
+
if (bodyDepth > 0) bodyDepth--;
|
|
643
711
|
if (inClass) {
|
|
644
712
|
indentLevel--;
|
|
645
713
|
lines.push(`${indent()}}`);
|
|
646
714
|
inClass = false;
|
|
715
|
+
inSubclass = false;
|
|
647
716
|
}
|
|
648
717
|
continue;
|
|
649
718
|
}
|
|
650
719
|
|
|
651
720
|
// Arrow function assignment: name = (params) -> body
|
|
652
|
-
|
|
721
|
+
//
|
|
722
|
+
// Emission shape depends on scope:
|
|
723
|
+
// bodyDepth === 0 → `declare function name(...): R;` at module
|
|
724
|
+
// scope. typecheck.js attaches this as an
|
|
725
|
+
// overload and copies typed params into the
|
|
726
|
+
// impl line.
|
|
727
|
+
// bodyDepth > 0 → `let name: (params) => R;` at module scope.
|
|
728
|
+
// typecheck.js's typed-local hoist pulls the
|
|
729
|
+
// `: (...) => R` into the body's `let name`,
|
|
730
|
+
// making the arrow contextually typed.
|
|
731
|
+
//
|
|
732
|
+
// The function-form is wrong inside bodies because it would create
|
|
733
|
+
// a phantom global that shadows / collides with the actual local.
|
|
734
|
+
//
|
|
735
|
+
// If the IDENTIFIER carries an explicit `name:: T = arrow` annotation,
|
|
736
|
+
// skip this path and let the typed-variable-assignment path below
|
|
737
|
+
// emit the typed form (otherwise we'd lose the user's annotation in
|
|
738
|
+
// favor of inferred `any` rest-param emission).
|
|
739
|
+
if (tag === 'IDENTIFIER' && !inClass && !t.data?.type &&
|
|
740
|
+
tokens[i + 1]?.[0] === '=' &&
|
|
653
741
|
(tokens[i + 2]?.[0] === 'PARAM_START' || tokens[i + 2]?.[0] === '(')) {
|
|
654
742
|
let fnName = t[1];
|
|
655
743
|
let j = i + 2;
|
|
@@ -669,10 +757,22 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
669
757
|
|
|
670
758
|
if (returnType || params.some(p => p.includes(':'))) {
|
|
671
759
|
let exp = exported ? 'export ' : '';
|
|
672
|
-
let declare = exported ? '' : 'declare ';
|
|
673
|
-
let ret = returnType ? `: ${expandSuffixes(returnType)}` : '';
|
|
674
760
|
let paramStr = params.join(', ');
|
|
675
|
-
|
|
761
|
+
if (bodyDepth === 0) {
|
|
762
|
+
let declare = exported ? '' : 'declare ';
|
|
763
|
+
let ret = returnType ? `: ${tsType(returnType)}` : '';
|
|
764
|
+
lines.push(`${indent()}${exp}${declare}function ${fnName}(${paramStr})${ret};`);
|
|
765
|
+
} else {
|
|
766
|
+
// `any` rather than `unknown` for the inferred-return case.
|
|
767
|
+
// The annotation's job is to give the body's `let X` a typed
|
|
768
|
+
// function shape so call-site param checks engage; we
|
|
769
|
+
// intentionally don't constrain the return type when the
|
|
770
|
+
// user didn't provide one. `unknown` would force callers to
|
|
771
|
+
// narrow before using the result, creating new false
|
|
772
|
+
// positives at every call site.
|
|
773
|
+
let ret = returnType ? tsType(returnType) : 'any';
|
|
774
|
+
lines.push(`${indent()}let ${fnName}: (${paramStr}) => ${ret};`);
|
|
775
|
+
}
|
|
676
776
|
continue;
|
|
677
777
|
}
|
|
678
778
|
}
|
|
@@ -680,7 +780,7 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
680
780
|
// Variable assignments with type annotations
|
|
681
781
|
if (tag === 'IDENTIFIER' && t.data?.type) {
|
|
682
782
|
let varName = t[1];
|
|
683
|
-
let type =
|
|
783
|
+
let type = tsType(t.data.type);
|
|
684
784
|
let next = tokens[i + 1];
|
|
685
785
|
|
|
686
786
|
if (next) {
|
|
@@ -714,7 +814,7 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
714
814
|
if (arrowToken && (arrowToken[0] === '->' || arrowToken[0] === '=>') &&
|
|
715
815
|
arrowToken.data?.returnType) {
|
|
716
816
|
// Typed arrow function assignment
|
|
717
|
-
let returnType =
|
|
817
|
+
let returnType = tsType(arrowToken.data.returnType);
|
|
718
818
|
let { params } = collectParams(tokens, i + 2);
|
|
719
819
|
let paramStr = params.join(', ');
|
|
720
820
|
lines.push(`${indent()}${exp}${declare}function ${varName}(${paramStr}): ${returnType};`);
|
|
@@ -779,15 +879,18 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
779
879
|
}
|
|
780
880
|
if (usesSignal) {
|
|
781
881
|
preamble.push(SIGNAL_INTERFACE);
|
|
782
|
-
preamble.push(SIGNAL_FN);
|
|
882
|
+
if (!explicitlyBound.has('__state')) preamble.push(SIGNAL_FN);
|
|
783
883
|
}
|
|
784
884
|
if (usesComputed) {
|
|
785
885
|
preamble.push(COMPUTED_INTERFACE);
|
|
786
|
-
preamble.push(COMPUTED_FN);
|
|
886
|
+
if (!explicitlyBound.has('__computed')) preamble.push(COMPUTED_FN);
|
|
787
887
|
}
|
|
788
|
-
if (usesSignal || usesComputed) {
|
|
888
|
+
if ((usesSignal || usesComputed) && !explicitlyBound.has('__effect')) {
|
|
789
889
|
preamble.push(EFFECT_FN);
|
|
790
890
|
}
|
|
891
|
+
if ((usesSignal || usesComputed || usesBatch) && !explicitlyBound.has('__batch')) {
|
|
892
|
+
preamble.push(BATCH_FN);
|
|
893
|
+
}
|
|
791
894
|
if (hasSchemaDecls) {
|
|
792
895
|
preamble.push(...SCHEMA_INTRINSIC_DECLS);
|
|
793
896
|
}
|
|
@@ -799,25 +902,16 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
799
902
|
}
|
|
800
903
|
|
|
801
904
|
// ============================================================================
|
|
802
|
-
//
|
|
905
|
+
// Convert a Rip type expression to its TypeScript form.
|
|
803
906
|
// ============================================================================
|
|
907
|
+
//
|
|
908
|
+
// Today this only strips the `::` annotation sigil to `:`. Kept as a
|
|
909
|
+
// dedicated function so every call site routes through one place if the
|
|
910
|
+
// conversion ever needs to grow.
|
|
804
911
|
|
|
805
|
-
function
|
|
912
|
+
function tsType(typeStr) {
|
|
806
913
|
if (!typeStr) return typeStr;
|
|
807
|
-
|
|
808
|
-
// Convert :: to : (annotation sigil to type separator)
|
|
809
|
-
typeStr = typeStr.replace(/::/g, ':');
|
|
810
|
-
|
|
811
|
-
// T?? → T | null | undefined
|
|
812
|
-
typeStr = typeStr.replace(/(\w+(?:<[^>]+>)?)\?\?/g, '$1 | null | undefined');
|
|
813
|
-
|
|
814
|
-
// T? → T | undefined (but not ?. or ?: which are different)
|
|
815
|
-
typeStr = typeStr.replace(/(\w+(?:<[^>]+>)?)\?(?![.:])/g, '$1 | undefined');
|
|
816
|
-
|
|
817
|
-
// T! → NonNullable<T>
|
|
818
|
-
typeStr = typeStr.replace(/(\w+(?:<[^>]+>)?)\!/g, 'NonNullable<$1>');
|
|
819
|
-
|
|
820
|
-
return typeStr;
|
|
914
|
+
return typeStr.replace(/::/g, ':');
|
|
821
915
|
}
|
|
822
916
|
|
|
823
917
|
// ============================================================================
|
|
@@ -911,18 +1005,18 @@ function emitComponentTypes(sexpr, lines, indent, indentLevel, componentVars, so
|
|
|
911
1005
|
if (!Array.isArray(member)) continue;
|
|
912
1006
|
let mHead = member[0]?.valueOf?.() ?? member[0];
|
|
913
1007
|
|
|
914
|
-
let target, propName, isProp, type,
|
|
1008
|
+
let target, propName, isProp, type, optional;
|
|
915
1009
|
|
|
916
1010
|
if (mHead === 'state' || mHead === 'readonly' || mHead === 'computed') {
|
|
917
1011
|
target = member[1];
|
|
918
1012
|
isProp = Array.isArray(target) && (target[0]?.valueOf?.() ?? target[0]) === '.' && (target[1]?.valueOf?.() ?? target[1]) === 'this';
|
|
919
1013
|
propName = isProp ? (target[2]?.valueOf?.() ?? target[2]) : (target?.valueOf?.() ?? target);
|
|
920
1014
|
type = isProp ? target[2]?.type : target?.type;
|
|
921
|
-
|
|
1015
|
+
optional = isProp ? !!target[2]?.optional : !!target?.optional;
|
|
922
1016
|
if (!isProp) {
|
|
923
1017
|
componentVars.add(propName);
|
|
924
1018
|
let wrapper = (mHead === 'computed') ? 'Computed' : 'Signal';
|
|
925
|
-
let typeStr = type ?
|
|
1019
|
+
let typeStr = type ? tsType(type) : (inferLiteralType(member[2]) || 'any');
|
|
926
1020
|
bodyMembers.push(` ${propName}: ${wrapper}<${typeStr}>;`);
|
|
927
1021
|
continue;
|
|
928
1022
|
}
|
|
@@ -930,7 +1024,7 @@ function emitComponentTypes(sexpr, lines, indent, indentLevel, componentVars, so
|
|
|
930
1024
|
isProp = (member[1]?.valueOf?.() ?? member[1]) === 'this';
|
|
931
1025
|
propName = isProp ? (member[2]?.valueOf?.() ?? member[2]) : null;
|
|
932
1026
|
type = isProp ? member[2]?.type : null;
|
|
933
|
-
|
|
1027
|
+
optional = isProp ? !!member[2]?.optional : false;
|
|
934
1028
|
if (!isProp && propName) componentVars.add(propName);
|
|
935
1029
|
} else if (mHead === 'object') {
|
|
936
1030
|
// Method definitions: (object (: methodName (-> (params...) (block ...))))
|
|
@@ -944,13 +1038,27 @@ function emitComponentTypes(sexpr, lines, indent, indentLevel, componentVars, so
|
|
|
944
1038
|
if (fHead !== '->' && fHead !== '=>') continue;
|
|
945
1039
|
let params = funcDef[1];
|
|
946
1040
|
if (!Array.isArray(params)) continue;
|
|
947
|
-
|
|
1041
|
+
// Unwrap `['default', name, value]` AST nodes (params with defaults
|
|
1042
|
+
// like `b = 1`) so we extract the underlying identifier rather than
|
|
1043
|
+
// stringifying the whole array via Array.toString — which produced
|
|
1044
|
+
// `default,b,1: any` in the emitted method signature.
|
|
1045
|
+
let unwrapDefault = (p) => {
|
|
1046
|
+
if (Array.isArray(p) && (p[0]?.valueOf?.() ?? p[0]) === 'default') {
|
|
1047
|
+
return { inner: p[1], hasDefault: true };
|
|
1048
|
+
}
|
|
1049
|
+
return { inner: p, hasDefault: false };
|
|
1050
|
+
};
|
|
1051
|
+
let hasTypedParams = params.some(p => unwrapDefault(p).inner?.type);
|
|
948
1052
|
if (!hasTypedParams) continue;
|
|
949
1053
|
let paramStrs = [];
|
|
950
1054
|
for (let p of params) {
|
|
951
|
-
let
|
|
952
|
-
let
|
|
953
|
-
|
|
1055
|
+
let { inner, hasDefault } = unwrapDefault(p);
|
|
1056
|
+
let pName = inner?.valueOf?.() ?? inner;
|
|
1057
|
+
let pType = inner?.type ? tsType(inner.type) : 'any';
|
|
1058
|
+
// Defaulted params are optional in the type signature so callers
|
|
1059
|
+
// may omit them.
|
|
1060
|
+
let opt = hasDefault ? '?' : '';
|
|
1061
|
+
paramStrs.push(`${pName}${opt}: ${pType}`);
|
|
954
1062
|
}
|
|
955
1063
|
bodyMembers.push(` ${methName}(${paramStrs.join(', ')}): void;`);
|
|
956
1064
|
}
|
|
@@ -965,9 +1073,11 @@ function emitComponentTypes(sexpr, lines, indent, indentLevel, componentVars, so
|
|
|
965
1073
|
|
|
966
1074
|
if (!isProp || !propName) continue;
|
|
967
1075
|
|
|
968
|
-
let typeStr = type ?
|
|
969
|
-
|
|
970
|
-
|
|
1076
|
+
let typeStr = type ? tsType(type) : 'any';
|
|
1077
|
+
// `?` on the prop name is the sole optionality marker;
|
|
1078
|
+
// `:=` defaults do not imply optionality.
|
|
1079
|
+
let opt = optional ? '?' : '';
|
|
1080
|
+
if (!optional) hasRequired = true;
|
|
971
1081
|
publicProps.push(` ${propName}${opt}: ${typeStr};`);
|
|
972
1082
|
if (mHead === 'state') {
|
|
973
1083
|
publicProps.push(` __bind_${propName}__?: Signal<${typeStr}>;`);
|
|
@@ -1014,7 +1124,7 @@ function emitComponentTypes(sexpr, lines, indent, indentLevel, componentVars, so
|
|
|
1014
1124
|
// Registration — install emitTypes into the compiler at module load.
|
|
1015
1125
|
// ============================================================================
|
|
1016
1126
|
// The compiler exposes setTypesEmitter() as a no-op-friendly hook. When
|
|
1017
|
-
// nothing imports
|
|
1127
|
+
// nothing imports dts.js (browser bundle), the emitter stays null
|
|
1018
1128
|
// and compile()s .d.ts output is silently skipped. CLI entry points and
|
|
1019
1129
|
// typecheck.js import this module specifically to install the emitter.
|
|
1020
1130
|
|
package/src/grammar/README.md
CHANGED
|
@@ -140,7 +140,7 @@ names in the core generators:
|
|
|
140
140
|
| `root` | Grammar start symbol | Root |
|
|
141
141
|
| `body-list` | Left-recursive with TERMINATOR | Body, ComponentBody |
|
|
142
142
|
| `comma-list` | Left-recursive with `,` | ArgList, ParamList |
|
|
143
|
-
| `concat-list` | Left-recursive, no separator | Interpolations,
|
|
143
|
+
| `concat-list` | Left-recursive, no separator | Interpolations, Cases |
|
|
144
144
|
| `left-rec-loop` | Self-referential with terminal continuation | IfBlock |
|
|
145
145
|
| `expression` | Contains the operation nonterminal | Expression |
|
|
146
146
|
| `operation` | Has binary operator rules | Operation |
|