synvert-core 0.62.1 → 0.64.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -1
  3. data/Gemfile +0 -2
  4. data/README.md +73 -33
  5. data/lib/synvert/core/configuration.rb +12 -0
  6. data/lib/synvert/core/exceptions.rb +0 -4
  7. data/lib/synvert/core/node_ext.rb +206 -103
  8. data/lib/synvert/core/rewriter/action/append_action.rb +4 -3
  9. data/lib/synvert/core/rewriter/action/delete_action.rb +13 -6
  10. data/lib/synvert/core/rewriter/action/insert_action.rb +16 -7
  11. data/lib/synvert/core/rewriter/action/insert_after_action.rb +3 -2
  12. data/lib/synvert/core/rewriter/action/prepend_action.rb +3 -2
  13. data/lib/synvert/core/rewriter/action/remove_action.rb +16 -10
  14. data/lib/synvert/core/rewriter/action/replace_action.rb +13 -5
  15. data/lib/synvert/core/rewriter/action/replace_erb_stmt_with_expr_action.rb +18 -11
  16. data/lib/synvert/core/rewriter/action/replace_with_action.rb +6 -5
  17. data/lib/synvert/core/rewriter/action/wrap_action.rb +13 -5
  18. data/lib/synvert/core/rewriter/action.rb +20 -9
  19. data/lib/synvert/core/rewriter/any_value.rb +1 -0
  20. data/lib/synvert/core/rewriter/condition/if_exist_condition.rb +4 -0
  21. data/lib/synvert/core/rewriter/condition/if_only_exist_condition.rb +4 -0
  22. data/lib/synvert/core/rewriter/condition/unless_exist_condition.rb +4 -0
  23. data/lib/synvert/core/rewriter/condition.rb +11 -3
  24. data/lib/synvert/core/rewriter/gem_spec.rb +7 -4
  25. data/lib/synvert/core/rewriter/helper.rb +2 -2
  26. data/lib/synvert/core/rewriter/instance.rb +195 -94
  27. data/lib/synvert/core/rewriter/ruby_version.rb +4 -4
  28. data/lib/synvert/core/rewriter/scope/goto_scope.rb +5 -6
  29. data/lib/synvert/core/rewriter/scope/within_scope.rb +9 -4
  30. data/lib/synvert/core/rewriter/scope.rb +8 -0
  31. data/lib/synvert/core/rewriter/warning.rb +1 -1
  32. data/lib/synvert/core/rewriter.rb +90 -43
  33. data/lib/synvert/core/version.rb +1 -1
  34. data/lib/synvert/core.rb +0 -1
  35. data/spec/spec_helper.rb +0 -3
  36. data/spec/synvert/core/node_ext_spec.rb +28 -7
  37. data/spec/synvert/core/rewriter/action_spec.rb +0 -4
  38. data/spec/synvert/core/rewriter/gem_spec_spec.rb +11 -10
  39. data/spec/synvert/core/rewriter/instance_spec.rb +7 -17
  40. data/synvert-core-ruby.gemspec +2 -1
  41. metadata +21 -7
@@ -3,13 +3,14 @@
3
3
  module Synvert::Core
4
4
  # InsertAfterAction to insert code next to the node.
5
5
  class Rewriter::InsertAfterAction < Rewriter::Action
6
+ private
7
+
8
+ # Calculate the begin and end positions.
6
9
  def calculate_position
7
10
  @begin_pos = @node.loc.expression.end_pos
8
11
  @end_pos = @begin_pos
9
12
  end
10
13
 
11
- private
12
-
13
14
  # Indent of the node.
14
15
  #
15
16
  # @param node [Parser::AST::Node]
@@ -3,8 +3,11 @@
3
3
  module Synvert::Core
4
4
  # PrependAction to prepend code to the top of node body.
5
5
  class Rewriter::PrependAction < Rewriter::Action
6
+ private
7
+
6
8
  DO_LENGTH = ' do'.length
7
9
 
10
+ # Calculate the begin and end positions.
8
11
  def calculate_position
9
12
  @begin_pos =
10
13
  case @node.type
@@ -26,8 +29,6 @@ module Synvert::Core
26
29
  @end_pos = @begin_pos
27
30
  end
28
31
 
29
- private
30
-
31
32
  # Indent of the node.
32
33
  #
33
34
  # @param node [Parser::AST::Node]
@@ -3,13 +3,21 @@
3
3
  module Synvert::Core
4
4
  # RemoveAction to remove current node.
5
5
  class Rewriter::RemoveAction < Rewriter::Action
6
+ # Initialize a RemoveAction.
7
+ #
8
+ # @param instance [Synvert::Core::Rewriter::RemoveAction]
6
9
  def initialize(instance)
7
10
  super(instance, nil)
8
11
  end
9
12
 
10
- # Begin position of code to replace.
11
- #
12
- # @return [Integer] begin position.
13
+ # The rewritten code, always empty string.
14
+ def rewritten_code
15
+ ''
16
+ end
17
+
18
+ private
19
+
20
+ # Calculate the begin the end positions.
13
21
  def calculate_position
14
22
  if take_whole_line?
15
23
  @begin_pos = start_index
@@ -23,22 +31,20 @@ module Synvert::Core
23
31
  end
24
32
  end
25
33
 
26
- # The rewritten code, always empty string.
27
- def rewritten_code
28
- ''
29
- end
30
-
31
- private
32
-
34
+ # Check if the source code of current node takes the whole line.
35
+ #
36
+ # @return [Boolean]
33
37
  def take_whole_line?
34
38
  @node.to_source == file_source[start_index...end_index].strip
35
39
  end
36
40
 
41
+ # Get the start position of the line
37
42
  def start_index
38
43
  index = file_source[0..@node.loc.expression.begin_pos].rindex("\n")
39
44
  index ? index + "\n".length : @node.loc.expression.begin_pos
40
45
  end
41
46
 
47
+ # Get the end position of the line
42
48
  def end_index
43
49
  index = file_source[@node.loc.expression.end_pos..-1].index("\n")
44
50
  index ? @node.loc.expression.end_pos + index + "\n".length : @node.loc.expression.end_pos
@@ -3,21 +3,29 @@
3
3
  module Synvert::Core
4
4
  # ReplaceAction to replace child node with code.
5
5
  class Rewriter::ReplaceAction < Rewriter::Action
6
+ # Initailize a ReplaceAction.
7
+ #
8
+ # @param instance [Synvert::Core::Rewriter::Instance]
9
+ # @param selectors [Array<Symbol|String>] used to select child nodes
10
+ # @param with [String] the new code
6
11
  def initialize(instance, *selectors, with:)
7
12
  super(instance, with)
8
13
  @selectors = selectors
9
14
  end
10
15
 
11
- def calculate_position
12
- @begin_pos = @selectors.map { |selector| @node.child_node_range(selector).begin_pos }.min
13
- @end_pos = @selectors.map { |selector| @node.child_node_range(selector).end_pos }.max
14
- end
15
-
16
16
  # The rewritten source code.
17
17
  #
18
18
  # @return [String] rewritten code.
19
19
  def rewritten_code
20
20
  rewritten_source
21
21
  end
22
+
23
+ private
24
+
25
+ # Calculate the begin the end positions.
26
+ def calculate_position
27
+ @begin_pos = @selectors.map { |selector| @node.child_node_range(selector).begin_pos }.min
28
+ @end_pos = @selectors.map { |selector| @node.child_node_range(selector).end_pos }.max
29
+ end
22
30
  end
23
31
  end
@@ -2,12 +2,28 @@
2
2
 
3
3
  module Synvert::Core
4
4
  # ReplaceErbStmtWithExprAction to replace erb stmt code to expr,
5
+ # @example
5
6
  # e.g. <% form_for ... %> => <%= form_for ... %>.
6
7
  class Rewriter::ReplaceErbStmtWithExprAction < Rewriter::Action
