ruby-next-core 0.9.2 → 0.10.4

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +15 -4
  4. data/lib/.rbnext/2.3/ruby-next/commands/core_ext.rb +167 -0
  5. data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +201 -0
  6. data/lib/.rbnext/2.3/ruby-next/language/eval.rb +66 -0
  7. data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +121 -0
  8. data/lib/.rbnext/2.3/ruby-next/language/rewriters/endless_range.rb +63 -0
  9. data/lib/.rbnext/2.3/ruby-next/language/rewriters/pattern_matching.rb +944 -0
  10. data/lib/.rbnext/2.3/ruby-next/language/rewriters/right_hand_assignment.rb +107 -0
  11. data/lib/.rbnext/2.3/ruby-next/utils.rb +65 -0
  12. data/lib/ruby-next.rb +8 -6
  13. data/lib/ruby-next/cli.rb +2 -2
  14. data/lib/ruby-next/commands/core_ext.rb +1 -1
  15. data/lib/ruby-next/commands/nextify.rb +3 -0
  16. data/lib/ruby-next/core.rb +27 -21
  17. data/lib/ruby-next/core/array/deconstruct.rb +9 -9
  18. data/lib/ruby-next/core/array/difference_union_intersection.rb +12 -12
  19. data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +3 -3
  20. data/lib/ruby-next/core/enumerable/filter.rb +8 -8
  21. data/lib/ruby-next/core/enumerable/filter_map.rb +25 -25
  22. data/lib/ruby-next/core/enumerable/tally.rb +7 -7
  23. data/lib/ruby-next/core/enumerator/produce.rb +12 -12
  24. data/lib/ruby-next/core/hash/deconstruct_keys.rb +9 -9
  25. data/lib/ruby-next/core/hash/except.rb +11 -0
  26. data/lib/ruby-next/core/hash/merge.rb +8 -8
  27. data/lib/ruby-next/core/kernel/then.rb +2 -2
  28. data/lib/ruby-next/core/proc/compose.rb +11 -11
  29. data/lib/ruby-next/core/string/split.rb +6 -6
  30. data/lib/ruby-next/core/struct/deconstruct.rb +2 -2
  31. data/lib/ruby-next/core/struct/deconstruct_keys.rb +17 -17
  32. data/lib/ruby-next/core/symbol/end_with.rb +4 -4
  33. data/lib/ruby-next/core/symbol/start_with.rb +4 -4
  34. data/lib/ruby-next/core/time/ceil.rb +6 -6
  35. data/lib/ruby-next/core/time/floor.rb +4 -4
  36. data/lib/ruby-next/core/unboundmethod/bind_call.rb +4 -4
  37. data/lib/ruby-next/core_ext.rb +1 -1
  38. data/lib/ruby-next/language.rb +12 -1
  39. data/lib/ruby-next/language/parser.rb +0 -3
  40. data/lib/ruby-next/language/proposed.rb +3 -0
  41. data/lib/ruby-next/language/rewriters/args_forward.rb +23 -20
  42. data/lib/ruby-next/language/rewriters/base.rb +1 -1
  43. data/lib/ruby-next/language/rewriters/endless_method.rb +25 -3
  44. data/lib/ruby-next/language/rewriters/find_pattern.rb +44 -0
  45. data/lib/ruby-next/language/rewriters/method_reference.rb +1 -1
  46. data/lib/ruby-next/language/rewriters/pattern_matching.rb +102 -12
  47. data/lib/ruby-next/language/rewriters/right_hand_assignment.rb +73 -11
  48. data/lib/ruby-next/language/rewriters/safe_navigation.rb +87 -0
  49. data/lib/ruby-next/language/rewriters/shorthand_hash.rb +47 -0
  50. data/lib/ruby-next/language/rewriters/squiggly_heredoc.rb +36 -0
  51. data/lib/ruby-next/language/unparser.rb +0 -14
  52. data/lib/ruby-next/logging.rb +1 -1
  53. data/lib/ruby-next/rubocop.rb +91 -9
  54. data/lib/ruby-next/setup_self.rb +22 -0
  55. data/lib/ruby-next/version.rb +1 -1
  56. data/lib/uby-next.rb +8 -4
  57. metadata +23 -9
