jsx_rosetta 0.4.0 → 0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +215 -1
- data/ROADMAP.md +92 -0
- data/lib/jsx_rosetta/ast/inflector.rb +15 -0
- data/lib/jsx_rosetta/backend/phlex.rb +372 -110
- data/lib/jsx_rosetta/backend/view_component/expression_translator.rb +297 -26
- data/lib/jsx_rosetta/backend/view_component.rb +214 -30
- data/lib/jsx_rosetta/ir/lowering.rb +445 -40
- data/lib/jsx_rosetta/ir/module_shape_classifier.rb +20 -1
- data/lib/jsx_rosetta/ir/types.rb +78 -17
- data/lib/jsx_rosetta/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 229956b183e3760f9555874fef214d7ce66d549a603e9d431f5c28251db25bb4
|
|
4
|
+
data.tar.gz: 226c173f5e1010d154801740a2924bbc776ff8eede26587ec1d9d796da51c5f1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4dbf8ac15094f2462a38c65dfbf539163cfb471077e4bd8874893eda3262da031df72da161793f4c65d75597d55c1d7ea09b395f991fb7fcf80b074d6a74e7ca
|
|
7
|
+
data.tar.gz: 94627c0471abeab22e92727047ec9e870439297bc6df6447da1bfb79914415d14abc665d3b2325d9496615e62c3f0099898db11a6c05dfd37acf739aa0af518c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,220 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [
|
|
3
|
+
## [0.5.1] - 2026-05-11
|
|
4
|
+
|
|
5
|
+
A correctness pass on the v0.5.0 Phlex output. A random-sample review
|
|
6
|
+
surfaced six classes of render-time NameError that `ruby -c` couldn't
|
|
7
|
+
catch (the file parses, but evaluation crashes on a bare reference).
|
|
8
|
+
Each one is now closed at the source — the file loads and at worst
|
|
9
|
+
renders empty where a TODO marker tells the reviewer what to fill in.
|
|
10
|
+
Stress test stays at 895/929 clean and 0/1240 syntax failures; lint
|
|
11
|
+
volume on the generated `.rb` files drops ~38% (5443 → 3373 offenses).
|
|
12
|
+
|
|
13
|
+
### Fixed — runtime NameErrors at render time
|
|
14
|
+
|
|
15
|
+
- **Destructure leaks downstream of an untranslatable init.** `const
|
|
16
|
+
{ fieldValue } = customField` is captured as a TODO, but every use
|
|
17
|
+
site (`fieldValue?.X`, `!fieldValue`, `fieldValue > 0`) used to leak
|
|
18
|
+
as a bare snake_case ref — file loaded, but `field_value&.__typename`
|
|
19
|
+
NameErrored on first render. The translator now bails the whole
|
|
20
|
+
expression when a known-local sits at a member-chain root, on the
|
|
21
|
+
operand side of unary, or on either side of binary. The caller emits
|
|
22
|
+
a `# TODO: translate condition: …` line and falls through to an
|
|
23
|
+
`if false` fallback so the file at least loads.
|
|
24
|
+
- **Inline arrow handlers on PascalCase components.** `const handleClick
|
|
25
|
+
= () => {}` attached to `<Button onClick={handleClick}>` no longer
|
|
26
|
+
leaks as bare `handle_click`. Unconsumed local-arrow names are unioned
|
|
27
|
+
into `Component#local_binding_names`, so use sites emit `on_click: nil`
|
|
28
|
+
instead of a reference to an undefined method.
|
|
29
|
+
- **CamelCase / snake_case rest-prop mismatch.** `**descriptionProps`
|
|
30
|
+
emitted `**descriptionProps` in the initializer signature but the body
|
|
31
|
+
read `**(@description_props || {})` — silently dropped. The rest-name
|
|
32
|
+
is now snake_cased on both sides. `IR::Prop` gains an `alias_name:`
|
|
33
|
+
field so renamed destructures (`"data-testid": dataTestId`) resolve
|
|
34
|
+
the alias to the prop's snake_case ivar at use sites.
|
|
35
|
+
- **Unitless numeric inline styles.** `style={{ marginBottom: 16 }}` used
|
|
36
|
+
to emit `style: 'margin-bottom: 16;'` — invalid CSS, silently ignored
|
|
37
|
+
by browsers. Numeric style values now get a `px` suffix for any
|
|
38
|
+
property not in React's `isUnitlessNumber` table (`zIndex`,
|
|
39
|
+
`lineHeight`, `opacity`, etc. stay bare).
|
|
40
|
+
- **Style declarations rooted at an unresolvable local.** `style={{
|
|
41
|
+
borderRadius: token.borderRadius }}` used to emit `"border-radius:
|
|
42
|
+
#{token.borderRadius};"` and NameError at render. The style
|
|
43
|
+
declaration now drops with a per-decl TODO when its value can't
|
|
44
|
+
translate.
|
|
45
|
+
- **Uppercase / SCREAMING_SNAKE imports in attribute values.** JSX
|
|
46
|
+
`<Foo bar={DEFAULT_PAGE_SIZE}>` used to emit `bar: default_page_size`
|
|
47
|
+
(NameError at the call site). Attribute-position interpolation now
|
|
48
|
+
drops with a TODO when any unresolved identifier starts with
|
|
49
|
+
uppercase — almost always an external import. Lowercase unresolved
|
|
50
|
+
identifiers still pass through unchanged (they may be Rails helpers
|
|
51
|
+
like `current_user`).
|
|
52
|
+
|
|
53
|
+
### Changed — rubocop output cleanup (5443 → 3373 offenses across 1240 .rb)
|
|
54
|
+
|
|
55
|
+
- **`Lint/MissingSuper`** — initializers emit `super()` to satisfy
|
|
56
|
+
Phlex 2.x's superclass.
|
|
57
|
+
- **`Style/Documentation`** — one-line class docstring above every
|
|
58
|
+
generated class (`# X — generated by jsx_rosetta from JSX. Review
|
|
59
|
+
before shipping.`).
|
|
60
|
+
- **`Style/StringLiterals`** — new helper `AST::Inflector.ruby_string_literal`
|
|
61
|
+
emits single-quoted strings when safe (no embedded single quote,
|
|
62
|
+
backslash, or control char). Non-ASCII like emojis stays in single
|
|
63
|
+
quotes. The translator also re-emits JS string literals (`"hi"` →
|
|
64
|
+
`'hi'`) when the same constraints hold.
|
|
65
|
+
- **`Style/NilComparison` / `Style/NonNilCheck`** — rewrite `x == nil`
|
|
66
|
+
and `x != nil` as `x.nil?` / `!x.nil?` at the binary-translation
|
|
67
|
+
layer.
|
|
68
|
+
- **`Lint/BinaryOperatorWithIdenticalOperands`** — dedupe `||` / `&&`
|
|
69
|
+
when both sides translate to the same Ruby. `value === null ||
|
|
70
|
+
value === undefined` collapses to `@value.nil?` instead of
|
|
71
|
+
`@value.nil? || @value.nil?`.
|
|
72
|
+
- **`Lint/EmptyInterpolation`** — bail on the whole template literal
|
|
73
|
+
when any interpolation segment fails translation or resolves to
|
|
74
|
+
literal `nil`. No more `"prefix-#{}-suffix"`.
|
|
75
|
+
- **`Lint/LiteralAsCondition` / `Lint/DuplicateElsifCondition`** —
|
|
76
|
+
emit a file-level `# rubocop:disable` directive (with a matching
|
|
77
|
+
enable at the bottom of the file) only when an intentional `if false`
|
|
78
|
+
fallback appears. The `# TODO: translate condition:` line above the
|
|
79
|
+
fallback already names the issue, so the cop's report would be
|
|
80
|
+
redundant noise.
|
|
81
|
+
- **`Style/IfInsideElse`** — refactor conditional rendering to chain
|
|
82
|
+
`if / elsif / elsif / else / end` instead of nested
|
|
83
|
+
`if / else / if / else / end`. Cascades into fewer
|
|
84
|
+
`Metrics/BlockNesting`, `Metrics/AbcSize`, and
|
|
85
|
+
`Lint/DuplicateElsifCondition` offenses too.
|
|
86
|
+
- **`Layout/TrailingWhitespace`** — post-process emitted output to
|
|
87
|
+
rstrip every line.
|
|
88
|
+
- **`Layout/FirstHashElementIndentation`** — force-inline object/array
|
|
89
|
+
literals inside `initialize` default values; the column mismatch
|
|
90
|
+
that triggered the cop only happens when the wrap kicks in there.
|
|
91
|
+
|
|
92
|
+
## [0.5.0] - 2026-05-11
|
|
93
|
+
|
|
94
|
+
Closes the four v0.5.0-candidate items from the v0.4.0 Phlex sample
|
|
95
|
+
review, plus the four larger features queued at the top of the
|
|
96
|
+
roadmap: Apollo + Next.js hook hint translation, class-component
|
|
97
|
+
support, AG-Grid column-descriptor module emission, and pretty-printing
|
|
98
|
+
for long object/array literals.
|
|
99
|
+
|
|
100
|
+
### Added — class-component support
|
|
101
|
+
|
|
102
|
+
- **`ClassDeclaration` → ViewComponent path.** Classes with a `render()`
|
|
103
|
+
method now lower as components instead of getting flagged with the
|
|
104
|
+
`:class_component` rejection. The `ExpressionTranslator` recognizes
|
|
105
|
+
`this.props.X` and translates to `@x` (plus a snake_case member chain
|
|
106
|
+
for `this.props.X.y.z`); `this.state.X` translates to `nil` since
|
|
107
|
+
there's no Rails-side equivalent without a backing data source. Other
|
|
108
|
+
class members (constructor, lifecycle hooks like
|
|
109
|
+
`componentDidCatch`/`getDerivedStateFromError`, custom event handlers)
|
|
110
|
+
get captured as LocalBinding-style TODO comments at the top of the
|
|
111
|
+
view template so the reviewer sees the verbatim sources. Props are
|
|
112
|
+
synthesized from direct `this.props.X` access AND from
|
|
113
|
+
`const { X } = this.props` destructure patterns — the generated
|
|
114
|
+
`initialize(...)` matches the original class's prop set.
|
|
115
|
+
Stress-test impact: the 4 class-component residuals (ErrorBoundary
|
|
116
|
+
and cousins) now translate cleanly.
|
|
117
|
+
|
|
118
|
+
### Added — AG-Grid column-descriptor module emission
|
|
119
|
+
|
|
120
|
+
- **Data-factory components.** `export const createColumns = (token,
|
|
121
|
+
sortedInfo) => [{...}, {...}]` now lowers as an `IR::Component` with
|
|
122
|
+
`mode: :data_factory`. Phlex emits a snake_case method that returns
|
|
123
|
+
the translated array — `def create_columns(token: nil, sorted_info: nil)`
|
|
124
|
+
— instead of `view_template`. JSX inside object properties extracts to
|
|
125
|
+
private methods on the class via the existing IR::Lambda path
|
|
126
|
+
(`render: method(:render_id_cell)`). ViewComponent backend emits a
|
|
127
|
+
plain Ruby class with the method and a TODO note (the .erb pair is
|
|
128
|
+
skipped — pure-data classes don't have a template). Multi-positional
|
|
129
|
+
Identifier params are now also supported by `lower_params` — previously
|
|
130
|
+
only single-arg React-style signatures lowered cleanly.
|
|
131
|
+
|
|
132
|
+
### Added — pretty-printing
|
|
133
|
+
|
|
134
|
+
- **Multi-line layout for long object/array literals.** When the single-
|
|
135
|
+
line rendering of an `IR::ObjectLiteral` or `IR::ArrayLiteral` exceeds
|
|
136
|
+
`LITERAL_INLINE_BUDGET` (80 chars), or contains a nested literal that
|
|
137
|
+
itself wrapped, the layout switches to one entry per line indented
|
|
138
|
+
two spaces past the parent line's indent. Closing bracket re-aligns
|
|
139
|
+
to the parent indent. Short literals (typical Select options, small
|
|
140
|
+
config objects) stay inline so non-AG-Grid output is unchanged. Helps
|
|
141
|
+
readability of the column-descriptor output from the new
|
|
142
|
+
data-factory path.
|
|
143
|
+
|
|
144
|
+
### Stress test outcome
|
|
145
|
+
|
|
146
|
+
- 929-file Phlex stress rerun: **895/929 clean translations**
|
|
147
|
+
(up from 887/929 in v0.4.0 — 8 additional files now translate via
|
|
148
|
+
the class-component and data-factory paths). **0/1240 emitted `.rb`
|
|
149
|
+
files fail `ruby -c`** (unchanged; 16 more files emitted vs v0.4.0).
|
|
150
|
+
221/929 files carry an Apollo TODO block (281 GraphQL operation
|
|
151
|
+
names captured); 105/929 carry a Next.js navigation-hook block.
|
|
152
|
+
- 385 specs, all green; rubocop clean.
|
|
153
|
+
|
|
154
|
+
### Added — framework hook hints
|
|
155
|
+
|
|
156
|
+
- **Apollo data-fetching hooks recognized.** `useQuery`, `useLazyQuery`,
|
|
157
|
+
`useMutation`, `useSubscription`, and `useApolloClient` are now
|
|
158
|
+
detected at lowering time. Each call lands in `Component#react_hooks`
|
|
159
|
+
tagged `library: :apollo`, with the GraphQL operation name extracted
|
|
160
|
+
from a bare-Identifier first argument (`useQuery(GET_USERS_QUERY, …)`
|
|
161
|
+
→ `operation: "GET_USERS_QUERY"`). Both backends emit a dedicated
|
|
162
|
+
Apollo TODO block above the template that points at the Rails analog
|
|
163
|
+
(move the fetch to the controller; mutations become form POSTs or
|
|
164
|
+
Turbo Stream responses). Operation names are echoed in the comment so
|
|
165
|
+
the reviewer can match the call back to its GraphQL document.
|
|
166
|
+
Destructured names (`{ data, loading, error }` from `useQuery`,
|
|
167
|
+
`[mutate, { loading }]` from `useMutation`) are captured in
|
|
168
|
+
`local_binding_names` so use sites translate to `nil` placeholders
|
|
169
|
+
instead of raising NameError at render time.
|
|
170
|
+
- **Next.js navigation hooks recognized.** `useRouter`, `usePathname`,
|
|
171
|
+
`useSearchParams`, `useParams`, `useSelectedLayoutSegment`, and
|
|
172
|
+
`useSelectedLayoutSegments` get the same treatment — tagged
|
|
173
|
+
`library: :next_js`, surfaced in a dedicated TODO block listing each
|
|
174
|
+
hook's Rails equivalent (`useRouter` → `redirect_to`; `usePathname`
|
|
175
|
+
→ `request.path`; `useSearchParams` / `useParams` → `params`;
|
|
176
|
+
`useSelectedLayoutSegment(s)` → pattern-match `request.path`).
|
|
177
|
+
|
|
178
|
+
The `ReactHookCall` IR type gains `library` and `operation` fields;
|
|
179
|
+
both backends group hooks by library and emit one TODO block per group.
|
|
180
|
+
React-only files keep the unchanged single-block output.
|
|
181
|
+
|
|
182
|
+
### Added
|
|
183
|
+
|
|
184
|
+
- **`BinaryExpression` and `LogicalExpression` translation.**
|
|
185
|
+
`email.emailAttachments.length > 0` now translates to
|
|
186
|
+
`@email.email_attachments.length > 0` instead of bailing to `if false`.
|
|
187
|
+
Covers `===`/`!==`/`==`/`!=`/`<`/`>`/`<=`/`>=`/`&&`/`||`/`??`. JS-only
|
|
188
|
+
operators map to their Ruby equivalents (`===` → `==`, `??` → `||`).
|
|
189
|
+
Recursive splitting respects nested parens and string literals; outer
|
|
190
|
+
parens are stripped so `(a > b) && c` parses cleanly.
|
|
191
|
+
- **Optional chaining (`?.`) → safe navigation (`&.`).** `user?.profile?.name`
|
|
192
|
+
now emits `@user&.profile&.name` instead of leaving a Ruby SyntaxError
|
|
193
|
+
in member-chain interpolations.
|
|
194
|
+
- **Nested render-function locals extracted to methods.**
|
|
195
|
+
`const renderHeader = () => <h1/>; ... {renderHeader()}` previously
|
|
196
|
+
dropped to `[untranslated: renderHeader()]`. New IR types `RenderMethod`
|
|
197
|
+
and `LocalRenderCall` mean the arrow gets extracted to a private method
|
|
198
|
+
on the Phlex class (`def render_header; h1 do; ...; end`) and the use
|
|
199
|
+
site emits a direct call. Args translate through the same path as
|
|
200
|
+
attribute interpolations. The ViewComponent backend emits a method
|
|
201
|
+
skeleton with a TODO since ERB-method bodies don't translate cleanly
|
|
202
|
+
to Ruby fragments.
|
|
203
|
+
|
|
204
|
+
### Fixed
|
|
205
|
+
|
|
206
|
+
- **`useCallback` / `useRef` / `useMemo` identifier bindings recognized.**
|
|
207
|
+
`const handleChange = useCallback(...)` followed by
|
|
208
|
+
`onChange={handleChange}` used to emit `on_change: handle_change`
|
|
209
|
+
referencing a nonexistent method. The binding name is now captured in
|
|
210
|
+
`Component#local_binding_names` so the translator emits `nil` at use
|
|
211
|
+
sites, matching the destructure-pattern behavior added in v0.4.0.
|
|
212
|
+
- **Conditional guard on a known local no longer collapses to `if nil`.**
|
|
213
|
+
`error && <X />` where `error` is destructured from a hook used to
|
|
214
|
+
translate to `if nil` (the local-binding placeholder) — Ruby-valid but
|
|
215
|
+
silently never-rendering. Both backends now treat a `"nil"` translation
|
|
216
|
+
as untranslatable, falling through to the TODO-emission path so the
|
|
217
|
+
reviewer sees what to fill in.
|
|
4
218
|
|
|
5
219
|
## [0.4.0] - 2026-05-11
|
|
6
220
|
|
data/ROADMAP.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
Forward-looking list of work items. Shipped releases are documented in
|
|
4
|
+
[CHANGELOG.md](CHANGELOG.md); the v0.1.0 design lives in [PLAN.md](PLAN.md).
|
|
5
|
+
|
|
6
|
+
Items are tagged by source so the lineage is traceable:
|
|
7
|
+
- **[review]** — surfaced by a subagent sample review of the Phlex
|
|
8
|
+
stress run (highest signal — these are gaps seen in the wild).
|
|
9
|
+
- **[plan-oos]** — explicitly listed as out-of-scope in a prior release plan.
|
|
10
|
+
- **[stress]** — surfaced by the 929-file stress run rejection logs.
|
|
11
|
+
|
|
12
|
+
## Next up
|
|
13
|
+
|
|
14
|
+
v0.5.0 ships every roadmap "Larger features" item that was queued. The
|
|
15
|
+
remaining work in this file is Stress-test residual triage and Polish.
|
|
16
|
+
|
|
17
|
+
## v0.5+ — Larger features
|
|
18
|
+
|
|
19
|
+
_All previously-listed items shipped in v0.5.0. Drop new larger
|
|
20
|
+
features here as they surface._
|
|
21
|
+
|
|
22
|
+
## Stress-test residuals (42/929 rejected)
|
|
23
|
+
|
|
24
|
+
The non-component-shape rejections each have a classifier-tagged error
|
|
25
|
+
message; most are intentional drops. Worth revisiting if a specific
|
|
26
|
+
shape becomes high-value:
|
|
27
|
+
|
|
28
|
+
- [ ] Custom-hooks modules (`useFoo.ts` returning behavior) —
|
|
29
|
+
classifier says "translate behavior to Stimulus." [stress]
|
|
30
|
+
- [ ] Side-effect-only modules — classifier says "register in a Rails
|
|
31
|
+
initializer." [stress]
|
|
32
|
+
- [ ] Types-only / constants-only modules — currently dropped; could
|
|
33
|
+
emit Ruby constants for the constants subset. [stress]
|
|
34
|
+
- [ ] Mixed-export modules — classifier asks the human to split the
|
|
35
|
+
file. No automated fix planned. [stress]
|
|
36
|
+
|
|
37
|
+
## Polish / quality
|
|
38
|
+
|
|
39
|
+
- [ ] **Spec coverage gap.** Some v0.4.0 fixes (template-literal
|
|
40
|
+
escaping, multi-line comment prefixing) have one spec each; consider
|
|
41
|
+
fuzzing across a broader corpus.
|
|
42
|
+
- [ ] **README update.** v0.3.0 added the Phlex backend, v0.4.0 closed
|
|
43
|
+
many gaps — README still describes the v0.2.0 ViewComponent-only
|
|
44
|
+
surface. Add a Phlex section and an example of the recursive
|
|
45
|
+
object-literal translation.
|
|
46
|
+
- [ ] **CHANGELOG TLDR.** The v0.4.0 entry is dense; a one-paragraph
|
|
47
|
+
"what this means for users" intro would help.
|
|
48
|
+
|
|
49
|
+
## Done — historical reference
|
|
50
|
+
|
|
51
|
+
See [CHANGELOG.md](CHANGELOG.md). Major arcs to date:
|
|
52
|
+
- **v0.1.0** — ViewComponent backend, three-stage pipeline.
|
|
53
|
+
- **v0.2.0** — Stimulus extraction, sidecar layout, helpers, RailsView,
|
|
54
|
+
routes, compound components, asChild polymorphism, React hooks.
|
|
55
|
+
- **v0.3.0** — Phlex 2.x backend (three naming strategies), 887/929
|
|
56
|
+
clean translations.
|
|
57
|
+
- **v0.4.0** — Closed nine gaps surfaced by a sample review of v0.3.0
|
|
58
|
+
Phlex output (A, B, D, E, F, G, H, J, K); 0/1224 syntax failures
|
|
59
|
+
(down from 25); 343 specs.
|
|
60
|
+
- **v0.5.0** — Closed all four v0.5.0 candidate items from the v0.4.0
|
|
61
|
+
sample review **plus** every "Larger features" item that was queued:
|
|
62
|
+
- useCallback / useRef / useMemo identifier-bound hook results captured
|
|
63
|
+
in `local_binding_names` so use sites emit `nil` instead of bare
|
|
64
|
+
snake_case refs to nonexistent methods.
|
|
65
|
+
- `BinaryExpression` / `LogicalExpression` translation in the
|
|
66
|
+
`ExpressionTranslator` (incl. `===`/`!==`/`??` mapping).
|
|
67
|
+
- Optional chaining (`?.`) → safe navigation (`&.`) in member chains.
|
|
68
|
+
- Nested render-function locals (`const renderHeader = () => <div/>;
|
|
69
|
+
... {renderHeader()}`) extracted to private methods on the class.
|
|
70
|
+
- `error && <X/>` guard on a known local no longer collapses to
|
|
71
|
+
`if nil` — falls through to the TODO path.
|
|
72
|
+
- Apollo hooks (`useQuery` / `useLazyQuery` / `useMutation` /
|
|
73
|
+
`useSubscription` / `useApolloClient`) detected with the GraphQL
|
|
74
|
+
operation name extracted from a bare-Identifier first argument;
|
|
75
|
+
emitted as a dedicated TODO block pointing at the Rails controller
|
|
76
|
+
fetch. Stress-test impact: 221/929 files now carry the Apollo block
|
|
77
|
+
(281 operation names captured).
|
|
78
|
+
- Next.js navigation hooks (`useRouter` / `usePathname` /
|
|
79
|
+
`useSearchParams` / `useParams` / `useSelectedLayoutSegment(s)`)
|
|
80
|
+
detected and surfaced in a dedicated TODO block listing each
|
|
81
|
+
hook's Rails analog. Stress-test impact: 105/929 files now carry
|
|
82
|
+
the Next.js block.
|
|
83
|
+
- Class-component support (`render()` method extraction, `this.props.X`
|
|
84
|
+
→ `@x` translation, non-render members captured as TODO comments).
|
|
85
|
+
The 4 class-component residuals now translate cleanly.
|
|
86
|
+
- Data-factory components (`export const createColumns = (token) =>
|
|
87
|
+
[{...}]`) lower with `mode: :data_factory`; Phlex emits a snake_case
|
|
88
|
+
method that returns the translated array, with JSX render lambdas
|
|
89
|
+
extracted to private methods.
|
|
90
|
+
- Pretty-printing for long `ObjectLiteral` / `ArrayLiteral` output:
|
|
91
|
+
multi-line layout when single-line exceeds 80 chars; short literals
|
|
92
|
+
stay inline.
|
|
@@ -19,6 +19,21 @@ module JsxRosetta
|
|
|
19
19
|
parts = string.split("_")
|
|
20
20
|
parts[0] + parts[1..].map(&:capitalize).join
|
|
21
21
|
end
|
|
22
|
+
|
|
23
|
+
# Emit a Ruby string literal in the rubocop-default single-quoted
|
|
24
|
+
# form when safe. Falls back to `String#inspect` (double-quoted with
|
|
25
|
+
# escapes) when the source contains characters that prevent the
|
|
26
|
+
# single-quoted form: single quotes themselves, backslashes (Ruby
|
|
27
|
+
# single-quoted strings only escape `\\` and `\'`), or control
|
|
28
|
+
# characters (`\n`, `\t`, etc — single-quoted strings render those
|
|
29
|
+
# literally). Non-ASCII characters (emojis, unicode) are fine in
|
|
30
|
+
# single-quoted strings, so they don't force the fallback.
|
|
31
|
+
def ruby_string_literal(value)
|
|
32
|
+
str = value.to_s
|
|
33
|
+
return str.inspect if str.include?("'") || str.include?("\\") || str.match?(/[\x00-\x1f\x7f]/)
|
|
34
|
+
|
|
35
|
+
"'#{str}'"
|
|
36
|
+
end
|
|
22
37
|
end
|
|
23
38
|
end
|
|
24
39
|
end
|