jsx_rosetta 0.3.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 +334 -0
- data/README.md +69 -7
- data/ROADMAP.md +92 -0
- data/lib/jsx_rosetta/ast/inflector.rb +15 -0
- data/lib/jsx_rosetta/ast/node.rb +28 -1
- data/lib/jsx_rosetta/backend/phlex.rb +1018 -0
- data/lib/jsx_rosetta/backend/rails_view.rb +3 -1
- data/lib/jsx_rosetta/backend/view_component/expression_translator.rb +338 -25
- data/lib/jsx_rosetta/backend/view_component.rb +361 -77
- data/lib/jsx_rosetta/backend.rb +1 -0
- data/lib/jsx_rosetta/cli.rb +33 -5
- data/lib/jsx_rosetta/ir/lowering.rb +790 -213
- data/lib/jsx_rosetta/ir/module_shape_classifier.rb +167 -0
- data/lib/jsx_rosetta/ir/types.rb +154 -22
- data/lib/jsx_rosetta/version.rb +1 -1
- data/lib/jsx_rosetta.rb +7 -6
- metadata +4 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../ast/node"
|
|
4
|
+
|
|
5
|
+
module JsxRosetta
|
|
6
|
+
module IR
|
|
7
|
+
# Heuristic classifier that labels a module whose top-level shape
|
|
8
|
+
# isn't a function component. Used by Lowering::no_component_error
|
|
9
|
+
# to produce triage-friendly messages explaining *why* a file didn't
|
|
10
|
+
# translate. Pure function: program in, label symbol out — no
|
|
11
|
+
# mutable state, no relationship to the rest of the lowering
|
|
12
|
+
# pipeline.
|
|
13
|
+
#
|
|
14
|
+
# Labels (in priority order — more specific shapes win):
|
|
15
|
+
# :class_component `class X extends React.Component { ... }`
|
|
16
|
+
# :hoc_wrapped `const X = React.memo(...)` etc.
|
|
17
|
+
# :columns_data top-level array literal export
|
|
18
|
+
# :hooks_only every export is a `use*` hook
|
|
19
|
+
# :utils_only every export is a lowercase non-hook helper
|
|
20
|
+
# :mixed_exports mixes hooks + non-hook lowercase exports
|
|
21
|
+
# :side_effects_only top-level expression statements, no exports
|
|
22
|
+
# :types_only types/constants only, no functions
|
|
23
|
+
# :unknown no signal — caller emits the bare error
|
|
24
|
+
class ModuleShapeClassifier
|
|
25
|
+
EXPORT_TYPES = %w[ExportNamedDeclaration ExportDefaultDeclaration].freeze
|
|
26
|
+
HOC_NAMES = %w[memo forwardRef lazy observer].freeze
|
|
27
|
+
|
|
28
|
+
def self.classify(program)
|
|
29
|
+
new(program).classify
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def initialize(program)
|
|
33
|
+
@program = program
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def classify
|
|
37
|
+
ast_shape = classify_ast_shape
|
|
38
|
+
return ast_shape if ast_shape
|
|
39
|
+
|
|
40
|
+
classify_by_export_names(top_level_export_names)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def classify_ast_shape
|
|
46
|
+
return :class_component if @program.body.any? { |stmt| class_component?(stmt) }
|
|
47
|
+
return :hoc_wrapped if @program.body.any? { |stmt| hoc_wrapped_export?(stmt) }
|
|
48
|
+
return :columns_data if @program.body.any? { |stmt| array_literal_export?(stmt) }
|
|
49
|
+
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def classify_by_export_names(names)
|
|
54
|
+
export_label = classify_by_export_pattern(names)
|
|
55
|
+
return export_label if export_label
|
|
56
|
+
|
|
57
|
+
classify_non_export_module
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def classify_by_export_pattern(names)
|
|
61
|
+
any_hooks = names.any? { |n| hook_name?(n) }
|
|
62
|
+
any_helpers = names.any? { |n| /\A[a-z]/.match?(n) && !hook_name?(n) }
|
|
63
|
+
return :mixed_exports if any_hooks && any_helpers
|
|
64
|
+
return :hooks_only if any_hooks
|
|
65
|
+
return :utils_only if any_helpers
|
|
66
|
+
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def classify_non_export_module
|
|
71
|
+
return :side_effects_only if @program.body.any? { |s| side_effect_statement?(s) }
|
|
72
|
+
return :types_only if top_level_has_anything?
|
|
73
|
+
|
|
74
|
+
:unknown
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def hook_name?(name)
|
|
78
|
+
name.start_with?("use") && name.length > 3 && name[3] == name[3].upcase
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Only flag class-component shape when the class has no usable render
|
|
82
|
+
# method. Classes WITH `render()` lower via the
|
|
83
|
+
# ClassDeclaration → ViewComponent path added in v0.5.0, so the
|
|
84
|
+
# classifier should leave them alone and let the regular component
|
|
85
|
+
# finder pick them up.
|
|
86
|
+
def class_component?(stmt)
|
|
87
|
+
decl = stmt.of_type?(*EXPORT_TYPES) ? stmt[:declaration] : stmt
|
|
88
|
+
return false unless AST::Node.matches?(decl, "ClassDeclaration")
|
|
89
|
+
|
|
90
|
+
!class_has_render_method?(decl)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def class_has_render_method?(class_decl)
|
|
94
|
+
body = class_decl.child(:body)
|
|
95
|
+
return false unless body
|
|
96
|
+
|
|
97
|
+
body[:body].any? do |member|
|
|
98
|
+
next false unless AST::Node.matches?(member, "ClassMethod", "MethodDefinition")
|
|
99
|
+
|
|
100
|
+
key = member.child(:key)
|
|
101
|
+
AST::Node.matches?(key, "Identifier") && key[:name] == "render" && member[:kind] != "constructor"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Recognize `export const X = React.memo(...)` (export wrapper) or a
|
|
106
|
+
# top-level `const X = lazy(() => ...)` followed by `export default X`
|
|
107
|
+
# — a VariableDeclaration whose init is a CallExpression to a known HOC.
|
|
108
|
+
def hoc_wrapped_export?(stmt)
|
|
109
|
+
decl = stmt.of_type?(*EXPORT_TYPES) ? stmt[:declaration] : stmt
|
|
110
|
+
return false unless AST::Node.matches?(decl, "VariableDeclaration")
|
|
111
|
+
|
|
112
|
+
decl[:declarations].any? do |d|
|
|
113
|
+
init = d[:init]
|
|
114
|
+
AST::Node.matches?(init, "CallExpression") && hoc_callee?(init[:callee])
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def hoc_callee?(callee)
|
|
119
|
+
return false unless callee.is_a?(AST::Node)
|
|
120
|
+
|
|
121
|
+
case callee.type
|
|
122
|
+
when "Identifier" then HOC_NAMES.include?(callee[:name])
|
|
123
|
+
when "MemberExpression"
|
|
124
|
+
property = callee.child(:property)
|
|
125
|
+
property&.of_type?("Identifier") && HOC_NAMES.include?(property[:name])
|
|
126
|
+
else false
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def array_literal_export?(stmt)
|
|
131
|
+
return false unless stmt.of_type?(*EXPORT_TYPES)
|
|
132
|
+
|
|
133
|
+
decl = stmt[:declaration]
|
|
134
|
+
return true if AST::Node.matches?(decl, "ArrayExpression")
|
|
135
|
+
return false unless AST::Node.matches?(decl, "VariableDeclaration")
|
|
136
|
+
|
|
137
|
+
decl[:declarations].any? { |d| AST::Node.matches?(d[:init], "ArrayExpression") }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def side_effect_statement?(stmt)
|
|
141
|
+
AST::Node.matches?(stmt, "ExpressionStatement")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def top_level_has_anything?
|
|
145
|
+
@program.body.any? { |stmt| stmt.is_a?(AST::Node) && !stmt.of_type?("ImportDeclaration") }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def top_level_export_names
|
|
149
|
+
@program.body.flat_map { |stmt| extract_top_level_names(stmt) }.compact
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def extract_top_level_names(stmt)
|
|
153
|
+
case stmt.type
|
|
154
|
+
when "FunctionDeclaration"
|
|
155
|
+
[stmt.child(:id)&.[](:name)]
|
|
156
|
+
when "VariableDeclaration"
|
|
157
|
+
stmt[:declarations].map { |d| AST::Node.matches?(d[:id], "Identifier") ? d[:id][:name] : nil }
|
|
158
|
+
when "ExportNamedDeclaration", "ExportDefaultDeclaration"
|
|
159
|
+
decl = stmt.child(:declaration)
|
|
160
|
+
decl ? extract_top_level_names(decl) : []
|
|
161
|
+
else
|
|
162
|
+
[]
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
data/lib/jsx_rosetta/ir/types.rb
CHANGED
|
@@ -20,18 +20,58 @@ module JsxRosetta
|
|
|
20
20
|
# the component body. Backends typically render these as
|
|
21
21
|
# a TODO comment block since arbitrary JS-to-Ruby
|
|
22
22
|
# translation isn't attempted.
|
|
23
|
+
# local_binding_names : [String] — flat list of all names bound by the
|
|
24
|
+
# component body (destructure patterns, hook tuples,
|
|
25
|
+
# ordinary const bindings). Backends pass this into the
|
|
26
|
+
# ExpressionTranslator so an identifier reference like
|
|
27
|
+
# `count` resolves to a `nil` placeholder instead of
|
|
28
|
+
# a bare unresolved snake_case identifier that NameErrors
|
|
29
|
+
# at render time. Includes hook destructures (e.g.
|
|
30
|
+
# `open` and `setOpen` from `useState`) even though
|
|
31
|
+
# those names don't appear in `local_bindings`.
|
|
23
32
|
# stimulus_methods : [StimulusMethod] — event handlers extracted from
|
|
24
33
|
# inline arrows / const-bound arrows used in onX={...}.
|
|
25
34
|
# When non-empty, backends should emit a sibling
|
|
26
35
|
# Stimulus controller file alongside the .rb/.erb pair.
|
|
27
|
-
# react_hooks : [ReactHookCall] —
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
36
|
+
# react_hooks : [ReactHookCall] — every recognized hook invocation
|
|
37
|
+
# in the component body, regardless of library.
|
|
38
|
+
# Includes React's built-in hooks (useState, useEffect,
|
|
39
|
+
# useRef, useContext, useMemo, useCallback, useReducer,
|
|
40
|
+
# useImperativeHandle, useLayoutEffect), Apollo hooks
|
|
41
|
+
# (useQuery, useMutation, useLazyQuery, useSubscription,
|
|
42
|
+
# useApolloClient), and Next.js navigation hooks
|
|
43
|
+
# (useRouter, usePathname, useSearchParams, useParams,
|
|
44
|
+
# useSelectedLayoutSegment(s)). Each call carries a
|
|
45
|
+
# `library` tag so backends can group them and emit a
|
|
46
|
+
# library-specific TODO pointing at the right Rails
|
|
47
|
+
# analog (Stimulus/server-render for React; controller
|
|
48
|
+
# fetch for Apollo; request.path/params for Next.js).
|
|
49
|
+
# module_bindings : [LocalBinding] — top-level `const`/`let` declarations
|
|
50
|
+
# outside the component function that aren't themselves
|
|
51
|
+
# components. Captured so backends can either translate
|
|
52
|
+
# to Ruby constants (literal initializers) or surface
|
|
53
|
+
# as a TODO comment block before the class definition.
|
|
54
|
+
# Without this capture, references to module-level
|
|
55
|
+
# constants from inside the JSX silently drop and
|
|
56
|
+
# produce unbacked snake_case references at render time.
|
|
57
|
+
# render_methods : [RenderMethod] — local arrow bindings that return JSX
|
|
58
|
+
# and are invoked from the JSX body (`const renderHeader
|
|
59
|
+
# = () => <div/>; ... {renderHeader()}`). Backends emit
|
|
60
|
+
# each as a private method on the generated class and
|
|
61
|
+
# reference it from a LocalRenderCall at the use site.
|
|
62
|
+
# mode : Symbol — `:view` for a normal Phlex/ViewComponent component
|
|
63
|
+
# whose body is rendered as JSX (the default); `:data_factory`
|
|
64
|
+
# for column-descriptor / option-list modules whose top-level
|
|
65
|
+
# export is a function returning an array of object literals.
|
|
66
|
+
# When `:data_factory`, the backend emits a snake_case method
|
|
67
|
+
# that returns the translated data, instead of `view_template`.
|
|
68
|
+
# JSX inside object properties still extracts to private
|
|
69
|
+
# methods on the class via the IR::Lambda path.
|
|
33
70
|
Component = Data.define(:name, :props, :body, :rest_prop_name,
|
|
34
|
-
:local_bindings, :
|
|
71
|
+
:local_bindings, :local_binding_names,
|
|
72
|
+
:module_bindings,
|
|
73
|
+
:stimulus_methods, :react_hooks,
|
|
74
|
+
:render_methods, :mode) do
|
|
35
75
|
include Node
|
|
36
76
|
end
|
|
37
77
|
|
|
@@ -45,22 +85,37 @@ module JsxRosetta
|
|
|
45
85
|
include Node
|
|
46
86
|
end
|
|
47
87
|
|
|
48
|
-
# A
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
88
|
+
# A hook invocation detected in the component body. Covers React's
|
|
89
|
+
# built-in hooks plus framework hooks we recognize (Apollo's `useQuery`/
|
|
90
|
+
# `useMutation`/etc., Next.js's `useRouter`/`usePathname`/etc.).
|
|
91
|
+
# Surfaced separately from local_bindings so backends can emit a more
|
|
92
|
+
# specific TODO pointing at the Rails equivalent for each library,
|
|
93
|
+
# instead of a generic "translate this JS".
|
|
52
94
|
#
|
|
53
|
-
# hook
|
|
54
|
-
# source
|
|
55
|
-
|
|
95
|
+
# hook : String — hook function name (`"useState"`, `"useQuery"`, …)
|
|
96
|
+
# source : String — verbatim JS of the entire statement.
|
|
97
|
+
# library : Symbol — `:react`, `:apollo`, or `:next_js`. Backends
|
|
98
|
+
# group hooks by library and emit one TODO block per group,
|
|
99
|
+
# since each library maps to a different Rails analog.
|
|
100
|
+
# operation : String | nil — for Apollo hooks called with a bare-Identifier
|
|
101
|
+
# first argument (`useQuery(GET_USERS_QUERY, …)`), the
|
|
102
|
+
# captured operation name. nil when the first argument is
|
|
103
|
+
# not a simple Identifier, or when the hook isn't Apollo.
|
|
104
|
+
# Backends echo it in the TODO so the reviewer can match
|
|
105
|
+
# the operation back to its GraphQL document and to the
|
|
106
|
+
# Rails controller / model fetch it should become.
|
|
107
|
+
ReactHookCall = Data.define(:hook, :source, :library, :operation) do
|
|
56
108
|
include Node
|
|
57
109
|
end
|
|
58
110
|
|
|
59
111
|
# A component prop, possibly with a default value.
|
|
60
112
|
#
|
|
61
|
-
# name
|
|
62
|
-
# default
|
|
63
|
-
|
|
113
|
+
# name : String — the prop name on the parent (e.g. "data-testid").
|
|
114
|
+
# default : Interpolation | nil
|
|
115
|
+
# alias_name : String | nil — the local binding name inside the body when
|
|
116
|
+
# the destructure renames it (`"data-testid": dataTestId`).
|
|
117
|
+
# Use sites of the alias resolve to the prop's ivar.
|
|
118
|
+
Prop = Data.define(:name, :default, :alias_name) do
|
|
64
119
|
include Node
|
|
65
120
|
end
|
|
66
121
|
|
|
@@ -250,11 +305,17 @@ module JsxRosetta
|
|
|
250
305
|
# Body translation is deferred to the human reviewer; we preserve the
|
|
251
306
|
# original JS body verbatim.
|
|
252
307
|
#
|
|
253
|
-
# name
|
|
254
|
-
#
|
|
255
|
-
#
|
|
256
|
-
#
|
|
257
|
-
|
|
308
|
+
# name : String — camelCase Stimulus method name (uniquified
|
|
309
|
+
# when two handlers collide on the same base name).
|
|
310
|
+
# body_source : String — verbatim JS body (the entire arrow function
|
|
311
|
+
# or the function expression body), preserved as a
|
|
312
|
+
# comment in the emitted controller skeleton.
|
|
313
|
+
# original_name : String — the requested base name before uniquification.
|
|
314
|
+
# Equals `name` when there was no collision. When
|
|
315
|
+
# `name != original_name`, backends emit a collision
|
|
316
|
+
# marker comment in the generated controller JS so the
|
|
317
|
+
# reviewer can see the silent rename.
|
|
318
|
+
StimulusMethod = Data.define(:name, :body_source, :original_name) do
|
|
258
319
|
include Node
|
|
259
320
|
end
|
|
260
321
|
|
|
@@ -272,5 +333,76 @@ module JsxRosetta
|
|
|
272
333
|
Loop = Data.define(:iterable, :item_binding, :index_binding, :body) do
|
|
273
334
|
include Node
|
|
274
335
|
end
|
|
336
|
+
|
|
337
|
+
# An object-literal value (`{ key: value, ... }`) inside JSX. Lowered
|
|
338
|
+
# from a JSX attribute or expression value whose root is an
|
|
339
|
+
# ObjectExpression. Each property's key is a String; the value can be
|
|
340
|
+
# any IR node (recursive). Backends render as a Ruby Hash literal,
|
|
341
|
+
# snake_casing identifier keys to match Ruby kwarg conventions.
|
|
342
|
+
#
|
|
343
|
+
# properties : [[String key, Node value]] — preserved in source order.
|
|
344
|
+
ObjectLiteral = Data.define(:properties) do
|
|
345
|
+
include Node
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# An array-literal value (`[a, b, ...]`) inside JSX. Lowered from a
|
|
349
|
+
# JSX attribute or expression value whose root is an ArrayExpression.
|
|
350
|
+
# Each element is an IR node (recursive). Backends render as a Ruby
|
|
351
|
+
# Array literal.
|
|
352
|
+
#
|
|
353
|
+
# elements : [Node]
|
|
354
|
+
ArrayLiteral = Data.define(:elements) do
|
|
355
|
+
include Node
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# An arrow/function expression appearing as an inline value (not in
|
|
359
|
+
# JSX child or event-handler position). Typical example: a `render`
|
|
360
|
+
# property inside an array-of-config-objects passed to an AG-Grid
|
|
361
|
+
# column descriptor or antd Select option. Backends emit as a Ruby
|
|
362
|
+
# method on the class (deterministically named) and reference it via
|
|
363
|
+
# `method(:name)` in the value position, since lambdas don't carry
|
|
364
|
+
# the Phlex execution context required to call tag.* helpers.
|
|
365
|
+
#
|
|
366
|
+
# params : [String]
|
|
367
|
+
# body : Node
|
|
368
|
+
Lambda = Data.define(:params, :body) do
|
|
369
|
+
include Node
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# A render-prop child: `<Form.List>{(fields) => <div>{fields}</div>}</Form.List>`.
|
|
373
|
+
# Backends emit this as a Ruby block on the render call, with the params
|
|
374
|
+
# bound as block arguments. Distinct from Loop (which iterates an
|
|
375
|
+
# iterable) and from Slot (which yields without args).
|
|
376
|
+
#
|
|
377
|
+
# params : [String] — param names (camelCase preserved; backends snake_case).
|
|
378
|
+
# body : Node — the lowered IR node produced by the arrow's body.
|
|
379
|
+
RenderProp = Data.define(:params, :body) do
|
|
380
|
+
include Node
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# A locally-declared JSX-returning arrow that's invoked inside the
|
|
384
|
+
# render body: `const renderHeader = (count) => <h1>{count}</h1>;
|
|
385
|
+
# ... {renderHeader(headerCount)}`. Backends emit one private method
|
|
386
|
+
# per RenderMethod on the generated class and reference it via a
|
|
387
|
+
# LocalRenderCall at each use site.
|
|
388
|
+
#
|
|
389
|
+
# name : String — snake_case method name on the class.
|
|
390
|
+
# params : [String] — arrow param names (camelCase preserved; backends
|
|
391
|
+
# snake_case to form Ruby parameter names).
|
|
392
|
+
# body : Node — the lowered IR node produced by the arrow's body.
|
|
393
|
+
RenderMethod = Data.define(:name, :params, :body) do
|
|
394
|
+
include Node
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# A call to a locally-declared JSX-returning arrow at its use site.
|
|
398
|
+
# Pairs with a sibling RenderMethod on Component#render_methods.
|
|
399
|
+
#
|
|
400
|
+
# method_name : String — snake_case method name (matches RenderMethod#name).
|
|
401
|
+
# args : [Interpolation] — argument expressions captured verbatim
|
|
402
|
+
# (each Interpolation's expression is translated by the
|
|
403
|
+
# backend's ExpressionTranslator at emission time).
|
|
404
|
+
LocalRenderCall = Data.define(:method_name, :args) do
|
|
405
|
+
include Node
|
|
406
|
+
end
|
|
275
407
|
end
|
|
276
408
|
end
|
data/lib/jsx_rosetta/version.rb
CHANGED
data/lib/jsx_rosetta.rb
CHANGED
|
@@ -14,18 +14,19 @@ module JsxRosetta
|
|
|
14
14
|
IR.lower(ast, source: source)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def self.translate(source, backend: :view_component,
|
|
18
|
-
typescript: false, source_filename: nil)
|
|
17
|
+
def self.translate(source, backend: :view_component, backend_options: {},
|
|
18
|
+
typescript: false, source_filename: nil, **legacy_options)
|
|
19
19
|
ast = parse(source, typescript: typescript, source_filename: source_filename)
|
|
20
20
|
components = IR.lower_all(ast, source: source)
|
|
21
|
-
backend_instance = backend_for(backend,
|
|
21
|
+
backend_instance = backend_for(backend, **legacy_options, **backend_options)
|
|
22
22
|
components.flat_map { |component| backend_instance.emit(component) }
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
def self.backend_for(name,
|
|
25
|
+
def self.backend_for(name, **options)
|
|
26
26
|
case name
|
|
27
|
-
when :view_component then Backend::ViewComponent.new(
|
|
28
|
-
when :rails_view then Backend::RailsView.new(
|
|
27
|
+
when :view_component then Backend::ViewComponent.new(**options.slice(:helpers, :layout))
|
|
28
|
+
when :rails_view then Backend::RailsView.new(**options.slice(:helpers, :layout))
|
|
29
|
+
when :phlex then Backend::Phlex.new(**options.slice(:suffix, :namespace))
|
|
29
30
|
else
|
|
30
31
|
raise Error, "unknown backend: #{name.inspect}"
|
|
31
32
|
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.
|
|
4
|
+
version: 0.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sean McCleary
|
|
@@ -28,6 +28,7 @@ files:
|
|
|
28
28
|
- LICENSE.txt
|
|
29
29
|
- PLAN.md
|
|
30
30
|
- README.md
|
|
31
|
+
- ROADMAP.md
|
|
31
32
|
- Rakefile
|
|
32
33
|
- exe/jsx_rosetta
|
|
33
34
|
- lib/jsx_rosetta.rb
|
|
@@ -38,6 +39,7 @@ files:
|
|
|
38
39
|
- lib/jsx_rosetta/ast/visitor.rb
|
|
39
40
|
- lib/jsx_rosetta/backend.rb
|
|
40
41
|
- lib/jsx_rosetta/backend/base.rb
|
|
42
|
+
- lib/jsx_rosetta/backend/phlex.rb
|
|
41
43
|
- lib/jsx_rosetta/backend/rails_view.rb
|
|
42
44
|
- lib/jsx_rosetta/backend/routes_script.rb
|
|
43
45
|
- lib/jsx_rosetta/backend/view_component.rb
|
|
@@ -45,6 +47,7 @@ files:
|
|
|
45
47
|
- lib/jsx_rosetta/cli.rb
|
|
46
48
|
- lib/jsx_rosetta/ir.rb
|
|
47
49
|
- lib/jsx_rosetta/ir/lowering.rb
|
|
50
|
+
- lib/jsx_rosetta/ir/module_shape_classifier.rb
|
|
48
51
|
- lib/jsx_rosetta/ir/types.rb
|
|
49
52
|
- lib/jsx_rosetta/node_bridge.rb
|
|
50
53
|
- lib/jsx_rosetta/parse_error.rb
|