node_mutation 1.0.0 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f708082625b70a33be17a520d51fab328979f7640f6125f7cec8a4f8c0fa348c
4
- data.tar.gz: 71219747c6be37cb7282658c5dc6087f33ac63ed195de48ab29f2aa79347ea85
3
+ metadata.gz: 16d1f6952b16d815e9cee50df011c99075ffd17bc17531efc8b6a99c39909d4a
4
+ data.tar.gz: 3f558c1b9e171310775fec6ccdb3a1fe0321aa0ce94b23ec8c861e3ae496dd31
5
5
  SHA512:
6
- metadata.gz: 74f6fdb99bec0cd69ae0310ebc728661981ab7e7c41624e2409238426095cfec7576f1905d49f7e9d25393723b9c43dbe6ebf9c72730df38a19308e68682fc49
7
- data.tar.gz: d2323af8c25e3a69779848be92594075970eb3c22b6c26fc411bc95710f51c9d37881a32c4196b3330eb28544d05dcbf72ef45521bce2e61025ea1ab5681eaae
6
+ metadata.gz: 7221a70883e380a9f88dda96db69890349dde212580f63fac75d02a71bb558587d4ccf7e6b33547b0b44846ee493fd7ed924e13eb7cc414b27979c3fb354876e
7
+ data.tar.gz: 7d1cc7ab27508b202589d22bd6affc07345d19ad12701b7a3aea2d9bc06125a43e17870f787a539a16faaf1ed7db14a13c28b4b8a46d0afaa3b1d2fe701c4ef8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # NodeMutation
2
2
 
3
+ ## 1.2.1 (2022-07-22)
4
+
5
+ * Fix `child_node_range` for const name
6
+ * Update `parser_node_ext` to 0.4.0
7
+
8
+ ## 1.2.0 (2022-07-02)
9
+
10
+ * Return new source instead of writing file
11
+ * Revert Add erb engine
12
+ * Revert Add `ReplaceErbStmtWithExprAction`
13
+
14
+ ## 1.1.0 (2022-07-02)
15
+
16
+ * Add erb engine
17
+ * Add `ReplaceErbStmtWithExprAction`
18
+
3
19
  ## 1.0.0 (2022-07-01)
4
20
 
5
21
  * Initial release
data/Gemfile CHANGED
@@ -13,5 +13,4 @@ gem "parser"
13
13
  gem "parser_node_ext"
14
14
  gem "guard"
15
15
  gem "guard-rspec"
16
- gem "fakefs", require: 'fakefs/safe'
17
16
  gem "pp"
data/Gemfile.lock CHANGED
@@ -1,13 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- node_mutation (1.0.0)
4
+ node_mutation (1.2.1)
5
5
  activesupport
6
+ erubis
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
10
- activesupport (7.0.3)
11
+ activesupport (7.0.3.1)
11
12
  concurrent-ruby (~> 1.0, >= 1.0.2)
12
13
  i18n (>= 1.6, < 2)
13
14
  minitest (>= 5.1)
@@ -16,7 +17,7 @@ GEM
16
17
  coderay (1.1.3)
17
18
  concurrent-ruby (1.1.10)
18
19
  diff-lcs (1.5.0)
19
- fakefs (1.8.0)
20
+ erubis (2.7.0)
20
21
  ffi (1.15.5)
21
22
  formatador (1.1.0)
22
23
  guard (2.18.0)
@@ -33,21 +34,21 @@ GEM
33
34
  guard (~> 2.1)
34
35
  guard-compat (~> 1.1)
35
36
  rspec (>= 2.99.0, < 4.0)
36
- i18n (1.10.0)
37
+ i18n (1.12.0)
37
38
  concurrent-ruby (~> 1.0)
38
39
  listen (3.7.1)
39
40
  rb-fsevent (~> 0.10, >= 0.10.3)
40
41
  rb-inotify (~> 0.9, >= 0.9.10)
41
42
  lumberjack (1.2.8)
42
43
  method_source (1.0.0)
43
- minitest (5.16.1)
44
+ minitest (5.16.2)
44
45
  nenv (0.3.0)
45
46
  notiffany (0.1.3)
46
47
  nenv (~> 0.1)
47
48
  shellany (~> 0.0)
48
49
  parser (3.1.2.0)
