synvert-core 0.64.0 → 1.0.1

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/.github/workflows/main.yml +1 -1
  3. data/.gitignore +4 -0
  4. data/CHANGELOG.md +5 -0
  5. data/Guardfile +11 -2
  6. data/README.md +2 -2
  7. data/Rakefile +15 -1
  8. data/lib/synvert/core/array_ext.rb +41 -0
  9. data/lib/synvert/core/engine/erb.rb +9 -8
  10. data/lib/synvert/core/node_ext.rb +47 -44
  11. data/lib/synvert/core/node_query/compiler/array.rb +34 -0
  12. data/lib/synvert/core/node_query/compiler/attribute.rb +51 -0
  13. data/lib/synvert/core/node_query/compiler/attribute_list.rb +24 -0
  14. data/lib/synvert/core/node_query/compiler/boolean.rb +23 -0
  15. data/lib/synvert/core/node_query/compiler/comparable.rb +79 -0
  16. data/lib/synvert/core/node_query/compiler/dynamic_attribute.rb +51 -0
  17. data/lib/synvert/core/node_query/compiler/expression.rb +88 -0
  18. data/lib/synvert/core/node_query/compiler/float.rb +23 -0
  19. data/lib/synvert/core/node_query/compiler/identifier.rb +41 -0
  20. data/lib/synvert/core/node_query/compiler/integer.rb +23 -0
  21. data/lib/synvert/core/node_query/compiler/invalid_operator_error.rb +7 -0
  22. data/lib/synvert/core/node_query/compiler/nil.rb +23 -0
  23. data/lib/synvert/core/node_query/compiler/parse_error.rb +7 -0
  24. data/lib/synvert/core/node_query/compiler/regexp.rb +37 -0
  25. data/lib/synvert/core/node_query/compiler/selector.rb +51 -0
  26. data/lib/synvert/core/node_query/compiler/string.rb +34 -0
  27. data/lib/synvert/core/node_query/compiler/symbol.rb +23 -0
  28. data/lib/synvert/core/node_query/compiler.rb +24 -0
  29. data/lib/synvert/core/node_query/lexer.rex +96 -0
  30. data/lib/synvert/core/node_query/lexer.rex.rb +293 -0
  31. data/lib/synvert/core/node_query/parser.racc.rb +518 -0
  32. data/lib/synvert/core/node_query/parser.y +84 -0
  33. data/lib/synvert/core/node_query.rb +36 -0
  34. data/lib/synvert/core/rewriter/action/delete_action.rb +4 -2
  35. data/lib/synvert/core/rewriter/action/replace_action.rb +4 -2
  36. data/lib/synvert/core/rewriter/action/replace_erb_stmt_with_expr_action.rb +2 -2
  37. data/lib/synvert/core/rewriter/action/wrap_action.rb +3 -2
  38. data/lib/synvert/core/rewriter/action.rb +2 -1
  39. data/lib/synvert/core/rewriter/helper.rb +5 -2
  40. data/lib/synvert/core/rewriter/instance.rb +25 -13
  41. data/lib/synvert/core/rewriter/scope/query_scope.rb +36 -0
  42. data/lib/synvert/core/rewriter/scope/within_scope.rb +1 -1
  43. data/lib/synvert/core/rewriter.rb +1 -0
  44. data/lib/synvert/core/version.rb +1 -1
  45. data/lib/synvert/core.rb +22 -5
  46. data/spec/synvert/core/engine/erb_spec.rb +2 -2
  47. data/spec/synvert/core/node_ext_spec.rb +36 -5
  48. data/spec/synvert/core/node_query/lexer_spec.rb +512 -0
  49. data/spec/synvert/core/node_query/parser_spec.rb +270 -0
  50. data/spec/synvert/core/rewriter/condition/if_only_exist_condition_spec.rb +1 -6
  51. data/spec/synvert/core/rewriter/helper_spec.rb +4 -1
  52. data/spec/synvert/core/rewriter/instance_spec.rb +24 -3
  53. data/spec/synvert/core/rewriter/scope/query_scope_spec.rb +74 -0
  54. data/spec/synvert/core/rewriter/scope/within_scope_spec.rb +12 -9
  55. data/spec/synvert/core/rewriter_spec.rb +4 -2
  56. data/synvert-core-ruby.gemspec +5 -1
  57. metadata +75 -2
@@ -34,7 +34,8 @@ module Synvert::Core
34
34
  # @return [String] rewritten code.
35
35
  def rewritten_code
36
36
  if rewritten_source.split("\n").length > 1
