synvert-core 0.64.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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)