@@ -34,7 +34,7 @@ module RubyNext
34
34
  end
35
35
 
36
36
  def key?(name)
37
- !!fetch(name) { false }
37
+ !!fetch(name) { false } # rubocop:disable Style/RedundantFetchBlock
38
38
  end
39
39
 
40
40
  def fetch(name)
@@ -6,7 +6,14 @@ module RubyNext
6
6
  class EndlessMethod < Base
7
7
  NAME = "endless-method"
8
8
  SYNTAX_PROBE = "obj = Object.new; def obj.foo() = 42"
9
- MIN_SUPPORTED_VERSION = Gem::Version.new("2.8.0")
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
10
+
11
+ unless Parser::Meta::NODE_TYPES.include?(:def_e)
12
+ def on_def(node)
13
+ return on_def_e(node) if node.loc.end.nil?
14
+ super(node)
15
+ end
16
+ end
10
17
 
11
18
  def on_def_e(node)
12
19
  context.track! self
@@ -14,24 +21,39 @@ module RubyNext
14
21
  replace(node.loc.assignment, "; ")
15
22
  insert_after(node.loc.expression, "; end")
16
23
 
24
+ new_loc = node.loc.dup
25
+ new_loc.instance_variable_set(:@end, node.loc.expression)
26
+
17
27
  process(
18
28
  node.updated(
19
29
  :def,
20
- node.children
30
+ node.children,
31
+ location: new_loc
21
32
  )
22
33
  )
23
34
  end
24
35
 
36
+ unless Parser::Meta::NODE_TYPES.include?(:def_e)
37
+ def on_defs(node)
38
+ return on_defs_e(node) if node.loc.end.nil?
39
+ super(node)
40
+ end
41
+ end
42
+
25
43
  def on_defs_e(node)
26
44
  context.track! self
27
45
 
28
46
  replace(node.loc.assignment, "; ")
29
47
  insert_after(node.loc.expression, "; end")
30
48
 
49
+ new_loc = node.loc.dup
50
+ new_loc.instance_variable_set(:@end, node.loc.expression)
51
+
31
52
  process(
32
53
  node.updated(
33
54
  :defs,
34
- node.children
55
+ node.children,
56
+ location: new_loc
35
57
  )
36
58
  )
37
59
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby-next/language/rewriters/pattern_matching"
4
+
5
+ module RubyNext
6
+ module Language
7
+ module Rewriters
8
+ using RubyNext
9
+
10
+ # Separate pattern matching rewriter for Ruby 2.7 to
11
+ # transpile only case...in with a find pattern
12
+ class FindPattern < PatternMatching
13
+ NAME = "pattern-matching-find-pattern"
14
+ SYNTAX_PROBE = "case 0; in [*,0,*]; true; else; 1; end"
15
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
16
+
17
+ def on_case_match(node)
18
+ @has_find_pattern = false
19
+ process_regular_node(node).then do |new_node|
20
+ return new_node unless has_find_pattern
21
+ super(node)
22
+ end
23
+ end
24
+
25
+ def on_in_match(node)
26
+ @has_find_pattern = false
27
+ process_regular_node(node).then do |new_node|
28
+ return new_node unless has_find_pattern
29
+ super(node)
30
+ end
31
+ end
32
+
33
+ def on_find_pattern(node)
34
+ @has_find_pattern = true
35
+ super(node)
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :has_find_pattern
41
+ end
42
+ end
43
+ end
44
+ end
@@ -6,7 +6,7 @@ module RubyNext
6
6
  class MethodReference < Base
7
7
  NAME = "method-reference"
8
8
  SYNTAX_PROBE = "Language.:transform"
9
- MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
10
10
 
11
11
  def on_meth_ref(node)
12
12
  context.track! self
@@ -10,6 +10,16 @@ module RubyNext
10
10
  def to_ast_node
11
11
  self
12
12
  end
13
+
14
+ # Useful to generate simple operation nodes
15
+ # (e.g., 'a + b')
16
+ def -(val)
17
+ ::Parser::AST::Node.new(:send, [self, :-, val.to_ast_node])
18
+ end
19
+
20
+ def +(val)
21
+ ::Parser::AST::Node.new(:send, [self, :+, val.to_ast_node])
22
+ end
13
23
  end
14
24
 
