synvert-core 0.64.0 → 1.0.3
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 +538 -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: 2e678636827f8ffcad275fe7c6e6bcbee9d278a954bf33a80822ff7235944469
|
4
|
+
data.tar.gz: fa6c7ad36ed9fa50901f4b6f9f373190aa3ad0cbba4bdea539daf66376bc6e0c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd53c12635ecb49e6d2c6b43d8a0513ac0fd2c2c0b6de8081bc30318512ae7714c092c2e36a721059b2ec811ec521757a1b32889d2e6ff4cb516ce83877cfef6
|
7
|
+
data.tar.gz: 4a0e169b94bc54a61e944d3e44d5bd40a884783c73c2ad491dbb23a25c77c73bb2e120f0feb61494a8bf8762c234cbc3d983185e0bc2eb18a6ec8cb3d2c933ab
|
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
|
+
[![AwesomeCode Status for xinminlabs/synvert-core-ruby](https://awesomecode.io/projects/033f7f02-7b22-41c3-a902-fca37f1ec72a/status)](https://awesomecode.io/repos/xinminlabs/synvert-core-ruby)
|
5
6
|
![Main workflow](https://github.com/xinminlabs/synvert-core-ruby/actions/workflows/main.yml/badge.svg)
|
6
|
-
[![Gem Version](https://badge.fury.io/rb/synvert-core.png)](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
|