37
- "\n\n" + rewritten_source.split("\n").map { |line| indent(@node) + line }.join("\n")
37
+ "\n\n" + rewritten_source.split("\n").map { |line| indent(@node) + line }
38
+ .join("\n")
38
39
  else
39
40
  "\n" + indent(@node) + rewritten_source
40
41
  end
@@ -65,7 +65,9 @@ module Synvert::Core
65
65
  #
66
66
  # strip_brackets("(1..100)") #=> "1..100"
67
67
  def strip_brackets(code)
68
- code.sub(/^\((.*)\)$/) { Regexp.last_match(1) }.sub(/^\[(.*)\]$/) { Regexp.last_match(1) }.sub(/^{(.*)}$/) {
68
+ code.sub(/^\((.*)\)$/) { Regexp.last_match(1) }
69
+ .sub(/^\[(.*)\]$/) { Regexp.last_match(1) }
70
+ .sub(/^{(.*)}$/) {
69
71
  Regexp.last_match(1).strip
70
72
  }
71
73
  end
@@ -81,7 +83,8 @@ module Synvert::Core
81
83
  # hash_node = Parser::CurrentRuby.parse("{ key1: 'value1', key2: 'value2' }")
82
84
  # reject_keys_from_hash(hash_node, :key1) => "key2: 'value2'"
83
85
  def reject_keys_from_hash(hash_node, *keys)
84
- hash_node.children.reject { |pair_node| keys.include?(pair_node.key.to_value) }.map(&:to_source).join(', ')
86
+ hash_node.children.reject { |pair_node| keys.include?(pair_node.key.to_value) }
87
+ .map(&:to_source).join(', ')
85
88
  end
86
89
  end
87
90
  end
@@ -7,6 +7,18 @@ module Synvert::Core
7
7
  # One instance can contain one or many {Synvert::Core::Rewriter::Scope} and {Synvert::Rewriter::Condition}.
8
8
  class Rewriter::Instance
9
9
  include Rewriter::Helper
10
+ # Initialize an Instance.
11
+ #
12
+ # @param rewriter [Synvert::Core::Rewriter]
13
+ # @param file_patterns [Array<String>] pattern list to find files, e.g. ['spec/**/*_spec.rb']
14
+ # @yield block code to find nodes, match conditions and rewrite code.
15
+ def initialize(rewriter, file_patterns, &block)
16
+ @rewriter = rewriter
17
+ @actions = []
18
+ @file_patterns = file_patterns
19
+ @block = block
20
+ rewriter.helpers.each { |helper| singleton_class.send(:define_method, helper[:name], &helper[:block]) }
21
+ end
10
22
 
11
23
  class << self
12
24
  # Get file source.
@@ -69,19 +81,6 @@ module Synvert::Core
69
81
  self.class.file_source(current_file)
70
82
  end
71
83
 
72
- # Initialize an Instance.
73
- #
74
- # @param rewriter [Synvert::Core::Rewriter]
75
- # @param file_patterns [Array<String>] pattern list to find files, e.g. ['spec/**/*_spec.rb']
76
- # @yield block code to find nodes, match conditions and rewrite code.
77
- def initialize(rewriter, file_patterns, &block)
78
- @rewriter = rewriter
79
- @actions = []
80
- @file_patterns = file_patterns
81
- @block = block
82
- rewriter.helpers.each { |helper| singleton_class.send(:define_method, helper[:name], &helper[:block]) }
83
- end
84
-
85
84
  # Process the instance.
86
85
  # It finds specified files, for each file, it executes the block code, rewrites the original code,
87
86
  # then write the code back to the original file.
@@ -127,6 +126,19 @@ module Synvert::Core
127
126
  # DSL #
128
127
  #######
129
128
 
129
+ # Parse +find_node+ dsl, it creates {Synvert::Core::Rewriter::QueryScope} to recursively find matching ast nodes,
130
+ # then continue operating on each matching ast node.
131
+ # @example
132
+ # # matches FactoryBot.create(:user)
133
+ # find_node '.send[receiver=FactoryBot][message=create][arguments.size=1]' do
134
+ # end
135
+ # @param query_string [String] query string to find matching ast nodes.
136
+ # @yield run on the matching nodes.
137
+ # @raise [Synvert::Core::NodeQuery::Compiler::ParseError] if query string is invalid.
138
+ def find_node(query_string, &block)
139
+ Rewriter::QueryScope.new(self, query_string, &block).process
140
+ end
141
+
130
142
  # Parse +within_node+ dsl, it creates a {Synvert::Core::Rewriter::WithinScope} to recursively find matching ast nodes,
131
143
  # then continue operating on each matching ast node.
132
144
  # @example
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synvert::Core
4
+ # QueryScope finds out nodes by using node query language, then changes its scope to matching node.
5
+ class Rewriter::QueryScope < Rewriter::Scope
6
+ # Initialize a QueryScope.
7
+ #
8
+ # @param instance [Synvert::Core::Rewriter::Instance]
9
+ # @param query_string [String]
10
+ # @yield run on all matching nodes
11
+ def initialize(instance, query_string, &block)
12
+ super(instance, &block)
13
+ @query_string = query_string
14
+ end
15
+
16
+ # Find out the matching nodes.
17
+ #
18
+ # It checks the current node and iterates all child nodes,
19
+ # then run the block code on each matching node.
20
+ # @raise [Synvert::Core::NodeQuery::Compiler::ParseError] if the query string is invalid.
21
+ def process
22
+ current_node = @instance.current_node
23
+ return unless current_node
24
+
25
+ @instance.process_with_node(current_node) do
26
+ NodeQuery::Parser.new.parse(@query_string).query_nodes(current_node).each do |node|
27
+ @instance.process_with_node(node) do
28
+ @instance.instance_eval(&@block)
29
+ end
30
+ end
31
+ end
32
+ rescue NodeQuery::Lexer::ScanError, Racc::ParseError => e
33
+ raise NodeQuery::Compiler::ParseError, "Invalid query string: #{@query_string}"
34
+ end
35
+ end
36
+ end
@@ -116,4 +116,4 @@ module Synvert::Core
116
116
  matching_nodes
117
117
  end
118
118
  end
119
- end
119
+ end
@@ -25,6 +25,7 @@ module Synvert::Core
25
25
  autoload :Instance, 'synvert/core/rewriter/instance'
26
26
 
27
27
  autoload :Scope, 'synvert/core/rewriter/scope'
28
+ autoload :QueryScope, 'synvert/core/rewriter/scope/query_scope'
28
29
  autoload :WithinScope, 'synvert/core/rewriter/scope/within_scope'
29
30
  autoload :GotoScope, 'synvert/core/rewriter/scope/goto_scope'
30
31
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Synvert
4
4
  module Core
5
- VERSION = '0.64.0'
5
+ VERSION = '1.0.1'
6
6
  end
7
7
  end
data/lib/synvert/core.rb CHANGED
@@ -10,6 +10,7 @@ require 'active_support/core_ext/object'
10
10
  require 'active_support/core_ext/array'
11
11
  require 'erubis'
12
12
  require 'set'
13
+ require 'synvert/core/array_ext'
13
14
  require 'synvert/core/node_ext'
14
15
 
15
16
  module Synvert
@@ -19,6 +20,7 @@ module Synvert
19
20
  autoload :Engine, 'synvert/core/engine'
20
21
  autoload :RewriterNotFound, 'synvert/core/exceptions'
21
22
  autoload :MethodNotSupported, 'synvert/core/exceptions'
23
+ autoload :NodeQuery, 'synvert/core/node_query'
22
24
  end
23
25
  end
24
26
 
@@ -37,17 +39,32 @@ module Synvert
37
39
  RAILS_MAILER_FILES = %w[app/mailers/**/*.rb engines/*/app/mailers/**/*.rb]
38
40
  RAILS_MIGRATION_FILES = %w[db/migrate/**/*.rb engines/*/db/migrate/**/*.rb]
39
41
  RAILS_MODEL_FILES = %w[app/models/**/*.rb engines/*/app/models/**/*.rb]
40
- RAILS_ROUTE_FILES = %w[config/routes.rb config/routes/**/*.rb engines/*/config/routes.rb engines/*/config/routes/**/*.rb]
42
+ RAILS_ROUTE_FILES = %w[
43
+ config/routes.rb
44
+ config/routes/**/*.rb
45
+ engines/*/config/routes.rb
46
+ engines/*/config/routes/**/*.rb
47
+ ]
41
48
  RAILS_VIEW_FILES = %w[app/views/**/*.html.{erb,haml,slim}]
42
49
 
43
50
  RAILS_CONTROLLER_TEST_FILES = %w[
44
- test/functional/**/*.rb test/controllers/**/*.rb engines/*/test/functional/**/*.rb engines/*/test/controllers/**/*.rb
45
- spec/functional/**/*.rb spec/controllers/**/*.rb engines/*/spec/functional/**/*.rb engines/*/spec/controllers/**/*.rb
51
+ test/functional/**/*.rb
52
+ test/controllers/**/*.rb
53
+ engines/*/test/functional/**/*.rb
54
+ engines/*/test/controllers/**/*.rb
55
+ spec/functional/**/*.rb
56
+ spec/controllers/**/*.rb
57
+ engines/*/spec/functional/**/*.rb
58
+ engines/*/spec/controllers/**/*.rb
46
59
  ]
47
60
  RAILS_INTEGRATION_TEST_FILES = %w[test/integration/**/*.rb spec/integration/**/*.rb]
48
61
  RAILS_MODEL_TEST_FILES = %w[
49
- test/unit/**/*.rb engines/*/test/unit/**/*.rb test/models/**/*.rb engines/*/test/models/**/*.rb
50
- spec/models/**/*.rb engines/*/spec/models/**/*.rb
62
+ test/unit/**/*.rb
63
+ engines/*/test/unit/**/*.rb
64
+ test/models/**/*.rb
65
+ engines/*/test/models/**/*.rb
66
+ spec/models/**/*.rb
67
+ engines/*/spec/models/**/*.rb
51
68
  ]
52
69
 
53
70
  RAILS_FACTORY_FILES = %w[test/factories/**/*.rb spec/factories/**/*.rb]
@@ -13,13 +13,13 @@ module Synvert::Core
13
13
  }