15
25
  refine String do
@@ -249,7 +259,7 @@ module RubyNext
249
259
  rewrite_case_in! node, matchee_ast, case_clause
250
260
 
251
261
  node.updated(
252
- :begin,
262
+ :kwbegin,
253
263
  [
254
264
  matchee_ast, case_clause
255
265
  ]
@@ -288,7 +298,7 @@ module RubyNext
288
298
  pattern
289
299
  ]
290
300
  ).tap do |new_node|
291
- replace(node.loc.expression, new_node)
301
+ replace(node.loc.expression, inline_blocks(unparse(new_node)))
292
302
  end
293
303
  end
294
304
 
@@ -307,14 +317,14 @@ module RubyNext
307
317
  body_indent = " " * clause.children[2].loc.column
308
318
  replace(
309
319
  clause.loc.expression,
310
- "when #{unparse(new_node.children[i].children[0])}" \
320
+ "when #{inline_blocks(unparse(new_node.children[i].children[0]))}" \
311
321
  "#{padding}" \
312
322
  "#{body_indent}#{clause.children[2].loc.expression.source}"
313
323
  )
314
324
  else
315
325
  replace(
316
326
  clause.loc.keyword.end.join(clause.children[0].loc.expression.end),
317
- new_node.children[i].children[0]
327
+ inline_blocks(unparse(new_node.children[i].children[0]))
318
328
  )
319
329
  remove(clause.children[1].loc.expression) if clause.children[1]
320
330
  replace(clause.loc.keyword, "when ")
@@ -430,6 +440,8 @@ module RubyNext
430
440
  right =
431
441
  if node.children.empty?
432
442
  case_eq_clause(s(:array), s(:lvar, locals[:arr]))
443
+ elsif node.children.size > 1 && node.children.first.type == :match_rest && node.children.last.type == :match_rest
444
+ array_find(*node.children)
433
445
  else
434
446
  array_element(0, *node.children)
435
447
  end
@@ -443,6 +455,7 @@ module RubyNext
443
455
  end
444
456
 
445
457
  alias array_pattern_with_tail_clause array_pattern_clause
458
+ alias find_pattern_clause array_pattern_clause
446
459
 
447
460
  def deconstruct_node(matchee)
448
461
  context.use_ruby_next!
@@ -478,9 +491,80 @@ module RubyNext
478
491
  end
479
492
  end
480
493
 
494
+ # [*a, 1, 2, *] -> arr.find.with_index { |_, i| (a = arr.take(i)) && arr[i] == 1 && arr[i + 1] == 2 }
495
+ def array_find(head, *nodes, tail)
496
+ index = s(:lvar, :__i__)
497
+
498
+ match_vars = []
499
+
500
+ head_match =
501
+ unless head.children.empty?
502
+ match_vars << s(:lvasgn, head.children[0].children[0])
503
+
504
+ arr_take = s(:send,
505
+ s(:lvar, locals[:arr]),
506
+ :take,
507
+ index)
508
+
509
+ match_var_clause(head.children[0], arr_take)
510
+ end
511
+
512
+ tail_match =
513
+ unless tail.children.empty?
514
+ match_vars << s(:lvasgn, tail.children[0].children[0])
515
+
516
+ match_var_clause(tail.children[0], arr_slice(index + nodes.size, -1))
517
+ end
518
+
519
+ nodes.each do |node|
520
+ if node.type == :match_var
521
+ match_vars << s(:lvasgn, node.children[0])
522
+ elsif node.type == :match_as
523
+ match_vars << s(:lvasgn, node.children[1].children[0])
524
+ end
525
+ end
526
+
527
+ pattern = array_rest_element(*nodes, index).then do |needle|
528
+ next needle unless head_match
529
+ s(:and,
530
+ needle,
531
+ head_match)
532
+ end.then do |headed_needle|
533
+ next headed_needle unless tail_match
534
+
535
+ s(:and,
536
+ headed_needle,
537
+ tail_match)
538
+ end
539
+
540
+ s(:block,
541
+ s(:send,
542
+ s(:send,
543
+ s(:lvar, locals[:arr]),
544
+ :find),
545
+ :with_index),
546
+ s(:args,
547
+ s(:arg, :_),
548
+ s(:arg, :__i__)),
549
+ pattern).then do |block|
550
+ next block if match_vars.empty?
551
+
552
+ # We need to declare match vars outside of `find` block
553
+ locals_declare = s(:masgn,
554
+ s(:mlhs, *match_vars),
555
+ s(:nil))
556
+
557
+ s(:or,
558
+ locals_declare,
559
+ block)
560
+ end
561
+ end
562
+
481
563
  def array_match_rest(index, node, *tail)
564
+ size = tail.size + 1
482
565
  child = node.children[0]
483
- rest = arr_rest_items(index, tail.size).then do |r|
566
+
567
+ rest = arr_slice(index, -size).then do |r|
484
568
  next r unless child
485
569
 
486
570
  match_var_clause(
@@ -493,16 +577,16 @@ module RubyNext
493
577
 
494
578
  s(:and,
495
579
  rest,
496
- array_rest_element(*tail))
580
+ array_rest_element(*tail, -(size - 1)))
497
581
  end
498
582
 
499
- def array_rest_element(head, *tail)
500
- send("#{head.type}_array_element", head, -(tail.size + 1)).then do |node|
583
+ def array_rest_element(head, *tail, index)
584
+ send("#{head.type}_array_element", head, index).then do |node|
501
585
  next node if tail.empty?
502
586
 
503
587
  s(:and,
504
588
  node,
505
- array_rest_element(*tail))
589
+ array_rest_element(*tail, index + 1))
506
590
  end
507
591
  end
508
592
 
@@ -549,12 +633,12 @@ module RubyNext
549
633
  s(:index, arr, index.to_ast_node)
550
634
  end
551
635
 
552
- def arr_rest_items(index, size, arr = s(:lvar, locals[:arr]))
636
+ def arr_slice(lindex, rindex, arr = s(:lvar, locals[:arr]))
553
637
  s(:index,
554
638
  arr,
555
639
  s(:irange,
556
- s(:int, index),
557
- s(:int, -(size + 1))))
640
+ lindex.to_ast_node,
641
+ rindex.to_ast_node))
558
642
  end
559
643
 
560
644
  #=========== ARRAY PATTERN (END) ===============
@@ -848,6 +932,12 @@ module RubyNext
848
932
 
849
933
  deconstructed_keys[key] = :"k#{deconstructed_keys.size}"
850
934
  end
935
+
936
+ # Unparser generates `do .. end` blocks, we want to
937
+ # have single-line blocks with `{ ... }`.
938
+ def inline_blocks(source)
939
+ source.gsub(/do \|_, __i__\|\n\s*([^\n]+)\n\s*end/, '{ |_, __i__| \1 }')
940
+ end
851
941
  end
852
942
  end
853
943
  end
@@ -6,39 +6,101 @@ module RubyNext
6
6
  class RightHandAssignment < Base
7
7
  NAME = "right-hand-assignment"
8
8
  SYNTAX_PROBE = "1 + 2 => a"
9
- MIN_SUPPORTED_VERSION = Gem::Version.new("2.8.0")
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
10
10
 
11
11
  def on_rasgn(node)
12
12
  context.track! self
13
13
 
14
+ node = super(node)
15
+
14
16
  val_node, asgn_node = *node
15
17
 
16
18
  remove(val_node.loc.expression.end.join(asgn_node.loc.expression))
17
19
  insert_before(val_node.loc.expression, "#{asgn_node.loc.expression.source} = ")
18
20
 
