klenod-build 0.0.26 → 0.0.27
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/lib/klenod/build/plugins/haml_plugin/parser.rb +1 -1
- data/lib/klenod/build/plugins/haml_plugin/transformer/ruby_builder.rb +117 -430
- data/lib/klenod/build/plugins/haml_plugin/transformer.rb +20 -28
- data/lib/klenod/build/plugins/haml_plugin.rb +6 -6
- data/lib/klenod/build/plugins/ruby_plugin.rb +9 -21
- data/lib/klenod/build/ruby_import_rewriter.rb +94 -94
- data/lib/klenod/build/version.rb +1 -1
- metadata +29 -29
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "prism"
|
|
3
4
|
require "ripper"
|
|
4
|
-
require "syntax_tree"
|
|
5
|
-
require "syntax_tree/dsl"
|
|
6
5
|
require "syntax_suggest/api"
|
|
7
6
|
require "syntax_suggest/explain_syntax"
|
|
8
7
|
|
|
@@ -12,17 +11,18 @@ module Klenod
|
|
|
12
11
|
module HamlPlugin
|
|
13
12
|
class Transformer
|
|
14
13
|
class RubyBuilder
|
|
15
|
-
|
|
14
|
+
# `node` is the Prism node parsed from user Ruby, for the places
|
|
15
|
+
# that inspect it. `statements` marks a statement list, which is
|
|
16
|
+
# wrapped in `begin`/`end` when it is used as an argument.
|
|
17
|
+
Fragment = Data.define(:source, :node, :statements) do
|
|
18
|
+
def initialize(source:, node: nil, statements: false)
|
|
19
|
+
super
|
|
20
|
+
end
|
|
16
21
|
|
|
17
|
-
Fragment = Data.define(:source, :node) do
|
|
18
22
|
def node?
|
|
19
23
|
!node.nil?
|
|
20
24
|
end
|
|
21
25
|
|
|
22
|
-
def statement_body
|
|
23
|
-
node.is_a?(SyntaxTree::Statements) ? node.body : [node].compact
|
|
24
|
-
end
|
|
25
|
-
|
|
26
26
|
def to_s
|
|
27
27
|
source
|
|
28
28
|
end
|
|
@@ -33,7 +33,6 @@ module Klenod
|
|
|
33
33
|
@variables = variables || {}
|
|
34
34
|
@expression_cache = {}
|
|
35
35
|
@statements_cache = {}
|
|
36
|
-
@program_cache = {}
|
|
37
36
|
@literal_cache = {}
|
|
38
37
|
end
|
|
39
38
|
|
|
@@ -81,7 +80,7 @@ module Klenod
|
|
|
81
80
|
haml_helper_source = statements_fragment(haml_helper_source) if haml_helper_source
|
|
82
81
|
|
|
83
82
|
header = [
|
|
84
|
-
Fragment.new("# frozen_string_literal: true"
|
|
83
|
+
Fragment.new("# frozen_string_literal: true"),
|
|
85
84
|
constant_assignment(
|
|
86
85
|
"KlenodImport",
|
|
87
86
|
call(receiver: nil, name: "method", arguments: [symbol("__klenod_import__")])
|
|
@@ -118,7 +117,6 @@ module Klenod
|
|
|
118
117
|
static_constants: [],
|
|
119
118
|
i18n_source: nil
|
|
120
119
|
)
|
|
121
|
-
skeleton = class_skeleton_fragment(component_class_name, component_base_class)
|
|
122
120
|
body_fragments =
|
|
123
121
|
[
|
|
124
122
|
method_definition("module_path", target: "self", body: file_expression),
|
|
@@ -141,24 +139,13 @@ module Klenod
|
|
|
141
139
|
*static_constants,
|
|
142
140
|
public_method_definition("render", body: render_source)
|
|
143
141
|
]
|
|
144
|
-
body =
|
|
145
|
-
body_fragments.flat_map { |fragment| statement_body_for(fragment) }
|
|
146
142
|
|
|
147
143
|
Fragment.new(
|
|
148
144
|
[
|
|
149
145
|
"class #{to_source(component_class_name)} < #{to_source(component_base_class)}",
|
|
150
146
|
indent(compact_join(body_fragments), 2),
|
|
151
147
|
"end"
|
|
152
|
-
].join("\n")
|
|
153
|
-
skeleton.node.copy(
|
|
154
|
-
bodystmt: BodyStmt(
|
|
155
|
-
Statements(body),
|
|
156
|
-
nil,
|
|
157
|
-
nil,
|
|
158
|
-
nil,
|
|
159
|
-
nil
|
|
160
|
-
)
|
|
161
|
-
)
|
|
148
|
+
].join("\n")
|
|
162
149
|
)
|
|
163
150
|
end
|
|
164
151
|
|
|
@@ -168,35 +155,24 @@ module Klenod
|
|
|
168
155
|
|
|
169
156
|
def expression(source, line_no: nil)
|
|
170
157
|
source = rewrite_ruby_source(source, line_no)
|
|
171
|
-
return Fragment.new(source, constant_path(source)) if source.match?(VALID_CONST_PATH)
|
|
172
158
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
Fragment.new(source, node)
|
|
159
|
+
Fragment.new(source, parse_expression(source, context: :expression))
|
|
176
160
|
end
|
|
177
161
|
|
|
178
162
|
def statements(source, line_no: nil)
|
|
179
163
|
source = rewrite_ruby_source(source, line_no)
|
|
180
164
|
node = parse_statements(source)
|
|
181
165
|
|
|
182
|
-
Fragment.new(source, node)
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def program(source)
|
|
186
|
-
node = parse_program(source)
|
|
187
|
-
|
|
188
|
-
Fragment.new(node ? format_node(node) : source, node)
|
|
166
|
+
Fragment.new(source, node, !node.nil?)
|
|
189
167
|
end
|
|
190
168
|
|
|
191
169
|
def program_from_fragments(*fragments)
|
|
192
|
-
fragments
|
|
193
|
-
body = fragments.flat_map { |fragment| statement_body_for(fragment) }
|
|
194
|
-
|
|
195
|
-
Fragment.new(compact_join(fragments), Program(Statements(body)))
|
|
170
|
+
Fragment.new(compact_join(fragments.flatten))
|
|
196
171
|
end
|
|
197
172
|
|
|
173
|
+
# Reprints parsed user Ruby exactly as it was written.
|
|
198
174
|
def fragment(node)
|
|
199
|
-
Fragment.new(
|
|
175
|
+
Fragment.new(node.slice, node)
|
|
200
176
|
end
|
|
201
177
|
|
|
202
178
|
def node_fragment(source, node)
|
|
@@ -224,189 +200,103 @@ module Klenod
|
|
|
224
200
|
end
|
|
225
201
|
|
|
226
202
|
def literal_fragment(value)
|
|
227
|
-
Fragment.new(literal_source(value)
|
|
203
|
+
Fragment.new(literal_source(value))
|
|
228
204
|
end
|
|
229
205
|
|
|
230
206
|
def frozen_literal(value)
|
|
231
|
-
|
|
207
|
+
Fragment.new(frozen_literal_source(value))
|
|
232
208
|
end
|
|
233
209
|
|
|
234
210
|
def import_call(dependency_id)
|
|
235
|
-
Fragment.new(
|
|
236
|
-
"__klenod_import__(#{literal_source(dependency_id)})",
|
|
237
|
-
CallNode(
|
|
238
|
-
nil,
|
|
239
|
-
nil,
|
|
240
|
-
Ident("__klenod_import__"),
|
|
241
|
-
ArgParen(Args([literal_node(dependency_id)]))
|
|
242
|
-
)
|
|
243
|
-
)
|
|
211
|
+
Fragment.new("__klenod_import__(#{literal_source(dependency_id)})")
|
|
244
212
|
end
|
|
245
213
|
|
|
246
214
|
def constant_assignment(name, value)
|
|
247
215
|
value = expression_fragment(value)
|
|
248
|
-
|
|
249
|
-
Fragment.new("#{name} = #{to_source(value)}", node)
|
|
216
|
+
Fragment.new("#{name} = #{to_source(value)}")
|
|
250
217
|
end
|
|
251
218
|
|
|
252
219
|
def call(receiver:, name:, arguments:)
|
|
253
220
|
receiver = expression_fragment(receiver) unless receiver.nil?
|
|
254
221
|
arguments = arguments.map { |argument| expression_fragment(argument) }
|
|
255
|
-
receiver_node = receiver.nil? ? nil : node_for(receiver)
|
|
256
|
-
|
|
257
|
-
node =
|
|
258
|
-
CallNode(
|
|
259
|
-
receiver_node,
|
|
260
|
-
receiver_node ? Period(".") : nil,
|
|
261
|
-
Ident(name.to_s),
|
|
262
|
-
ArgParen(Args(arguments.map { |argument| node_for(argument) }))
|
|
263
|
-
)
|
|
264
222
|
receiver_prefix = receiver ? "#{to_source(receiver)}." : nil
|
|
265
|
-
|
|
266
|
-
Fragment.new(source, node)
|
|
223
|
+
Fragment.new("#{receiver_prefix}#{name}(#{arguments.map { |argument| to_source(argument) }.join(", ")})")
|
|
267
224
|
end
|
|
268
225
|
|
|
269
226
|
def method_definition(name, body:, target: nil, parameters: [])
|
|
270
|
-
|
|
271
|
-
body_source = compact_join(body)
|
|
272
|
-
node =
|
|
273
|
-
DefNode(
|
|
274
|
-
target && node_for(expression_fragment(target)),
|
|
275
|
-
target ? Period(".") : nil,
|
|
276
|
-
Ident(name.to_s),
|
|
277
|
-
Params(parameters.map { |parameter| Ident(parameter.to_s) }, [], nil, [], [], nil, nil),
|
|
278
|
-
body_statement(body)
|
|
279
|
-
)
|
|
227
|
+
body_source = compact_join(Array(body))
|
|
280
228
|
target_source = target ? "#{to_source(expression_fragment(target))}." : ""
|
|
281
229
|
params_source = parameters.empty? ? "" : "(#{parameters.join(", ")})"
|
|
282
|
-
|
|
283
|
-
Fragment.new(source, node)
|
|
230
|
+
Fragment.new(["def #{target_source}#{name}#{params_source}", indent(body_source, 2), "end"].join("\n"))
|
|
284
231
|
end
|
|
285
232
|
|
|
286
233
|
def public_method_definition(name, body:, parameters: [])
|
|
287
234
|
method = method_definition(name, parameters: parameters, body: body)
|
|
288
|
-
|
|
289
|
-
Command(
|
|
290
|
-
Ident("public"),
|
|
291
|
-
Args([method.node]),
|
|
292
|
-
nil
|
|
293
|
-
)
|
|
294
|
-
Fragment.new("public #{method.source}", node)
|
|
235
|
+
Fragment.new("public #{method.source}")
|
|
295
236
|
end
|
|
296
237
|
|
|
297
238
|
def nil_expression
|
|
298
|
-
Fragment.new("nil"
|
|
239
|
+
Fragment.new("nil")
|
|
299
240
|
end
|
|
300
241
|
|
|
301
242
|
def file_expression
|
|
302
|
-
Fragment.new("__FILE__"
|
|
243
|
+
Fragment.new("__FILE__")
|
|
303
244
|
end
|
|
304
245
|
|
|
305
246
|
def symbol(value)
|
|
306
|
-
value
|
|
307
|
-
Fragment.new(symbol_source(value), symbol_node(value))
|
|
247
|
+
Fragment.new(symbol_source(value.to_s))
|
|
308
248
|
end
|
|
309
249
|
|
|
310
250
|
def symbol_fragment(value)
|
|
311
|
-
Fragment.new(symbol_source(value.to_s)
|
|
251
|
+
Fragment.new(symbol_source(value.to_s))
|
|
312
252
|
end
|
|
313
253
|
|
|
314
254
|
def styles_lookup(name)
|
|
315
|
-
name
|
|
316
|
-
|
|
317
|
-
Fragment.new(
|
|
318
|
-
"ClassNames[#{symbol_source(name)}]",
|
|
319
|
-
ARef(VarRef(Const("ClassNames")), Args([symbol_node(name)]))
|
|
320
|
-
)
|
|
255
|
+
Fragment.new("ClassNames[#{symbol_source(name.to_s)}]")
|
|
321
256
|
end
|
|
322
257
|
|
|
323
258
|
def class_name_lookup(name)
|
|
324
|
-
name
|
|
325
|
-
|
|
326
|
-
Fragment.new(
|
|
327
|
-
"ClassNames[#{symbol_source(name)}]",
|
|
328
|
-
ARef(VarRef(Const("ClassNames")), Args([symbol_node(name)]))
|
|
329
|
-
)
|
|
259
|
+
Fragment.new("ClassNames[#{symbol_source(name.to_s)}]")
|
|
330
260
|
end
|
|
331
261
|
|
|
332
262
|
def class_names(values)
|
|
333
263
|
fragments = values.map { |value| expression_fragment(value) }
|
|
334
264
|
|
|
335
|
-
Fragment.new(
|
|
336
|
-
"ClassNames.class_name(#{fragments.map(&:source).join(", ")})",
|
|
337
|
-
CallNode(
|
|
338
|
-
constant_path("ClassNames"),
|
|
339
|
-
Period("."),
|
|
340
|
-
Ident("class_name"),
|
|
341
|
-
ArgParen(Args(fragments.map { |fragment| node_for(fragment) }))
|
|
342
|
-
)
|
|
343
|
-
)
|
|
265
|
+
Fragment.new("ClassNames.class_name(#{fragments.map(&:source).join(", ")})")
|
|
344
266
|
end
|
|
345
267
|
|
|
346
268
|
def scoped_class_name(values)
|
|
347
269
|
fragments = values.map { |value| expression_fragment(value) }
|
|
348
270
|
|
|
349
|
-
Fragment.new(
|
|
350
|
-
"ClassNames.class_name(#{fragments.map(&:source).join(", ")})",
|
|
351
|
-
nil
|
|
352
|
-
)
|
|
271
|
+
Fragment.new("ClassNames.class_name(#{fragments.map(&:source).join(", ")})")
|
|
353
272
|
end
|
|
354
273
|
|
|
355
274
|
def parenthesized_expression(source, line_no: nil)
|
|
356
275
|
source = rewrite_ruby_source(source, line_no)
|
|
357
|
-
|
|
358
|
-
unless
|
|
276
|
+
result = parse_result(source)
|
|
277
|
+
unless result
|
|
359
278
|
raise_ruby_parse_error(source, line_no: line_no, context: "Could not parse Haml output script", syntax_error: true)
|
|
360
279
|
end
|
|
361
280
|
|
|
362
|
-
|
|
281
|
+
# A trailing comment would otherwise swallow the closing parenthesis.
|
|
282
|
+
closing = result.comments.empty? ? ")" : "\n)"
|
|
283
|
+
Fragment.new("(#{source}#{closing}", result.value.statements.body.first)
|
|
363
284
|
end
|
|
364
285
|
|
|
365
286
|
def hash_expression(source, line_no: nil)
|
|
366
287
|
source = rewrite_ruby_source(source, line_no)
|
|
367
288
|
node = parse_expression(source, context: :hash_expression)
|
|
368
|
-
return nil unless node.is_a?(
|
|
289
|
+
return nil unless node.is_a?(Prism::HashNode)
|
|
369
290
|
|
|
370
291
|
Fragment.new(source, node)
|
|
371
292
|
end
|
|
372
293
|
|
|
373
|
-
def class_skeleton_fragment(component_class_name, component_base_class)
|
|
374
|
-
Fragment.new(
|
|
375
|
-
"",
|
|
376
|
-
ClassDeclaration(
|
|
377
|
-
constant_path(component_class_name, declaration: true),
|
|
378
|
-
constant_path(component_base_class),
|
|
379
|
-
BodyStmt(
|
|
380
|
-
Statements([]),
|
|
381
|
-
nil,
|
|
382
|
-
nil,
|
|
383
|
-
nil,
|
|
384
|
-
nil
|
|
385
|
-
)
|
|
386
|
-
)
|
|
387
|
-
)
|
|
388
|
-
end
|
|
389
|
-
|
|
390
|
-
def constant_path(value, declaration: false)
|
|
391
|
-
parts = to_source(value).split("::")
|
|
392
|
-
raise ArgumentError, "Expected constant path: #{to_source(value).inspect}" if parts.empty? || parts.any?(&:empty?)
|
|
393
|
-
|
|
394
|
-
return ConstRef(Const(parts.fetch(0))) if declaration && parts.length == 1
|
|
395
|
-
return VarRef(Const(parts.fetch(0))) if parts.length == 1
|
|
396
|
-
|
|
397
|
-
parts.drop(1).reduce(VarRef(Const(parts.fetch(0)))) do |parent, part|
|
|
398
|
-
ConstPathRef(parent, Const(part))
|
|
399
|
-
end
|
|
400
|
-
end
|
|
401
|
-
|
|
402
294
|
def source_mark(line_no, _source)
|
|
403
295
|
"# #{Runtime::SourceMap::MARK_PREFIX}:#{line_no}"
|
|
404
296
|
end
|
|
405
297
|
|
|
406
298
|
def marked_expression(mark, expression)
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
source_marked_fragment(mark, source, node_for(expression))
|
|
299
|
+
Fragment.new("#{mark}\n#{to_source(expression)}", nil, true)
|
|
410
300
|
end
|
|
411
301
|
|
|
412
302
|
def factory_call(factory:, tag:, children:, props:, mark: nil)
|
|
@@ -415,7 +305,7 @@ module Klenod
|
|
|
415
305
|
|
|
416
306
|
def component_factory_call(factory:, tag:, children:, props:, mark: nil)
|
|
417
307
|
call = source_factory_call(factory: factory, tag: tag, children: [], props: props, mark: mark)
|
|
418
|
-
body = Fragment.new("[#{children.map { |child| argument_source(child) }.join(", ")}]"
|
|
308
|
+
body = Fragment.new("[#{children.map { |child| argument_source(child) }.join(", ")}]")
|
|
419
309
|
script_block("#{call.source} do", body)
|
|
420
310
|
end
|
|
421
311
|
|
|
@@ -435,7 +325,9 @@ module Klenod
|
|
|
435
325
|
|
|
436
326
|
def script_block(source, body, line_no: nil)
|
|
437
327
|
source = rewrite_ruby_source(source, nil)
|
|
438
|
-
|
|
328
|
+
return Fragment.new(block_source(source, body)) if block_script?(source)
|
|
329
|
+
|
|
330
|
+
raise_ruby_parse_error(source, line_no: line_no, context: "Could not build Ruby block from Haml script")
|
|
439
331
|
end
|
|
440
332
|
|
|
441
333
|
def silent_script_block(source, body, line_no: nil)
|
|
@@ -473,10 +365,9 @@ module Klenod
|
|
|
473
365
|
raise_ruby_parse_error(source, line_no: line_no, context: "Could not parse Haml silent script", syntax_error: true)
|
|
474
366
|
end
|
|
475
367
|
|
|
476
|
-
return Fragment.new(source, statements) if return_with_children
|
|
368
|
+
return Fragment.new(source, statements, true) if return_with_children
|
|
477
369
|
|
|
478
|
-
|
|
479
|
-
Fragment.new(["begin", indent(source, 2), indent(to_source(body), 2), "end"].join("\n"), node)
|
|
370
|
+
Fragment.new(["begin", indent(source, 2), indent(to_source(body), 2), "end"].join("\n"))
|
|
480
371
|
end
|
|
481
372
|
|
|
482
373
|
def keyword_script?(source)
|
|
@@ -508,7 +399,7 @@ module Klenod
|
|
|
508
399
|
end
|
|
509
400
|
|
|
510
401
|
def silent_branches(branches, line_no: nil)
|
|
511
|
-
fragment =
|
|
402
|
+
fragment = ast_branches(branches)
|
|
512
403
|
return fragment if fragment
|
|
513
404
|
|
|
514
405
|
raise_ruby_parse_error(branch_source(branches), line_no: line_no, context: "Could not parse Haml silent branches", syntax_error: true)
|
|
@@ -517,32 +408,17 @@ module Klenod
|
|
|
517
408
|
def ruby_filters(nodes)
|
|
518
409
|
return "" if nodes.empty?
|
|
519
410
|
|
|
520
|
-
|
|
411
|
+
Fragment.new(nodes.map { |node| ["begin", indent(to_source(node), 2), "end"].join("\n") }.join("\n"), nil, true)
|
|
521
412
|
end
|
|
522
413
|
|
|
523
414
|
def render_ruby_filter(node)
|
|
524
415
|
source = to_source(node)
|
|
525
|
-
parsed =
|
|
526
|
-
unless parsed
|
|
416
|
+
parsed = node.is_a?(Fragment) && node.node?
|
|
417
|
+
unless parsed || parse_statements(source)
|
|
527
418
|
raise_ruby_parse_error(source, line_no: nil, context: "Could not parse Ruby filter")
|
|
528
419
|
end
|
|
529
420
|
|
|
530
|
-
|
|
531
|
-
ast_begin([
|
|
532
|
-
*statement_body_for(parsed),
|
|
533
|
-
nil_node
|
|
534
|
-
])
|
|
535
|
-
)
|
|
536
|
-
end
|
|
537
|
-
|
|
538
|
-
def format_node(node)
|
|
539
|
-
if @profiler
|
|
540
|
-
@profiler.measure(:haml_format_node, node: node.class.name) do
|
|
541
|
-
SyntaxTree::Formatter.format(+"", node, 0)
|
|
542
|
-
end
|
|
543
|
-
else
|
|
544
|
-
SyntaxTree::Formatter.format(+"", node, 0)
|
|
545
|
-
end
|
|
421
|
+
Fragment.new(["begin", indent(source.rstrip, 2), " nil", "end"].join("\n"))
|
|
546
422
|
end
|
|
547
423
|
|
|
548
424
|
def indent(value, spaces)
|
|
@@ -564,11 +440,12 @@ module Klenod
|
|
|
564
440
|
raise_ruby_parse_error(source, line_no: line_no, context: context)
|
|
565
441
|
end
|
|
566
442
|
|
|
443
|
+
# Whether the script opens a literal block for its Haml children.
|
|
444
|
+
# Calls, `super`, and zsuper can take one; lambdas cannot.
|
|
567
445
|
def block_script?(source)
|
|
568
|
-
|
|
569
|
-
node = parse_expression(fixed_source, context: :block_script_predicate)
|
|
446
|
+
node = parse_expression(fix_syntax_by_adding_missing_pairs(source), context: :block_script)
|
|
570
447
|
|
|
571
|
-
node.is_a?(
|
|
448
|
+
node.respond_to?(:block) && node.block.is_a?(Prism::BlockNode)
|
|
572
449
|
end
|
|
573
450
|
|
|
574
451
|
private
|
|
@@ -616,14 +493,20 @@ module Klenod
|
|
|
616
493
|
.filter_map do |((line, column), type, token, _state), index|
|
|
617
494
|
kind, prefix, pattern = VARIABLE_TOKEN_KINDS[type]
|
|
618
495
|
receiver = @variables[kind]
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
496
|
+
next unless receiver
|
|
497
|
+
|
|
498
|
+
# Parenthesize the receiver so a low-precedence expression
|
|
499
|
+
# such as `a || b` stays one operand, e.g. in `$*.fetch(:x)`.
|
|
500
|
+
replacement =
|
|
501
|
+
if type == :on_gvar && token == "$*"
|
|
502
|
+
"(#{receiver})"
|
|
503
|
+
elsif token.match?(pattern)
|
|
504
|
+
"(#{receiver})[#{symbol_source(token.delete_prefix(prefix))}]"
|
|
505
|
+
end
|
|
506
|
+
next unless replacement
|
|
507
|
+
|
|
508
|
+
replacement = "{#{replacement}}" if tokens[index - 1]&.fetch(1) == :on_embvar
|
|
509
|
+
[offset_for(line_offsets, line, column), token.bytesize, replacement]
|
|
627
510
|
end
|
|
628
511
|
.reverse_each
|
|
629
512
|
.each_with_object(source.dup) do |(offset, length, replacement), rewritten|
|
|
@@ -644,7 +527,7 @@ module Klenod
|
|
|
644
527
|
|
|
645
528
|
expression.is_a?(Fragment) ? expression : self.expression(to_source(expression))
|
|
646
529
|
else
|
|
647
|
-
Fragment.new("[#{expressions.map { |item| argument_source(item) }.join(", ")}]"
|
|
530
|
+
Fragment.new("[#{expressions.map { |item| argument_source(item) }.join(", ")}]")
|
|
648
531
|
end
|
|
649
532
|
end
|
|
650
533
|
|
|
@@ -656,39 +539,23 @@ module Klenod
|
|
|
656
539
|
attribute_splats = props.delete(:__klenod_attribute_splats__) || []
|
|
657
540
|
prop_sources = ["{#{keyword_props_source(props, mark: mark).join(", ")}}", *attribute_splats.map { |value| argument_source(value) }]
|
|
658
541
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
*children.map { |child| argument_source(child) },
|
|
662
|
-
"**HamlHelper.merge_props(self.class, #{prop_sources.join(", ")})"
|
|
663
|
-
].compact
|
|
664
|
-
Fragment.new("#{to_source(factory)}[#{source_parts.join(", ")}]", nil)
|
|
665
|
-
end
|
|
542
|
+
leading_source = [to_source(tag), *children.map { |child| argument_source(child) }].join(", ")
|
|
543
|
+
props_source = "**HamlHelper.merge_props(self.class, #{prop_sources.join(", ")})"
|
|
666
544
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
# SyntaxTree rejects `next` and `break` at top level even though a
|
|
677
|
-
# silent script can be nested in an iterator or keyword loop. Parse
|
|
678
|
-
# those statements inside a harmless temporary loop, then retain
|
|
679
|
-
# only its body for the surrounding generated block.
|
|
680
|
-
def control_flow_statements(source)
|
|
681
|
-
return unless source.match?(/\A(?:next|break)\b/)
|
|
682
|
-
|
|
683
|
-
wrapper = parse_expression(["loop do", indent(source, 2), "end"].join("\n"), context: :control_flow_statement)
|
|
684
|
-
wrapper&.block&.bodystmt&.statements
|
|
545
|
+
# Multi-line children carry their own source marks. Repeat the
|
|
546
|
+
# tag's mark so errors raised while merging props map to the tag
|
|
547
|
+
# rather than to its last child.
|
|
548
|
+
if mark && leading_source.include?("\n")
|
|
549
|
+
Fragment.new("#{to_source(factory)}[#{leading_source},\n#{mark}\n#{props_source}]")
|
|
550
|
+
else
|
|
551
|
+
Fragment.new("#{to_source(factory)}[#{leading_source}, #{props_source}]")
|
|
552
|
+
end
|
|
685
553
|
end
|
|
686
554
|
|
|
687
|
-
def
|
|
688
|
-
|
|
689
|
-
return nil unless node
|
|
555
|
+
def ast_silent_script(source)
|
|
556
|
+
return nil unless parse_statements(source)
|
|
690
557
|
|
|
691
|
-
Fragment.new(
|
|
558
|
+
Fragment.new(["begin", indent(source, 2), " nil", "end"].join("\n"))
|
|
692
559
|
end
|
|
693
560
|
|
|
694
561
|
def ast_silent_script_block(source, body)
|
|
@@ -707,48 +574,6 @@ module Klenod
|
|
|
707
574
|
Fragment.new(source, node)
|
|
708
575
|
end
|
|
709
576
|
|
|
710
|
-
def ast_silent_branches(branches)
|
|
711
|
-
node = branch_node(branches)
|
|
712
|
-
return nil unless node
|
|
713
|
-
|
|
714
|
-
Fragment.new(branch_source(branches), node)
|
|
715
|
-
end
|
|
716
|
-
|
|
717
|
-
def branch_node(branches)
|
|
718
|
-
parse_expression(branch_source(branches), context: :branches)
|
|
719
|
-
end
|
|
720
|
-
|
|
721
|
-
def ast_ruby_filters(nodes)
|
|
722
|
-
begins =
|
|
723
|
-
nodes.map do |node|
|
|
724
|
-
statements =
|
|
725
|
-
if node.is_a?(Fragment)
|
|
726
|
-
node
|
|
727
|
-
else
|
|
728
|
-
parse_statements(node)
|
|
729
|
-
end
|
|
730
|
-
return nil unless statements
|
|
731
|
-
|
|
732
|
-
ast_begin(statement_body_for(statements))
|
|
733
|
-
end
|
|
734
|
-
|
|
735
|
-
source = nodes.map { |node| ["begin", indent(to_source(node), 2), "end"].join("\n") }.join("\n")
|
|
736
|
-
|
|
737
|
-
Fragment.new(source, Statements(begins))
|
|
738
|
-
end
|
|
739
|
-
|
|
740
|
-
def ast_begin(statement_nodes)
|
|
741
|
-
Begin(
|
|
742
|
-
BodyStmt(
|
|
743
|
-
Statements(statement_nodes),
|
|
744
|
-
nil,
|
|
745
|
-
nil,
|
|
746
|
-
nil,
|
|
747
|
-
nil
|
|
748
|
-
)
|
|
749
|
-
)
|
|
750
|
-
end
|
|
751
|
-
|
|
752
577
|
def block_source(source, body)
|
|
753
578
|
body_source = to_source(body)
|
|
754
579
|
|
|
@@ -765,7 +590,7 @@ module Klenod
|
|
|
765
590
|
# in their original Ruby block/loop context.
|
|
766
591
|
def captured_block_source(source, body)
|
|
767
592
|
captured_body = "HamlHelper.append_capture(#{argument_source(body)})"
|
|
768
|
-
script = block_source(source, Fragment.new(captured_body
|
|
593
|
+
script = block_source(source, Fragment.new(captured_body))
|
|
769
594
|
["HamlHelper.capture do", indent(script, 2), "end"].join("\n")
|
|
770
595
|
end
|
|
771
596
|
|
|
@@ -787,48 +612,6 @@ module Klenod
|
|
|
787
612
|
"#{body}\nend"
|
|
788
613
|
end
|
|
789
614
|
|
|
790
|
-
def body_statement(body)
|
|
791
|
-
BodyStmt(
|
|
792
|
-
Statements(Array(body).flat_map { |statement| statement_body_for(statement) }),
|
|
793
|
-
nil,
|
|
794
|
-
nil,
|
|
795
|
-
nil,
|
|
796
|
-
nil
|
|
797
|
-
)
|
|
798
|
-
end
|
|
799
|
-
|
|
800
|
-
def block_script_node(source, body)
|
|
801
|
-
node = parse_expression(fix_syntax_by_adding_missing_pairs(source), context: :block_script)
|
|
802
|
-
return nil unless node.is_a?(SyntaxTree::MethodAddBlock)
|
|
803
|
-
|
|
804
|
-
MethodAddBlock(
|
|
805
|
-
node.call,
|
|
806
|
-
SyntaxTree::BlockNode.new(
|
|
807
|
-
opening: node.block.opening,
|
|
808
|
-
block_var: node.block.block_var,
|
|
809
|
-
bodystmt: block_body_for(node.block, body),
|
|
810
|
-
location: node.block.location
|
|
811
|
-
)
|
|
812
|
-
)
|
|
813
|
-
end
|
|
814
|
-
|
|
815
|
-
def block_body_for(block, body)
|
|
816
|
-
statements = Statements(Array(body).flat_map { |statement| statement_body_for(statement) })
|
|
817
|
-
|
|
818
|
-
block.opening.is_a?(SyntaxTree::LBrace) ? statements : body_statement(body)
|
|
819
|
-
end
|
|
820
|
-
|
|
821
|
-
def ast_keyword_props(props, mark:)
|
|
822
|
-
return nil if props.empty?
|
|
823
|
-
|
|
824
|
-
AssocSplat(
|
|
825
|
-
HashLiteral(
|
|
826
|
-
LBrace("{"),
|
|
827
|
-
props.map { |name, value| Assoc(prop_key_node(name), argument_node(value, mark: mark)) }
|
|
828
|
-
)
|
|
829
|
-
)
|
|
830
|
-
end
|
|
831
|
-
|
|
832
615
|
def keyword_props_source(props, mark:)
|
|
833
616
|
return [] if props.empty?
|
|
834
617
|
|
|
@@ -844,42 +627,14 @@ module Klenod
|
|
|
844
627
|
"#{symbol_source(name)} =>"
|
|
845
628
|
end
|
|
846
629
|
|
|
847
|
-
def
|
|
630
|
+
def frozen_literal_source(value)
|
|
848
631
|
case value
|
|
849
632
|
when Hash
|
|
850
|
-
|
|
851
|
-
HashLiteral(
|
|
852
|
-
LBrace("{"),
|
|
853
|
-
value.map { |key, child| Assoc(literal_node(key), frozen_literal_node(child)) }
|
|
854
|
-
)
|
|
855
|
-
)
|
|
633
|
+
"{#{value.map { |key, child| "#{literal_source(key)} => #{frozen_literal_source(child)}" }.join(", ")}}.freeze"
|
|
856
634
|
when Array
|
|
857
|
-
|
|
635
|
+
"[#{value.map { |child| frozen_literal_source(child) }.join(", ")}].freeze"
|
|
858
636
|
else
|
|
859
|
-
|
|
860
|
-
end
|
|
861
|
-
end
|
|
862
|
-
|
|
863
|
-
def literal_node(value)
|
|
864
|
-
case value
|
|
865
|
-
when String
|
|
866
|
-
if value.match?(/\\|#[@${]/)
|
|
867
|
-
expression_node(value.inspect)
|
|
868
|
-
else
|
|
869
|
-
StringLiteral([TStringContent(value)], "\"")
|
|
870
|
-
end
|
|
871
|
-
when Integer
|
|
872
|
-
Int(value.to_s)
|
|
873
|
-
when Float
|
|
874
|
-
FloatLiteral(value.to_s)
|
|
875
|
-
when true
|
|
876
|
-
VarRef(Kw("true"))
|
|
877
|
-
when false
|
|
878
|
-
VarRef(Kw("false"))
|
|
879
|
-
when nil
|
|
880
|
-
nil_node
|
|
881
|
-
else
|
|
882
|
-
expression_node(value.inspect)
|
|
637
|
+
literal_source(value)
|
|
883
638
|
end
|
|
884
639
|
end
|
|
885
640
|
|
|
@@ -900,22 +655,6 @@ module Klenod
|
|
|
900
655
|
end
|
|
901
656
|
end
|
|
902
657
|
|
|
903
|
-
def freeze_node(node)
|
|
904
|
-
CallNode(node, Period("."), Ident("freeze"), nil)
|
|
905
|
-
end
|
|
906
|
-
|
|
907
|
-
def nil_node
|
|
908
|
-
VarRef(Kw("nil"))
|
|
909
|
-
end
|
|
910
|
-
|
|
911
|
-
def symbol_node(value)
|
|
912
|
-
if value.match?(/\A[a-zA-Z_]\w*[!?=]?\z/)
|
|
913
|
-
SymbolLiteral(Ident(value))
|
|
914
|
-
else
|
|
915
|
-
DynaSymbol([TStringContent(value)], ":\"")
|
|
916
|
-
end
|
|
917
|
-
end
|
|
918
|
-
|
|
919
658
|
def symbol_source(value)
|
|
920
659
|
if value.match?(/\A[a-zA-Z_]\w*[!?=]?\z/)
|
|
921
660
|
":#{value}"
|
|
@@ -924,51 +663,6 @@ module Klenod
|
|
|
924
663
|
end
|
|
925
664
|
end
|
|
926
665
|
|
|
927
|
-
def prop_key_node(name)
|
|
928
|
-
name = name.to_s
|
|
929
|
-
return Label("#{name}:") if name.match?(/\A[a-zA-Z_]\w*\z/)
|
|
930
|
-
|
|
931
|
-
symbol_node(name)
|
|
932
|
-
end
|
|
933
|
-
|
|
934
|
-
def source_marked_fragment(mark, source, node)
|
|
935
|
-
marked_source = "#{mark}\n#{source}"
|
|
936
|
-
return Fragment.new(marked_source, nil) unless node
|
|
937
|
-
|
|
938
|
-
node = Statements([comment_node(mark), node])
|
|
939
|
-
|
|
940
|
-
Fragment.new(marked_source, node)
|
|
941
|
-
end
|
|
942
|
-
|
|
943
|
-
def comment_node(value)
|
|
944
|
-
Comment(value, false)
|
|
945
|
-
end
|
|
946
|
-
|
|
947
|
-
def node_for(value)
|
|
948
|
-
return value.node if value.is_a?(Fragment)
|
|
949
|
-
|
|
950
|
-
parse_expression(to_source(value), context: :node_for)
|
|
951
|
-
end
|
|
952
|
-
|
|
953
|
-
def argument_node(value, mark: nil)
|
|
954
|
-
fragment = expression_fragment(value)
|
|
955
|
-
statements = []
|
|
956
|
-
statements << comment_node(mark) if mark
|
|
957
|
-
|
|
958
|
-
if fragment.node.is_a?(SyntaxTree::Statements)
|
|
959
|
-
statements.concat(fragment.node.body)
|
|
960
|
-
elsif mark
|
|
961
|
-
node = node_for(fragment)
|
|
962
|
-
return nil unless node
|
|
963
|
-
|
|
964
|
-
statements << node
|
|
965
|
-
else
|
|
966
|
-
return node_for(fragment)
|
|
967
|
-
end
|
|
968
|
-
|
|
969
|
-
ast_begin(statements)
|
|
970
|
-
end
|
|
971
|
-
|
|
972
666
|
def argument_source(value, mark: nil)
|
|
973
667
|
fragment = expression_fragment(value)
|
|
974
668
|
source = to_source(fragment)
|
|
@@ -977,7 +671,7 @@ module Klenod
|
|
|
977
671
|
source = "#{mark}\n#{source}"
|
|
978
672
|
end
|
|
979
673
|
|
|
980
|
-
if fragment.
|
|
674
|
+
if fragment.statements || mark || (source.include?("\n") && !multiline_argument_expression?(source))
|
|
981
675
|
["begin", indent(source, 2), "end"].join("\n")
|
|
982
676
|
else
|
|
983
677
|
source
|
|
@@ -988,41 +682,33 @@ module Klenod
|
|
|
988
682
|
source.start_with?("if ", "unless ", "case", "begin")
|
|
989
683
|
end
|
|
990
684
|
|
|
991
|
-
def statement_body_for(value)
|
|
992
|
-
return value.statement_body if value.is_a?(Fragment)
|
|
993
|
-
|
|
994
|
-
value.is_a?(SyntaxTree::Statements) ? value.body : [value].compact
|
|
995
|
-
end
|
|
996
|
-
|
|
997
|
-
def expression_node(source)
|
|
998
|
-
source = source.to_s
|
|
999
|
-
return constant_path(source) if source.match?(VALID_CONST_PATH)
|
|
1000
|
-
|
|
1001
|
-
parse_expression(source, context: :expression_node) || raise(ArgumentError, "Could not parse Ruby expression: #{source.inspect}")
|
|
1002
|
-
end
|
|
1003
|
-
|
|
1004
685
|
def parse_expression(source, context:)
|
|
1005
686
|
cached_parse(@expression_cache, source, :"haml_parse_expression:#{context}") do
|
|
1006
|
-
|
|
1007
|
-
.parse(source)
|
|
1008
|
-
&.statements
|
|
1009
|
-
&.body
|
|
1010
|
-
&.find { |node| !node.instance_of?(SyntaxTree::Comment) }
|
|
687
|
+
parse_result(source)&.value&.statements&.body&.first
|
|
1011
688
|
end
|
|
1012
|
-
rescue SyntaxTree::Parser::ParseError
|
|
1013
|
-
nil
|
|
1014
689
|
end
|
|
1015
690
|
|
|
1016
691
|
def parse_statements(source)
|
|
1017
|
-
cached_parse(@statements_cache, source, :haml_parse_statements) {
|
|
1018
|
-
rescue SyntaxTree::Parser::ParseError
|
|
1019
|
-
nil
|
|
692
|
+
cached_parse(@statements_cache, source, :haml_parse_statements) { parse_result(source)&.value&.statements }
|
|
1020
693
|
end
|
|
1021
694
|
|
|
1022
|
-
def
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
695
|
+
def parse_result(source)
|
|
696
|
+
result = prism_parse(source)
|
|
697
|
+
result if result.success?
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
# Haml scripts are fragments of a larger render method, so accept
|
|
701
|
+
# `yield`, `next`, and `break` outside of a block or loop.
|
|
702
|
+
#
|
|
703
|
+
# Parse each fragment as the line it came from. Haml strips script
|
|
704
|
+
# text, and Prism 1.9 rejects a hash pattern with several keys at
|
|
705
|
+
# the very end of its input (`props => title:, count:`) while
|
|
706
|
+
# accepting the same line followed by a newline.
|
|
707
|
+
def prism_parse(source)
|
|
708
|
+
source = source.to_s
|
|
709
|
+
source = "#{source}\n" unless source.end_with?("\n")
|
|
710
|
+
|
|
711
|
+
Prism.parse(source, partial_script: true)
|
|
1026
712
|
end
|
|
1027
713
|
|
|
1028
714
|
def cached_parse(cache, source, event_name)
|
|
@@ -1045,7 +731,7 @@ module Klenod
|
|
|
1045
731
|
end
|
|
1046
732
|
|
|
1047
733
|
def raise_ruby_parse_error(source, line_no:, context:, syntax_error: false)
|
|
1048
|
-
|
|
734
|
+
error_line = parse_error_line(source)
|
|
1049
735
|
explain =
|
|
1050
736
|
SyntaxSuggest::ExplainSyntax.new(
|
|
1051
737
|
code_lines: SyntaxSuggest::CodeLine.from_source(source)
|
|
@@ -1058,20 +744,21 @@ module Klenod
|
|
|
1058
744
|
message << "Errors:\n #{errors.join("\n ")}" unless errors.empty?
|
|
1059
745
|
message << "Missing:\n #{missing.join("\n ")}" unless missing.empty?
|
|
1060
746
|
|
|
1061
|
-
raise RubyParseError.new(message.join("\n\n"), line: source_line_for_parse_error(line_no,
|
|
747
|
+
raise RubyParseError.new(message.join("\n\n"), line: source_line_for_parse_error(line_no, error_line))
|
|
1062
748
|
end
|
|
1063
749
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
error
|
|
750
|
+
# Prism first reports an unclosed `{` or `(` at the line that opens
|
|
751
|
+
# it. The unexpected token that broke it is the line to show.
|
|
752
|
+
def parse_error_line(source)
|
|
753
|
+
errors = prism_parse(source).errors
|
|
754
|
+
error = errors.reject { it.type.end_with?("_term") }.min_by { it.location.start_offset } || errors.first
|
|
755
|
+
error&.location&.start_line
|
|
1069
756
|
end
|
|
1070
757
|
|
|
1071
|
-
def source_line_for_parse_error(line_no,
|
|
1072
|
-
return line_no unless line_no &&
|
|
758
|
+
def source_line_for_parse_error(line_no, error_line)
|
|
759
|
+
return line_no unless line_no && error_line
|
|
1073
760
|
|
|
1074
|
-
line_no +
|
|
761
|
+
line_no + error_line - 1
|
|
1075
762
|
end
|
|
1076
763
|
end
|
|
1077
764
|
end
|