phlex 2.3.1 → 2.4.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 534f2cde723f1707d19c32e8f23b06aa740cc0adaf62187318355eba8d8fdbea
4
- data.tar.gz: de396ffb79b471cd9c72d3099a66de40411184aa7ff03c6285dc9b5ace50b589
3
+ metadata.gz: 66da2693f1133dc2ee688f43e91cca9ce2fb3537a3df44b364d89c653a4c85a0
4
+ data.tar.gz: 4148a9f44e78765e788758c61e38a681d47833a30d5e4a6e6cae1d99dbf61cb7
5
5
  SHA512:
6
- metadata.gz: 707f796517d40bd0e763b3cfb00f8a10a1f7502598b0535e309b7689debcdb189a0ed49ec48928f3587e8bd724e6f2549becf68a7a3c7a0b5da9564f2543435a
7
- data.tar.gz: 790bd859f956ef99102d0bd1deabea6af89ad7c3f2268cd2522d84f6f4d0ded6c8d94c5cfe18ee3777afce2972234d851c1d58b991d491d5e2fb5f6f1e206020
6
+ metadata.gz: 642bbd9e026528f5e5a8c505b6b8ff557e40ad90a9ac7ec8b83af85c32825f5963d31ce5c420b29658db8ffc0dbe337daae1f99cd42437db9d45500582be4a06
7
+ data.tar.gz: c4eb6e00e6424f381cbbdc0a68c4923f6d14b6296673d669a0fa4a3a7a3b411d247108590863f949e5801414d0bf0d2880a85c9d9931e03959188867a9a7737f
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Phlex::Compiler::ClassCompiler < Refract::Visitor
4
+ def initialize(compiler)
5
+ super()
6
+ @compiler = compiler
7
+ @compiled_snippets = []
8
+ end
9
+
10
+ def compile(node)
11
+ visit(node.body)
12
+ @compiled_snippets.freeze
13
+ end
14
+
15
+ visit Refract::DefNode do |node|
16
+ return if node.name == :initialize
17
+ return if node.receiver
18
+
19
+ @compiled_snippets << Phlex::Compiler::MethodCompiler.new(
20
+ @compiler.component
21
+ ).compile(node)
22
+ end
23
+
24
+ visit Refract::ClassNode do |node|
25
+ nil
26
+ end
27
+
28
+ visit Refract::ModuleNode do |node|
29
+ nil
30
+ end
31
+
32
+ visit Refract::BlockNode do |node|
33
+ nil
34
+ end
35
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex::Compiler
4
+ class Compilation
5
+ def initialize(component, path, line, source, tree)
6
+ @component = component
7
+ @path = path
8
+ @line = line
9
+ @source = source
10
+ @tree = tree
11
+ freeze
12
+ end
13
+
14
+ attr_reader :component, :line, :source, :path
15
+
16
+ def compile
17
+ result = FileCompiler.new(self).compile(@tree)
18
+
19
+ result.compiled_snippets.each do |snippet|
20
+ start_line = snippet.start_line
21
+
22
+ namespaced = result.namespace.reverse_each.reduce(snippet) do |body, scope|
23
+ start_line -= 1
24
+
25
+ scope.copy(
26
+ body: Refract::StatementsNode.new(
27
+ body: [body]
28
+ )
29
+ )
30
+ end
31
+
32
+ source = Refract::Formatter.new.format_node(namespaced)
33
+
34
+ redefine_method(
35
+ source,
36
+ start_line
37
+ )
38
+ end
39
+ end
40
+
41
+ def redefine_method(source, line)
42
+ eval("# frozen_string_literal: true\n#{source}", TOPLEVEL_BINDING, @path, line - 1)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Phlex::Compiler::FileCompiler < Refract::Visitor
4
+ Result = Data.define(:namespace, :compiled_snippets)
5
+
6
+ def initialize(compiler)
7
+ super()
8
+ @compiler = compiler
9
+ @current_namespace = []
10
+ end
11
+
12
+ def compile(node)
13
+ catch(:phlex_compiler_result) do
14
+ visit(node)
15
+ end
16
+ end
17
+
18
+ visit Refract::ModuleNode do |node|
19
+ @current_namespace.push(node)
20
+ super(node)
21
+ @current_namespace.pop
22
+ end
23
+
24
+ visit Refract::ClassNode do |node|
25
+ @current_namespace.push(node)
26
+
27
+ if @compiler.line == node.start_line
28
+ throw :phlex_compiler_result, Result.new(
29
+ namespace: @current_namespace.dup.freeze,
30
+ compiled_snippets: Phlex::Compiler::ClassCompiler.new(@compiler).compile(node)
31
+ )
32
+ else
33
+ super(node)
34
+ end
35
+
36
+ @current_namespace.pop
37
+ end
38
+
39
+ visit Refract::DefNode do |node|
40
+ nil
41
+ end
42
+
43
+ visit Refract::BlockNode do |node|
44
+ nil
45
+ end
46
+ end
@@ -0,0 +1,591 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ module Phlex::Compiler
6
+ class MethodCompiler < Refract::MutationVisitor
7
+ def initialize(component)
8
+ super()
9
+ @component = component
10
+ @current_buffer = nil
11
+ @preamble = []
12
+ end
13
+
14
+ def compile(node)
15
+ visit(node)
16
+ end
17
+
18
+ def around_visit(node)
19
+ result = super
20
+
21
+ # We want to clear the buffer when there’s a node that isn’t a statements node,
22
+ # but we should ignore nils, which are usually other buffers.
23
+ clear_buffer unless result in Refract::StatementsNode | nil
24
+
25
+ result
26
+ end
27
+
28
+ visit Refract::ClassNode do |node|
29
+ node
30
+ end
31
+
32
+ visit Refract::ModuleNode do |node|
33
+ node
34
+ end
35
+
36
+ visit Refract::DefNode do |node|
37
+ if @stack.size == 1
38
+ node.copy(
39
+ body: Refract::StatementsNode.new(
40
+ body: [
41
+ Refract::StatementsNode.new(
42
+ body: @preamble
43
+ ),
44
+ Refract::NilNode.new,
45
+ visit(node.body),
46
+ ]
47
+ )
48
+ )
49
+ else
50
+ node
51
+ end
52
+ end
53
+
54
+ visit Refract::CallNode do |node|
55
+ if nil == node.receiver
56
+ if (tag = standard_element?(node))
57
+ return compile_standard_element(node, tag)
58
+ elsif (tag = void_element?(node))
59
+ return compile_void_element(node, tag)
60
+ elsif whitespace_helper?(node)
61
+ return compile_whitespace_helper(node)
62
+ elsif doctype_helper?(node)
63
+ return compile_doctype_helper(node)
64
+ elsif plain_helper?(node)
65
+ return compile_plain_helper(node)
66
+ elsif fragment_helper?(node)
67
+ return compile_fragment_helper(node)
68
+ elsif comment_helper?(node)
69
+ return compile_comment_helper(node)
70
+ elsif raw_helper?(node)
71
+ return compile_raw_helper(node)
72
+ end
73
+ end
74
+
75
+ super(node)
76
+ end
77
+
78
+ visit Refract::BlockNode do |node|
79
+ node.copy(
80
+ body: compile_block_body_node(
81
+ node.body
82
+ )
83
+ )
84
+ end
85
+
86
+ def compile_standard_element(node, tag)
87
+ node => Refract::CallNode
88
+
89
+ Refract::StatementsNode.new(
90
+ body: [
91
+ buffer("<#{tag}"),
92
+ *(
93
+ if node.arguments
94
+ compile_phlex_attributes(node.arguments)
95
+ end
96
+ ),
97
+ buffer(">"),
98
+ *(
99
+ if node.block
100
+ compile_phlex_block(node.block)
101
+ end
102
+ ),
103
+ buffer("</#{tag}>"),
104
+ ]
105
+ )
106
+ end
107
+
108
+ def compile_void_element(node, tag)
109
+ node => Refract::CallNode
110
+
111
+ Refract::StatementsNode.new(
112
+ body: [
113
+ buffer("<#{tag}"),
114
+ *(
115
+ if node.arguments
116
+ compile_phlex_attributes(node.arguments)
117
+ end
118
+ ),
119
+ buffer(">"),
120
+ ]
121
+ )
122
+ end
123
+
124
+ def compile_phlex_attributes(node)
125
+ arguments = node.arguments
126
+
127
+ if arguments.size == 1 && Refract::KeywordHashNode === (first_argument = arguments[0])
128
+ attributes = first_argument.elements
129
+ literal_attributes = attributes.all? do |attribute|
130
+ Refract::AssocNode === attribute && static_attribute_value_literal?(attribute)
131
+ end
132
+
133
+ if literal_attributes
134
+ return buffer(
135
+ Phlex::SGML::Attributes.generate_attributes(
136
+ eval(
137
+ "{#{Refract::Formatter.new.format_node(node)}}"
138
+ )
139
+ )
140
+ )
141
+ end
142
+
143
+ clear_buffer
144
+
145
+ Refract::CallNode.new(
146
+ name: :__render_attributes__,
147
+ arguments: Refract::ArgumentsNode.new(
148
+ arguments: [
149
+ node,
150
+ ]
151
+ )
152
+ )
153
+ end
154
+ end
155
+
156
+ def compile_phlex_block(node)
157
+ case node
158
+ when Refract::BlockNode
159
+ if output_block?(node)
160
+ return visit(node.body)
161
+ elsif static_content_block?(node)
162
+ content = node.body.body.first
163
+ case content
164
+ when Refract::StringNode, Refract::SymbolNode
165
+ return buffer(Phlex::Escape.html_escape(content.unescaped))
166
+ when Refract::InterpolatedStringNode
167
+ return compile_interpolated_string_node(content)
168
+ when Refract::NilNode
169
+ return nil
170
+ else
171
+ raise
172
+ end
173
+ end
174
+ end
175
+
176
+ clear_buffer
177
+ Refract::CallNode.new(
178
+ name: :__yield_content__,
179
+ block: node
180
+ )
181
+ end
182
+
183
+ def compile_block_body_node(node)
184
+ node => Refract::StatementsNode
185
+
186
+ Refract::StatementsNode.new(
187
+ body: [
188
+ Refract::IfNode.new(
189
+ inline: false,
190
+ predicate: Refract::CallNode.new(
191
+ receiver: Refract::SelfNode.new,
192
+ name: :==,
193
+ arguments: Refract::ArgumentsNode.new(
194
+ arguments: [
195
+ Refract::LocalVariableReadNode.new(
196
+ name: self_local
197
+ ),
198
+ ]
199
+ )
200
+ ),
201
+ statements: Refract::StatementsNode.new(
202
+ body: node.body.map { |n| visit(n) }
203
+ ),
204
+ subsequent: Refract::ElseNode.new(
205
+ statements: node
206
+ )
207
+ ),
208
+ ]
209
+ )
210
+ end
211
+
212
+ def compile_interpolated_string_node(node)
213
+ node => Refract::InterpolatedStringNode
214
+
215
+ Refract::StatementsNode.new(
216
+ body: node.parts.map do |part|
217
+ case part
218
+ when Refract::StringNode
219
+ buffer(Phlex::Escape.html_escape(part.unescaped))
220
+ when Refract::EmbeddedVariableNode
221
+ interpolate(part.variable)
222
+ when Refract::EmbeddedStatementsNode
223
+ interpolate(part.statements)
224
+ else
225
+ raise Phlex::Compiler::Error, "Unexpected node type in InterpolatedStringNode: #{part.class}"
226
+ end
227
+ end
228
+ )
229
+ end
230
+
231
+ def compile_whitespace_helper(node)
232
+ node => Refract::CallNode
233
+
234
+ if node.block
235
+ Refract::StatementsNode.new(
236
+ body: [
237
+ buffer(" "),
238
+ compile_phlex_block(node.block),
239
+ buffer(" "),
240
+ ]
241
+ )
242
+ else
243
+ buffer(" ")
244
+ end
245
+ end
246
+
247
+ def compile_doctype_helper(node)
248
+ node => Refract::CallNode
249
+
250
+ buffer("<!doctype html>")
251
+ end
252
+
253
+ def compile_plain_helper(node)
254
+ node => Refract::CallNode
255
+
256
+ if node.arguments in [Refract::StringNode]
257
+ buffer(node.arguments.arguments.first.unescaped)
258
+ else
259
+ node
260
+ end
261
+ end
262
+
263
+ def compile_fragment_helper(node)
264
+ node => Refract::CallNode
265
+
266
+ node.copy(
267
+ block: compile_fragment_helper_block(node.block)
268
+ )
269
+ end
270
+
271
+ def compile_fragment_helper_block(node)
272
+ node => Refract::BlockNode
273
+
274
+ node.copy(
275
+ body: Refract::StatementsNode.new(
276
+ body: [
277
+ Refract::LocalVariableWriteNode.new(
278
+ name: :__phlex_original_should_render__,
279
+ value: Refract::LocalVariableReadNode.new(
280
+ name: should_render_local
281
+ )
282
+ ),
283
+ Refract::LocalVariableWriteNode.new(
284
+ name: should_render_local,
285
+ value: Refract::CallNode.new(
286
+ receiver: Refract::LocalVariableReadNode.new(
287
+ name: state_local
288
+ ),
289
+ name: :should_render?
290
+ )
291
+ ),
292
+ visit(node.body),
293
+ Refract::LocalVariableWriteNode.new(
294
+ name: should_render_local,
295
+ value: Refract::LocalVariableReadNode.new(
296
+ name: :__phlex_original_should_render__
297
+ )
298
+ ),
299
+ ]
300
+ )
301
+ )
302
+ end
303
+
304
+ def compile_comment_helper(node)
305
+ node => Refract::CallNode
306
+
307
+ Refract::StatementsNode.new(
308
+ body: [
309
+ buffer("<!-- "),
310
+ compile_phlex_block(node.block),
311
+ buffer(" -->"),
312
+ ]
313
+ )
314
+ end
315
+
316
+ def compile_raw_helper(node)
317
+ node => Refract::CallNode
318
+
319
+ node
320
+ end
321
+
322
+ private def buffer(value)
323
+ if @current_buffer
324
+ @current_buffer << Refract::StringNode.new(
325
+ unescaped: value
326
+ )
327
+
328
+ nil
329
+ else
330
+ new_buffer = [
331
+ Refract::StringNode.new(
332
+ unescaped: value
333
+ ),
334
+ ]
335
+
336
+ @current_buffer = new_buffer
337
+
338
+ Refract::IfNode.new(
339
+ inline: false,
340
+ predicate: Refract::LocalVariableReadNode.new(
341
+ name: should_render_local
342
+ ),
343
+ statements: Refract::StatementsNode.new(
344
+ body: [
345
+ Refract::CallNode.new(
346
+ receiver: Refract::CallNode.new(
347
+ name: buffer_local,
348
+ ),
349
+ name: :<<,
350
+ arguments: Refract::ArgumentsNode.new(
351
+ arguments: [
352
+ Refract::InterpolatedStringNode.new(
353
+ parts: new_buffer
354
+ ),
355
+ ]
356
+ )
357
+ ),
358
+ ]
359
+ )
360
+ )
361
+ end
362
+ end
363
+
364
+ private def interpolate(statements, escape: true)
365
+ embedded_statement = Refract::EmbeddedStatementsNode.new(
366
+ statements: Refract::StatementsNode.new(
367
+ body: [
368
+ Refract::CallNode.new(
369
+ receiver: Refract::ConstantPathNode.new(
370
+ parent: Refract::ConstantPathNode.new(
371
+ name: "Phlex"
372
+ ),
373
+ name: "Escape"
374
+ ),
375
+ name: :html_escape,
376
+ arguments: Refract::ArgumentsNode.new(
377
+ arguments: [
378
+ Refract::CallNode.new(
379
+ receiver: Refract::ParenthesesNode.new(
380
+ body: statements
381
+ ),
382
+ name: :to_s
383
+ ),
384
+ ]
385
+ )
386
+ ),
387
+ ]
388
+ )
389
+ )
390
+
391
+ if @current_buffer
392
+ @current_buffer << embedded_statement
393
+
394
+ nil
395
+ else
396
+ new_buffer = [embedded_statement]
397
+
398
+ @current_buffer = new_buffer
399
+
400
+ Refract::IfNode.new(
401
+ predicate: Refract::LocalVariableReadNode.new(
402
+ name: should_render_local
403
+ ),
404
+ statements: Refract::StatementsNode.new(
405
+ body: [
406
+ Refract::CallNode.new(
407
+ receiver: Refract::CallNode.new(
408
+ name: buffer_local,
409
+ ),
410
+ name: :<<,
411
+ arguments: Refract::ArgumentsNode.new(
412
+ arguments: [
413
+ Refract::InterpolatedStringNode.new(
414
+ parts: new_buffer
415
+ ),
416
+ ]
417
+ )
418
+ ),
419
+ ]
420
+ )
421
+ )
422
+ end
423
+ end
424
+
425
+ private def clear_buffer
426
+ @current_buffer = nil
427
+ end
428
+
429
+ private def output_block?(node)
430
+ node.body.body.any? do |child|
431
+ Refract::CallNode === child && (
432
+ standard_element?(child) ||
433
+ void_element?(child) ||
434
+ plain_helper?(child) ||
435
+ whitespace_helper?(child) ||
436
+ raw_helper?(child)
437
+ )
438
+ end
439
+ end
440
+
441
+ private def static_content_block?(node)
442
+ return false unless node.body.body.length == 1
443
+ node.body.body.first in Refract::StringNode | Refract::InterpolatedStringNode | Refract::SymbolNode | Refract::NilNode
444
+ end
445
+
446
+ private def standard_element?(node)
447
+ if (tag = Phlex::HTML::StandardElements.__registered_elements__[node.name]) &&
448
+ (Phlex::HTML::StandardElements == @component.instance_method(node.name).owner)
449
+
450
+ tag
451
+ else
452
+ false
453
+ end
454
+ end
455
+
456
+ private def void_element?(node)
457
+ if (tag = Phlex::HTML::VoidElements.__registered_elements__[node.name]) &&
458
+ (Phlex::HTML::VoidElements == @component.instance_method(node.name).owner)
459
+
460
+ tag
461
+ else
462
+ false
463
+ end
464
+ end
465
+
466
+ private def static_attribute_value_literal?(value)
467
+ case value
468
+ when Refract::SymbolNode, Refract::StringNode, Refract::IntegerNode, Refract::FloatNode, Refract::TrueNode, Refract::FalseNode, Refract::NilNode
469
+ true
470
+ when Refract::ArrayNode
471
+ value.elements.all? { |n| static_token_value_literal?(n) }
472
+ when Refract::HashNode
473
+ value.elements.all? { |n| static_attribute_value_literal?(n) }
474
+ when Refract::AssocNode
475
+ (Refract::StringNode === value.key || Refract::SymbolNode === value.key) && static_attribute_value_literal?(value.value)
476
+ when Refract::CallNode
477
+ if value in { receiver: Prism::ConstantReadNode[name: :Set]| Prism::ConstantPathNode[name: :Set, parent: nil], name: :[] }
478
+ value.arguments.arguments.all? { |n| static_token_value_literal?(n) }
479
+ else
480
+ false
481
+ end
482
+ else
483
+ false
484
+ end
485
+ end
486
+
487
+ private def static_token_value_literal?(value)
488
+ case value
489
+ when Prism::SymbolNode, Prism::StringNode, Prism::IntegerNode, Prism::FloatNode, Prism::NilNode
490
+ true
491
+ when Prism::ArrayNode
492
+ value.elements.all? { |n| static_token_value_literal?(n) }
493
+ else
494
+ false
495
+ end
496
+ end
497
+
498
+ private def whitespace_helper?(node)
499
+ node.name == :whitespace && own_method_without_scope?(node)
500
+ end
501
+
502
+ private def doctype_helper?(node)
503
+ node.name == :doctype && own_method_without_scope?(node)
504
+ end
505
+
506
+ private def plain_helper?(node)
507
+ node.name == :plain && own_method_without_scope?(node)
508
+ end
509
+
510
+ private def fragment_helper?(node)
511
+ node.name == :fragment && own_method_without_scope?(node)
512
+ end
513
+
514
+ private def comment_helper?(node)
515
+ node.name == :comment && own_method_without_scope?(node)
516
+ end
517
+
518
+ private def raw_helper?(node)
519
+ node.name == :raw && own_method_without_scope?(node)
520
+ end
521
+
522
+ ALLOWED_OWNERS = Set[Phlex::SGML, Phlex::HTML, Phlex::SVG]
523
+ private def own_method_without_scope?(node)
524
+ ALLOWED_OWNERS.include?(@component.instance_method(node.name).owner)
525
+ end
526
+
527
+ private def state_local
528
+ :__phlex_state__.tap do |local|
529
+ unless @state_local_set
530
+ @preamble << Refract::LocalVariableWriteNode.new(
531
+ name: local,
532
+ value: Refract::InstanceVariableReadNode.new(
533
+ name: :@_state
534
+ )
535
+ )
536
+
537
+ @state_local_set = true
538
+ end
539
+ end
540
+ end
541
+
542
+ private def buffer_local
543
+ :__phlex_buffer__.tap do |local|
544
+ unless @buffer_local_set
545
+ @preamble << Refract::LocalVariableWriteNode.new(
546
+ name: local,
547
+ value: Refract::CallNode.new(
548
+ receiver: Refract::LocalVariableReadNode.new(
549
+ name: state_local
550
+ ),
551
+ name: :buffer,
552
+ )
553
+ )
554
+
555
+ @buffer_local_set = true
556
+ end
557
+ end
558
+ end
559
+
560
+ private def self_local
561
+ :__phlex_self__.tap do |local|
562
+ unless @self_local_set
563
+ @preamble << Refract::LocalVariableWriteNode.new(
564
+ name: local,
565
+ value: Refract::SelfNode.new
566
+ )
567
+
568
+ @self_local_set = true
569
+ end
570
+ end
571
+ end
572
+
573
+ private def should_render_local
574
+ :__phlex_should_render__.tap do |local|
575
+ unless @should_render_local_set
576
+ @preamble << Refract::LocalVariableWriteNode.new(
577
+ name: :__phlex_should_render__,
578
+ value: Refract::CallNode.new(
579
+ receiver: Refract::LocalVariableReadNode.new(
580
+ name: state_local
581
+ ),
582
+ name: :should_render?
583
+ )
584
+ )
585
+
586
+ @should_render_local_set = true
587
+ end
588
+ end
589
+ end
590
+ end
591
+ end