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/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<a href="https://github.com/shreeve/rip-lang/commits/main"><img src="https://img.shields.io/badge/version-3.17.
|
|
12
|
+
<a href="https://github.com/shreeve/rip-lang/commits/main"><img src="https://img.shields.io/badge/version-3.17.5-blue.svg" alt="Version"></a>
|
|
13
13
|
<a href="#zero-dependencies"><img src="https://img.shields.io/badge/dependencies-ZERO-brightgreen.svg" alt="Dependencies"></a>
|
|
14
14
|
<a href="#"><img src="https://img.shields.io/badge/tests-1%2C436%2F1%2C436-brightgreen.svg" alt="Tests"></a>
|
|
15
15
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
|
package/docs/RIP-LANG.md
CHANGED
|
@@ -343,6 +343,7 @@ Multiple lines
|
|
|
343
343
|
| `?!` | Presence | `@checked?!` | `(this.checked ? true : undefined)` — Houdini operator |
|
|
344
344
|
| `=~` | Match | `str =~ /pat/` | Ruby-style regex match, captures in `_` |
|
|
345
345
|
| `::` | Prototype | `String::trim` | `String.prototype.trim` |
|
|
346
|
+
| `as` | Type cast | `x as Foo` | Type-checker-only assertion — erased at runtime |
|
|
346
347
|
| `[-n]` | Negative index | `arr[-1]` | `arr.at(-1)` |
|
|
347
348
|
| `*` | String repeat | `"-" * 40` | `"-".repeat(40)` |
|
|
348
349
|
| `<` `<=` | Chained comparison | `1 < x < 10` | `(1 < x) && (x < 10)` |
|
|
@@ -884,6 +885,23 @@ Rip's reactive features are **language-level operators**, not library imports.
|
|
|
884
885
|
| `<~` | Render-ready | "loads from" | Server-backed state, loaded before render (component bodies) |
|
|
885
886
|
| `=!` | Readonly | "equals, dammit!" | Constant (`const`) |
|
|
886
887
|
|
|
888
|
+
### Uniform meaning — including component members
|
|
889
|
+
|
|
890
|
+
The declaration operators mean the **same thing everywhere**, and that includes members declared inside a `component` body:
|
|
891
|
+
|
|
892
|
+
| Operator | In a component, a member declared with it is… |
|
|
893
|
+
|----------|-----------------------------------------------|
|
|
894
|
+
| `x = v` | a **plain, non-reactive** field — `this.x = v`. Reads/writes are ordinary property access; the UI does **not** re-render when it changes. |
|
|
895
|
+
| `x := v` | **reactive state** — `this.x = __state(v)`. Reads/writes flow through the signal and re-render dependents. |
|
|
896
|
+
| `x =! v` | a readonly **const** field. |
|
|
897
|
+
| `x ~= e` | a **computed**; `x ~> …` an **effect**; `x: -> …` (or `x = -> …`) a **method**. |
|
|
898
|
+
|
|
899
|
+
A plain `=` member is **not** a reactive member and is **not** a valid `ref:` target — `ref:` still requires a `:=` state cell.
|
|
900
|
+
|
|
901
|
+
**Props.** `@`-prefixed reactive props are declared with `@name :=` (or bare `@name` for a required prop) so that a parent updating the prop re-renders the child. `@name = v` is a *plain public field*: its initial value is read once from props and it is non-reactive (rare — prefer `:=` unless you specifically want a one-time prop snapshot).
|
|
902
|
+
|
|
903
|
+
**Silent-freeze diagnostic.** Because a `=` member is non-reactive, the compiler raises an error if a private `=` member is **read** in `render` / a `~=` computed / a `~>` effect **and reassigned** somewhere in the component — that combination would read the value once and silently never update. The fix is to declare it with `:=`.
|
|
904
|
+
|
|
887
905
|
## Reactive Behavior
|
|
888
906
|
|
|
889
907
|
| | `:=` state | `~=` computed | `~>` effect |
|
|
@@ -1107,6 +1125,47 @@ declare const count: Signal<number>;
|
|
|
1107
1125
|
declare const doubled: Computed<number>;
|
|
1108
1126
|
```
|
|
1109
1127
|
|
|
1128
|
+
## Type Cast (`expr as Type`)
|
|
1129
|
+
|
|
1130
|
+
`expr as Type` is a TypeScript-style cast. It is **purely a type-checker
|
|
1131
|
+
construct**: it is **erased at runtime** (the emitted JavaScript is exactly
|
|
1132
|
+
`expr`, with no trace of the cast) and only feeds the shadow-TS type checker,
|
|
1133
|
+
where it asserts/narrows the value's type. The grammar **never parses a type** —
|
|
1134
|
+
the type rewriter collapses the `as Type` run into a single opaque-string marker
|
|
1135
|
+
token, and the grammar reduces a structural postfix node (`['cast', expr,
|
|
1136
|
+
typeStr]`), conceptually like `!` or `?.`. So no type syntax ever reaches the
|
|
1137
|
+
parser.
|
|
1138
|
+
|
|
1139
|
+
```coffee
|
|
1140
|
+
y = x as Foo # runtime: y = x ; checker: y is Foo
|
|
1141
|
+
m = x as Map<string, number> # generics, unions (A | B), intersections,
|
|
1142
|
+
u = x as A | B # object/array types are all accepted
|
|
1143
|
+
chained = x as A as B # chaining works (left-associative)
|
|
1144
|
+
style = el.style as unknown as Record<string, string> # via `unknown`
|
|
1145
|
+
```
|
|
1146
|
+
|
|
1147
|
+
It is **not** a cast in `for x as iter`, `for x as! iter`, `import x as y`,
|
|
1148
|
+
`export x as y`, or after `.`/`?.` (`obj.as` is a property) — those keep their
|
|
1149
|
+
existing meaning.
|
|
1150
|
+
|
|
1151
|
+
**Narrowing — every expression form.** Because the cast is a grammar node that
|
|
1152
|
+
reduces *after* the full postfix expression is built, it narrows for the checker
|
|
1153
|
+
on **all** carriers — identifier, member access, and call / index /
|
|
1154
|
+
parenthesized results alike:
|
|
1155
|
+
|
|
1156
|
+
```coffee
|
|
1157
|
+
v = raw as Aug # ✅ narrows (identifier)
|
|
1158
|
+
v = obj.prop as Aug # ✅ narrows (member access)
|
|
1159
|
+
v = foo() as Aug # ✅ narrows (call result)
|
|
1160
|
+
v = arr[0] as Aug # ✅ narrows (index result)
|
|
1161
|
+
v = (raw) as Aug # ✅ narrows (parenthesized)
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
**Precedence** matches TypeScript: `as` binds looser than member/call/index and
|
|
1165
|
+
arithmetic (`a + b as T` is `(a + b) as T`) but tighter than relational and
|
|
1166
|
+
comparison operators, and chains are left-associative (`x as A as B` is
|
|
1167
|
+
`(x as A) as B`).
|
|
1168
|
+
|
|
1110
1169
|
## Two-Way Binding (`<=>`)
|
|
1111
1170
|
|
|
1112
1171
|
The `<=>` operator creates bidirectional reactive bindings inside render blocks.
|
|
@@ -1612,6 +1671,14 @@ App = component
|
|
|
1612
1671
|
~> p "count is now #{count}" # re-runs when count changes
|
|
1613
1672
|
```
|
|
1614
1673
|
|
|
1674
|
+
**Declaration order (convention):** order the top-of-component block as
|
|
1675
|
+
props → locals → refs, separated by one blank line. Within the locals
|
|
1676
|
+
group the sub-order is `:=` → `=` → `=!` → `~=` → `~>`. Because `:=`
|
|
1677
|
+
(reactive state) and `=` (plain non-reactive field) are semantically
|
|
1678
|
+
distinct, keep them grouped separately — all reactive state first, then
|
|
1679
|
+
plain mutable fields — never interleaved. The `Ref`-suffix `:=` cells
|
|
1680
|
+
used as `ref:` targets go in the final group.
|
|
1681
|
+
|
|
1615
1682
|
**Render Blocks — Template Syntax:**
|
|
1616
1683
|
|
|
1617
1684
|
```coffee
|
|
@@ -1630,9 +1697,12 @@ App = component
|
|
|
1630
1697
|
"Click me"
|
|
1631
1698
|
```
|
|
1632
1699
|
|
|
1633
|
-
**
|
|
1700
|
+
**Event Directives:**
|
|
1634
1701
|
|
|
1635
|
-
|
|
1702
|
+
Events bind on the element where you declare them — never by method name alone.
|
|
1703
|
+
A bare `@click` is shorthand for `@click: @onClick`: it resolves to a component
|
|
1704
|
+
method named `on` + the capitalized event (`@click` → `onClick`, `@keydown` →
|
|
1705
|
+
`onKeydown`), bound to that element:
|
|
1636
1706
|
|
|
1637
1707
|
```coffee
|
|
1638
1708
|
Checkbox = component
|
|
@@ -1644,10 +1714,27 @@ Checkbox = component
|
|
|
1644
1714
|
@onClick()
|
|
1645
1715
|
render
|
|
1646
1716
|
button role: 'checkbox', aria-checked: !!@checked
|
|
1717
|
+
@click # binds onClick to this button
|
|
1718
|
+
@keydown # binds onKeydown
|
|
1647
1719
|
slot
|
|
1648
1720
|
```
|
|
1649
1721
|
|
|
1650
|
-
The
|
|
1722
|
+
The bare form works on the tag-head line (`button @click`) or on its own line in
|
|
1723
|
+
the element's body. For a handler whose name doesn't match the event — or any
|
|
1724
|
+
inline/expression handler — use the explicit form, which accepts any expression:
|
|
1725
|
+
|
|
1726
|
+
```coffee
|
|
1727
|
+
button @click: someOtherHandler
|
|
1728
|
+
button @keydown: @onTriggerKeydown
|
|
1729
|
+
button @click: (e) -> e.stopPropagation()
|
|
1730
|
+
```
|
|
1731
|
+
|
|
1732
|
+
Defining `onClick` alone attaches nothing — the listener exists only where the
|
|
1733
|
+
template declares `@click`, so it's always visible exactly where it fires. A bare
|
|
1734
|
+
`@<name>` that isn't a real DOM event is a compile error (use `= @name` or
|
|
1735
|
+
`"#{@name}"` to render a member as text); bare `@error` is rejected because it
|
|
1736
|
+
collides with the `onError` lifecycle hook (write `@error: handler` for a DOM
|
|
1737
|
+
error listener).
|
|
1651
1738
|
|
|
1652
1739
|
**Conditional Rendering:**
|
|
1653
1740
|
|
package/docs/RIP-TYPES.md
CHANGED
|
@@ -631,6 +631,39 @@ Configure via the `"rip"` key in `package.json`:
|
|
|
631
631
|
| `strict` | Enable TS strict family for `rip check` (default: `false`) |
|
|
632
632
|
| `checkAll` | Type-check every non-`@nocheck` `.rip` file, not just annotated ones (default: `false`) |
|
|
633
633
|
| `exclude` | Glob patterns for files to skip during `rip check` |
|
|
634
|
+
| `types` | Extra hand-written ambient `.d.ts` files to include as explicit program roots (paths relative to the project root). The **escape hatch** for ad-hoc / non-dependency `.d.ts` — most dependency-provided ambients arrive automatically (see below). A missing path is warned and skipped, not fatal. |
|
|
635
|
+
| `ambient` | Declared **by a package** (not a consumer): the ambient `.d.ts` files this package ships, so consumers that depend on it get them automatically. Paths are relative to the package root; the file must also be in `files`. |
|
|
636
|
+
|
|
637
|
+
### Ambient `.d.ts` includes — two mechanisms
|
|
638
|
+
|
|
639
|
+
A `.d.ts` added as an explicit program root contributes its global `declare`s to
|
|
640
|
+
every `.rip` file in the project — like tsconfig's `files`, and unlike the `types`
|
|
641
|
+
compiler option it is **not** suppressed by the auto-discovered `@types/*` set.
|
|
642
|
+
Rip feeds that list from two places:
|
|
643
|
+
|
|
644
|
+
1. **Automatic (`rip.ambient`, zero-config).** A package advertises the ambient
|
|
645
|
+
types it ships via its own `package.json#rip.ambient`. Any project that
|
|
646
|
+
declares that package as a dependency (`dependencies`, `devDependencies`,
|
|
647
|
+
`peerDependencies`, or `optionalDependencies`) auto-includes those files
|
|
648
|
+
during `rip check` — resolved relative to the dependency's installed location.
|
|
649
|
+
"You used the framework, so its types are just there."
|
|
650
|
+
|
|
651
|
+
For example, `@rip-lang/app` ships `aria.d.ts` for the global `ARIA` helpers:
|
|
652
|
+
|
|
653
|
+
```json
|
|
654
|
+
{ "name": "@rip-lang/app", "rip": { "ambient": ["aria.d.ts"] }, "files": ["index.rip", "aria.d.ts"] }
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
So an app or package that depends on `@rip-lang/app` gets `ARIA` typed with no
|
|
658
|
+
configuration at all.
|
|
659
|
+
|
|
660
|
+
2. **Explicit (`rip.types`, escape hatch).** Point at any `.d.ts` by path
|
|
661
|
+
(relative to the project root) — for ad-hoc declarations or a `.d.ts` that
|
|
662
|
+
isn't shipped by a dependency.
|
|
663
|
+
|
|
664
|
+
Both feeds are merged and **deduped by absolute path**, so pointing `rip.types`
|
|
665
|
+
at a file a dependency already provides is harmless. Missing/unresolvable entries
|
|
666
|
+
are warned and skipped, never fatal.
|
|
634
667
|
|
|
635
668
|
### File Level
|
|
636
669
|
|