7
- def initialize(instance, code = nil)
8
- super
8
+ # Initialize a ReplaceErbStmtWithExprAction.
9
+ #
10
+ # @param instance [Synvert::Core::Rewriter::Instance]
11
+ def initialize(instance)
12
+ super(instance, nil)
13
+ end
14
+
15
+ # The rewritten erb expr code.
16
+ #
17
+ # @return [String] rewritten code.
18
+ def rewritten_code
19
+ @node.loc.expression.source_buffer.source[begin_pos...end_pos]
20
+ .sub(Engine::ERUBY_STMT_SPLITTER, '@output_buffer.append= ')
21
+ .sub(Engine::ERUBY_STMT_SPLITTER, Engine::ERUBY_EXPR_SPLITTER)
9
22
  end
10
23
 
24
+ private
25
+
26
+ # Calculate the begin the end positions.
11
27
  def calculate_position
12
28
  node_begin_pos = @node.loc.expression.begin_pos
13
29
  while @node.loc.expression.source_buffer.source[node_begin_pos -= 1] == ' '
@@ -20,14 +36,5 @@ module Synvert::Core
20
36
  end
21
37
  @end_pos = node_begin_pos
22
38
  end
23
-
24
- # The rewritten erb expr code.
25
- #
26
- # @return [String] rewritten code.
27
- def rewritten_code
28
- @node.loc.expression.source_buffer.source[begin_pos...end_pos]
29
- .sub(Engine::ERUBY_STMT_SPLITTER, '@output_buffer.append= ')
30
- .sub(Engine::ERUBY_STMT_SPLITTER, Engine::ERUBY_EXPR_SPLITTER)
31
- end
32
39
  end
33
40
  end
@@ -3,11 +3,6 @@
3
3
  module Synvert::Core
4
4
  # ReplaceWithAction to replace code.
5
5
  class Rewriter::ReplaceWithAction < Rewriter::Action
6
- def calculate_position
7
- @begin_pos = @node.loc.expression.begin_pos
8
- @end_pos = @node.loc.expression.end_pos
9
- end
10
-
11
6
  # The rewritten source code with proper indent.
12
7
  #
13
8
  # @return [String] rewritten code.
@@ -25,6 +20,12 @@ module Synvert::Core
25
20
 
26
21
  private
27
22
 
23
+ # Calculate the begin the end positions.
24
+ def calculate_position
25
+ @begin_pos = @node.loc.expression.begin_pos
26
+ @end_pos = @node.loc.expression.end_pos
27
+ end
28
+
28
29
  # Indent of the node
29
30
  #
30
31
  # @return [String] n times whitesphace
@@ -6,16 +6,16 @@ module Synvert::Core
6
6
  # Note: if WrapAction is conflicted with another action (begin_pos and end_pos are overlapped),
7
7
  # we have to put those 2 actions into 2 within_file scopes.
8
8
  class Rewriter::WrapAction < Rewriter::Action
9
+ # Initialize a WrapAction.
10
+ #
11
+ # @param instance [Synvert::Core::Rewriter::WrapAction]
12
+ # @param with [String] new code to wrap
13
+ # @param indent [Integer, nil] number of whitespaces
9
14
  def initialize(instance, with:, indent: nil)
10
15
  super(instance, with)
11
16
  @indent = indent || @node.column
12
17
  end
13
18
 
14
- def calculate_position
15
- @begin_pos = @node.loc.expression.begin_pos
16
- @end_pos = @node.loc.expression.end_pos
17
- end
18
-
19
19
  # The rewritten source code.
20
20
  #
21
21
  # @return [String] rewritten code.
@@ -24,5 +24,13 @@ module Synvert::Core
24
24
  @node.to_source.split("\n").map { |line| " #{line}" }.join("\n") +
25
25
  "\n#{' ' * @indent}end"
26
26
  end
27
+
28
+ private
29
+
30
+ # Calculate the begin the end positions.
31
+ def calculate_position
32
+ @begin_pos = @node.loc.expression.begin_pos
33
+ @end_pos = @node.loc.expression.end_pos
34
+ end
27
35
  end
28
36
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Synvert::Core
4
- # Action defines rewriter action, add, replace or remove code.
4
+ # Action defines rewriter action, insert, replace or delete code.
5
5
  class Rewriter::Action
6
6
  DEFAULT_INDENT = 2
7
7
 
@@ -14,25 +14,21 @@ module Synvert::Core
14
14
  # Initialize an action.
15
15
  #
16
16
  # @param instance [Synvert::Core::Rewriter::Instance]
17
- # @param code [String] new code to add, replace or remove.
17
+ # @param code [String] new code to insert, replace or delete.
18
18
  def initialize(instance, code)
19
19
  @instance = instance
20
20
  @code = code
21
21
  @node = @instance.current_node
22
22
  end
23
23
 
24
+ # Calculate begin and end positions, and return self.
25
+ #
26
+ # @return [Synvert::Core::Rewriter::Action] self
24
27
  def process
25
28
  calculate_position
26
29
  self
27
30
  end
28
31
 
29
- # Line number of current node.
30
- #
31
- # @return [Integer] line number.
32
- def line
33
- @node.loc.expression.line
34
- end
35
-
36
32
  # The rewritten source code with proper indent.
37
33
  #
38
34
  # @return [String] rewritten code.
@@ -46,6 +42,13 @@ module Synvert::Core
46
42
 
47
43
  protected
48
44
 
45
+ # Calculate the begin the end positions.
46
+ #
47
+ # @abstract
48
+ def calculate_position
49
+ raise NotImplementedError, 'must be implemented by subclasses'
50
+ end
51
+
49
52
  # The rewritten source code.
50
53
  #
51
54
  # @return [String] rewritten source code.
@@ -53,12 +56,14 @@ module Synvert::Core
53
56
  @rewritten_source ||= @node.rewritten_source(@code)
54
57
  end
55
58
 
59
+ # Squeeze spaces from source code.
56
60
  def squeeze_spaces
57
61
  if file_source[@begin_pos - 1] == ' ' && file_source[@end_pos] == ' '
58
62
  @begin_pos -= 1
59
63
  end
60
64
  end
61
65
 
66
+ # Squeeze empty lines from source code.
62
67
  def squeeze_lines
63
68
  lines = file_source.split("\n")
64
69
  begin_line = @node.loc.expression.first_line
@@ -71,6 +76,9 @@ module Synvert::Core
71
76
  end
72
77
  end
73
78
 
79
+ # Remove unused comma.
80
+ # e.g. `foobar(foo, bar)`, if we remove `foo`, the comma should also be removed,
81
+ # the code should be changed to `foobar(bar)`.
74
82
  def remove_comma
75
83
  if ',' == file_source[@begin_pos - 1]
76
84
  @begin_pos -= 1
@@ -83,6 +91,9 @@ module Synvert::Core
83
91
  end
84
92
  end
85
93
 
94
+ # Return file source.
95
+ #
96
+ # @return [String]
86
97
  def file_source
87
98
  @file_source ||= @instance.file_source
88
99
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Synvert::Core
4
+ # A new type to match any value.
4
5
  class Rewriter::AnyValue
5
6
  end
6
7
  end
@@ -3,7 +3,11 @@
3
3
  module Synvert::Core
4
4
  # IfExistCondition checks if matching node exists in the node children.
5
5
  class Rewriter::IfExistCondition < Rewriter::Condition
6
+ private
7
+
6
8
  # check if any child node matches the rules.
9
+ #
10
+ # @return [Boolean]
7
11
  def match?
8
12
  match = false
9
13
  @instance.current_node.recursive_children do |child_node|
@@ -3,7 +3,11 @@
3
3
  module Synvert::Core
4
4
  # IfOnlyExistCondition checks if node has only one child node and the child node matches rules.
5
5
  class Rewriter::IfOnlyExistCondition < Rewriter::Condition
6
+ private
7
+
6
8
  # check if only have one child node and the child node matches rules.