14
14
  </style>
15
15
  <%end%>
16
-
16
+
17
17
  <%
18
18
  foo = 'bar'
19
19
  post = Post.find(:first)
20
20
  bar = 'foo'
21
21
  %>
22
-
22
+
23
23
  <% if User.current &&
24
24
  User.current.admin %>
25
25
  <%= rounded_content("page") do %>
@@ -3,6 +3,25 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Parser::AST::Node do
6
+ describe '#parent' do
7
+ it 'gets parent node' do
8
+ node = parse('FactoryBot.create(:user)')
9
+ child_node = node.children.first
10
+ expect(child_node.parent).to eq node
11
+ end
12
+ end
13
+
14
+ describe '#siblings' do
15
+ it 'gets sibling nodes' do
16
+ node = parse(<<~EOS)
17
+ def foobar; end
18
+ def foo; end
19
+ def bar; end
20
+ EOS
21
+ expect(node.children.first.siblings).to eq [node.children.second, node.children.last]
22
+ end
23
+ end
24
+
6
25
  describe '#name' do
7
26
  it 'gets for class node' do
8
27
  node = parse('class Synvert; end')
@@ -129,17 +148,17 @@ describe Parser::AST::Node do
129
148
  describe '#arguments' do
130
149
  it 'gets for def node' do
