jsx_rosetta 0.1.0 → 0.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ae2ca5d2ff613e264c7a6027c5a95c57781916a38b52090d6c9d62ba7cafd8c
4
- data.tar.gz: 3e6a2e6f2a54e1dd01002e3e1312d3c1144e3af7e315c55cb3eeee137b858c79
3
+ metadata.gz: 57170a56efc4593faf87b9ee0e3ac055f862cb058b9185ed57bb9a12d47f9af4
4
+ data.tar.gz: bd52ab90f51bc6be003f33b7cc10cda266002fa55386bc079f199fbcc9f3130b
5
5
  SHA512:
6
- metadata.gz: 11b3d8dea5bb887a1b55ebccc0119ce5703bf2c39251a0144dda973d01dd05f7e9b58337fb383e87f3c66d1dfac9a465e6cc5a14dca827e0612073401e4f908c
7
- data.tar.gz: 2742e096f9947b4999183e682c0f1f3b405a68894bc9a2bbb4f140fb45f22a5c611c7e5759f88ea9b987bbd98a501f0bbb248638e83b494825881a6ccac60e76
6
+ metadata.gz: 145db314ab8d61384fbebbf8969310664c358c6b561bf1dbf742e97feb71497e58703d300c9d6b1e75159dfee27ee7a0173662a631ab143597b0b6633fff67ff
7
+ data.tar.gz: 423f772ec2e33618ad6e6567d20caa9a4c375c8c05a5cb8425b681010ee89ecddda2fd9a6ddde0c2ab0777962c1d9e51cd3ec69f28236cc0e1390cdf25ccd90e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.0] - 2026-05-10
4
+
5
+ Driven by an empirical probe of v0.1.0 against a 39-file Next.js production
6
+ slice (`reserv-web/src/components/rolloverbook`). The slice exposed three
7
+ return-shape gaps and a crash on nested destructure; this release fixes all
8
+ four. Probe outcome: 33/39 → **39/39 emit**.
9
+
10
+ ### Fixed
11
+
12
+ - **Nested-destructured props no longer crash the lowering.**
13
+ `function X({ outer: { inner } })` previously surfaced as
14
+ `Inflector.underscore(nil)` in the backend. The lowering now uses the
15
+ outer key as the prop name. Renamed destructures (`{ outer: inner }`)
16
+ similarly use the source-side key (the prop name the parent passes),
17
+ not the renamed local.
18
+
19
+ ### Added — return-shape lowering
20
+
21
+ - **Top-level conditional / short-circuit returns** —
22
+ `return cond ? <A/> : <B/>` and `return cond && <A/>` now lower to
23
+ IR::Conditional via a new return-position dispatcher. Previously raised
24
+ "unexpected JSX node in lowering: ConditionalExpression".
25
+ - **Multi-branch `if/else if/else` all-return bodies** — components
26
+ whose every return path lives inside an if-chain (no top-level
27
+ unconditional return) lower to a chained IR::Conditional. Branches
28
+ may be braced single-statement blocks (`if (x) { return <A/>; }`) or
29
+ bare returns (`if (x) return <A/>;`). Branches with side-effect
30
+ statements before the return still raise — those imply behavior we
31
+ can't preserve.
32
+
3
33
  ## [Unreleased]
4
34
 
5
35
  ### Added — translator (lowering)
@@ -209,14 +209,8 @@ module JsxRosetta
209
209
 
210
210
  def lower_object_prop(property)
211
211
  value = property[:value]
212
- if value.type == "AssignmentPattern"
213
- Prop.new(
214
- name: value[:left][:name],
215
- default: Interpolation.new(expression: source_of(value[:right]))
216
- )
217
- else
218
- Prop.new(name: value[:name], default: nil)
219
- end
212
+ default = (Interpolation.new(expression: source_of(value[:right])) if value.type == "AssignmentPattern")
213
+ Prop.new(name: property[:key][:name], default: default)
220
214
  end
221
215
 
222
216
  def lower_function_body(body)
@@ -224,9 +218,12 @@ module JsxRosetta
224
218
  when "BlockStatement"
225
219
  collect_local_bindings(body[:body])
226
220
  return_stmt = body[:body].find { |stmt| stmt.type == "ReturnStatement" }
227
- raise lowering_error("component function has no return statement", node: body) unless return_stmt
221
+ return lower_return_value(return_stmt[:argument]) if return_stmt
228
222
 
229
- lower_jsx(return_stmt[:argument])
223
+ chained = lower_if_return_chain_from_body(body[:body])
224
+ return chained if chained
225
+
226
+ raise lowering_error("component function has no return statement", node: body)
230
227
  when "JSXElement", "JSXFragment"
231
228
  @local_jsx = {}
232
229
  lower_jsx(body)
@@ -235,6 +232,65 @@ module JsxRosetta
235
232
  end
236
233
  end
237
234
 
235
+ # Dispatch a value in return position. Distinct from lower_jsx because
236
+ # `return cond ? <A/> : <B/>` and `return cond && <A/>` are valid return
237
+ # shapes that aren't JSX nodes and need to lower as Conditional.
238
+ def lower_return_value(node)
239
+ case node.type
240
+ when "ConditionalExpression" then lower_ternary_expression(node)
241
+ when "LogicalExpression" then lower_logical_expression(node)
242
+ else lower_jsx(node)
243
+ end
244
+ end
245
+
246
+ # Recognize a body whose only return paths are inside an
247
+ # `if/else if/else` chain at the bottom (no unconditional return).
248
+ # Lowers the chain to nested IR::Conditional. Returns nil when the
249
+ # shape doesn't fit.
250
+ def lower_if_return_chain_from_body(statements)
251
+ if_stmt = statements.last
252
+ return nil unless if_stmt.is_a?(AST::Node) && if_stmt.type == "IfStatement"
253
+
254
+ lower_if_return_chain(if_stmt)
255
+ end
256
+
257
+ def lower_if_return_chain(if_stmt)
258
+ consequent = lower_return_branch(if_stmt[:consequent])
259
+ return nil unless consequent
260
+
261
+ alternate_node = if_stmt[:alternate]
262
+ alternate = case alternate_node&.type
263
+ when nil then nil
264
+ when "IfStatement" then lower_if_return_chain(alternate_node)
265
+ else lower_return_branch(alternate_node)
266
+ end
267
+ return nil if alternate_node && alternate.nil?
268
+
269
+ Conditional.new(
270
+ test: Interpolation.new(expression: source_of(if_stmt[:test])),
271
+ consequent: consequent,
272
+ alternate: alternate
273
+ )
274
+ end
275
+
276
+ # An if-chain branch lowers to a return value only when it is a
277
+ # single-statement block ending in `return X;` (or a bare `return X;`
278
+ # without braces). Multi-statement branches imply side effects we
279
+ # don't preserve, so we bail.
280
+ def lower_return_branch(branch)
281
+ case branch.type
282
+ when "ReturnStatement"
283
+ branch[:argument] && lower_return_value(branch[:argument])
284
+ when "BlockStatement"
285
+ return nil if branch[:body].size != 1
286
+
287
+ inner = branch[:body].first
288
+ return nil unless inner.type == "ReturnStatement" && inner[:argument]
289
+
290
+ lower_return_value(inner[:argument])
291
+ end
292
+ end
293
+
238
294
  def collect_local_bindings(statements)
239
295
  @local_jsx = {}
240
296
  @local_arrows = {}
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JsxRosetta
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsx_rosetta
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean McCleary