19
- process(
20
- asgn_node.updated(
21
- nil,
22
- asgn_node.children + [val_node]
23
- )
21
+ asgn_node.updated(
22
+ nil,
23
+ asgn_node.children + [val_node]
24
24
  )
25
25
  end
26
26
 
27
+ def on_vasgn(node)
28
+ return super(node) unless rightward?(node)
29
+
30
+ context.track! self
31
+
32
+ name, val_node = *node
33
+
34
+ remove(val_node.loc.expression.end.join(node.loc.name))
35
+ insert_before(val_node.loc.expression, "#{name} = ")
36
+
37
+ super(node)
38
+ end
39
+
40
+ def on_casgn(node)
41
+ return super(node) unless rightward?(node)
42
+
43
+ context.track! self
44
+
45
+ scope_node, name, val_node = *node
46
+
47
+ if scope_node
48
+ scope = scope_node.type == :cbase ? scope_node.loc.expression.source : "#{scope_node.loc.expression.source}::"
49
+ name = "#{scope}#{name}"
50
+ end
51
+
52
+ remove(val_node.loc.expression.end.join(node.loc.name))
53
+ insert_before(val_node.loc.expression, "#{name} = ")
54
+
55
+ super(node)
56
+ end
57
+
27
58
  def on_mrasgn(node)
28
59
  context.track! self
29
60
 
61
+ node = super(node)
62
+
30
63
  lhs, rhs = *node
31
64
 
32
65
  replace(lhs.loc.expression.end.join(rhs.loc.expression), ")")
33
66
  insert_before(lhs.loc.expression, "#{rhs.loc.expression.source} = (")
34
67
 
35
- process(
36
- node.updated(
37
- :masgn,
38
- [rhs, lhs]
39
- )
68
+ node.updated(
69
+ :masgn,
70
+ [rhs, lhs]
40
71
  )
41
72
  end
73
+
74
+ def on_masgn(node)
75
+ return super(node) unless rightward?(node)
76
+
77
+ context.track! self
78
+
79
+ rhs, lhs = *node
80
+
81
+ replace(lhs.loc.expression.end.join(rhs.loc.expression), ")")
82
+ insert_before(lhs.loc.expression, "#{rhs.loc.expression.source} = (")
83
+
84
+ super(node)
85
+ end
86
+
87
+ private
88
+
89
+ def rightward?(node)
90
+ # Location could be empty for node built by rewriters
91
+ return false unless node.loc&.operator
92
+
93
+ assignee_loc =
94
+ if node.type == :masgn
95
+ node.children[0].loc.expression
96
+ else
97
+ node.loc.name
98
+ end
99
+
100
+ return false unless assignee_loc
101
+
102
+ assignee_loc.begin_pos > node.loc.operator.end_pos
103
+ end
42
104
  end
43
105
  end
44
106
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class SafeNavigation < Base
7
+ NAME = "safe-navigation"
8
+ SYNTAX_PROBE = "nil&.x&.nil?"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.3.0")
10
+
11
+ def on_csend(node)
12
+ node = super(node)
13
+
14
+ context.track! self
15
+
16
+ receiver, *args = *node
17
+
18
+ new_node = node.updated(
19
+ :and,
20
+ [
21
+ process(safe_navigation(receiver)),
22
+ s(:send, decsendize(receiver), *args)
23
+ ]
24
+ )
25
+
26
+ replace(node.loc.expression, new_node)
27
+
28
+ new_node
29
+ end
30
+
31
+ def on_block(node)
32
+ return super(node) unless node.children[0].type == :csend
33
+
34
+ context.track!(self)
35
+
36
+ new_node = super(node.updated(
37
+ :and,
38
+ [
39
+ process(safe_navigation(node.children[0].children[0])),
40
+ process(node.updated(nil, node.children.map(&method(:decsendize))))
41
+ ]
42
+ ))
43
+
44
+ replace(node.loc.expression, new_node)
45
+
46
+ new_node
47
+ end
48
+
49
+ def on_op_asgn(node)
50
+ return super(node) unless node.children[0].type == :csend
51
+
52
+ context.track!(self)
53
+
54
+ new_node = super(node.updated(
55
+ :and,
56
+ [
57
+ process(safe_navigation(node.children[0].children[0])),
58
+ process(node.updated(nil, node.children.map(&method(:decsendize))))
59
+ ]
60
+ ))
61
+
62
+ replace(node.loc.expression, new_node)
63
+
64
+ new_node
65
+ end
66
+
67
+ private
68
+
69
+ def decsendize(node)
70
+ return node unless node.is_a?(::Parser::AST::Node) && node.type == :csend
71
+
72
+ node.updated(:send, node.children.map(&method(:decsendize)))
73
+ end
74
+
75
+ # Transform: x&.y -> (!x.nil? && x.y) || nil
76
+ # This allows us to handle `false&.to_s == "false"`
77
+ def safe_navigation(node)
78
+ s(:or,
79
+ s(:send,
80
+ s(:send, node, :nil?),
81
+ :!),
82
+ s(:nil))
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end