49
50
  ast (~> 2.4.1)
50
- parser_node_ext (0.2.0)
51
+ parser_node_ext (0.4.0)
51
52
  parser
52
53
  pp (0.3.0)
53
54
  prettyprint
@@ -74,7 +75,7 @@ GEM
74
75
  rspec-support (3.11.0)
75
76
  shellany (0.0.1)
76
77
  thor (1.2.1)
77
- tzinfo (2.0.4)
78
+ tzinfo (2.0.5)
78
79
  concurrent-ruby (~> 1.0)
79
80
 
80
81
  PLATFORMS
@@ -82,7 +83,6 @@ PLATFORMS
82
83
  x86_64-linux
83
84
 
84
85
  DEPENDENCIES
85
- fakefs
86
86
  guard
87
87
  guard-rspec
88
88
  node_mutation!
data/README.md CHANGED
@@ -25,7 +25,7 @@ Or install it yourself as:
25
25
  ```ruby
26
26
  require 'node_mutation'
27
27
 
28
- mutation = NodeMutation.new(file_path, source)
28
+ mutation = NodeMutation.new(source)
29
29
  ```
30
30
 
31
31
  2. call the rewrite apis:
@@ -54,7 +54,13 @@ mutation.wrap node, with: 'module Foo'
54
54
  3. process actions and write the new source code to file:
55
55
 
56
56
  ```ruby
57
- mutation.process
57
+ result = mutation.process
58
+ # if it makes any change to the source
59
+ result.affected?
60
+ # if any action is conflicted
61
+ result.conflicted
62
+ # return the new source if it is affected
63
+ result.new_source
58
64
  ```
59
65
 
60
66
  ## Write Adapter
@@ -2,7 +2,7 @@
2
2
 
3
3
  # WrapAction to wrap node within a block, class or module.
4
4
  #
5
- # Note: if WrapAction is conflicted with another action (begin_pos and end_pos are overlapped),
5
+ # Note: if WrapAction is conflicted with another action (start and end are overlapped),
6
6
  # we have to put those 2 actions into 2 within_file scopes.
7
7
  class NodeMutation::WrapAction < NodeMutation::Action
8
8
  # Initialize a WrapAction.
@@ -70,7 +70,7 @@ class NodeMutation::Action
70
70
  after_line_is_blank = lines[end_line] == ''
71
71
 
72
72
  if lines.length > 1 && before_line_is_blank && after_line_is_blank
73
- @end_pos += "\n".length
73
+ @end += "\n".length
74
74
  end
75
75
  end