9
+ #
10
+ # @return [Boolean]
7
11
  def match?
8
12
  @instance.current_node.body.size == 1 && @instance.current_node.body.first.match?(@rules)
9
13
  end
@@ -3,7 +3,11 @@
3
3
  module Synvert::Core
4
4
  # UnlessExistCondition checks if matching node doesn't exist in the node children.
5
5
  class Rewriter::UnlessExistCondition < Rewriter::Condition
6
+ private
7
+
6
8
  # check if none of child node matches the rules.
9
+ #
10
+ # return [Boolean]
7
11
  def match?
8
12
  match = false
9
13
  @instance.current_node.recursive_children do |child_node|
@@ -3,12 +3,11 @@
3
3
  module Synvert::Core
4
4
  # Condition checks if rules matches.
5
5
  class Rewriter::Condition
6
- # Initialize a condition.
6
+ # Initialize a Condition.
7
7
  #
8
8
  # @param instance [Synvert::Core::Rewriter::Instance]
9
9
  # @param rules [Hash]
10
- # @param block [Block]
11
- # @return [Synvert::Core::Rewriter::Condition]
10
+ # @yield run when condition matches
12
11
  def initialize(instance, rules, &block)
13
12
  @instance = instance
14
13
  @rules = rules
@@ -19,5 +18,14 @@ module Synvert::Core
19
18
  def process
20
19
  @instance.instance_eval(&@block) if match?
21
20
  end
21
+
22
+ protected
23
+
24
+ # Check if condition matches
25
+ #
26
+ # @abstract
27
+ def match?
28
+ raise NotImplementedError, 'must be implemented by subclasses'
29
+ end
22
30
  end
23
31
  end
@@ -3,12 +3,16 @@
3
3
  module Synvert::Core
4
4
  # GemSpec checks and compares gem version.
5
5
  class Rewriter::GemSpec
6
+ # @!attribute [r] name
7
+ # @return [String] the name of gem_spec
8
+ # @!attribute [r] version
9
+ # @return [String] the version of gem_spec
6
10
  attr_reader :name, :version
7
11
 
8
- # Initialize a gem_spec.
12
+ # Initialize a GemSpec.
9
13
  #
10
14
  # @param name [String] gem name
11
- # @param version [String] gem version, e.g. '~> 2.0.0',
15
+ # @param version [String] gem version, e.g. '~> 2.0.0'
12
16
  def initialize(name, version)
13
17
  @name = name
14
18
  @version = version
@@ -17,9 +21,8 @@ module Synvert::Core
17
21
  # Check if the specified gem version in Gemfile.lock matches gem_spec comparator.
18
22
  #
19
23
  # @return [Boolean] true if matches, otherwise false.
20
- # @raise [Synvert::Core::GemfileLockNotFound] raise if Gemfile.lock does not exist.
21
24
  def match?
22
- gemfile_lock_path = File.join(Configuration.path, 'Gemfile.lock')
25
+ gemfile_lock_path = File.expand_path(File.join(Configuration.path, 'Gemfile.lock'))
23
26
 
24
27
  # if Gemfile.lock does not exist, just ignore this check
25
28
  return true unless File.exist?(gemfile_lock_path)
@@ -24,7 +24,7 @@ module Synvert::Core
24
24
 
25
25
  # Add arguments with parenthesis if necessary.
26
26
  #
27
- # @return [String] return `({{arguments}})` if node.arguments present, otherwise return nothing.
27
+ # @return [String] return (!{{arguments}}) if node.arguments present, otherwise return nothing.
28
28
  #
29
29
  # @example
30
30
  #
@@ -73,7 +73,7 @@ module Synvert::Core
73
73
  # Reject some keys from hash node.
74
74
  #
75
75
  # @param hash_node [Parser::AST::Node]
76
- # @param keys [Array] keys should be rejected from the hash.
76
+ # @param keys [Array<String, Symbol>] keys should be rejected from the hash.
77
77
  # @return [String] source of of the hash node after rejecting some keys.
78
78
  #
79
79
  # @example