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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +1 -1
- data/.gitignore +4 -0
- data/CHANGELOG.md +5 -0
- data/Guardfile +11 -2
- data/README.md +2 -2
- data/Rakefile +15 -1
- data/lib/synvert/core/array_ext.rb +41 -0
- data/lib/synvert/core/engine/erb.rb +9 -8
- data/lib/synvert/core/node_ext.rb +47 -44
- data/lib/synvert/core/node_query/compiler/array.rb +34 -0
- data/lib/synvert/core/node_query/compiler/attribute.rb +51 -0
- data/lib/synvert/core/node_query/compiler/attribute_list.rb +24 -0
- data/lib/synvert/core/node_query/compiler/boolean.rb +23 -0
- data/lib/synvert/core/node_query/compiler/comparable.rb +79 -0
- data/lib/synvert/core/node_query/compiler/dynamic_attribute.rb +51 -0
- data/lib/synvert/core/node_query/compiler/expression.rb +88 -0
- data/lib/synvert/core/node_query/compiler/float.rb +23 -0
- data/lib/synvert/core/node_query/compiler/identifier.rb +41 -0
- data/lib/synvert/core/node_query/compiler/integer.rb +23 -0
- data/lib/synvert/core/node_query/compiler/invalid_operator_error.rb +7 -0
- data/lib/synvert/core/node_query/compiler/nil.rb +23 -0
- data/lib/synvert/core/node_query/compiler/parse_error.rb +7 -0
- data/lib/synvert/core/node_query/compiler/regexp.rb +37 -0
- data/lib/synvert/core/node_query/compiler/selector.rb +51 -0
- data/lib/synvert/core/node_query/compiler/string.rb +34 -0
- data/lib/synvert/core/node_query/compiler/symbol.rb +23 -0
- data/lib/synvert/core/node_query/compiler.rb +24 -0
- data/lib/synvert/core/node_query/lexer.rex +96 -0
- data/lib/synvert/core/node_query/lexer.rex.rb +293 -0
- data/lib/synvert/core/node_query/parser.racc.rb +518 -0
- data/lib/synvert/core/node_query/parser.y +84 -0
- data/lib/synvert/core/node_query.rb +36 -0
- data/lib/synvert/core/rewriter/action/delete_action.rb +4 -2
- data/lib/synvert/core/rewriter/action/replace_action.rb +4 -2
- data/lib/synvert/core/rewriter/action/replace_erb_stmt_with_expr_action.rb +2 -2
- data/lib/synvert/core/rewriter/action/wrap_action.rb +3 -2
- data/lib/synvert/core/rewriter/action.rb +2 -1
- data/lib/synvert/core/rewriter/helper.rb +5 -2
- data/lib/synvert/core/rewriter/instance.rb +25 -13
- data/lib/synvert/core/rewriter/scope/query_scope.rb +36 -0
- data/lib/synvert/core/rewriter/scope/within_scope.rb +1 -1
- data/lib/synvert/core/rewriter.rb +1 -0
- data/lib/synvert/core/version.rb +1 -1
- data/lib/synvert/core.rb +22 -5
- data/spec/synvert/core/engine/erb_spec.rb +2 -2
- data/spec/synvert/core/node_ext_spec.rb +36 -5
- data/spec/synvert/core/node_query/lexer_spec.rb +512 -0
- data/spec/synvert/core/node_query/parser_spec.rb +270 -0
- data/spec/synvert/core/rewriter/condition/if_only_exist_condition_spec.rb +1 -6
- data/spec/synvert/core/rewriter/helper_spec.rb +4 -1
- data/spec/synvert/core/rewriter/instance_spec.rb +24 -3
- data/spec/synvert/core/rewriter/scope/query_scope_spec.rb +74 -0
- data/spec/synvert/core/rewriter/scope/within_scope_spec.rb +12 -9
- data/spec/synvert/core/rewriter_spec.rb +4 -2
- data/synvert-core-ruby.gemspec +5 -1
- 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 }
|
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) }
|
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) }
|
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
|
@@ -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
|
|
data/lib/synvert/core/version.rb
CHANGED
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[
|
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
|
45
|
-
|
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
|
50
|
-
|
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]
|
@@ -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 :
|
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 :
|
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 :
|
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(
|
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)
|