unparser 0.6.5 → 0.8.0

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -6
  3. data/bin/unparser +1 -1
  4. data/lib/unparser/adamantium.rb +3 -1
  5. data/lib/unparser/anima.rb +11 -0
  6. data/lib/unparser/ast/local_variable_scope.rb +28 -25
  7. data/lib/unparser/ast.rb +18 -22
  8. data/lib/unparser/buffer.rb +43 -15
  9. data/lib/unparser/cli.rb +30 -7
  10. data/lib/unparser/color.rb +5 -0
  11. data/lib/unparser/either.rb +6 -6
  12. data/lib/unparser/emitter/args.rb +5 -1
  13. data/lib/unparser/emitter/argument.rb +6 -4
  14. data/lib/unparser/emitter/array.rb +0 -4
  15. data/lib/unparser/emitter/array_pattern.rb +1 -9
  16. data/lib/unparser/emitter/assignment.rb +17 -8
  17. data/lib/unparser/emitter/begin.rb +0 -6
  18. data/lib/unparser/emitter/binary.rb +1 -1
  19. data/lib/unparser/emitter/block.rb +13 -6
  20. data/lib/unparser/emitter/def.rb +1 -1
  21. data/lib/unparser/emitter/dstr.rb +6 -5
  22. data/lib/unparser/emitter/dsym.rb +1 -1
  23. data/lib/unparser/emitter/ensure.rb +16 -0
  24. data/lib/unparser/emitter/flipflop.rb +7 -2
  25. data/lib/unparser/emitter/flow_modifier.rb +1 -7
  26. data/lib/unparser/emitter/for.rb +1 -1
  27. data/lib/unparser/emitter/hash.rb +0 -16
  28. data/lib/unparser/emitter/hash_pattern.rb +1 -1
  29. data/lib/unparser/emitter/in_pattern.rb +9 -1
  30. data/lib/unparser/emitter/index.rb +0 -4
  31. data/lib/unparser/emitter/kwbegin.rb +1 -1
  32. data/lib/unparser/emitter/match_pattern.rb +7 -11
  33. data/lib/unparser/emitter/match_pattern_p.rb +6 -1
  34. data/lib/unparser/emitter/mlhs.rb +7 -1
  35. data/lib/unparser/emitter/op_assign.rb +0 -5
  36. data/lib/unparser/emitter/pair.rb +31 -5
  37. data/lib/unparser/emitter/primitive.rb +19 -6
  38. data/lib/unparser/emitter/range.rb +23 -2
  39. data/lib/unparser/emitter/regexp.rb +5 -17
  40. data/lib/unparser/emitter/rescue.rb +7 -1
  41. data/lib/unparser/emitter/root.rb +2 -9
  42. data/lib/unparser/emitter/send.rb +1 -5
  43. data/lib/unparser/emitter/string.rb +31 -0
  44. data/lib/unparser/emitter/xstr.rb +8 -1
  45. data/lib/unparser/emitter.rb +9 -10
  46. data/lib/unparser/generation.rb +33 -29
  47. data/lib/unparser/node_details/send.rb +4 -3
  48. data/lib/unparser/node_details.rb +1 -0
  49. data/lib/unparser/node_helpers.rb +19 -9
  50. data/lib/unparser/util.rb +23 -0
  51. data/lib/unparser/validation.rb +70 -28
  52. data/lib/unparser/writer/array.rb +51 -0
  53. data/lib/unparser/writer/binary.rb +8 -4
  54. data/lib/unparser/writer/dynamic_string.rb +127 -146
  55. data/lib/unparser/writer/regexp.rb +101 -0
  56. data/lib/unparser/writer/resbody.rb +37 -3
  57. data/lib/unparser/writer/rescue.rb +3 -7
  58. data/lib/unparser/writer/send/unary.rb +9 -4
  59. data/lib/unparser/writer/send.rb +8 -14
  60. data/lib/unparser/writer.rb +31 -1
  61. data/lib/unparser.rb +149 -38
  62. metadata +38 -20
@@ -6,7 +6,7 @@ module Unparser
6
6
  # Emitter base class
7
7
  class Emitter
8
8
  include Adamantium, AbstractType, Constants, Generation, NodeHelpers
9
- include Anima.new(:buffer, :comments, :node, :local_variable_scope)
9
+ include Anima.new(:buffer, :comments, :explicit_encoding, :local_variable_scope, :node)
10
10
 
