mayu-live 0.0.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 +7 -0
- data/COPYING +661 -0
- data/README.md +598 -0
- data/exe/mayu +33 -0
- data/lib/mayu/app_metrics.rb +93 -0
- data/lib/mayu/banner.rb +12 -0
- data/lib/mayu/client/README.md +17 -0
- data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js +1 -0
- data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.br +0 -0
- data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.map +1 -0
- data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.map.br +0 -0
- data/lib/mayu/client/dist/custom-elements/mayu-alert-cd7ad2a4.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-alert-cd7ad2a4.js.map +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-disconnected-9f349f46.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-disconnected-9f349f46.js.map +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-exception-63df4e8c.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-exception-63df4e8c.js.map +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-ping-c498c2a6.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-ping-c498c2a6.js.map +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-progress-bar-eb3e1ac8.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-progress-bar-eb3e1ac8.js.map +1 -0
- data/lib/mayu/client/dist/entries.json +3 -0
- data/lib/mayu/client/dist/main-4b49dbc4.js +1 -0
- data/lib/mayu/client/dist/main-4b49dbc4.js.br +0 -0
- data/lib/mayu/client/dist/main-4b49dbc4.js.map +1 -0
- data/lib/mayu/client/dist/main-4b49dbc4.js.map.br +0 -0
- data/lib/mayu/client/package.json +39 -0
- data/lib/mayu/client/rollup.config.js +81 -0
- data/lib/mayu/client/src/DecompressionStream.ts +15 -0
- data/lib/mayu/client/src/DecompressionStreamPolyfill.ts +43 -0
- data/lib/mayu/client/src/MimeTypes.ts +4 -0
- data/lib/mayu/client/src/NodeTree.ts +445 -0
- data/lib/mayu/client/src/custom-elements/mayu-alert.html +137 -0
- data/lib/mayu/client/src/custom-elements/mayu-alert.ts +62 -0
- data/lib/mayu/client/src/custom-elements/mayu-disconnected.html +134 -0
- data/lib/mayu/client/src/custom-elements/mayu-disconnected.ts +51 -0
- data/lib/mayu/client/src/custom-elements/mayu-exception.html +79 -0
- data/lib/mayu/client/src/custom-elements/mayu-exception.ts +28 -0
- data/lib/mayu/client/src/custom-elements/mayu-log.html +70 -0
- data/lib/mayu/client/src/custom-elements/mayu-log.ts +42 -0
- data/lib/mayu/client/src/custom-elements/mayu-ping.html +36 -0
- data/lib/mayu/client/src/custom-elements/mayu-ping.ts +53 -0
- data/lib/mayu/client/src/custom-elements/mayu-progress-bar.html +44 -0
- data/lib/mayu/client/src/custom-elements/mayu-progress-bar.ts +40 -0
- data/lib/mayu/client/src/custom-elements/types.d.ts +4 -0
- data/lib/mayu/client/src/global.d.ts +26 -0
- data/lib/mayu/client/src/h.ts +27 -0
- data/lib/mayu/client/src/logger.ts +56 -0
- data/lib/mayu/client/src/main.ts +271 -0
- data/lib/mayu/client/src/serializeEvent.ts +90 -0
- data/lib/mayu/client/src/stream.ts +175 -0
- data/lib/mayu/client/src/types.ts +1 -0
- data/lib/mayu/client/src/utils.ts +71 -0
- data/lib/mayu/client/tsconfig.json +18 -0
- data/lib/mayu/colors.rb +34 -0
- data/lib/mayu/commands/base.rb +22 -0
- data/lib/mayu/commands/build.rb +82 -0
- data/lib/mayu/commands.rb +53 -0
- data/lib/mayu/component/base.rb +177 -0
- data/lib/mayu/component/handler_ref.rb +99 -0
- data/lib/mayu/component/helpers.rb +93 -0
- data/lib/mayu/component/interface.rb +18 -0
- data/lib/mayu/component/wrapper.rb +165 -0
- data/lib/mayu/component.rb +54 -0
- data/lib/mayu/configuration.rb +195 -0
- data/lib/mayu/disable_sorbet.rb +23 -0
- data/lib/mayu/environment.rb +151 -0
- data/lib/mayu/event_stream.rb +158 -0
- data/lib/mayu/fetch.rb +88 -0
- data/lib/mayu/html.rb +53 -0
- data/lib/mayu/html.yaml +767 -0
- data/lib/mayu/message_cipher.rb +172 -0
- data/lib/mayu/message_cipher.test.rb +16 -0
- data/lib/mayu/metrics/collector.rb +161 -0
- data/lib/mayu/metrics/exporter.rb +47 -0
- data/lib/mayu/metrics/reporter.rb +187 -0
- data/lib/mayu/metrics.rb +82 -0
- data/lib/mayu/ref_counter.rb +57 -0
- data/lib/mayu/resources/README.md +14 -0
- data/lib/mayu/resources/asset.rb +71 -0
- data/lib/mayu/resources/assets.rb +76 -0
- data/lib/mayu/resources/dependency_graph.rb +306 -0
- data/lib/mayu/resources/dot_exporter.rb +167 -0
- data/lib/mayu/resources/generators/base.rb +18 -0
- data/lib/mayu/resources/generators/copy_file.rb +26 -0
- data/lib/mayu/resources/generators/image.rb +106 -0
- data/lib/mayu/resources/generators/write_file.rb +39 -0
- data/lib/mayu/resources/hot_swap/file_watcher.rb +69 -0
- data/lib/mayu/resources/hot_swap.rb +46 -0
- data/lib/mayu/resources/mermaid_exporter.rb +210 -0
- data/lib/mayu/resources/registry.rb +190 -0
- data/lib/mayu/resources/resolver/base.rb +32 -0
- data/lib/mayu/resources/resolver/filesystem.rb +94 -0
- data/lib/mayu/resources/resolver/static.rb +27 -0
- data/lib/mayu/resources/resolver.rb +13 -0
- data/lib/mayu/resources/resource.rb +150 -0
- data/lib/mayu/resources/transformers/__test__/css/adjacent_selectors.in.css +3 -0
- data/lib/mayu/resources/transformers/__test__/css/adjacent_selectors.out.css +6 -0
- data/lib/mayu/resources/transformers/__test__/css/attributes.in.css +3 -0
- data/lib/mayu/resources/transformers/__test__/css/attributes.out.css +6 -0
- data/lib/mayu/resources/transformers/__test__/css/composes.in.css +6 -0
- data/lib/mayu/resources/transformers/__test__/css/composes.out.css +10 -0
- data/lib/mayu/resources/transformers/__test__/css/element_selectors.in.css +3 -0
- data/lib/mayu/resources/transformers/__test__/css/element_selectors.out.css +6 -0
- data/lib/mayu/resources/transformers/__test__/css/has.in.css +7 -0
- data/lib/mayu/resources/transformers/__test__/css/has.out.css +10 -0
- data/lib/mayu/resources/transformers/__test__/css/media_queries.in.css +8 -0
- data/lib/mayu/resources/transformers/__test__/css/media_queries.out.css +12 -0
- data/lib/mayu/resources/transformers/__test__/css/pseudo_classes.in.css +5 -0
- data/lib/mayu/resources/transformers/__test__/css/pseudo_classes.out.css +6 -0
- data/lib/mayu/resources/transformers/__test__/haml/README.md +10 -0
- data/lib/mayu/resources/transformers/__test__/haml/case.haml +8 -0
- data/lib/mayu/resources/transformers/__test__/haml/case.rb +15 -0
- data/lib/mayu/resources/transformers/__test__/haml/class_names.haml +13 -0
- data/lib/mayu/resources/transformers/__test__/haml/class_names.rb +26 -0
- data/lib/mayu/resources/transformers/__test__/haml/comments.haml +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/comments.rb +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/css.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/css.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/dashes.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/dashes.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/early_return.haml +4 -0
- data/lib/mayu/resources/transformers/__test__/haml/early_return.rb +9 -0
- data/lib/mayu/resources/transformers/__test__/haml/early_return2.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/early_return2.rb +6 -0
- data/lib/mayu/resources/transformers/__test__/haml/handlers.haml +6 -0
- data/lib/mayu/resources/transformers/__test__/haml/handlers.rb +12 -0
- data/lib/mayu/resources/transformers/__test__/haml/if_else.haml +6 -0
- data/lib/mayu/resources/transformers/__test__/haml/if_else.rb +12 -0
- data/lib/mayu/resources/transformers/__test__/haml/interpolation.haml +8 -0
- data/lib/mayu/resources/transformers/__test__/haml/interpolation.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/object_ref_as_key.haml +1 -0
- data/lib/mayu/resources/transformers/__test__/haml/object_ref_as_key.rb +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/props.haml +4 -0
- data/lib/mayu/resources/transformers/__test__/haml/props.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots.haml +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots.rb +9 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots_dynamic.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots_dynamic.rb +9 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots_fallback.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots_fallback.rb +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing.haml +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing.rb +14 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing2.haml +10 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing2.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing3.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing3.rb +10 -0
- data/lib/mayu/resources/transformers/css/rouge_lexer.rb +841 -0
- data/lib/mayu/resources/transformers/css.rb +100 -0
- data/lib/mayu/resources/transformers/css.test.rb +87 -0
- data/lib/mayu/resources/transformers/haml.rb +984 -0
- data/lib/mayu/resources/transformers/haml.test.rb +114 -0
- data/lib/mayu/resources/types/README.md +36 -0
- data/lib/mayu/resources/types/base.rb +35 -0
- data/lib/mayu/resources/types/component.rb +198 -0
- data/lib/mayu/resources/types/image.rb +169 -0
- data/lib/mayu/resources/types/javascript.rb +50 -0
- data/lib/mayu/resources/types/nil.rb +23 -0
- data/lib/mayu/resources/types/stylesheet.rb +119 -0
- data/lib/mayu/resources/types/svg.rb +69 -0
- data/lib/mayu/resources/types.rb +37 -0
- data/lib/mayu/routes.rb +170 -0
- data/lib/mayu/routing/builder.rb +108 -0
- data/lib/mayu/routing/matcher.rb +58 -0
- data/lib/mayu/routing/routes.rb +85 -0
- data/lib/mayu/routing.rb +17 -0
- data/lib/mayu/server/app.rb +494 -0
- data/lib/mayu/server/controller.rb +152 -0
- data/lib/mayu/server/errors.rb +110 -0
- data/lib/mayu/server/file_server.rb +140 -0
- data/lib/mayu/server.rb +63 -0
- data/lib/mayu/session.rb +358 -0
- data/lib/mayu/state/README.md +6 -0
- data/lib/mayu/state/action_creator.rb +191 -0
- data/lib/mayu/state/action_wrapper.rb +30 -0
- data/lib/mayu/state/loader.rb +220 -0
- data/lib/mayu/state/store.rb +82 -0
- data/lib/mayu/state.rb +8 -0
- data/lib/mayu/state.test.rb +97 -0
- data/lib/mayu/utils.rb +114 -0
- data/lib/mayu/vdom/children.rb +117 -0
- data/lib/mayu/vdom/component_marshaler.rb +53 -0
- data/lib/mayu/vdom/css_attributes.rb +131 -0
- data/lib/mayu/vdom/descriptor.rb +151 -0
- data/lib/mayu/vdom/descriptor.test.rb +26 -0
- data/lib/mayu/vdom/dom.rb +239 -0
- data/lib/mayu/vdom/h.rb +22 -0
- data/lib/mayu/vdom/id_generator.rb +55 -0
- data/lib/mayu/vdom/interfaces.rb +186 -0
- data/lib/mayu/vdom/marshalling.rb +78 -0
- data/lib/mayu/vdom/reconciliation.rb +205 -0
- data/lib/mayu/vdom/reconciliation.test.rb +56 -0
- data/lib/mayu/vdom/special_elements.rb +108 -0
- data/lib/mayu/vdom/update_context.rb +180 -0
- data/lib/mayu/vdom/vdom.perf.test.rb +146 -0
- data/lib/mayu/vdom/vnode.rb +266 -0
- data/lib/mayu/vdom/vtree.rb +672 -0
- data/lib/mayu/vdom/vtree.test.rb +68 -0
- data/lib/mayu/vdom.rb +8 -0
- data/lib/mayu/vdom.test.rb +73 -0
- data/lib/mayu/version.rb +6 -0
- data/lib/mayu.rb +8 -0
- data/mayu-live.gemspec +70 -0
- metadata +612 -0
@@ -0,0 +1,984 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "ripper"
|
5
|
+
require "syntax_suggest"
|
6
|
+
require "syntax_suggest/code_line"
|
7
|
+
require "syntax_suggest/explain_syntax"
|
8
|
+
require "syntax_suggest/lex_all"
|
9
|
+
require "syntax_suggest/ripper_errors"
|
10
|
+
require "syntax_tree"
|
11
|
+
require "syntax_tree/haml"
|
12
|
+
require_relative "css"
|
13
|
+
|
14
|
+
module Mayu
|
15
|
+
module Resources
|
16
|
+
module Transformers
|
17
|
+
module Haml
|
18
|
+
class MutationVisitor < SyntaxTree::Visitor::MutationVisitor
|
19
|
+
# This class visits more nodes than the parent class.
|
20
|
+
# This should probably be fixed in the syntax_tree gem,
|
21
|
+
# but I don't know what other nodes need to be fixed.
|
22
|
+
|
23
|
+
def self.build(&block)
|
24
|
+
new.tap { yield _1 }
|
25
|
+
end
|
26
|
+
|
27
|
+
def visit_assign(node)
|
28
|
+
node.copy(target: visit(node.target), value: visit(node.value))
|
29
|
+
end
|
30
|
+
|
31
|
+
def visit_assoc_splat(node)
|
32
|
+
node.copy(value: visit(node.value))
|
33
|
+
end
|
34
|
+
|
35
|
+
def visit_assoc(node)
|
36
|
+
node.copy(key: visit(node.key), value: visit(node.value))
|
37
|
+
end
|
38
|
+
|
39
|
+
def visit_aref(node)
|
40
|
+
node.copy(
|
41
|
+
collection: visit(node.collection),
|
42
|
+
index: visit(node.index)
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def visit_opassign(node)
|
47
|
+
node.copy(target: visit(node.target), value: visit(node.value))
|
48
|
+
end
|
49
|
+
|
50
|
+
def visit_binary(node)
|
51
|
+
node.copy(left: visit(node.left), right: visit(node.right))
|
52
|
+
end
|
53
|
+
|
54
|
+
def visit_if_op(node)
|
55
|
+
node.copy(
|
56
|
+
predicate: visit(node.predicate),
|
57
|
+
truthy: visit(node.truthy),
|
58
|
+
falsy: visit(node.falsy)
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class TransformResult < T::Struct
|
64
|
+
const :filename, String
|
65
|
+
const :output, String
|
66
|
+
const :content_hash, String
|
67
|
+
const :css, T.nilable(CSS::TransformResult)
|
68
|
+
const :source_map, T::Hash[String, T.untyped]
|
69
|
+
end
|
70
|
+
|
71
|
+
class TransformOptions < T::Struct
|
72
|
+
const :source, String
|
73
|
+
const :source_path, String
|
74
|
+
const :source_line, Integer
|
75
|
+
|
76
|
+
# TODO: Remove content_hash, it does not seem to be used?
|
77
|
+
const :content_hash, T.nilable(String)
|
78
|
+
|
79
|
+
const :transform_elements_to_classes, T::Boolean, default: true
|
80
|
+
const :enable_new_helper_ident, T::Boolean, default: false
|
81
|
+
|
82
|
+
def source_path_without_extension
|
83
|
+
File.join(
|
84
|
+
File.dirname(source_path),
|
85
|
+
File.basename(source_path, ".*")
|
86
|
+
).delete_prefix("./")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
extend T::Sig
|
91
|
+
|
92
|
+
sig { params(options: TransformOptions).returns(TransformResult) }
|
93
|
+
def self.transform(options)
|
94
|
+
result =
|
95
|
+
SyntaxTree::Haml.parse(options.source).accept(
|
96
|
+
Transformer.new(options)
|
97
|
+
)
|
98
|
+
|
99
|
+
TransformResult.new(
|
100
|
+
filename: options.source_path,
|
101
|
+
output: result.source,
|
102
|
+
content_hash: Digest::SHA256.digest(result.source),
|
103
|
+
css: result.styles.first,
|
104
|
+
source_map: {
|
105
|
+
}
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
class RubyBuilder
|
110
|
+
include SyntaxTree::DSL
|
111
|
+
|
112
|
+
def initialize(options)
|
113
|
+
@options = options
|
114
|
+
end
|
115
|
+
|
116
|
+
def assign_const(name, value) = Assign(VarField(Const(name)), value)
|
117
|
+
def self_var_ref = VarRef(Kw("self"))
|
118
|
+
|
119
|
+
def create_program(setup, styles, render)
|
120
|
+
Program(
|
121
|
+
Statements(
|
122
|
+
[
|
123
|
+
Comment("# frozen_string_literal: true", false),
|
124
|
+
assign_const("Self", setup_component(styles)),
|
125
|
+
*setup,
|
126
|
+
create_render(render)
|
127
|
+
]
|
128
|
+
)
|
129
|
+
).accept(StateAndPropsTransformer.new.visitor)
|
130
|
+
end
|
131
|
+
|
132
|
+
def const_path(*names)
|
133
|
+
names.reduce(nil) do |parent, name|
|
134
|
+
const = Const(name)
|
135
|
+
|
136
|
+
if T.cast(parent, T.untyped)
|
137
|
+
ConstPathRef(parent, const)
|
138
|
+
else
|
139
|
+
TopConstRef(const)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def setup_component(styles)
|
145
|
+
CallNode(
|
146
|
+
nil,
|
147
|
+
nil,
|
148
|
+
Ident("setup_component"),
|
149
|
+
ArgParen(
|
150
|
+
Args(
|
151
|
+
[
|
152
|
+
BareAssocHash(
|
153
|
+
assocs(
|
154
|
+
assets:
|
155
|
+
array(styles.map { string_literal(_1.filename) }),
|
156
|
+
styles: props_hash(CSS.merge_classnames(styles))
|
157
|
+
)
|
158
|
+
)
|
159
|
+
]
|
160
|
+
)
|
161
|
+
)
|
162
|
+
)
|
163
|
+
end
|
164
|
+
|
165
|
+
def assocs(**kwargs)
|
166
|
+
kwargs.map { |key, value| Assoc(Label("#{key}:"), value) }
|
167
|
+
end
|
168
|
+
|
169
|
+
def array(elems)
|
170
|
+
ArrayLiteral(LBracket("["), Args(elems))
|
171
|
+
end
|
172
|
+
|
173
|
+
def create_render(statements)
|
174
|
+
Command(
|
175
|
+
Ident("public"),
|
176
|
+
Args(
|
177
|
+
[
|
178
|
+
DefNode(
|
179
|
+
nil,
|
180
|
+
nil,
|
181
|
+
Ident("render"),
|
182
|
+
nil,
|
183
|
+
BodyStmt(
|
184
|
+
Statements(
|
185
|
+
[
|
186
|
+
# set_fiber_local("current_component", VarRef(Kw("self"))),
|
187
|
+
*statements
|
188
|
+
]
|
189
|
+
),
|
190
|
+
nil,
|
191
|
+
nil,
|
192
|
+
nil,
|
193
|
+
nil
|
194
|
+
)
|
195
|
+
)
|
196
|
+
]
|
197
|
+
),
|
198
|
+
nil
|
199
|
+
)
|
200
|
+
end
|
201
|
+
|
202
|
+
def set_fiber_local(ident, value)
|
203
|
+
Assign(
|
204
|
+
ARefField(
|
205
|
+
VarRef(Const("Fiber")),
|
206
|
+
Args([SymbolLiteral(Ident(ident))])
|
207
|
+
),
|
208
|
+
value
|
209
|
+
)
|
210
|
+
end
|
211
|
+
|
212
|
+
def slot(name = nil, fallback: nil)
|
213
|
+
if fallback in [_, *]
|
214
|
+
return(
|
215
|
+
MethodAddBlock(
|
216
|
+
slot(name, fallback: nil),
|
217
|
+
BlockNode(
|
218
|
+
Kw("do"),
|
219
|
+
nil,
|
220
|
+
BodyStmt(Statements(Array(fallback)), nil, nil, nil, nil)
|
221
|
+
)
|
222
|
+
)
|
223
|
+
)
|
224
|
+
end
|
225
|
+
|
226
|
+
# call_helpers(:slot, [Ident("children"), name].compact)
|
227
|
+
call_helpers(:slot, [name].compact)
|
228
|
+
end
|
229
|
+
|
230
|
+
def tag(name, children, attrs_to_merge)
|
231
|
+
ARef(
|
232
|
+
ConstPathRef(
|
233
|
+
ConstPathRef(VarRef(Const("Mayu")), Const("VDOM")),
|
234
|
+
Const("H")
|
235
|
+
),
|
236
|
+
Args(
|
237
|
+
[
|
238
|
+
tag_name_or_class(name),
|
239
|
+
*children,
|
240
|
+
merge_props(attrs_to_merge)
|
241
|
+
].flatten.compact
|
242
|
+
)
|
243
|
+
)
|
244
|
+
end
|
245
|
+
|
246
|
+
def tag_name_or_class(name)
|
247
|
+
case name
|
248
|
+
in /\A[A-Z]/
|
249
|
+
Ident(name)
|
250
|
+
else
|
251
|
+
SymbolLiteral(Ident(name))
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def splat_hash(node)
|
256
|
+
BareAssocHash([AssocSplat(node)])
|
257
|
+
end
|
258
|
+
|
259
|
+
def merge_props(attrs_to_merge)
|
260
|
+
return if attrs_to_merge.empty?
|
261
|
+
|
262
|
+
splat_hash(call_helpers(:merge_props, attrs_to_merge))
|
263
|
+
end
|
264
|
+
|
265
|
+
def first_or_array(nodes)
|
266
|
+
case nodes
|
267
|
+
in [node]
|
268
|
+
node
|
269
|
+
else
|
270
|
+
ArrayLiteral(LBracket("["), Args(nodes))
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def sym(str)
|
275
|
+
if str.match(/\A[\w_]+\z/)
|
276
|
+
SymbolLiteral(Ident(str))
|
277
|
+
else
|
278
|
+
DynaSymbol([TStringContent(str)], '"')
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def props_hash(attrs)
|
283
|
+
HashLiteral(
|
284
|
+
LBrace("{"),
|
285
|
+
attrs.map do |key, value|
|
286
|
+
if key.to_s == "class" && value in String
|
287
|
+
Assoc(
|
288
|
+
SymbolLiteral(Ident(key.to_s)),
|
289
|
+
first_or_array(value.split.map { sym(_1) })
|
290
|
+
)
|
291
|
+
else
|
292
|
+
Assoc(
|
293
|
+
sym(key.to_s),
|
294
|
+
case value
|
295
|
+
in Symbol
|
296
|
+
SymbolLiteral(Ident(value.to_s))
|
297
|
+
in String
|
298
|
+
StringLiteral([TStringContent(value.to_s)], :'"')
|
299
|
+
in SyntaxTree::ArrayLiteral
|
300
|
+
value
|
301
|
+
in TrueClass | FalseClass | NilClass
|
302
|
+
VarRef(Kw(value.to_s))
|
303
|
+
end
|
304
|
+
)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
)
|
308
|
+
end
|
309
|
+
|
310
|
+
def try_split_string_literal(node)
|
311
|
+
case node
|
312
|
+
in SyntaxTree::StringLiteral
|
313
|
+
split_string_literal(node)
|
314
|
+
in [SyntaxTree::StringLiteral => node]
|
315
|
+
split_string_literal(node)
|
316
|
+
else
|
317
|
+
node
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def split_string_literal(string_literal)
|
322
|
+
string_literal
|
323
|
+
# string_literal
|
324
|
+
# .parts
|
325
|
+
# .map do |part|
|
326
|
+
# case part
|
327
|
+
# in SyntaxTree::TStringContent
|
328
|
+
# string_literal(part.value)
|
329
|
+
# in SyntaxTree::StringEmbExpr
|
330
|
+
# part.statements
|
331
|
+
# end
|
332
|
+
# end
|
333
|
+
# .flatten
|
334
|
+
end
|
335
|
+
|
336
|
+
def ruby_script(statements)
|
337
|
+
case statements
|
338
|
+
in []
|
339
|
+
nil
|
340
|
+
in [SyntaxTree::StringLiteral => string_literal]
|
341
|
+
split_string_literal(string_literal)
|
342
|
+
in [statement]
|
343
|
+
statement
|
344
|
+
else
|
345
|
+
Begin(BodyStmt(Statements(statements), nil, nil, nil, nil))
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def silent(node)
|
350
|
+
case node
|
351
|
+
in SyntaxTree::ReturnNode
|
352
|
+
node
|
353
|
+
else
|
354
|
+
Begin(
|
355
|
+
BodyStmt(
|
356
|
+
Statements([node, VarRef(Kw("nil"))]),
|
357
|
+
nil,
|
358
|
+
nil,
|
359
|
+
nil,
|
360
|
+
nil
|
361
|
+
)
|
362
|
+
)
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def compute(node)
|
367
|
+
# MethodAddBlock(
|
368
|
+
# call_helpers(:compute),
|
369
|
+
# BlockNode(
|
370
|
+
# Kw("do"),
|
371
|
+
# nil,
|
372
|
+
# BodyStmt(Statements([node]), nil, nil, nil, nil)
|
373
|
+
# )
|
374
|
+
# )
|
375
|
+
node
|
376
|
+
end
|
377
|
+
|
378
|
+
def mayu_const_path
|
379
|
+
ConstPathRef(VarRef(Const("Mayu")), Const("VDOM"))
|
380
|
+
# Const("Mayu")
|
381
|
+
end
|
382
|
+
|
383
|
+
def call_helpers(method, *args)
|
384
|
+
CallNode(
|
385
|
+
Ident("mayu"),
|
386
|
+
Period("."),
|
387
|
+
Ident(method.to_s),
|
388
|
+
wrap_args(args.flatten.compact)
|
389
|
+
)
|
390
|
+
end
|
391
|
+
|
392
|
+
def helper_ident
|
393
|
+
if @options.enable_new_helper_ident
|
394
|
+
CallNode(VarRef(Kw("self")), Period("."), Ident("Mayu"), nil)
|
395
|
+
else
|
396
|
+
Ident("mayu")
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def wrap_args(args)
|
401
|
+
args.empty? ? nil : ArgParen(Args(args))
|
402
|
+
end
|
403
|
+
|
404
|
+
def string_literal(value) =
|
405
|
+
StringLiteral([TStringContent(value.to_s)], '"')
|
406
|
+
def call_freeze(node) =
|
407
|
+
CallNode(node, Period("."), Ident("freeze"), nil)
|
408
|
+
end
|
409
|
+
|
410
|
+
class ParseError < StandardError
|
411
|
+
end
|
412
|
+
|
413
|
+
class Transformer < SyntaxTree::Haml::Visitor
|
414
|
+
class Result < T::Struct
|
415
|
+
const :program, SyntaxTree::Program
|
416
|
+
const :styles, T::Array[CSS::TransformResult]
|
417
|
+
|
418
|
+
def source
|
419
|
+
SyntaxTree::Formatter.format("", program)
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
def initialize(options)
|
424
|
+
@options = options
|
425
|
+
@builder = RubyBuilder.new(options)
|
426
|
+
@state = {}
|
427
|
+
end
|
428
|
+
|
429
|
+
def visit_root(node)
|
430
|
+
setup = []
|
431
|
+
styles = []
|
432
|
+
render = []
|
433
|
+
|
434
|
+
node.children.each do |child|
|
435
|
+
case child
|
436
|
+
in { type: :filter, value: { name: "ruby" } }
|
437
|
+
if setup.empty? && styles.empty?
|
438
|
+
setup.push(child)
|
439
|
+
else
|
440
|
+
render.push(child)
|
441
|
+
end
|
442
|
+
in type: :script | :silent_script
|
443
|
+
render.push(child)
|
444
|
+
in { type: :filter, value: { name: "css" } }
|
445
|
+
styles.push(child.accept(self))
|
446
|
+
in type: :tag
|
447
|
+
render.push(child)
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
Result.new(
|
452
|
+
program:
|
453
|
+
@builder.create_program(
|
454
|
+
group_control_statements(setup),
|
455
|
+
styles,
|
456
|
+
group_control_statements(render)
|
457
|
+
),
|
458
|
+
styles:
|
459
|
+
)
|
460
|
+
end
|
461
|
+
|
462
|
+
def visit_haml_comment(node)
|
463
|
+
nil
|
464
|
+
end
|
465
|
+
|
466
|
+
def visit_slot_tag(node)
|
467
|
+
node.value => { attributes:, dynamic_attributes: }
|
468
|
+
|
469
|
+
name = nil
|
470
|
+
|
471
|
+
if new = dynamic_attributes.new
|
472
|
+
parse_ruby(dynamic_attributes.new) => [parsed_attributes]
|
473
|
+
hash = parsed_attributes.accept(HashKeyExtractorVisitor.new)
|
474
|
+
|
475
|
+
name = hash[:name] || hash["name"]
|
476
|
+
end
|
477
|
+
|
478
|
+
if attr = attributes["name"]
|
479
|
+
name ||= @builder.string_literal(attr)
|
480
|
+
end
|
481
|
+
|
482
|
+
return(
|
483
|
+
@builder.slot(
|
484
|
+
name,
|
485
|
+
fallback: node.children.map { _1.accept(self) }
|
486
|
+
)
|
487
|
+
)
|
488
|
+
end
|
489
|
+
|
490
|
+
def visit_tag(node)
|
491
|
+
node.value => {
|
492
|
+
name:, attributes:, dynamic_attributes:, self_closing:, value:
|
493
|
+
}
|
494
|
+
|
495
|
+
return visit_slot_tag(node) if name == "slot"
|
496
|
+
|
497
|
+
attrs = []
|
498
|
+
|
499
|
+
if @options.transform_elements_to_classes
|
500
|
+
attrs.push(@builder.props_hash(class: :"__#{name}"))
|
501
|
+
end
|
502
|
+
|
503
|
+
attrs.push(@builder.props_hash(attributes)) unless attributes.empty?
|
504
|
+
|
505
|
+
if old = dynamic_attributes.old
|
506
|
+
attrs.push(*parse_ruby(old))
|
507
|
+
end
|
508
|
+
|
509
|
+
if new = dynamic_attributes.new
|
510
|
+
attrs.push(
|
511
|
+
*parse_ruby(new)
|
512
|
+
.map { _1.accept(string_keys_to_labels_mutation_visitor) }
|
513
|
+
.map { _1.accept(wrap_handler_mutation_visitor) }
|
514
|
+
)
|
515
|
+
end
|
516
|
+
|
517
|
+
if object_ref = node.value[:object_ref]
|
518
|
+
unless object_ref == :nil
|
519
|
+
parse_ruby(object_ref) => [key]
|
520
|
+
attrs.push(@builder.props_hash(key:))
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
@builder.tag(
|
525
|
+
name,
|
526
|
+
if value
|
527
|
+
if node.value[:parse]
|
528
|
+
parse_ruby(value, fix: false) => statements
|
529
|
+
@builder.ruby_script(statements)
|
530
|
+
elsif !value.empty?
|
531
|
+
@builder.string_literal(value.to_s)
|
532
|
+
end
|
533
|
+
else
|
534
|
+
visit_tag_children(node.children)
|
535
|
+
end,
|
536
|
+
attrs
|
537
|
+
)
|
538
|
+
end
|
539
|
+
|
540
|
+
def visit_tag_children(children)
|
541
|
+
children
|
542
|
+
.reject { _1 in { type: :plain, value: { text: "" } } }
|
543
|
+
.then { join_plain_nodes(_1) }
|
544
|
+
.then { prepend_whitespace(_1) }
|
545
|
+
.then { append_whitespace(_1) }
|
546
|
+
.then { group_control_statements(_1) }
|
547
|
+
.flatten
|
548
|
+
end
|
549
|
+
|
550
|
+
def join_plain_nodes(children)
|
551
|
+
children
|
552
|
+
.chunk_while do |prev, curr|
|
553
|
+
(
|
554
|
+
(prev in { type: :plain, value: { text: prev_text } }) &&
|
555
|
+
(curr in { type: :plain, value: { text: new_text } })
|
556
|
+
)
|
557
|
+
end
|
558
|
+
.map do |chunk|
|
559
|
+
case chunk
|
560
|
+
in [{ type: :plain } => first, *]
|
561
|
+
text = chunk.map { _1.value[:text].to_s.strip }.join(" ")
|
562
|
+
first.value[:text] = text
|
563
|
+
first
|
564
|
+
else
|
565
|
+
chunk
|
566
|
+
end
|
567
|
+
end
|
568
|
+
.flatten
|
569
|
+
.compact
|
570
|
+
end
|
571
|
+
|
572
|
+
IN_RE = /\A\s*in\s+/
|
573
|
+
|
574
|
+
def group_control_statements(children)
|
575
|
+
children
|
576
|
+
.chunk_while do |a, b|
|
577
|
+
case [a, b]
|
578
|
+
in [
|
579
|
+
{ type: :script, value: { keyword: "if" | "elsif" } },
|
580
|
+
{ type: :script, value: { keyword: "elsif" | "else" } }
|
581
|
+
]
|
582
|
+
true
|
583
|
+
in [
|
584
|
+
{ type: :script, value: { keyword: "case" | "when" } },
|
585
|
+
{ type: :script, value: { keyword: "when" | "else" } }
|
586
|
+
]
|
587
|
+
true
|
588
|
+
in [
|
589
|
+
{
|
590
|
+
type: :script,
|
591
|
+
value: { keyword: "case" } | { text: IN_RE }
|
592
|
+
},
|
593
|
+
{
|
594
|
+
type: :script,
|
595
|
+
value: { keyword: "else" } | { text: IN_RE }
|
596
|
+
}
|
597
|
+
]
|
598
|
+
true
|
599
|
+
in [
|
600
|
+
{ type: :script, value: { keyword: "begin" } },
|
601
|
+
{
|
602
|
+
type: :script,
|
603
|
+
value: { keyword: "rescue" | "else" | "ensure" }
|
604
|
+
}
|
605
|
+
]
|
606
|
+
true
|
607
|
+
in [
|
608
|
+
{ type: :script, value: { keyword: "rescue" } },
|
609
|
+
{ type: :script, value: { keyword: "else" | "ensure" } }
|
610
|
+
]
|
611
|
+
true
|
612
|
+
in [
|
613
|
+
{ type: :script, value: { keyword: "else" } },
|
614
|
+
{ type: :script, value: { keyword: "ensure" } }
|
615
|
+
]
|
616
|
+
true
|
617
|
+
else
|
618
|
+
false
|
619
|
+
end
|
620
|
+
end
|
621
|
+
.map do |chunk|
|
622
|
+
case chunk
|
623
|
+
in [{ type: :script, value: { keyword: "if" } }, *]
|
624
|
+
@builder.compute(group_condition(:if, chunk))
|
625
|
+
in [{ type: :script, value: { keyword: "case" } }, *]
|
626
|
+
@builder.compute(group_condition(:case, chunk))
|
627
|
+
in [{ type: :script, value: { keyword: "begin" } }, *]
|
628
|
+
@builder.compute(group_condition(:begin, chunk))
|
629
|
+
else
|
630
|
+
chunk.map { _1.accept(self) }
|
631
|
+
end
|
632
|
+
end
|
633
|
+
.flatten
|
634
|
+
.compact
|
635
|
+
end
|
636
|
+
|
637
|
+
def group_condition(type, chunk)
|
638
|
+
parse_ruby(join_ruby_script_nodes(chunk), fix: true) => [statement]
|
639
|
+
|
640
|
+
visitor = SyntaxTree::Visitor::MutationVisitor.new
|
641
|
+
|
642
|
+
chunk.shift if type == :case
|
643
|
+
|
644
|
+
visitor.mutate("Statements") do |node|
|
645
|
+
top = chunk.shift
|
646
|
+
|
647
|
+
if node.child_nodes in [SyntaxTree::VoidStmt]
|
648
|
+
@builder.Statements(visit_tag_children(top.children))
|
649
|
+
else
|
650
|
+
unless top.children.empty?
|
651
|
+
raise "Line #{top.line} should not have children."
|
652
|
+
end
|
653
|
+
|
654
|
+
node
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
658
|
+
@builder.ruby_script([statement.accept(visitor)])
|
659
|
+
end
|
660
|
+
|
661
|
+
def join_ruby_script_nodes(nodes)
|
662
|
+
nodes.map { _1.value[:text] }.join("\n")
|
663
|
+
end
|
664
|
+
|
665
|
+
def prepend_whitespace(children)
|
666
|
+
[nil, *children].each_cons(2)
|
667
|
+
.map do |prev, curr|
|
668
|
+
if prev in {
|
669
|
+
type: :tag, value: { nuke_outer_whitespace: true }
|
670
|
+
}
|
671
|
+
if curr in { type: :plain, value: { text: } }
|
672
|
+
curr.value = { text: " #{text}" }
|
673
|
+
else
|
674
|
+
next make_space(curr), curr
|
675
|
+
end
|
676
|
+
end
|
677
|
+
|
678
|
+
curr
|
679
|
+
end
|
680
|
+
end
|
681
|
+
|
682
|
+
def append_whitespace(children)
|
683
|
+
[*children, nil].each_cons(2)
|
684
|
+
.flat_map do |curr, succ|
|
685
|
+
if succ in {
|
686
|
+
type: :tag, value: { nuke_inner_whitespace: true }
|
687
|
+
}
|
688
|
+
if curr in { type: :plain, value: { text: } }
|
689
|
+
curr.value = { text: "#{text} " }
|
690
|
+
else
|
691
|
+
next curr, make_space(curr)
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
curr
|
696
|
+
end
|
697
|
+
end
|
698
|
+
|
699
|
+
def make_space(ref_node)
|
700
|
+
::Haml::Parser::ParseNode.new(
|
701
|
+
:plain,
|
702
|
+
ref_node.line,
|
703
|
+
{ text: " " },
|
704
|
+
ref_node.parent,
|
705
|
+
[]
|
706
|
+
)
|
707
|
+
end
|
708
|
+
|
709
|
+
def visit_filter(node)
|
710
|
+
case node.value
|
711
|
+
in { name: "ruby", text: }
|
712
|
+
@builder.ruby_script(parse_ruby(text)) if text
|
713
|
+
in { name: "css", text: }
|
714
|
+
CSS.transform(
|
715
|
+
source: text,
|
716
|
+
source_path:
|
717
|
+
@options.source_path_without_extension + ".haml (inline css)",
|
718
|
+
source_line: node.line
|
719
|
+
)
|
720
|
+
in { name: "plain", text: }
|
721
|
+
case text.inspect.each_line.to_a
|
722
|
+
in []
|
723
|
+
# noop
|
724
|
+
in [line]
|
725
|
+
@builder.string_literal(text)
|
726
|
+
in [*lines]
|
727
|
+
@builder.Heredoc(lines.map { @builder.TStringContent(_1) })
|
728
|
+
end
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
732
|
+
def visit_plain(node)
|
733
|
+
node.value => { text: }
|
734
|
+
@builder.string_literal(CGI.escape_html(text))
|
735
|
+
end
|
736
|
+
|
737
|
+
def visit_script(node)
|
738
|
+
case node.value[:text].strip
|
739
|
+
when /\Areturn\s+(?<type>if|unless)\s+(?<condition_source>.+)/
|
740
|
+
$~ => { type:, condition_source: }
|
741
|
+
|
742
|
+
parse_ruby(condition_source, fix: true) => [condition]
|
743
|
+
|
744
|
+
statements =
|
745
|
+
@builder.Statements(
|
746
|
+
[
|
747
|
+
@builder.ReturnNode(
|
748
|
+
@builder.Args(visit_tag_children(node.children))
|
749
|
+
)
|
750
|
+
]
|
751
|
+
)
|
752
|
+
|
753
|
+
case type
|
754
|
+
in "if"
|
755
|
+
@builder.IfNode(condition, statements, nil)
|
756
|
+
in "unless"
|
757
|
+
@builder.UnlessNode(condition, statements, nil)
|
758
|
+
end
|
759
|
+
when /\Areturn/
|
760
|
+
@builder.ReturnNode(
|
761
|
+
@builder.Args(visit_tag_children(node.children))
|
762
|
+
)
|
763
|
+
else
|
764
|
+
@builder.compute(transform_script_node(node))
|
765
|
+
end
|
766
|
+
end
|
767
|
+
|
768
|
+
def with_state(name, value, &block)
|
769
|
+
@state[name], prev = value, @state[name]
|
770
|
+
yield prev
|
771
|
+
ensure
|
772
|
+
@state[name] = prev
|
773
|
+
end
|
774
|
+
|
775
|
+
def visit_silent_script(node)
|
776
|
+
with_state(:is_silent, true) do |was_silent|
|
777
|
+
if was_silent
|
778
|
+
visit_script(node)
|
779
|
+
else
|
780
|
+
@builder.silent(visit_script(node))
|
781
|
+
end
|
782
|
+
end
|
783
|
+
end
|
784
|
+
|
785
|
+
def transform_script_node(node)
|
786
|
+
source = node.value.fetch(:text).strip
|
787
|
+
|
788
|
+
if node.children.empty?
|
789
|
+
parse_ruby(source, fix: false) => statements
|
790
|
+
return @builder.ruby_script(statements)
|
791
|
+
end
|
792
|
+
|
793
|
+
parse_ruby(source, fix: true) => [statement]
|
794
|
+
|
795
|
+
visitor = SyntaxTree::Visitor::MutationVisitor.new
|
796
|
+
|
797
|
+
visitor.mutate("Statements[body: [VoidStmt]]") do
|
798
|
+
@builder.Statements(visit_tag_children(node.children))
|
799
|
+
end
|
800
|
+
|
801
|
+
@builder.ruby_script([statement.accept(visitor)])
|
802
|
+
end
|
803
|
+
|
804
|
+
def parse_ruby(source, fix: false)
|
805
|
+
source = fix_syntax_by_adding_missing_pairs(source) if fix
|
806
|
+
|
807
|
+
SyntaxTree.parse(source).statements.body
|
808
|
+
rescue SyntaxTree::Parser::ParseError => e
|
809
|
+
explain =
|
810
|
+
SyntaxSuggest::ExplainSyntax.new(
|
811
|
+
code_lines: SyntaxSuggest::CodeLine.from_source(source)
|
812
|
+
).call
|
813
|
+
|
814
|
+
msg = ["Failed parsing Ruby: #{source}"]
|
815
|
+
|
816
|
+
msg.push <<~MSG unless explain.errors.empty?
|
817
|
+
Errors:
|
818
|
+
#{explain.errors.join(" \n")}
|
819
|
+
MSG
|
820
|
+
|
821
|
+
msg.push <<~MSG unless explain.missing.empty?
|
822
|
+
Missing:
|
823
|
+
#{explain.missing.map { explain.why(_1) }.join(" \n")}
|
824
|
+
MSG
|
825
|
+
|
826
|
+
raise ParseError, "\n#{msg.join("\n")}"
|
827
|
+
end
|
828
|
+
|
829
|
+
def fix_syntax_by_adding_missing_pairs(source)
|
830
|
+
left_right = SyntaxSuggest::LeftRightLexCount.new
|
831
|
+
SyntaxSuggest::LexAll.new(source:).each { left_right.count_lex(_1) }
|
832
|
+
left_right.missing
|
833
|
+
[source, *left_right.missing].join("\n")
|
834
|
+
end
|
835
|
+
|
836
|
+
def wrap_handler_mutation_visitor
|
837
|
+
visitor = SyntaxTree::Visitor::MutationVisitor.new
|
838
|
+
|
839
|
+
visitor.mutate(
|
840
|
+
"Assoc[key: Label, value: VCall[value: Ident]]"
|
841
|
+
) do |assoc|
|
842
|
+
if assoc.key.value.start_with?("on")
|
843
|
+
handler_name = @builder.SymbolLiteral(assoc.value.value)
|
844
|
+
|
845
|
+
@builder.Assoc(
|
846
|
+
assoc.key,
|
847
|
+
@builder.call_helpers(:handler, [handler_name])
|
848
|
+
)
|
849
|
+
else
|
850
|
+
assoc
|
851
|
+
end
|
852
|
+
end
|
853
|
+
|
854
|
+
visitor
|
855
|
+
end
|
856
|
+
|
857
|
+
def string_keys_to_labels_mutation_visitor
|
858
|
+
visitor = SyntaxTree::Visitor::MutationVisitor.new
|
859
|
+
|
860
|
+
visitor.mutate("Assoc[key: StringLiteral]") do |assoc|
|
861
|
+
@builder.Assoc(
|
862
|
+
@builder.Label(
|
863
|
+
assoc.key.parts.map(&:value).join.gsub("-", "_") + ":"
|
864
|
+
),
|
865
|
+
assoc.value
|
866
|
+
)
|
867
|
+
end
|
868
|
+
|
869
|
+
visitor
|
870
|
+
end
|
871
|
+
end
|
872
|
+
|
873
|
+
class StateAndPropsTransformer
|
874
|
+
include SyntaxTree::DSL
|
875
|
+
|
876
|
+
COLLECTIONS = {
|
877
|
+
SyntaxTree::IVar => "state",
|
878
|
+
SyntaxTree::GVar => "props"
|
879
|
+
}
|
880
|
+
|
881
|
+
def visitor
|
882
|
+
MutationVisitor.build do |visitor|
|
883
|
+
visitor.mutate(
|
884
|
+
"VarRef[value: GVar[value: /\\A\\$\\w+/]]"
|
885
|
+
) { |var_ref| aref(var_ref.value) }
|
886
|
+
|
887
|
+
visitor.mutate(
|
888
|
+
"Assign[target: VarField[value: GVar]]"
|
889
|
+
) do |assign|
|
890
|
+
assign => { target: { target: { value: var_name } } }
|
891
|
+
loc = assign.target.location
|
892
|
+
raise "Can not write to prop #{var_name} on line #{loc.start_line} col #{loc.start_column}"
|
893
|
+
end
|
894
|
+
|
895
|
+
visitor.mutate("VarRef[value: IVar]") do |var_ref|
|
896
|
+
aref(var_ref.value)
|
897
|
+
end
|
898
|
+
|
899
|
+
# visitor.mutate(
|
900
|
+
# "OpAssign[target: VarField[value: IVar]]"
|
901
|
+
# ) do |assign|
|
902
|
+
# assign.copy(target: aref_field(assign.target.value))
|
903
|
+
# end
|
904
|
+
#
|
905
|
+
# visitor.mutate(
|
906
|
+
# "Assign[target: VarField[value: IVar]]"
|
907
|
+
# ) do |assign|
|
908
|
+
# assign.copy(target: aref_field(assign.target.value))
|
909
|
+
# end
|
910
|
+
end
|
911
|
+
end
|
912
|
+
|
913
|
+
private
|
914
|
+
|
915
|
+
def aref(node)
|
916
|
+
ARef(
|
917
|
+
call_self(COLLECTIONS.fetch(node.class)),
|
918
|
+
Args([var_to_symbol(node)])
|
919
|
+
)
|
920
|
+
end
|
921
|
+
|
922
|
+
def aref_field(node)
|
923
|
+
ARefField(
|
924
|
+
call_self(COLLECTIONS.fetch(node.class)),
|
925
|
+
Args([var_to_symbol(node)])
|
926
|
+
)
|
927
|
+
end
|
928
|
+
|
929
|
+
def call_self(method)
|
930
|
+
CallNode(VarRef(Kw("self")), Period("."), Ident(method), nil)
|
931
|
+
end
|
932
|
+
|
933
|
+
def var_to_symbol(node)
|
934
|
+
SymbolLiteral(Ident(strip_var_prefix(node.value)))
|
935
|
+
end
|
936
|
+
|
937
|
+
def strip_var_prefix(str)
|
938
|
+
str[/\A[@$]?(.*)/, 1]
|
939
|
+
end
|
940
|
+
end
|
941
|
+
class HashKeyExtractorVisitor
|
942
|
+
extend T::Sig
|
943
|
+
|
944
|
+
sig do
|
945
|
+
params(node: SyntaxTree::HashLiteral).returns(
|
946
|
+
T::Hash[T.untyped, T.untyped]
|
947
|
+
)
|
948
|
+
end
|
949
|
+
def visit_hash(node)
|
950
|
+
hash = {}
|
951
|
+
|
952
|
+
node.assocs.each do |child|
|
953
|
+
if extract_key(child.key) in key
|
954
|
+
hash[key] = extract_value(child.value)
|
955
|
+
end
|
956
|
+
end
|
957
|
+
|
958
|
+
hash
|
959
|
+
end
|
960
|
+
|
961
|
+
sig do
|
962
|
+
params(node: SyntaxTree::Node).returns(
|
963
|
+
T.nilable(T.any(String, Symbol))
|
964
|
+
)
|
965
|
+
end
|
966
|
+
def extract_key(node)
|
967
|
+
case node
|
968
|
+
when SyntaxTree::StringLiteral
|
969
|
+
node.parts => [{ value: }]
|
970
|
+
value
|
971
|
+
when SyntaxTree::Label
|
972
|
+
node.value
|
973
|
+
end
|
974
|
+
end
|
975
|
+
|
976
|
+
sig { params(node: SyntaxTree::Node).returns(T.untyped) }
|
977
|
+
def extract_value(node)
|
978
|
+
node
|
979
|
+
end
|
980
|
+
end
|
981
|
+
end
|
982
|
+
end
|
983
|
+
end
|
984
|
+
end
|