rip-lang 3.17.3 → 3.17.5
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 +1 -1
- package/docs/RIP-LANG.md +90 -3
- package/docs/RIP-TYPES.md +33 -0
- package/docs/demo/routes/card.rip +1 -1
- package/docs/dist/rip.js +531 -123
- package/docs/dist/rip.min.js +237 -203
- package/docs/dist/rip.min.js.br +0 -0
- package/docs/example/index.json +1 -1
- package/docs/example/index.json.br +0 -0
- package/docs/extensions/vscode/print/index.html +2 -1
- package/docs/extensions/vscode/print/print-1.0.16.vsix +0 -0
- package/docs/extensions/vscode/print/print-latest.vsix +0 -0
- package/docs/extensions/vscode/rip/index.html +2 -1
- package/docs/extensions/vscode/rip/rip-latest.vsix +0 -0
- package/docs/extensions/vscode/rip/vscode-rip-0.8.0.vsix +0 -0
- package/docs/ui/bundle.json +40 -40
- package/docs/ui/bundle.json.br +0 -0
- package/docs/ui/index.css +8 -0
- package/docs/ui/index.html +4 -4
- package/package.json +5 -5
- package/src/AGENTS.md +43 -10
- package/src/compiler.js +38 -4
- package/src/components.js +474 -89
- package/src/dts.js +26 -28
- package/src/generated/dom-events.js +2 -1
- package/src/generated/dom-tags.js +2 -1
- package/src/grammar/grammar.rip +8 -0
- package/src/parser.js +28 -27
- package/src/schema/runtime-validate.js +7 -0
- package/src/schema/runtime.generated.js +7 -0
- package/src/typecheck.js +159 -35
- package/src/types.js +153 -0
package/docs/ui/bundle.json.br
CHANGED
|
Binary file
|
package/docs/ui/index.css
CHANGED
|
@@ -1281,6 +1281,14 @@ body:has(.source-overlay) { overflow: hidden; }
|
|
|
1281
1281
|
border: none; outline: none; padding: 0 12px; height: 36px;
|
|
1282
1282
|
font: inherit; font-size: 14px; flex: 1; min-width: 0;
|
|
1283
1283
|
}
|
|
1284
|
+
|
|
1285
|
+
/* Round the end children to the inner radius (12px border-radius − 1px
|
|
1286
|
+
border) so an addon fills flush into the corner. Without this, the
|
|
1287
|
+
square-cornered addon is clipped by overflow:hidden to a slightly
|
|
1288
|
+
different curve than the border, leaving a faint sliver at the corners
|
|
1289
|
+
(most visible on the gray prefix/suffix when focused). */
|
|
1290
|
+
& > :first-child { border-top-left-radius: 11px; border-bottom-left-radius: 11px; }
|
|
1291
|
+
& > :last-child { border-top-right-radius: 11px; border-bottom-right-radius: 11px; }
|
|
1284
1292
|
}
|
|
1285
1293
|
|
|
1286
1294
|
/* ── ButtonGroup ── */
|
package/docs/ui/index.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
10
10
|
<link rel="stylesheet" href="index.css">
|
|
11
11
|
<style>body { opacity: 0; } body.ready { opacity: 1; transition: opacity 200ms ease-in; }</style>
|
|
12
|
-
<script defer src="../dist/rip.min.js" data-src="
|
|
12
|
+
<script defer src="../dist/rip.min.js" data-src="/app" data-mount="WidgetGallery">
|
|
13
13
|
</script>
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
|
@@ -103,9 +103,9 @@ export WidgetGallery = component
|
|
|
103
103
|
return unless entry
|
|
104
104
|
sourceName = entry.name
|
|
105
105
|
sourceLines = entry.lines
|
|
106
|
-
|
|
107
|
-
return unless
|
|
108
|
-
sourceCode =
|
|
106
|
+
resp = fetch! "browser/components/#{id}.rip"
|
|
107
|
+
return unless resp.ok
|
|
108
|
+
sourceCode = resp.text!
|
|
109
109
|
_closeSource: -> sourceCode = null
|
|
110
110
|
_stopProp: (e) -> e.stopPropagation()
|
|
111
111
|
_ensureHljs: (cb) ->
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rip-lang",
|
|
3
|
-
"version": "3.17.
|
|
3
|
+
"version": "3.17.5",
|
|
4
4
|
"description": "A modern language that compiles to JavaScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/compiler.js",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"packages/*"
|
|
10
10
|
],
|
|
11
11
|
"catalog": {
|
|
12
|
-
"typescript": "
|
|
12
|
+
"typescript": "6.0.3"
|
|
13
13
|
},
|
|
14
14
|
"browser": "docs/dist/rip.min.js",
|
|
15
15
|
"exports": {
|
|
@@ -38,7 +38,6 @@
|
|
|
38
38
|
"build": "bun scripts/build.js",
|
|
39
39
|
"bump": "bun scripts/bump.js",
|
|
40
40
|
"gen:dom": "bun scripts/gen-dom.js",
|
|
41
|
-
"gallery": "bun scripts/gallery.js",
|
|
42
41
|
"bundle:demo": "bun scripts/bundle-app.js docs/demo/routes --prefix _route --css docs/demo/css -o docs/example/index.json -t 'Rip App Demo'",
|
|
43
42
|
"bundle:ui": "bun scripts/bundle-app.js packages/ui/browser/components --prefix _pkg/ui -o docs/ui/bundle.json -t 'Rip UI'",
|
|
44
43
|
"parser": "bun src/grammar/solar.rip -o src/parser.js src/grammar/grammar.rip",
|
|
@@ -49,6 +48,7 @@
|
|
|
49
48
|
"serve": "bun scripts/serve.js",
|
|
50
49
|
"test": "bun test/runner.js",
|
|
51
50
|
"test:types": "bun test/types/runner.js",
|
|
51
|
+
"test:check": "bun test/check.test.js",
|
|
52
52
|
"test:sourcemap-corpus": "bun test/types/sourcemap-corpus.js",
|
|
53
53
|
"test:diagnostic-corpus": "bun test/types/diagnostic-corpus.js",
|
|
54
54
|
"test:markers": "bun test/markers.test.js",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"test:server": "./bin/rip packages/server/tests/runner.rip",
|
|
67
67
|
"test:time": "bun run --cwd packages/time test",
|
|
68
68
|
"test:validate": "./bin/rip packages/validate/test.rip",
|
|
69
|
-
"test:all": "bun run test && bun run test:types && bun run test:sourcemap-corpus && bun run test:diagnostic-corpus && bun run test:markers && bun run test:error-span && bun run test:schema && bun run test:schema-fresh && bun run test:docs && bun run test:loader && bun run test:graph && bun run test:bundle && bun run test:grammar && bun run test:parser-fresh && bun run test:semantic && bun run test:server && bun run test:time && bun run test:validate",
|
|
69
|
+
"test:all": "bun run test && bun run test:types && bun run test:check && bun run test:sourcemap-corpus && bun run test:diagnostic-corpus && bun run test:markers && bun run test:error-span && bun run test:schema && bun run test:schema-fresh && bun run test:docs && bun run test:loader && bun run test:graph && bun run test:bundle && bun run test:grammar && bun run test:parser-fresh && bun run test:semantic && bun run test:server && bun run test:time && bun run test:validate",
|
|
70
70
|
"test:ui": "bun run --cwd packages/ui test:e2e",
|
|
71
71
|
"test:ui:chromium": "bun run --cwd packages/ui test:e2e:chromium",
|
|
72
72
|
"test:ui:axe": "bun run --cwd packages/ui test:e2e:axe",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"license": "MIT",
|
|
103
103
|
"dependencies": {
|
|
104
104
|
"@types/bun": "1.3.14",
|
|
105
|
-
"typescript": "
|
|
105
|
+
"typescript": "6.0.3"
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
108
|
"vscode-oniguruma": "2.0.1",
|
package/src/AGENTS.md
CHANGED
|
@@ -33,7 +33,7 @@ The browser bundle (`docs/dist/rip.min.js`) is built from `src/browser.js` plus
|
|
|
33
33
|
| `src/error.js` | yes | runtime error formatting |
|
|
34
34
|
| `src/sourcemaps.js` | yes | inline source-map generation |
|
|
35
35
|
| `src/generated/dom-tags.js` | yes | HTML/SVG tag set for render-block tag detection |
|
|
36
|
-
| `src/generated/dom-events.js` | yes | event-name set
|
|
36
|
+
| `src/generated/dom-events.js` | yes | DOM event-name set the bare `@event` shorthand validates against |
|
|
37
37
|
| `src/dts.js` | **no** | `.d.ts` emitter + intrinsic decl tables for the type system — CLI / typecheck only |
|
|
38
38
|
| `src/schema/dts.js` | **no** | `.d.ts` emitter for schema declarations — CLI / typecheck only |
|
|
39
39
|
| `src/typecheck.js` | **no** | TypeScript LSP integration — CLI only |
|
|
@@ -433,6 +433,22 @@ Key entry points:
|
|
|
433
433
|
- `emitTemplateLoop` — emits `__reconcile(...)`
|
|
434
434
|
- `emitBlockFactory` — shared factory emitter used by conditionals and loops
|
|
435
435
|
|
|
436
|
+
### Member Declaration Semantics (uniform `=` / `:=` / `=!`)
|
|
437
|
+
|
|
438
|
+
The declaration operator decides a member's category in `emitComponent`'s categorizer (the `for (let stmt of statements)` loop). The buckets:
|
|
439
|
+
|
|
440
|
+
- `:=` → `stateVars` → `__state(...)`; added to `reactiveMembers` **and** `refTargetMembers`.
|
|
441
|
+
- `=` (non-function, non-lifecycle) → `plainVars` → a **plain property** `this.name = value` (public: `this.name = props.name ?? value`). **Not** added to `reactiveMembers` and **not** to `refTargetMembers`. This is uniform with `=` everywhere else in the language — a `=` member is a plain, reassignable field, not reactive.
|
|
442
|
+
- `=!` → `readonlyVars` → plain const field.
|
|
443
|
+
- `~=` → `derivedVars` (`__computed`); `~>` → `effects`; a `->`/`=>` value or `name: -> …` → `methods`.
|
|
444
|
+
- bare `@name` / `@name :=` → public `stateVars` (reactive props, share the parent signal via `__bind_name__`).
|
|
445
|
+
|
|
446
|
+
Because a plain member is in `componentMembers` but **not** `reactiveMembers`, `transformComponentMembers` rewrites its reads/writes to plain `this.name` (reactive members become `this.name.value`). One subtlety lives in the `block`/`program` branch of `transformComponentMembers`: a bare `name = value` introduces a *local* only when `name` is **not** a `componentMembers` entry — otherwise a write to a plain member would shadow it with a `let`. (Previously this checked `reactiveMembers`, which was equivalent only because every non-method `=` member used to be reactive.)
|
|
447
|
+
|
|
448
|
+
Both emit paths handle `plainVars`: the runtime class (`_init`) and the type-check stub (`stubComponents` — a `declare name: T` property plus an `_init` assignment, mirroring `readonlyVars`). `dts.js` leaves private `=` members invisible in emitted `.d.ts` (unchanged — they were already not surfaced).
|
|
449
|
+
|
|
450
|
+
**Silent-freeze diagnostic.** After categorization, `emitComponent` errors when a **private** `plainVars` member is *read* in `render` / a `~=` computed / a `~>` effect **and** *reassigned* anywhere in the component (methods, effects, lifecycle hooks, computed bodies, render event handlers). That pair is the silent-freeze class: a non-reactive field read reactively but mutated never re-renders. The message steers to `:=`. Public `@name = v` fields are exempt (explicit rare opt-in).
|
|
451
|
+
|
|
436
452
|
### Factory Mode
|
|
437
453
|
|
|
438
454
|
Block factories need locals and `ctx.member` references instead of `this._elN` and `this.member`.
|
|
@@ -446,15 +462,30 @@ Block factories need locals and `ctx.member` references instead of `this._elN` a
|
|
|
446
462
|
|
|
447
463
|
Factory mode is entered in `emitConditionBranch` and `emitTemplateLoop` via save/restore of `[_createLines, _setupLines, _factoryMode, _factoryVars]`.
|
|
448
464
|
|
|
449
|
-
###
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
-
|
|
457
|
-
-
|
|
465
|
+
### Event Directives
|
|
466
|
+
|
|
467
|
+
Events bind where they're declared in `render`, never by method name. A bare
|
|
468
|
+
`@click` is shorthand for `@click: @onClick` — a `this`-member handler that
|
|
469
|
+
resolves only to a component method (never a local) — bound to the element it
|
|
470
|
+
sits on. Defining `onClick` alone attaches nothing.
|
|
471
|
+
|
|
472
|
+
- positions: a tag-head argument (`button @click`, `button x: 1, @click`) or an
|
|
473
|
+
element-body statement line (`@click` on its own line under the element). Both
|
|
474
|
+
desugar in `rewriteRender` (`isBareEventAttr` + `elementBodyLevels`); a `:`
|
|
475
|
+
before it (`value: @click`) or being nested in `()`/`[]`/`{}` excludes it.
|
|
476
|
+
- name resolution: `@<event>` → `on<Event>`, capitalizing only the first letter
|
|
477
|
+
(`@keydown` → `onKeydown`, `@pointerdown` → `onPointerdown`) — not React-style
|
|
478
|
+
- validation (in the emitter choke point `_checkBareEventHandler`): the name must
|
|
479
|
+
be in the generated `src/generated/dom-events.js` set, else a compile error
|
|
480
|
+
steering to `= @name`; bare `@error` is rejected (clashes with the `onError`
|
|
481
|
+
lifecycle hook — bind a DOM error listener with `@error: handler`); a missing
|
|
482
|
+
`on<Event>` method is a compile error
|
|
483
|
+
- a `.`-chain (`@toast.title`) is a member path, never a bare event
|
|
484
|
+
- explicit `@event: handler` takes any expression (locals, `obj.method`, inline
|
|
485
|
+
arrows) and works in any attribute position; `@event.modifier: handler` wraps
|
|
486
|
+
to a computed key
|
|
487
|
+
- a bare `@member` standing as a render child (not a directive) is a compile
|
|
488
|
+
error — `= @member` / `"#{@member}"` render text, `slot` projects children
|
|
458
489
|
- after bumping `typescript`, refresh the generated DOM metadata with `bun run gen:dom`
|
|
459
490
|
|
|
460
491
|
### Inherited Props (`extends <tag>`)
|
|
@@ -479,6 +510,8 @@ export Checkbox = component
|
|
|
479
510
|
@onClick() if e.key in ['Enter', ' ']
|
|
480
511
|
render
|
|
481
512
|
button role: 'checkbox', aria-checked: !!@checked
|
|
513
|
+
@click
|
|
514
|
+
@keydown
|
|
482
515
|
slot
|
|
483
516
|
```
|
|
484
517
|
|
package/src/compiler.js
CHANGED
|
@@ -282,6 +282,9 @@ export class CodeEmitter {
|
|
|
282
282
|
|
|
283
283
|
// Symbol literals
|
|
284
284
|
'symbol': 'emitSymbol',
|
|
285
|
+
|
|
286
|
+
// `expr as Type` cast — runtime-erased, narrows in the check path
|
|
287
|
+
'cast': 'emitCast',
|
|
285
288
|
};
|
|
286
289
|
|
|
287
290
|
constructor(options = {}) {
|
|
@@ -932,8 +935,28 @@ export class CodeEmitter {
|
|
|
932
935
|
// Main dispatch
|
|
933
936
|
// ---------------------------------------------------------------------------
|
|
934
937
|
|
|
938
|
+
// `expr as Type` cast wrap. At runtime the cast is fully erased, so this only
|
|
939
|
+
// fires in the shadow-TS check path (`inlineTypes`), where it re-materializes
|
|
940
|
+
// a real TS assertion `(code as Type)`. `typeStr` is the opaque type string
|
|
941
|
+
// collapsed by the type rewriter onto the CAST marker token.
|
|
942
|
+
castWrap(code, typeStr) {
|
|
943
|
+
if (!typeStr || !this.options.inlineTypes) return code;
|
|
944
|
+
return `(${code} as ${typeStr})`;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// `['cast', expr, typeStr]` — postfix type assertion. Emit just the inner
|
|
948
|
+
// expression on the runtime path (the cast leaves zero trace); in the check
|
|
949
|
+
// path wrap it as a real TS assertion so it narrows for the checker. Covers
|
|
950
|
+
// every carrier (identifier, member, call/index/paren result) because the
|
|
951
|
+
// grammar reduces this AFTER the full postfix expression is built. Chains
|
|
952
|
+
// nest (`['cast',['cast',x,'A'],'B']`) and re-wrap recursively.
|
|
953
|
+
emitCast(head, rest) {
|
|
954
|
+
let [expr, typeStr] = rest;
|
|
955
|
+
return this.castWrap(this.emit(expr, 'value'), typeStr);
|
|
956
|
+
}
|
|
957
|
+
|
|
935
958
|
emit(sexpr, context = 'statement') {
|
|
936
|
-
// String object with metadata (quote, bang, optional, heregex, etc.)
|
|
959
|
+
// String object with metadata (quote, bang, optional, heregex, cast, etc.)
|
|
937
960
|
if (sexpr instanceof String) {
|
|
938
961
|
// Dammit operator (!)
|
|
939
962
|
if (meta(sexpr, 'bang') === true) {
|
|
@@ -1174,9 +1197,9 @@ export class CodeEmitter {
|
|
|
1174
1197
|
|
|
1175
1198
|
if (this.usesReactivity && !skip) {
|
|
1176
1199
|
if (skipRT) {
|
|
1177
|
-
code += 'var { __state, __computed, __effect, __batch, __readonly, __setErrorHandler, __handleError, __catchErrors } = globalThis.__rip;\n';
|
|
1200
|
+
code += 'var { __state, __computed, __effect, __batch, __detachRef, __readonly, __setErrorHandler, __handleError, __catchErrors } = globalThis.__rip;\n';
|
|
1178
1201
|
} else if (typeof globalThis !== 'undefined' && globalThis.__rip) {
|
|
1179
|
-
code += 'const { __state, __computed, __effect, __batch, __readonly, __setErrorHandler, __handleError, __catchErrors } = globalThis.__rip;\n';
|
|
1202
|
+
code += 'const { __state, __computed, __effect, __batch, __detachRef, __readonly, __setErrorHandler, __handleError, __catchErrors } = globalThis.__rip;\n';
|
|
1180
1203
|
} else {
|
|
1181
1204
|
code += this.getReactiveRuntime();
|
|
1182
1205
|
}
|
|
@@ -4756,6 +4779,17 @@ function __batch(fn) {
|
|
|
4756
4779
|
}
|
|
4757
4780
|
}
|
|
4758
4781
|
|
|
4782
|
+
// Compare-and-clear a template ref cell on element detach. Uses the
|
|
4783
|
+
// NON-tracking read() so clearing never subscribes the teardown path to
|
|
4784
|
+
// the cell, and only nulls the cell if it still holds THIS element — a
|
|
4785
|
+
// keyed move or a re-render that already pointed the cell elsewhere must
|
|
4786
|
+
// not be clobbered. Writing null notifies subscribers (an effect reading
|
|
4787
|
+
// the ref re-runs when the element disappears); callers wrap teardown in
|
|
4788
|
+
// __batch where parent subscribers may be live.
|
|
4789
|
+
function __detachRef(cell, el) {
|
|
4790
|
+
if (cell && typeof cell.read === 'function' && cell.read() === el) cell.value = null;
|
|
4791
|
+
}
|
|
4792
|
+
|
|
4759
4793
|
// Returns the AbortSignal of the currently-running effect, or null if
|
|
4760
4794
|
// called outside an effect or before AbortController is available.
|
|
4761
4795
|
// Designed for async-aware effect bodies — capture the signal BEFORE
|
|
@@ -4815,7 +4849,7 @@ function __catchErrors(fn) {
|
|
|
4815
4849
|
|
|
4816
4850
|
// Register on globalThis for runtime deduplication
|
|
4817
4851
|
if (typeof globalThis !== 'undefined') {
|
|
4818
|
-
globalThis.__rip = { __state, __computed, __effect, __batch, __readonly, __setErrorHandler, __handleError, __catchErrors, __getEffectSignal };
|
|
4852
|
+
globalThis.__rip = { __state, __computed, __effect, __batch, __detachRef, __readonly, __setErrorHandler, __handleError, __catchErrors, __getEffectSignal };
|
|
4819
4853
|
// Stdlib-style global so user code can call getEffectSignal() in a
|
|
4820
4854
|
// ~> body without importing or destructuring. Mirrors how p, pp,
|
|
4821
4855
|
// assert, etc. are registered for ergonomic use.
|