76
76
 
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erubis'
4
+
5
+ module NodeMutation::Engine
6
+ ERUBY_EXPR_SPLITTER = '; ;'
7
+ ERUBY_STMT_SPLITTER = '; ;'
8
+
9
+ class Erb
10
+ class << self
11
+ # convert erb to ruby code.
12
+ #
13
+ # @param source [String] erb source code.
14
+ # @return [String] ruby source code.
15
+ def encode(source)
16
+ Erubis.new(source.gsub('-%>', '%>'), escape: false, trim: false).src
17
+ end
18
+
19
+ # convert ruby code to erb.
20
+ #
21
+ # @param source [String] ruby source code.
22
+ # @return [String] erb source code.
23
+ def decode(source)
24
+ source = decode_ruby_stmt(source)
25
+ source = decode_ruby_output(source)
26
+ source = decode_html_output(source)
27
+ source = remove_erubis_buf(source)
28
+ end
29
+
30
+ private
31
+
32
+ def decode_ruby_stmt(source)
33
+ source.gsub(/#{ERUBY_STMT_SPLITTER}(.+?)#{ERUBY_STMT_SPLITTER}/mo) { "<%#{Regexp.last_match(1)}%>" }
34
+ end
35
+
36
+ def decode_ruby_output(source)
37
+ source.gsub(/@output_buffer.append=\((.+?)\);#{ERUBY_EXPR_SPLITTER}/mo) {
38
+ "<%=#{Regexp.last_match(1)}%>"
39
+ }.gsub(/@output_buffer.append= (.+?)\s+(do|\{)(\s*\|[^|]*\|)?\s*#{ERUBY_EXPR_SPLITTER}/mo) { |m|
40
+ "<%=#{m.sub('@output_buffer.append= ', '').sub(ERUBY_EXPR_SPLITTER, '')}%>"
41
+ }
42
+ end
43
+
44
+ def decode_html_output(source)
45
+ source.gsub(/@output_buffer.safe_append='(.+?)'.freeze;/m) { reverse_escape_text(Regexp.last_match(1)) }
46
+ .gsub(
47
+ /@output_buffer.safe_append=\((.+?)\);#{ERUBY_EXPR_SPLITTER}/mo
48
+ ) { reverse_escape_text(Regexp.last_match(1)) }
49
+ .gsub(
50
+ /@output_buffer.safe_append=(.+?)\s+(do|\{)(\s*\|[^|]*\|)?\s*#{ERUBY_EXPR_SPLITTER}/mo
51
+ ) { reverse_escape_text(Regexp.last_match(1)) }
52
+ end
53
+
54
+ def remove_erubis_buf(source)
55
+ source
56
+ .sub('@output_buffer = output_buffer || ActionView::OutputBuffer.new;', '')
57
+ .sub('@output_buffer.to_s', '')
58
+ end
59
+
60
+ def reverse_escape_text(source)
61
+ source.gsub("\\\\", "\\").gsub("\\'", "'")
62
+ end
63
+ end
64
+ end
65
+
66
+ # borrowed from rails
67
+ class Erubis < ::Erubis::Eruby
68
+ BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
69
+ def add_preamble(src)
70
+ @newline_pending = 0
71
+ src << '@output_buffer = output_buffer || ActionView::OutputBuffer.new;'
72
+ end
73
+
74
+ def add_text(src, text)
75
+ return if text.empty?
76
+
77
+ if text == "\n"
78
+ @newline_pending += 1
79
+ else
80
+ src << "@output_buffer.safe_append='"
81
+ src << ("\n" * @newline_pending) if @newline_pending > 0
82
+ src << escape_text(text)
83
+ src << "'.freeze;"
84
+
85
+ @newline_pending = 0
86
+ end
87
+ end
88
+
89
+ # Erubis toggles <%= and <%== behavior when escaping is enabled.
90
+ # We override to always treat <%== as escaped.
91
+ def add_expr(src, code, indicator)
92
+ case indicator
93
+ when '=='
94
+ add_expr_escaped(src, code)
95
+ else
96
+ super
97
+ end
98
+ end
99
+
100
+ def add_expr_literal(src, code)
101
+ flush_newline_if_pending(src)
102
+ if BLOCK_EXPR.match?(code)
103
+ src << '@output_buffer.append= ' << code << ERUBY_EXPR_SPLITTER
104
+ else
105
+ src << '@output_buffer.append=(' << code << ');' << ERUBY_EXPR_SPLITTER
106
+ end
107
+ end
108
+
109
+ def add_expr_escaped(src, code)
110
+ flush_newline_if_pending(src)
111
+ if BLOCK_EXPR.match?(code)
112
+ src << '@output_buffer.safe_append= ' << code << ERUBY_EXPR_SPLITTER
113
+ else
114
+ src << '@output_buffer.safe_append=(' << code << ');' << ERUBY_EXPR_SPLITTER
115
+ end
116
+ end
117
+
118
+ def add_stmt(src, code)
119
+ flush_newline_if_pending(src)
120
+ if code != "\n" && code != ''
121
+ index =
122
+ case code
123
+ when /\A(\s*)\r?\n/
124
+ Regexp.last_match(1).length
125
+ when /\A(\s+)/
126
+ Regexp.last_match(1).end_with?(' ') ? Regexp.last_match(1).length - 1 : Regexp.last_match(1).length
127
+ else
128
+ 0
129
+ end
130
+ code.insert(index, ERUBY_STMT_SPLITTER)
131
+ code.insert(-1, ERUBY_STMT_SPLITTER[0...-1])
132
+ end
133
+ super
134
+ end
135
+
136
+ def add_postamble(src)
137
+ flush_newline_if_pending(src)
138
+ src << '@output_buffer.to_s'
139
+ end
140
+
141
+ def flush_newline_if_pending(src)
142
+ if @newline_pending > 0
143
+ src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
144
+ @newline_pending = 0
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NodeMutation::Engine
4
+ # Engine defines how to encode / decode other files (like erb).
5
+ autoload :Erb, 'node_mutation/engine/erb'
6
+ end
@@ -77,7 +77,7 @@ class NodeMutation::ParserAdapter < NodeMutation::Adapter
77
77
  start: node.arguments.first.loc.expression.begin_pos,
78
78
  end: node.arguments.last.loc.expression.end_pos
79
79
  )
80
- when %i[class name], %i[def name], %i[defs name]
80
+ when %i[class name], %i[const name], %i[def name], %i[defs name]
81
81
  OpenStruct.new(start: node.loc.name.begin_pos, end: node.loc.name.end_pos)
82
82
  when %i[defs dot]
83
83
  OpenStruct.new(start: node.loc.operator.begin_pos, end: node.loc.operator.end_pos) if node.loc.operator
@@ -171,4 +171,4 @@ class NodeMutation::ParserAdapter < NodeMutation::Adapter
171
171
  return child_node
172
172
  end
173
173
  end
174
- end
174
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class NodeMutation::Result
4
+ def initialize(options)
5
+ @options = options
6
+ end
7
+
8
+ def affected?
9
+ @options[:affected]
10
+ end
11
+
12
+ def conflicted?
13
+ @options[:conflicted]
14
+ end
15
+
16
+ def new_source
17
+ @options[:new_source]
18
+ end
19
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class NodeMutation
4
- VERSION = "1.0.0"
4
+ VERSION = "1.2.1"
5
5
  end
data/lib/node_mutation.rb CHANGED
@@ -9,7 +9,7 @@ class NodeMutation
9
9
  class MethodNotSupported < StandardError; end
10
10
  class ConflictActionError < StandardError; end
11
11
 
12
- KEEPING_RUNNING = 1
12
+ KEEP_RUNNING = 1
13
13
  THROW_ERROR = 2
14
14
 
15
15
  autoload :Adapter, "node_mutation/adapter"
@@ -22,9 +22,9 @@ class NodeMutation
22
22
  autoload :RemoveAction, 'node_mutation/action/remove_action'
23
23
  autoload :PrependAction, 'node_mutation/action/prepend_action'
24
24
  autoload :ReplaceAction, 'node_mutation/action/replace_action'
25
- autoload :ReplaceErbStmtWithExprAction, 'node_mutation/action/replace_erb_stmt_with_expr_action'
26
25
  autoload :ReplaceWithAction, 'node_mutation/action/replace_with_action'
27
26
  autoload :WrapAction, 'node_mutation/action/wrap_action'
27
+ autoload :Result, 'node_mutation/result'
28
28
 
29
29
  attr_reader :actions
30
30
 
@@ -47,18 +47,16 @@ class NodeMutation
47
47
  end
48
48
 
49
49
  # Get the strategy
50
- # @return [Integer] current strategy, could be {NodeMutation::KEEPING_RUNNING} or {NodeMutation::THROW_ERROR},
51
- # by default is {NodeMutation::KEEPING_RUNNING}
50
+ # @return [Integer] current strategy, could be {NodeMutation::KEEP_RUNNING} or {NodeMutation::THROW_ERROR},
51
+ # by default is {NodeMutation::KEEP_RUNNING}
52
52
  def self.strategy
53
53
  @strategy ||= KEEP_RUNNING
54
54
  end
55
55
 
56
56
  # Initialize a NodeMutation.
57
- # @param file_path [String] file path
58
- # @param source [String] source of the file
59
- def initialize(file_path, source)
60
- @file_path = file_path
61
- @source = +source
57
+ # @param source [String] file source
58
+ def initialize(source)
59
+ @source = source
62
60
  @actions = []
63
61
  end
64
62
 
@@ -169,8 +167,8 @@ class NodeMutation
169
167
  # source code of the ast node is
170
168
  # assert(object.empty?)
171
169
  # then we call
172
- # replace :message, with: 'assert_empty'
173
- # replace :arguments, with: '{{arguments.first.receiver}}'
170
+ # mutation.replace(node, :message, with: 'assert_empty')
171
+ # mutation.replace(node, :arguments, with: '{{arguments.first.receiver}}')
174
172
  # the source code will be rewritten to
175
173
  # assert_empty(object)
176
174
  def replace(node, *selectors, with:)
@@ -219,21 +217,25 @@ class NodeMutation
219
217
  # if strategy is set to KEEP_RUNNING.
220
218
  # @return {{conflict: Boolean}} if actions are conflicted
221
219
  def process
222
- conflict_actions = []
223
- if @actions.length > 0
224
- @actions.sort_by! { |action| [action.start, action.end] }
225
- conflict_actions = get_conflict_actions
226
- if conflict_actions.size > 0 && NodeMutation.strategy == THROW_ERROR
227
- raise ConflictActionError, "mutation actions are conflicted"
228
- end
229
- @actions.reverse_each do |action|
230
- @source[action.start...action.end] = action.new_code
231
- end
232
- @actions = []
220
+ if @actions.length == 0
221
+ return NodeMutation::Result.new(affected: false)
222
+ end
233
223
 
234
- File.write(@file_path, @source)
224
+ conflict_actions = []
225
+ source = +@source
226
+ @actions.sort_by! { |action| [action.start, action.end] }
227
+ conflict_actions = get_conflict_actions
228
+ if conflict_actions.size > 0 && NodeMutation.strategy == THROW_ERROR
229
+ raise ConflictActionError, "mutation actions are conflicted"
230
+ end
231
+ @actions.reverse_each do |action|
232
+ source[action.start...action.end] = action.new_code
235
233
  end
236
- OpenStruct.new(conflict: !conflict_actions.empty?)
234
+ NodeMutation::Result.new(
235
+ affected: true,
236
+ conflicted: !conflict_actions.empty?,
237
+ new_source: source
238
+ )
237
239
  end
238
240
 
239
241
  private
@@ -30,6 +30,7 @@ Gem::Specification.new do |spec|
30
30
 
31
31
  # Uncomment to register a new dependency of your gem
32
32
  spec.add_dependency "activesupport"
33
+ spec.add_dependency "erubis"
33
34
 
34
35
  # For more information and examples about making a new gem, check out our
35
36
  # guide at: https://bundler.io/guides/creating_gem.html
@@ -0,0 +1,9 @@
1
+ class NodeMutation::Result
2
+ def initialize: (source: String) -> NodeMutation::Result
3
+
4
+ def affected?: () -> bool
5
+
6
+ def conflicted?: () -> bool
7
+
8
+ def new_source: () -> String
9
+ end
@@ -7,7 +7,7 @@ module NodeMutation[T]
7
7
  class ConflictActionError < StandardError
8
8
  end
9
9
 
10
- KEEPING_RUNNING: Integer
10
+ KEEP_RUNNING: Integer
11
11
 
12
12
  THROW_ERROR: Integer
13
13
 
@@ -19,7 +19,7 @@ module NodeMutation[T]
19
19
 
20
20
  def self.strategry: () -> Integer
21
21
 
22
- def initialize: (file_path: String, source: String) -> NodeMutation
22
+ def initialize: (source: String) -> NodeMutation
23
23
 
24
24
  def append: (node: T, code: String) -> void
25
25
 
@@ -39,5 +39,5 @@ module NodeMutation[T]
39
39
 
40
40
  def wrap: (node: T, with: String) -> void
41
41
 
42
- def process: () -> void
42
+ def process: () -> NodeMutation::Result
43
43
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: node_mutation
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Huang
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-07-01 00:00:00.000000000 Z
11
+ date: 2022-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: erubis
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  description: ast node mutation apis
28
42
  email:
29
43
  - flyerhzm@gmail.com
@@ -51,11 +65,15 @@ files:
51
65
  - lib/node_mutation/action/replace_with_action.rb
52
66
  - lib/node_mutation/action/wrap_action.rb
53
67
  - lib/node_mutation/adapter.rb
68
+ - lib/node_mutation/engine.rb
69
+ - lib/node_mutation/engine/erb.rb
54
70
  - lib/node_mutation/parser_adapter.rb
71
+ - lib/node_mutation/result.rb
55
72
  - lib/node_mutation/version.rb
56
73
  - node_mutation.gemspec
57
74
  - sig/node_mutation.rbs
58
75
  - sig/node_mutation/adapter.rbs
76
+ - sig/node_mutation/result.rbs
59
77
  homepage: https://github.com/xinminlabs/node-mutation-ruby
60
78
  licenses: []
61
79
  metadata: