Module: Ruact::ServerFunctions::NameBridge
- Defined in:
- lib/ruact/server_functions/name_bridge.rb
Overview
Translates a Ruby symbol into the JS identifier exported from ‘app/javascript/.ruact/server-functions.ts`. The bridge is Ruby-side only; the Vite plugin reads the already-translated identifier from the JSON snapshot and emits it verbatim (Story 8.0a design decision: one source of truth for naming).
Rules (locked by Story 8.0 ADR + 2026-05-13 review-patch tightening):
-
Symbol must match ‘/A[a-z0-9_]*z/`; otherwise raises ConfigurationError at controller-class load time.
-
A single leading underscore is preserved (e.g. ‘:_internal_dump` →`“_internalDump”`).
-
Runs of underscores collapse and uppercase the following alphanumeric (e.g. ‘:foo__bar` → `“fooBar”`).
-
The source symbol cannot be made of underscores alone (‘:_`, `:__`, …) —would otherwise emit `“_”`, which carries no semantic content and collides with the common “ignored value” lint convention.
-
The translated JS identifier cannot match a JavaScript reserved word or future-reserved word (ES2020+ list plus the module-top-level ‘await` / `async`). `:class` → `“class”` would compile under Babel loose-mode but break under `tsc –noEmit` and most ESLint configs.
Constant Summary collapse
- VALID_SYMBOL =
/\A[a-z_][a-z0-9_]*\z/- UNDERSCORE_ONLY =
/\A_+\z/- RESERVED_JS_IDENTIFIERS =
ES2020+ reserved + strict-mode reserved + contextually-reserved at module top level + strict-mode invalid binding names. The codegen emits in a module context (the generated ‘app/javascript/.ruact/server-functions.ts` is `“type”: “module”`, so all code runs in strict mode), so `await`, `eval`, and `arguments` are all reserved as identifier names. Keep this list sorted; matches the MDN reference plus the contextual additions and the strict-mode `eval`/`arguments` ban from the 2026-05-13 Re-run review patch.
%w[ arguments async await break case catch class const continue debugger default delete do else enum eval export extends false finally for function if implements import in instanceof interface let new null package private protected public return static super switch this throw true try typeof var void while with yield ].to_set.freeze
- RESERVED_BY_RUACT =
Story 8.2 (2026-05-17 review patches R2 + R12) — names already bound at the top of ‘app/javascript/.ruact/server-functions.ts`, either by the helper re-export (`revalidate`) or the runtime import (`_makeRef`). A `ruact_action :revalidate` or `ruact_action :_make_ref` would emit a clashing `export const` next to the existing binding and crash at module-load time. The rule fires at controller-class load so the failure surfaces during boot, not at first request.
%w[ _makeRef _makeServerFunction revalidate ].to_set.freeze
Class Method Summary collapse
-
.to_js_identifier(symbol) ⇒ String
The corresponding JS identifier.
Class Method Details
.to_js_identifier(symbol) ⇒ String
Returns the corresponding JS identifier.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/ruact/server_functions/name_bridge.rb', line 76 def to_js_identifier(symbol) str = symbol.to_s unless str.match?(VALID_SYMBOL) raise Ruact::ConfigurationError, "ruact_action / ruact_query symbol :#{symbol} must match /^[a-z_][a-z0-9_]*$/" end if str.match?(UNDERSCORE_ONLY) raise Ruact::ConfigurationError, "ruact_action / ruact_query symbol :#{symbol} cannot be composed " \ "entirely of underscores (no semantic content)" end leading = str.start_with?("_") ? "_" : "" body = str.sub(/\A_+/, "") js_id = leading + body.gsub(/_+([a-z0-9])/) { Regexp.last_match(1).upcase } if RESERVED_JS_IDENTIFIERS.include?(js_id) raise Ruact::ConfigurationError, "ruact_action / ruact_query symbol :#{symbol} maps to JS reserved " \ "word \"#{js_id}\" — pick a different Ruby symbol (e.g. :#{symbol}_action)" end if RESERVED_BY_RUACT.include?(js_id) raise Ruact::ConfigurationError, "ruact_action / ruact_query symbol :#{symbol} maps to \"#{js_id}\", " \ "which is already exported by the ruact runtime from " \ "`@/.ruact/server-functions` and would emit a duplicate export. " \ "Pick a different Ruby symbol (e.g. :#{symbol}_action)." end js_id end |