node_mutation 1.0.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
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: