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 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.3-blue.svg" alt="Version"></a>
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
- **Auto-Wired Event Handlers:**
1700
+ **Event Directives:**
1634
1701
 
1635
- Methods named `on` + capitalized event name are automatically bound to the component's root element:
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 compiler wires `addEventListener('click', ...)` and `addEventListener('keydown', ...)` to the root `button`. To override for a specific event, write an explicit binding on the root: `button @click: someOtherHandler`. Lifecycle hooks (`onError`) are not auto-wired.
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
 
@@ -7,4 +7,4 @@ export Card = component
7
7
  div.card
8
8
  if title
9
9
  h3 "#{title}"
10
- @children
10
+ slot