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.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f889cb977b7bf6e847bdeeac011ee0d23210ae8db7a0652e8c00d5d98c72a701
|
4
|
+
data.tar.gz: 46d220401cc8c9c0422428c2915862148b4ad8dc236f637ed52dbc45207de742
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 926df3dd8aa3689938e90c530fb432e0703cff2f89f35f46950b3630eb98b8da6de0e0d5eb75c5c24262f3712400d4570ac05abd5ebbbe6c6954d6c997048432
|
7
|
+
data.tar.gz: 321fb626763ca31e6156c462bb6846ce73d00639a620b0b7048b024fbe50d14f63f6d3209e3a534b0475c29c29e74dcf0a7c204f14ad0927244bd5cbb157b3d6
|
data/.github/workflows/main.yml
CHANGED
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
data/Guardfile
CHANGED
@@ -2,6 +2,15 @@
|
|
2
2
|
|
3
3
|
guard :rspec, cmd: 'bundle exec rspec' do
|
4
4
|
watch(%r{^spec/.+_spec\.rb$})
|
5
|
-
watch(%r{^lib/(.+)\.rb$})
|
6
|
-
watch('
|
5
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
6
|
+
watch('lib/synvert/core/node_query/lexer.rex.rb') { 'spec/synvert/core/node_query/lexer_spec.rb' }
|
7
|
+
watch('lib/synvert/core/node_query/compiler.rb') { 'spec/synvert/core/node_query/parser_spec.rb' }
|
8
|
+
watch(%r{^lib/synvert/core/node_query/compiler/.*\.rb$}) { 'spec/synvert/core/node_query/parser_spec.rb' }
|
9
|
+
watch('lib/synvert/core/node_query/parser.racc.rb') { 'spec/synvert/core/node_query/parser_spec.rb' }
|
10
|
+
watch('spec/spec_helper.rb') { "spec" }
|
11
|
+
end
|
12
|
+
|
13
|
+
guard :rake, task: 'generate' do
|
14
|
+
watch('lib/synvert/core/node_query/lexer.rex')
|
15
|
+
watch('lib/synvert/core/node_query/parser.y')
|
7
16
|
end
|
data/README.md
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
<img src="https://synvert.xinminlabs.com/img/logo_96.png" alt="logo" width="32" height="32" />
|
4
4
|
|
5
|
+
[](https://awesomecode.io/repos/xinminlabs/synvert-core-ruby)
|
5
6
|

|
6
|
-
[](http://badge.fury.io/rb/synvert-core)
|
7
7
|
|
8
8
|
Synvert core provides a set of DSLs to rewrite ruby code. e.g.
|
9
9
|
|
@@ -77,4 +77,4 @@ Actions:
|
|
77
77
|
* [wrap](./Synvert/Core/Rewriter/Instance.html#wrap-instance_method) - wrap the current node with code
|
78
78
|
* [replace_with](./Synvert/Core/Rewriter/Instance.html#replace_with-instance_method) - replace the whole code of current node
|
79
79
|
* [warn](./Synvert/Core/Rewriter/Instance.html#warn-instance_method) - warn message
|
80
|
-
* [replace_erb_stmt_with_expr](./Synvert/Core/Rewriter/Instance.html#replace_erb_stmt_with_expr-instance_method) - replace erb stmt code to expr code
|
80
|
+
* [replace_erb_stmt_with_expr](./Synvert/Core/Rewriter/Instance.html#replace_erb_stmt_with_expr-instance_method) - replace erb stmt code to expr code
|
data/Rakefile
CHANGED
@@ -2,7 +2,21 @@
|
|
2
2
|
|
3
3
|
require "bundler/gem_tasks"
|
4
4
|
require 'rspec/core/rake_task'
|
5
|
-
|
6
5
|
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
require 'oedipus_lex'
|
7
|
+
Rake.application.rake_require "oedipus_lex"
|
8
|
+
|
9
|
+
file "lib/synvert/core/node_query/lexer.rex.rb" => "lib/synvert/core/node_query/lexer.rex"
|
10
|
+
file "lib/synvert/core/node_query/parser.racc.rb" => "lib/synvert/core/node_query/parser.y"
|
11
|
+
|
12
|
+
task :lexer => "lib/synvert/core/node_query/lexer.rex.rb"
|
13
|
+
task :parser => "lib/synvert/core/node_query/parser.racc.rb"
|
14
|
+
task :generate => [:lexer, :parser]
|
15
|
+
|
16
|
+
rule '.racc.rb' => '.y' do |t|
|
17
|
+
cmd = "bundle exec racc -l -v -o #{t.name} #{t.source}"
|
18
|
+
sh cmd
|
19
|
+
end
|
7
20
|
|
8
21
|
task :default => :spec
|
22
|
+
task :spec => :generate
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Extend Array.
|
4
|
+
class Array
|
5
|
+
# Get child node by the name.
|
6
|
+
#
|
7
|
+
# @param child_name [String] name of child node.
|
8
|
+
# @return [Parser::AST::Node] the child node.
|
9
|
+
def child_node_by_name(child_name)
|
10
|
+
direct_child_name, nested_child_name = child_name.split('.', 2)
|
11
|
+
child_direct_child_node = direct_child_name =~ /\A\d+\z/ ? self[direct_child_name.to_i - 1] : self.send(direct_child_name)
|
12
|
+
return child_direct_child_node.child_node_by_name(nested_child_name) if nested_child_name
|
13
|
+
return child_direct_child_node if child_direct_child_node
|
14
|
+
|
15
|
+
raise Synvert::Core::MethodNotSupported,
|
16
|
+
"child_node_by_name is not handled for #{map(&:debug_info).join("\n")}, child_name: #{child_name}"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get the source range of child node.
|
20
|
+
#
|
21
|
+
# @param child_name [String] name of child node.
|
22
|
+
# @return [Parser::Source::Range] source range of child node.
|
23
|
+
def child_node_range(child_name)
|
24
|
+
direct_child_name, nested_child_name = child_name.split('.', 2)
|
25
|
+
child_direct_child_node = direct_child_name =~ /\A\d+\z/ ? self[direct_child_name.to_i - 1] : self.send(direct_child_name)
|
26
|
+
if nested_child_name
|
27
|
+
return child_direct_child_node.child_node_range(nested_child_name)
|
28
|
+
elsif child_direct_child_node
|
29
|
+
return (
|
30
|
+
Parser::Source::Range.new(
|
31
|
+
'(string)',
|
32
|
+
child_direct_child_node.loc.expression.begin_pos,
|
33
|
+
child_direct_child_node.loc.expression.end_pos
|
34
|
+
)
|
35
|
+
)
|
36
|
+
else
|
37
|
+
raise Synvert::Core::MethodNotSupported,
|
38
|
+
"child_node_range is not handled for #{map(&:debug_info).join("\n")}, child_name: #{child_name}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -43,11 +43,13 @@ module Synvert::Core
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def decode_html_output(source)
|
46
|
-
source.gsub(/@output_buffer.safe_append='(.+?)'.freeze;/m) { reverse_escape_text(Regexp.last_match(1)) }
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
46
|
+
source.gsub(/@output_buffer.safe_append='(.+?)'.freeze;/m) { reverse_escape_text(Regexp.last_match(1)) }
|
47
|
+
.gsub(
|
48
|
+
/@output_buffer.safe_append=\((.+?)\);#{ERUBY_EXPR_SPLITTER}/mo
|
49
|
+
) { reverse_escape_text(Regexp.last_match(1)) }
|
50
|
+
.gsub(
|
51
|
+
/@output_buffer.safe_append=(.+?)\s+(do|\{)(\s*\|[^|]*\|)?\s*#{ERUBY_EXPR_SPLITTER}/mo
|
52
|
+
) { reverse_escape_text(Regexp.last_match(1)) }
|
51
53
|
end
|
52
54
|
|
53
55
|
def remove_erubis_buf(source)
|
@@ -64,6 +66,7 @@ module Synvert::Core
|
|
64
66
|
|
65
67
|
# borrowed from rails
|
66
68
|
class Erubis < ::Erubis::Eruby
|
69
|
+
BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
67
70
|
def add_preamble(src)
|
68
71
|
@newline_pending = 0
|
69
72
|
src << '@output_buffer = output_buffer || ActionView::OutputBuffer.new;'
|
@@ -76,7 +79,7 @@ module Synvert::Core
|
|
76
79
|
@newline_pending += 1
|
77
80
|
else
|
78
81
|
src << "@output_buffer.safe_append='"
|
79
|
-
src << "\n" * @newline_pending if @newline_pending > 0
|
82
|
+
src << ("\n" * @newline_pending) if @newline_pending > 0
|
80
83
|
src << escape_text(text)
|
81
84
|
src << "'.freeze;"
|
82
85
|
|
@@ -95,8 +98,6 @@ module Synvert::Core
|
|
95
98
|
end
|
96
99
|
end
|
97
100
|
|
98
|
-
BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
99
|
-
|
100
101
|
def add_expr_literal(src, code)
|
101
102
|
flush_newline_if_pending(src)
|
102
103
|
if BLOCK_EXPR.match?(code)
|
@@ -27,8 +27,41 @@ module Parser::AST
|
|
27
27
|
# +type: 'send', receiver: { type: 'send', receiver: { type: 'send', message: 'config' }, message: 'active_record' }, message: 'identity_map='+
|
28
28
|
#
|
29
29
|
# Source Code to Ast Node
|
30
|
-
# {https://synvert-playground.xinminlabs.com}
|
30
|
+
# {https://synvert-playground.xinminlabs.com?language=ruby}
|
31
31
|
class Node
|
32
|
+
# Initialize a Node.
|
33
|
+
#
|
34
|
+
# It extends Parser::AST::Node and set parent for its child nodes.
|
35
|
+
def initialize(type, children = [], properties = {})
|
36
|
+
@mutable_attributes = {}
|
37
|
+
super
|
38
|
+
# children could be nil for s(:array)
|
39
|
+
Array(children).each do |child_node|
|
40
|
+
if child_node.is_a?(Parser::AST::Node)
|
41
|
+
child_node.parent = self
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get the parent node.
|
47
|
+
# @return [Parser::AST::Node] parent node.
|
48
|
+
def parent
|
49
|
+
@mutable_attributes[:parent]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Set the parent node.
|
53
|
+
# @param node [Parser::AST::Node] parent node.
|
54
|
+
def parent=(node)
|
55
|
+
@mutable_attributes[:parent] = node
|
56
|
+
end
|
57
|
+
|
58
|
+
# Get the sibling nodes.
|
59
|
+
# @return [Array<Parser::AST::Node>] sibling nodes.
|
60
|
+
def siblings
|
61
|
+
index = parent.children.index(self)
|
62
|
+
parent.children[index + 1..]
|
63
|
+
end
|
64
|
+
|
32
65
|
# Get the name of node.
|
33
66
|
# It supports :arg, :blockarg, :class, :const, :cvar, :def, :defs, :ivar,
|
34
67
|
# :lvar, :mlhs, :module and :restarg nodes.
|
@@ -123,9 +156,9 @@ module Parser::AST
|
|
123
156
|
def arguments
|
124
157
|
case type
|
125
158
|
when :def, :block
|
126
|
-
children[1]
|
159
|
+
children[1].children
|
127
160
|
when :defs
|
128
|
-
children[2]
|
161
|
+
children[2].children
|
129
162
|
when :send, :csend
|
130
163
|
children[2..-1]
|
131
164
|
when :defined?
|
@@ -311,7 +344,7 @@ module Parser::AST
|
|
311
344
|
# Return the exact value of node.
|
312
345
|
# It supports :array, :begin, :erange, :false, :float, :irange, :int, :str, :sym and :true nodes.
|
313
346
|
# @example
|
314
|
-
# node # s(:array, s(:str, "str"), s(:sym, :str))
|
347
|
+
# node # s(:array, s(:str, "str"), s(:sym, :str))
|
315
348
|
# node.to_value # ['str', :str]
|
316
349
|
# @return [Object] exact value.
|
317
350
|
# @raise [Synvert::Core::MethodNotSupported] if calls on other node.
|
@@ -323,6 +356,8 @@ module Parser::AST
|
|
323
356
|
true
|
324
357
|
when :false
|
325
358
|
false
|
359
|
+
when :nil
|
360
|
+
nil
|
326
361
|
when :array
|
327
362
|
children.map(&:to_value)
|
328
363
|
when :irange
|
@@ -425,27 +460,12 @@ module Parser::AST
|
|
425
460
|
if respond_to?(direct_child_name)
|
426
461
|
child_node = send(direct_child_name)
|
427
462
|
|
428
|
-
if nested_child_name
|
429
|
-
if child_node.is_a?(Array)
|
430
|
-
child_direct_child_name, child_nested_child_name = nested_child_name.split('.', 2)
|
431
|
-
child_direct_child_node = child_direct_child_name =~ /\A\d+\z/ ? child_node[child_direct_child_name.to_i - 1] : child_node.send(child_direct_child_name)
|
432
|
-
return child_direct_child_node.child_node_by_name(child_nested_child_name) if child_nested_child_name
|
433
|
-
return child_direct_child_node if child_direct_child_node
|
434
|
-
|
435
|
-
raise Synvert::Core::MethodNotSupported,
|
436
|
-
"child_node_by_name is not handled for #{debug_info}, child_name: #{child_name}"
|
437
|
-
end
|
438
|
-
|
439
|
-
return child_node.child_node_by_name(nested_child_name)
|
440
|
-
end
|
463
|
+
return child_node.child_node_by_name(nested_child_name) if nested_child_name
|
441
464
|
|
442
465
|
return nil if child_node.nil?
|
443
466
|
|
444
467
|
return child_node if child_node.is_a?(Parser::AST::Node)
|
445
468
|
|
446
|
-
# arguments
|
447
|
-
return nil if child_node.empty?
|
448
|
-
|
449
469
|
return child_node
|
450
470
|
end
|
451
471
|
|
@@ -460,7 +480,11 @@ module Parser::AST
|
|
460
480
|
def child_node_range(child_name)
|
461
481
|
case [type, child_name.to_sym]
|
462
482
|
when %i[block pipes], %i[def parentheses], %i[defs parentheses]
|
463
|
-
Parser::Source::Range.new(
|
483
|
+
Parser::Source::Range.new(
|
484
|
+
'(string)',
|
485
|
+
arguments.first.loc.expression.begin_pos - 1,
|
486
|
+
arguments.last.loc.expression.end_pos + 1
|
487
|
+
)
|
464
488
|
when %i[block arguments], %i[def arguments], %i[defs arguments]
|
465
489
|
Parser::Source::Range.new(
|
466
490
|
'(string)',
|
@@ -490,28 +514,7 @@ module Parser::AST
|
|
490
514
|
if respond_to?(direct_child_name)
|
491
515
|
child_node = send(direct_child_name)
|
492
516
|
|
493
|
-
if nested_child_name
|
494
|
-
if child_node.is_a?(Array)
|
495
|
-
child_direct_child_name, child_nested_child_name = nested_child_name.split('.', 2)
|
496
|
-
child_direct_child_node = child_direct_child_name =~ /\A\d+\z/ ? child_node[child_direct_child_name.to_i - 1] : child_node.send(child_direct_child_name)
|
497
|
-
if child_nested_child_name
|
498
|
-
return child_direct_child_node.child_node_range(child_nested_child_name)
|
499
|
-
elsif child_direct_child_node
|
500
|
-
return (
|
501
|
-
Parser::Source::Range.new(
|
502
|
-
'(string)',
|
503
|
-
child_direct_child_node.loc.expression.begin_pos,
|
504
|
-
child_direct_child_node.loc.expression.end_pos
|
505
|
-
)
|
506
|
-
)
|
507
|
-
else
|
508
|
-
raise Synvert::Core::MethodNotSupported,
|
509
|
-
"child_node_range is not handled for #{debug_info}, child_name: #{child_name}"
|
510
|
-
end
|
511
|
-
end
|
512
|
-
|
513
|
-
return child_node.child_node_range(nested_child_name)
|
514
|
-
end
|
517
|
+
return child_node.child_node_range(nested_child_name) if nested_child_name
|
515
518
|
|
516
519
|
return nil if child_node.nil?
|
517
520
|
|
@@ -703,7 +706,7 @@ module Parser::AST
|
|
703
706
|
if type == :block && caller.type == :send && caller.receiver.nil? && caller.message == :lambda
|
704
707
|
new_source = to_source
|
705
708
|
if arguments.size > 1
|
706
|
-
new_source = new_source[0...arguments.loc.
|
709
|
+
new_source = new_source[0...arguments.first.loc.expression.begin_pos - 2] + new_source[arguments.last.loc.expression.end_pos + 1..-1]
|
707
710
|
new_source = new_source.sub('lambda', "->(#{arguments.map(&:to_source).join(', ')})")
|
708
711
|
else
|
709
712
|
new_source = new_source.sub('lambda', '->')
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Synvert::Core::NodeQuery::Compiler
|
4
|
+
# Array represents a ruby array value.
|
5
|
+
class Array
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
# Initialize an Array.
|
9
|
+
# @param value the first value of the array
|
10
|
+
# @param rest the rest value of the array
|
11
|
+
def initialize(value: nil, rest: nil)
|
12
|
+
@value = value
|
13
|
+
@rest = rest
|
14
|
+
end
|
15
|
+
|
16
|
+
# Get the expected value.
|
17
|
+
# @return [Array]
|
18
|
+
def expected_value
|
19
|
+
expected = []
|
20
|
+
expected.push(@value) if @value
|
21
|
+
expected += @rest.expected_value if @rest
|
22
|
+
expected
|
23
|
+
end
|
24
|
+
|
25
|
+
# Get valid operators.
|
26
|
+
def valid_operators
|
27
|
+
ARRAY_VALID_OPERATORS
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
[@value, @rest].compact.join(', ')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Synvert::Core::NodeQuery::Compiler
|
4
|
+
# Attribute is a pair of key, value and operator,
|
5
|
+
class Attribute
|
6
|
+
# Initialize a Attribute.
|
7
|
+
# @param key [String] the key
|
8
|
+
# @param value the value can be any class implement {Synvert::Core::NodeQuery::Compiler::Comparable}
|
9
|
+
# @param operator [Symbol] the operator
|
10
|
+
def initialize(key:, value:, operator: :==)
|
11
|
+
@key = key
|
12
|
+
@value = value
|
13
|
+
@operator = operator
|
14
|
+
end
|
15
|
+
|
16
|
+
# Check if the node matches the attribute.
|
17
|
+
# @param node [Parser::AST::Node] the node
|
18
|
+
# @return [Boolean]
|
19
|
+
def match?(node)
|
20
|
+
@value.base_node = node if @value.is_a?(DynamicAttribute)
|
21
|
+
node && @value.match?(node.child_node_by_name(@key), @operator)
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
case @operator
|
26
|
+
when :!=
|
27
|
+
"#{@key}!=#{@value}"
|
28
|
+
when :=~
|
29
|
+
"#{@key}=~#{@value}"
|
30
|
+
when :!~
|
31
|
+
"#{@key}!~#{@value}"
|
32
|
+
when :>
|
33
|
+
"#{@key}>#{@value}"
|
34
|
+
when :>=
|
35
|
+
"#{@key}>=#{@value}"
|
36
|
+
when :<
|
37
|
+
"#{@key}<#{@value}"
|
38
|
+
when :<=
|
39
|
+
"#{@key}<=#{@value}"
|
40
|
+
when :in
|
41
|
+
"#{@key} in (#{@value})"
|
42
|
+
when :not_in
|
43
|
+
"#{@key} not in (#{@value})"
|
44
|
+
when :includes
|
45
|
+
"#{@key} includes #{@value}"
|
46
|
+
else
|
47
|
+
"#{@key}=#{@value}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Synvert::Core::NodeQuery::Compiler
|
4
|
+
# AttributeList contains one or more {Synvert::Core::NodeQuery::Compiler::Attribute}.
|
5
|
+
class AttributeList
|
6
|
+
# Initialize a AttributeList.
|
7
|
+
# @param attribute [Synvert::Core::NodeQuery::Compiler::Attribute] the attribute
|
8
|
+
# @param rest [Synvert::Core::NodeQuery::Compiler::AttributeList] the rest attribute list
|
9
|
+
def initialize(attribute:, rest: nil)
|
10
|
+
@attribute = attribute
|
11
|
+
@rest = rest
|
12
|
+
end
|
13
|
+
|
14
|
+
# Check if the node matches the attribute list.
|
15
|
+
# @return [Boolean]
|
16
|
+
def match?(node)
|
17
|
+
@attribute.match?(node) && (!@rest || @rest.match?(node))
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"[#{@attribute}]#{@rest}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Synvert::Core::NodeQuery::Compiler
|
4
|
+
# Boolean represents a ruby boolean value.
|
5
|
+
class Boolean
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
# Initialize a Boolean.
|
9
|
+
# @param value [Boolean] the boolean value
|
10
|
+
def initialize(value:)
|
11
|
+
@value = value
|
12
|
+
end
|
13
|
+
|
14
|
+
# Get valid operators.
|
15
|
+
def valid_operators
|
16
|
+
SIMPLE_VALID_OPERATORS
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
@value.to_s
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Synvert::Core::NodeQuery::Compiler
|
4
|
+
# Compare acutal value with expected value.
|
5
|
+
module Comparable
|
6
|
+
SIMPLE_VALID_OPERATORS = [:==, :!=, :includes]
|
7
|
+
NUMBER_VALID_OPERATORS = [:==, :!=, :>, :>=, :<, :<=, :includes]
|
8
|
+
ARRAY_VALID_OPERATORS = [:==, :!=, :in, :not_in]
|
9
|
+
REGEXP_VALID_OPERATORS = [:=~, :!~]
|
10
|
+
|
11
|
+
# Check if the actual value matches the expected value.
|
12
|
+
#
|
13
|
+
# @param node [Parser::AST::Node] node to calculate actual value
|
14
|
+
# @param operator [Symbol] operator to compare with expected value, operator can be <code>:==</code>, <code>:!=</code>, <code>:></code>, <code>:>=</code>, <code>:<</code>, <code>:<=</code>, <code>:includes</code>, <code>:in</code>, <code>:not_in</code>, <code>:=~</code>, <code>!~</code>
|
15
|
+
# @return [Boolean] true if actual value matches the expected value
|
16
|
+
# @raise [Synvert::Core::NodeQuery::Compiler::InvalidOperatorError] if operator is invalid
|
17
|
+
def match?(node, operator)
|
18
|
+
raise InvalidOperatorError, "invalid operator #{operator}" unless valid_operator?(operator)
|
19
|
+
|
20
|
+
case operator
|
21
|
+
when :!=
|
22
|
+
if expected_value.is_a?(::Array)
|
23
|
+
actual = actual_value(node)
|
24
|
+
!actual.is_a?(::Array) || actual.size != expected_value.size ||
|
25
|
+
actual.zip(expected_value).any? { |actual_node, expected_node| expected_node.match?(actual_node, :!=) }
|
26
|
+
else
|
27
|
+
actual_value(node) != expected_value
|
28
|
+
end
|
29
|
+
when :=~
|
30
|
+
actual_value(node) =~ expected_value
|
31
|
+
when :!~
|
32
|
+
actual_value(node) !~ expected_value
|
33
|
+
when :>
|
34
|
+
actual_value(node) > expected_value
|
35
|
+
when :>=
|
36
|
+
actual_value(node) >= expected_value
|
37
|
+
when :<
|
38
|
+
actual_value(node) < expected_value
|
39
|
+
when :<=
|
40
|
+
actual_value(node) <= expected_value
|
41
|
+
when :in
|
42
|
+
expected_value.any? { |expected| expected.match?(node, :==) }
|
43
|
+
when :not_in
|
44
|
+
expected_value.all? { |expected| expected.match?(node, :!=) }
|
45
|
+
when :includes
|
46
|
+
actual_value(node).any? { |actual| actual == expected_value }
|
47
|
+
else
|
48
|
+
if expected_value.is_a?(::Array)
|
49
|
+
actual = actual_value(node)
|
50
|
+
actual.is_a?(::Array) && actual.size == expected_value.size &&
|
51
|
+
actual.zip(expected_value).all? { |actual_node, expected_node| expected_node.match?(actual_node, :==) }
|
52
|
+
else
|
53
|
+
actual_value(node) == expected_value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Get the actual value from ast node.
|
59
|
+
# @return if node is a {Parser::AST::Node}, return the node value, otherwise, return the node itself.
|
60
|
+
def actual_value(node)
|
61
|
+
if node.is_a?(::Parser::AST::Node)
|
62
|
+
node.to_value
|
63
|
+
else
|
64
|
+
node
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get the expected value
|
69
|
+
def expected_value
|
70
|
+
@value
|
71
|
+
end
|
72
|
+
|
73
|
+
# Check if the operator is valid.
|
74
|
+
# @return [Boolean] true if the operator is valid
|
75
|
+
def valid_operator?(operator)
|
76
|
+
valid_operators.include?(operator)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Synvert::Core::NodeQuery::Compiler
|
4
|
+
# DynamicAttribute represents a ruby dynamic attribute.
|
5
|
+
# e.g. code is <code>{ a: a }</code>, query is <code>'.hash > .pair[key={{value}}]'</code>,
|
6
|
+
# <code>{{value}}</code> is the dynamic attribute.
|
7
|
+
class DynamicAttribute
|
8
|
+
include Comparable
|
9
|
+
|
10
|
+
attr_accessor :base_node
|
11
|
+
|
12
|
+
# Initialize a DynamicAttribute.
|
13
|
+
# @param value [String] the dynamic attribute value
|
14
|
+
def initialize(value:)
|
15
|
+
@value = value
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get the actual value of a node.
|
19
|
+
#
|
20
|
+
# @param node the node
|
21
|
+
# @return [String] if node is a {Parser::AST::Node}, return the node source code, otherwise return the node itself.
|
22
|
+
def actual_value(node)
|
23
|
+
if node.is_a?(::Parser::AST::Node)
|
24
|
+
node.to_source
|
25
|
+
else
|
26
|
+
node
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get the expected value.
|
31
|
+
#
|
32
|
+
# @return [String] Query the node by @valud from base_node, if the node is a {Parser::AST::Node}, return the node source code, otherwise return the node itself.
|
33
|
+
def expected_value
|
34
|
+
node = base_node.child_node_by_name(@value)
|
35
|
+
if node.is_a?(::Parser::AST::Node)
|
36
|
+
node.to_source
|
37
|
+
else
|
38
|
+
node
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get valid operators.
|
43
|
+
def valid_operators
|
44
|
+
SIMPLE_VALID_OPERATORS
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
"{{#{@value}}}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|