ruby-next-core 0.9.2 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +14 -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 +198 -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/utils.rb +65 -0
  11. data/lib/ruby-next.rb +8 -6
  12. data/lib/ruby-next/cli.rb +2 -2
  13. data/lib/ruby-next/commands/core_ext.rb +1 -1
  14. data/lib/ruby-next/core.rb +27 -21
  15. data/lib/ruby-next/core/array/deconstruct.rb +9 -9
  16. data/lib/ruby-next/core/array/difference_union_intersection.rb +12 -12
  17. data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +3 -3
  18. data/lib/ruby-next/core/enumerable/filter.rb +8 -8
  19. data/lib/ruby-next/core/enumerable/filter_map.rb +25 -25
  20. data/lib/ruby-next/core/enumerable/tally.rb +7 -7
  21. data/lib/ruby-next/core/enumerator/produce.rb +12 -12
  22. data/lib/ruby-next/core/hash/deconstruct_keys.rb +9 -9
  23. data/lib/ruby-next/core/hash/except.rb +11 -0
  24. data/lib/ruby-next/core/hash/merge.rb +8 -8
  25. data/lib/ruby-next/core/kernel/then.rb +2 -2
  26. data/lib/ruby-next/core/proc/compose.rb +11 -11
  27. data/lib/ruby-next/core/string/split.rb +6 -6
  28. data/lib/ruby-next/core/struct/deconstruct.rb +2 -2
  29. data/lib/ruby-next/core/struct/deconstruct_keys.rb +17 -17
  30. data/lib/ruby-next/core/symbol/end_with.rb +4 -4
  31. data/lib/ruby-next/core/symbol/start_with.rb +4 -4
  32. data/lib/ruby-next/core/time/ceil.rb +6 -6
  33. data/lib/ruby-next/core/time/floor.rb +4 -4
  34. data/lib/ruby-next/core/unboundmethod/bind_call.rb +4 -4
  35. data/lib/ruby-next/core_ext.rb +1 -1
  36. data/lib/ruby-next/language.rb +12 -1
  37. data/lib/ruby-next/language/parser.rb +0 -3
  38. data/lib/ruby-next/language/proposed.rb +3 -0
  39. data/lib/ruby-next/language/rewriters/args_forward.rb +23 -20
  40. data/lib/ruby-next/language/rewriters/base.rb +1 -1
  41. data/lib/ruby-next/language/rewriters/endless_method.rb +25 -3
  42. data/lib/ruby-next/language/rewriters/find_pattern.rb +44 -0
  43. data/lib/ruby-next/language/rewriters/method_reference.rb +1 -1
  44. data/lib/ruby-next/language/rewriters/pattern_matching.rb +102 -12
  45. data/lib/ruby-next/language/rewriters/right_hand_assignment.rb +1 -1
  46. data/lib/ruby-next/language/rewriters/safe_navigation.rb +87 -0
  47. data/lib/ruby-next/language/rewriters/shorthand_hash.rb +47 -0
  48. data/lib/ruby-next/language/rewriters/squiggly_heredoc.rb +36 -0
  49. data/lib/ruby-next/language/unparser.rb +0 -14
  50. data/lib/ruby-next/logging.rb +1 -1
  51. data/lib/ruby-next/rubocop.rb +15 -9
  52. data/lib/ruby-next/setup_self.rb +22 -0
  53. data/lib/ruby-next/version.rb +1 -1
  54. data/lib/uby-next.rb +8 -4
  55. metadata +20 -7
@@ -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,7 +6,7 @@ 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
@@ -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
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class ShorthandHash < Base
7
+ NAME = "shorthand-hash"
8
+ SYNTAX_PROBE = "data = {x}"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
10
+
11
+ def on_ipair(node)
12
+ context.track! self
13
+
14
+ ident, = *node.children
15
+
16
+ key = key_from_ident(ident)
17
+
18
+ replace(
19
+ node.loc.expression,
20
+ "#{key}: #{key}"
21
+ )
22
+
23
+ node.updated(
24
+ :pair,
25
+ [
26
+ s(:sym, key),
27
+ ident
28
+ ]
29
+ )
30
+ end
31
+
32
+ private
33
+
34
+ def key_from_ident(node)
35
+ case node.type
36
+ when :send
37
+ node.children[1]
38
+ when :lvar
39
+ node.children[0]
40
+ else
41
+ raise ArgumentError, "Unsupport ipair node: #{node}"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class SquigglyHeredoc < Base
7
+ NAME = "squiggly-heredoc"
8
+ SYNTAX_PROBE = "txt = <<~TXT\n bla\n TXT"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.3.0")
10
+
11
+ def on_str(node)
12
+ node = super(node) if defined?(super_method)
13
+ return node unless node.loc.respond_to?(:heredoc_body) && node.loc.expression.source.include?("<<~")
14
+
15
+ context.track! self
16
+
17
+ replace(node.loc.expression, node.loc.expression.source.tr("~", "-"))
18
+
19
+ heredoc_loc = node.loc.heredoc_body.join(node.loc.heredoc_end)
20
+ heredoc_source, heredoc_end = heredoc_loc.source.split(/\n([^\n]+)\z/)
21
+
22
+ indent = heredoc_source.lines.map { |line| line.match(/^\s*/)[0].size }.min
23
+
24
+ new_source = heredoc_source.gsub!(%r{^\s{#{indent}}}, "")
25
+
26
+ replace(heredoc_loc, [new_source, heredoc_end].join("\n"))
27
+
28
+ node
29
+ end
30
+
31
+ alias on_dstr on_str
32
+ alias on_xstr on_str
33
+ end
34
+ end
35
+ end
36
+ end
@@ -6,17 +6,3 @@ require "parser/current"
6
6
  $VERBOSE = save_verbose
7
7
 
8
8
  require "unparser"
9
-
10
- unless defined?(Unparser::UnknownEmitterError)
11
- module Unparser
12
- class UnknownEmitterError < ArgumentError
13
- end
14
-
15
- Emitter.singleton_class.prepend(Module.new do
16
- def emitter(node, parent)
17
- raise UnknownEmitterError, "No emitter for node: #{node.type.inspect}" unless Emitter::REGISTRY.key?(node.type)
18
- super
19
- end
20
- end)
21
- end
22
- end