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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea27da2bceb8ec0f2dd837e5aa80135860a2539c7e49170b2bd46af227d1043e
4
- data.tar.gz: a901be4a294000eecfbbbf407416015cd9b38499ebc24c744147aebdeba5388f
3
+ metadata.gz: f889cb977b7bf6e847bdeeac011ee0d23210ae8db7a0652e8c00d5d98c72a701
4
+ data.tar.gz: 46d220401cc8c9c0422428c2915862148b4ad8dc236f637ed52dbc45207de742
5
5
  SHA512:
6
- metadata.gz: b9d0451d59718fe04e23d48a90e7e705fb455592d2636055064d9c5d7f6a1db20980ff1a38b147d77b16fdc8b059b95a8a95848c183a4115ddb1730541bf0757
7
- data.tar.gz: 63246572cc0d9c3d44d7bb12f6d716e11b15fd1978376588e235a4c2f9b5e7849962ed4a81e38bd1a9f74c2cb877325148c486c9f3045ebf8c0aa80459d9b6aa
6
+ metadata.gz: 926df3dd8aa3689938e90c530fb432e0703cff2f89f35f46950b3630eb98b8da6de0e0d5eb75c5c24262f3712400d4570ac05abd5ebbbe6c6954d6c997048432
7
+ data.tar.gz: 321fb626763ca31e6156c462bb6846ce73d00639a620b0b7048b024fbe50d14f63f6d3209e3a534b0475c29c29e74dcf0a7c204f14ad0927244bd5cbb157b3d6
@@ -19,7 +19,7 @@ jobs:
19
19
  runs-on: ubuntu-latest
20
20
  strategy:
21
21
  matrix:
22
- ruby-version: ['2.5', '2.6', '2.7', '3.0', '3.1']
22
+ ruby-version: ['2.6', '2.7', '3.0', '3.1']
23
23
 
24
24
  steps:
25
25
  - uses: actions/checkout@v2
data/.gitignore CHANGED
@@ -15,3 +15,7 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+
19
+ lib/synvert/core/node_query/lexer.rex.rb
20
+ lib/synvert/core/node_query/parser.racc.rb
21
+ lib/synvert/core/node_query/parser.output
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.0.0 (2022-04-25)
4
+
5
+ * Introduce new node query language
6
+ * Drop ruby 2.5 support
7
+
3
8
  ## 0.64.0 (2022-04-02)
4
9
 
5
10
  * Read absolute path of Gemfile.lock
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$}) { |m| "spec/#{m[1]}_spec.rb" }
6
- watch('spec/spec_helper.rb') { "spec" }
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)) }.gsub(
47
- /@output_buffer.safe_append=\((.+?)\);#{ERUBY_EXPR_SPLITTER}/mo
48
- ) { reverse_escape_text(Regexp.last_match(1)) }.gsub(
49
- /@output_buffer.safe_append=(.+?)\s+(do|\{)(\s*\|[^|]*\|)?\s*#{ERUBY_EXPR_SPLITTER}/mo
50
- ) { reverse_escape_text(Regexp.last_match(1)) }
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)) ["str", :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('(string)', arguments.loc.expression.begin_pos, arguments.loc.expression.end_pos)
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.begin.to_range.begin - 1] + new_source[arguments.loc.end.to_range.end..-1]
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