11
11
  public :node
12
12
 
@@ -22,10 +22,9 @@ module Unparser
22
22
  #
23
23
  # @return [Parser::AST::Node]
24
24
  #
25
- # @api private
26
- #
25
+ # mutant:disable
27
26
  def local_variable_scope
28
- AST::LocalVariableScope.new(node)
27
+ AST::LocalVariableScope.new(node: node, static_local_variables: Set.new)
29
28
  end
30
29
 
31
30
  def self.included(descendant)
@@ -67,7 +66,7 @@ module Unparser
67
66
  # @api private
68
67
  #
69
68
  # rubocop:disable Metrics/ParameterLists
70
- def self.emitter(buffer:, comments:, node:, local_variable_scope:)
69
+ def self.emitter(buffer:, explicit_encoding:, comments:, node:, local_variable_scope:)
71
70
  type = node.type
72
71
 
73
72
  klass = REGISTRY.fetch(type) do
@@ -75,10 +74,11 @@ module Unparser
75
74
  end
76
75
 
77
76
  klass.new(
78
- buffer: buffer,
79
- comments: comments,
80
- local_variable_scope: local_variable_scope,
81
- node: node
77
+ buffer:,
78
+ comments:,
79
+ explicit_encoding:,
80
+ local_variable_scope:,
81
+ node:
82
82
  )
83
83
  end
84
84
  # rubocop:enable Metrics/ParameterLists
@@ -90,6 +90,5 @@ module Unparser
90
90
  # @api private
91
91
  #
92
92
  abstract_method :dispatch
93
-
94
93
  end # Emitter
95
94
  end # Unparser
@@ -7,8 +7,6 @@ module Unparser
7
7
 
8
8
  private_constant(*constants(false))
9
9
 
10
- def emit_heredoc_reminders; end
11
-
12
10
  def symbol_name; end
13
11
 
14
12
  def write_to_buffer
@@ -121,40 +119,50 @@ module Unparser
121
119
  end
122
120
 
123
121
  def emit_body(node, indent: true)
124
- if indent
125
- buffer.indent
126
- nl
127
- end
128
-
129
- if n_begin?(node)
130
- if node.children.one?
131
- visit_deep(node)
122
+ with_indent(indent: indent) do
123
+ if n_begin?(node)
124
+ if node.children.empty?
125
+ write('()')
126
+ elsif node.children.one?
127
+ visit_deep(node)
128
+ else
129
+ emit_body_inner(node)
130
+ end
132
131
  else
133
- emit_body_inner(node)
132
+ visit_deep(node)
134
133
  end
135
- else
136
- visit_deep(node)
137
134
  end
135
+ end
138
136
 
139
- if indent
140
- buffer.unindent
141
- nl
142
- end
137
+ def with_indent(indent:)
138
+ return yield unless indent
139
+
140
+ buffer.indent
141
+ nl
142
+ yield
143
+ buffer.unindent
144
+ nl
143
145
  end
144
146
 
145
147
  def emit_body_inner(node)
146
148
  head, *tail = node.children
147
149
  emit_body_member(head)
150
+ write(';') if requires_explicit_statement_terminator?(head, tail)
148
151
 
149
152
  tail.each do |child|
150
- nl
153
+ buffer.ensure_nl
151
154
 
152
155
  nl if EXTRA_NL.include?(child.type)
153
156
 
154
157
  emit_body_member(child)
158
+ write(';') if requires_explicit_statement_terminator?(child, tail)
155
159
  end
156
160
  end
157
161
 
162
+ def requires_explicit_statement_terminator?(node, nodes_group)
163
+ n_range?(node) && node.children.fetch(1).nil? && !node.eql?(nodes_group.fetch(-1))
164
+ end
165
+
158
166
  def emit_body_member(node)
159
167
  if n_rescue?(node)
160
168
  emit_rescue_postcontrol(node)
@@ -204,21 +212,20 @@ module Unparser
204
212
  end
205
213
 
206
214
  def emit_rescue_postcontrol(node)
207
- writer = writer_with(Writer::Rescue, node)
215
+ writer = writer_with(Writer::Rescue, node:)
208
216
  writer.emit_postcontrol
209
- writer.emit_heredoc_reminders
210
217
  end
211
218
 
212
219
  def emit_rescue_regular(node)
213
- writer_with(Writer::Rescue, node).emit_regular
220
+ writer_with(Writer::Rescue, node:).emit_regular
214
221
  end
215
222
 
216
- def writer_with(klass, node)
217
- klass.new(to_h.merge(node: node))
223
+ def emitter(node)
224
+ Emitter.emitter(**to_h, node: node)
218
225
  end
219
226
 
220
- def emitter(node)
221
- Emitter.emitter(**to_h.merge(node: node))
227
+ def writer_with(klass, node:, **attributes)
228
+ klass.new(to_h.merge(node: node, **attributes))
222
229
  end
223
230
 
224
231
  def visit(node)
@@ -226,10 +233,7 @@ module Unparser
226
233
  end
227
234
 
228
235
  def visit_deep(node)
229
- emitter(node).tap do |emitter|
230
- emitter.write_to_buffer
231
- emitter.emit_heredoc_reminders
232
- end
236
+ emitter(node).tap(&:write_to_buffer)
233
237
  end
234
238
 
235
239
  def first_child
@@ -19,9 +19,10 @@ module Unparser
19
19
  end
20
20
 
21
21
  def binary_syntax_allowed?
22
- selector_binary_operator? \
23
- && arguments.one? \
24
- && !n_splat?(arguments.first) \
22
+ selector_binary_operator? \
23
+ && n_send?(node) \
24
+ && arguments.one? \
25
+ && !n_splat?(arguments.first) \
25
26
  && !n_kwargs?(arguments.first)
26
27
  end
27
28
 
@@ -4,6 +4,7 @@ module Unparser
4
4
  module NodeDetails
5
5
  include Constants, NodeHelpers
6
6
 
7
+ # mutant:disable
7
8
  def self.included(descendant)
8
9
  descendant.class_eval do
9
10
  include Adamantium, Concord.new(:node)
@@ -31,7 +31,16 @@ module Unparser
31
31
  node.type.equal?(type)
32
32
  end
33
33
 
34
+ def n_flipflop?(node)
35
+ n_iflipflop?(node) || n_eflipflop?(node)
36
+ end
37
+
38
+ def n_range?(node)
39
+ n_irange?(node) || n_erange?(node)
40
+ end
41
+
34
42
  %i[
43
+ and
35
44
  arg
36
45
  args
37
46
  array
@@ -41,18 +50,26 @@ module Unparser
41
50
  cbase
42
51
  const
43
52
  dstr
53
+ eflipflop
44
54
  empty_else
55
+ erange
45
56
  ensure
57
+ gvar
46
58
  hash
47
59
  hash_pattern
48
60
  if
61
+ iflipflop
49
62
  in_pattern
50
63
  int
64
+ irange
51
65
  kwarg
52
66
  kwargs
53
67
  kwsplat
54
68
  lambda
69
+ lvar
55
70
  match_rest
71
+ mlhs
72
+ or
56
73
  pair
57
74
  rescue
58
75
  send
@@ -60,20 +77,13 @@ module Unparser
60
77
  splat
61
78
  str
62
79
  sym
63
- ].each do |type|
80
+ xstr
81
+ ].to_set.each do |type|
64
82
  name = "n_#{type}?"
65
83
  define_method(name) do |node|
66
84
  n?(type, node)
67
85
  end
68
86
  private(name)
69
87
  end
70
-
71
- def unwrap_single_begin(node)
72
- if n_begin?(node) && node.children.one?
73
- node.children.first
74
- else
75
- node
76
- end
77
- end
78
88
  end # NodeHelpers
79
89
  end # Unparser
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unparser
4
+ # Original code before vendoring and reduction from: https://github.com/mbj/mutant/blob/main/lib/mutant/util.rb
5
+ module Util
6
+ # Error raised by `Util.one` if size is not exactly one
7
+ SizeError = Class.new(IndexError)
8
+
9
+ # Return only element in array if it contains exactly one member
10
+ #
11
+ # @param array [Array]
12
+ #
13
+ # @return [Object] first entry
14
+ def self.one(array)
15
+ case array
16
+ in [value]
17
+ value
18
+ else
19
+ fail SizeError, "expected size to be exactly 1 but size was #{array.size}"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -7,31 +7,38 @@ module Unparser
7
7
  :generated_node,
8
8
  :generated_source,
9
9
  :identification,
10
- :original_node,
10
+ :original_ast,
11
11
  :original_source
12
12
  )
13
13
 
14
+ class PhaseException
15
+ include Anima.new(:exception, :phase)
16
+ end
17
+
14
18
  # Test if source could be unparsed successfully
15
19
  #
16
20
  # @return [Boolean]
17
21
  #
18
22
  # @api private
19
23
  #
24
+ # rubocop:disable Style/OperatorMethodCall
25
+ # mutant:disable
20
26
  def success?
21
27
  [
22
28
  original_source,
23
- original_node,
29
+ original_ast,
24
30
  generated_source,
25
31
  generated_node
26
32
  ].all?(&:right?) && generated_node.from_right.==(original_node.from_right)
27
33
  end
34
+ # rubocop:enable Style/OperatorMethodCall
28
35
 
29
36
  # Return error report
30
37
  #
31
38
  # @return [String]
32
39
  #
33
40
  # @api private
34
- #
41
+ # mutant:disable
35
42
  def report
36
43
  message = [identification]
37
44
 
@@ -45,48 +52,57 @@ module Unparser
45
52
  end
46
53
  memoize :report
47
54
 
55
+ # mutant:disable
56
+ def original_node
57
+ original_ast.fmap(&:node)
58
+ end
59
+
48
60
  # Create validator from string
49
61
  #
50
62
  # @param [String] original_source
51
63
  #
52
64
  # @return [Validator]
65
+ # mutant:disable
53
66
  def self.from_string(original_source)
54
- original_node = Unparser
55
- .parse_either(original_source)
67
+ original_ast = parse_ast_either(original_source)
56
68
 
57
- generated_source = original_node
69
+ generated_source = original_ast
58
70
  .lmap(&method(:const_unit))
59
- .bind(&Unparser.method(:unparse_either))
71
+ .bind(&method(:unparse_ast_either))
60
72
 
61
73
  generated_node = generated_source
62
74
  .lmap(&method(:const_unit))
63
- .bind(&Unparser.method(:parse_either))
75
+ .bind(&method(:parse_ast_either))
76
+ .fmap(&:node)
64
77
 
65
78
  new(
66
- identification: '(string)',
67
- original_source: Either::Right.new(original_source),
68
- original_node: original_node,
79
+ generated_node: generated_node,
69
80
  generated_source: generated_source,
70
- generated_node: generated_node
81
+ identification: '(string)',
82
+ original_ast: original_ast,
83
+ original_source: Either::Right.new(original_source)
71
84
  )
72
85
  end
73
86
 
74
- # Create validator from node
87
+ # Create validator from ast
75
88
  #
76
- # @param [Parser::AST::Node] original_node
89
+ # @param [Unparser::AST] ast
77
90
  #
78
91
  # @return [Validator]
79
- def self.from_node(original_node)
80
- generated_source = Unparser.unparse_either(original_node)
92
+ #
93
+ # mutant:disable
94
+ def self.from_ast(ast:)
95
+ generated_source = Unparser.unparse_ast_either(ast)
81
96
 
82
97
  generated_node = generated_source
83
98
  .lmap(&method(:const_unit))
84
- .bind(&Unparser.public_method(:parse_either))
99
+ .bind(&method(:parse_ast_either))
100
+ .fmap(&:node)
85
101
 
86
102
  new(
87
103
  identification: '(string)',
88
104
  original_source: generated_source,
89
- original_node: Either::Right.new(original_node),
105
+ original_ast: Either::Right.new(ast),
90
106
  generated_source: generated_source,
91
107
  generated_node: generated_node
92
108
  )
@@ -97,24 +113,45 @@ module Unparser
97
113
  # @param [Pathname] path
98
114
  #
99
115
  # @return [Validator]
116
+ #
117
+ # mutant:disable
100
118
  def self.from_path(path)
101
- from_string(path.read).with(identification: path.to_s)
119
+ from_string(path.read.freeze).with(identification: path.to_s)
102
120
  end
103
121
 
122
+ # mutant:disable
123
+ def self.unparse_ast_either(ast)
124
+ Unparser.unparse_ast_either(ast)
125
+ end
126
+ private_class_method :unparse_ast_either
127
+
128
+ # mutant:disable
129
+ def self.parse_ast_either(source)
130
+ Unparser.parse_ast_either(source)
131
+ end
132
+ private_class_method :parse_ast_either
133
+
134
+ # mutant:disable
135
+ def self.const_unit(_); end
136
+ private_class_method :const_unit
137
+
104
138
  private
105
139
 
140
+ # mutant:disable
106
141
  def make_report(label, attribute_name)
107
142
  ["#{label}:"].concat(public_send(attribute_name).either(method(:report_exception), ->(value) { [value] }))
108
143
  end
109
144
 
110
- def report_exception(exception)
111
- if exception
112
- [exception.inspect].concat(exception.backtrace.take(20))
145
+ # mutant:disable
146
+ def report_exception(phase_exception)
147
+ if phase_exception
148
+ [phase_exception.inspect].concat(phase_exception.backtrace.take(20))
113
149
  else
114
- ['undefined']
150
+ %w[undefined]
115
151
  end
116
152
  end
117
153
 
154
+ # mutant:disable
118
155
  def node_diff_report
119
156
  diff = nil
120
157
 
@@ -130,14 +167,13 @@ module Unparser
130
167
  diff ? ['Node-Diff:', diff] : []
131
168
  end
132
169
 
133
- def self.const_unit(_value); end
134
- private_class_method :const_unit
135
-
136
170
  class Literal < self
171
+ # mutant:disable
137
172
  def success?
138
173
  original_source.eql?(generated_source)
139
174
  end
140
175
 
176
+ # mutant:disable
141
177
  def report
142
178
  message = [identification]
143
179
 
@@ -153,20 +189,26 @@ module Unparser
153
189
 
154
190
  private
155
191
 
192
+ # mutant:disable
156
193
  def source_diff_report
157
194
  diff = nil
158
195
 
159
196
  original_source.fmap do |original|
160
197
  generated_source.fmap do |generated|
161
198
  diff = Diff.new(
162
- original.split("\n", -1),
163
- generated.split("\n", -1)
199
+ encode(original).split("\n", -1),
200
+ encode(generated).split("\n", -1)
164
201
  ).colorized_diff
165
202
  end
166
203
  end
167
204
 
168
205
  diff ? ['Source-Diff:', diff] : []
169
206
  end
207
+
208
+ # mutant:disable
209
+ def encode(string)
210
+ string.encode('UTF-8', invalid: :replace, undef: :replace)
211
+ end
170
212
  end # Literal
171
213
  end # Validation
172
214
  end # Unparser
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unparser
4
+ module Writer
5
+ class Array
6
+ include Writer, Adamantium
7
+
8
+ MAP = {
9
+ dsym: '%I',
10
+ sym: '%i',
11
+ dstr: '%W',
12
+ str: '%w'
13
+ }.freeze
14
+ private_constant(*constants(false))
15
+
16
+ def emit_compact # rubocop:disable Metrics/AbcSize
17
+ children_generic_type = array_elements_generic_type
18
+
19
+ write(MAP.fetch(children_generic_type))
20
+
21
+ parentheses('[', ']') do
22
+ delimited(children, ' ') do |child|
23
+ if n_sym?(child) || n_str?(child)
24
+ write(Util.one(child.children).to_s)
25
+ else
26
+ write('#{')
27
+ emitter(Util.one(Util.one(child.children).children)).write_to_buffer
28
+ write('}')
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def array_elements_generic_type
37
+ children_types = children.to_set(&:type)
38
+
39
+ if children_types == Set[:sym, :dsym]
40
+ :dsym
41
+ elsif children_types == Set[:str, :dstr]
42
+ :dstr
43
+ elsif children_types == Set[]
44
+ :sym
45
+ else
46
+ Util.one(children_types.to_a)
47
+ end
48
+ end
49
+ end # Array
50
+ end # Writer
51
+ end # Unparser
@@ -39,7 +39,7 @@ module Unparser
39
39
  tANDOP: '&&'
40
40
  }.freeze
41
41
 
42
- NEED_KEYWORD = %i[return break next].freeze
42
+ NEED_KEYWORD = %i[return break next match_pattern_p].freeze
43
43
 
44
44
  private_constant(*constants(false))
45
45
 
@@ -52,9 +52,13 @@ module Unparser
52
52
  end
53
53
 
54
54
  def dispatch
55
- left_emitter.write_to_buffer
56
- write(' ', MAP.fetch(effective_symbol), ' ')
57
- visit(right)
55
+ if node.type.eql?(:and) && left.type.equal?(:or)
56
+ emit_with(KEYWORD_TOKENS)
57
+ else
58
+ left_emitter.write_to_buffer
59
+ write(' ', MAP.fetch(effective_symbol), ' ')
60
+ visit(right)
61
+ end
58
62
  end
59
63
 
60
64
  private