131
150
  node = parse('def test(foo, bar); foo + bar; end')
132
- expect(node.arguments.type).to eq :args
151
+ expect(node.arguments.map(&:type)).to eq [:arg, :arg]
133
152
  end
134
153
 
135
154
  it 'gets for defs node' do
136
155
  node = parse('def self.test(foo, bar); foo + bar; end')
137
- expect(node.arguments.type).to eq :args
156
+ expect(node.arguments.map(&:type)).to eq [:arg, :arg]
138
157
  end
139
158
 
140
159
  it 'gets for block node' do
141
160
  node = parse('RSpec.configure do |config|; end')
142
- expect(node.arguments.type).to eq :args
161
+ expect(node.arguments.map(&:type)).to eq [:arg]
143
162
  end
144
163
 
145
164
  it 'gets for send node' do
@@ -388,6 +407,11 @@ describe Parser::AST::Node do
388
407
  expect(node.to_value).to be_falsey
389
408
  end
390
409
 
410
+ it 'gets for nil' do
411
+ node = parse('nil')
412
+ expect(node.to_value).to be_nil
413
+ end
414
+
391
415
  it 'gets for irange' do
392
416
  node = parse('(1..10)')
393
417
  expect(node.to_value).to eq(1..10)
@@ -534,7 +558,14 @@ describe Parser::AST::Node do
534
558
  it 'matches arguments with nested hash' do
535
559
  source = '{ user_id: user.id }'
536
560
  node = parse(source)
537
- expect(node).to be_match(type: 'hash', user_id_value: { type: 'send', receiver: { type: 'send', message: 'user' }, message: 'id' })
561
+ expect(node).to be_match(
562
+ type: 'hash',
563
+ user_id_value: {
564
+ type: 'send',
565
+ receiver: { type: 'send', message: 'user' },
566
+ message: 'id'
567
+ }
568
+ )
538
569
  end
539
570
 
540
571
  it 'matches arguments contain' do
@@ -862,7 +893,7 @@ describe Parser::AST::Node do
862
893
  expect(range.to_range).to eq(16...27)
863
894
  end
864
895
 
865
- it "checks array' value" do
896
+ it "checks array's value" do
866
897
  node = parse('factory :admin, class: User do; end')
867
898
  range = node.child_node_range('caller.arguments.second.class_value')
868
899
  expect(range.to_range).to eq(23...27)