unparser 0.4.3 → 0.4.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -45,7 +45,7 @@ module Unparser
45
45
  #
46
46
  # @return [undefined]
47
47
  #
48
- # @pai private
48
+ # @api private
49
49
  #
50
50
  def define_group(name, range)
51
51
  define_method(name) do
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Unparser
4
+ UnknownNodeError = Class.new(ArgumentError)
4
5
 
5
6
  # Emitter base class
6
7
  #
@@ -79,7 +80,7 @@ module Unparser
79
80
 
80
81
  # Register emitter for type
81
82
  #
82
- # @param [Symbol] type
83
+ # @param [Symbol] types
83
84
  #
84
85
  # @return [undefined]
85
86
  #
@@ -116,7 +117,7 @@ module Unparser
116
117
  def self.emitter(node, parent)
117
118
  type = node.type
118
119
  klass = REGISTRY.fetch(type) do
119
- raise ArgumentError, "No emitter for node: #{type.inspect}"
120
+ raise UnknownNodeError, "Unknown node type: #{type.inspect}"
120
121
  end
121
122
  klass.new(node, parent)
122
123
  end
@@ -249,7 +250,6 @@ module Unparser
249
250
  # Emit delimited body
250
251
  #
251
252
  # @param [Enumerable<Parser::AST::Node>] nodes
252
- # @param [String] delimiter
253
253
  #
254
254
  # @return [undefined]
255
255
  #
@@ -262,7 +262,6 @@ module Unparser
262
262
  # Emit delimited body
263
263
  #
264
264
  # @param [Enumerable<Parser::AST::Node>] nodes
265
- # @param [String] delimiter
266
265
  #
267
266
  # @return [undefined]
268
267
  #
@@ -432,7 +431,7 @@ module Unparser
432
431
 
433
432
  # Emit non nil body
434
433
  #
435
- # @param [Parser::AST::Node] node
434
+ # @param [Parser::AST::Node] body
436
435
  #
437
436
  # @return [undefined]
438
437
  #
@@ -240,7 +240,7 @@ module Unparser
240
240
 
241
241
  handle :procarg0
242
242
 
243
- children :first_argument
243
+ PARENS = %i[restarg mlhs].freeze
244
244
 
245
245
  private
246
246
 
@@ -251,22 +251,18 @@ module Unparser
251
251
  # @api private
252
252
  #
253
253
  def dispatch
254
- if first_argument.instance_of?(Symbol)
255
- write(first_argument.to_s)
254
+ if needs_parens?
255
+ parentheses do
256
+ delimited(children)
257
+ end
256
258
  else
257
- emit_multiple_children
259
+ delimited(children)
258
260
  end
259
261
  end
260
262
 
261
- # Emit multiple children
262
- #
263
- # @return [undefined]
264
- #
265
- # @api private
266
- #
267
- def emit_multiple_children
268
- parentheses do
269
- delimited(children)
263
+ def needs_parens?
264
+ children.length > 1 || children.any? do |node|
265
+ PARENS.include?(node.type)
270
266
  end
271
267
  end
272
268
  end
@@ -107,7 +107,7 @@ module Unparser
107
107
 
108
108
  # Write rational format
109
109
  #
110
- # @param [#to_s]
110
+ # @param [#to_s] value
111
111
  #
112
112
  # @return [undefined]
113
113
  #
@@ -27,7 +27,7 @@ module Unparser
27
27
  def dispatch
28
28
  visit(begin_node)
29
29
  write(TOKENS.fetch(node.type))
30
- visit(end_node)
30
+ visit(end_node) if end_node
31
31
  end
32
32
 
33
33
  end # Range
@@ -5,7 +5,8 @@ module Unparser
5
5
 
6
6
  # Helper for building nodes
7
7
  #
8
- # @param [Symbol]
8
+ # @param [Symbol] type
9
+ # @param [Parser::AST::Node] children
9
10
  #
10
11
  # @return [Parser::AST::Node]
11
12
  #
@@ -19,9 +20,10 @@ module Unparser
19
20
 
20
21
  # Helper for building nodes
21
22
  #
22
- # @param [Symbol]
23
+ # @param [Symbol] type
23
24
  #
24
25
  # @return [Parser::AST::Node]
26
+ # @param [Array] children
25
27
  #
26
28
  # @api private
27
29
  #
@@ -114,7 +114,7 @@ module Unparser
114
114
 
115
115
  # Return preprocessor result
116
116
  #
117
- # @param [Parser::AST::Node]
117
+ # @return [Parser::AST::Node]
118
118
  #
119
119
  # @api private
120
120
  #
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unparser
4
+ # Validation of unparser results
5
+ #
6
+ # ignore :reek:TooManyMethods
7
+ class Validation
8
+ include Adamantium::Flat, Anima.new(
9
+ :generated_node,
10
+ :generated_source,
11
+ :identification,
12
+ :original_node,
13
+ :original_source
14
+ )
15
+
16
+ # Test if source could be unparsed successfully
17
+ #
18
+ # @return [Boolean]
19
+ #
20
+ # @api private
21
+ #
22
+ def success?
23
+ [
24
+ original_source,
25
+ original_node,
26
+ generated_source,
27
+ generated_node
28
+ ].all?(&:right?) && generated_node.from_right.eql?(original_node.from_right)
29
+ end
30
+
31
+ # Return error report
32
+ #
33
+ # @return [String]
34
+ #
35
+ # @api private
36
+ #
37
+ def report
38
+ message = [identification]
39
+
40
+ message.concat(make_report('Original-Source', :original_source))
41
+ message.concat(make_report('Generated-Source', :generated_source))
42
+ message.concat(make_report('Original-Node', :original_node))
43
+ message.concat(make_report('Generated-Node', :generated_node))
44
+ message.concat(node_diff_report)
45
+
46
+ message.join("\n")
47
+ end
48
+ memoize :report
49
+
50
+ # Create validator from string
51
+ #
52
+ # @param [String] original_source
53
+ #
54
+ # @return [Validator]
55
+ def self.from_string(original_source)
56
+ original_node = Unparser
57
+ .parse_either(original_source)
58
+ .fmap(&Preprocessor.method(:run))
59
+
60
+ generated_source = original_node
61
+ .lmap(&method(:const_unit))
62
+ .bind(&method(:unparse_either))
63
+
64
+ generated_node = generated_source
65
+ .lmap(&method(:const_unit))
66
+ .bind(&Unparser.method(:parse_either))
67
+ .fmap(&Preprocessor.method(:run))
68
+
69
+ new(
70
+ identification: '(string)',
71
+ original_source: MPrelude::Either::Right.new(original_source),
72
+ original_node: original_node,
73
+ generated_source: generated_source,
74
+ generated_node: generated_node
75
+ )
76
+ end
77
+
78
+ # Create validator from file
79
+ #
80
+ # @param [Pathname] path
81
+ #
82
+ # @return [Validator]
83
+ def self.from_path(path)
84
+ from_string(path.read).with(identification: path.to_s)
85
+ end
86
+
87
+ private
88
+
89
+ # Create a labeled report from
90
+ #
91
+ # @param [String] label
92
+ # @param [Symbol] attribute_name
93
+ #
94
+ # @return [Array<String>]
95
+ def make_report(label, attribute_name)
96
+ ["#{label}:"].concat(public_send(attribute_name).either(method(:report_exception), ->(value) { [value] }))
97
+ end
98
+
99
+ # Report optional exception
100
+ #
101
+ # @param [Exception, nil] exception
102
+ #
103
+ # @return [Array<String>]
104
+ def report_exception(exception)
105
+ if exception
106
+ [exception.inspect].concat(exception.backtrace.take(20))
107
+ else
108
+ ['undefined']
109
+ end
110
+ end
111
+
112
+ # Report the node diff
113
+ #
114
+ # @return [Array<String>]
115
+ def node_diff_report
116
+ diff = nil
117
+
118
+ original_node.fmap do |original|
119
+ generated_node.fmap do |generated|
120
+ diff = Diff.new(
121
+ original.to_s.lines.map(&:chomp),
122
+ generated.to_s.lines.map(&:chomp)
123
+ ).colorized_diff
124
+ end
125
+ end
126
+
127
+ diff ? ['Node-Diff:', diff] : []
128
+ end
129
+
130
+ # Create unit represented as nil
131
+ #
132
+ # @param [Object] _value
133
+ #
134
+ # @return [nil]
135
+ def self.const_unit(_value); end
136
+ private_class_method :const_unit
137
+
138
+ # Unparse capturing errors
139
+ #
140
+ # @param [Parser::AST::Node] node
141
+ #
142
+ # @return [Either<RuntimeError, String>]
143
+ def self.unparse_either(node)
144
+ MPrelude::Either
145
+ .wrap_error(RuntimeError) { Unparser.unparse(node) }
146
+ end
147
+ private_class_method :unparse_either
148
+ end # Validation
149
+ end # Unparser
@@ -81,26 +81,40 @@ describe 'Unparser on ruby corpus', mutant: false do
81
81
  end
82
82
  end
83
83
 
84
- LOADER = Morpher.build do
85
- s(:block,
86
- s(:guard, s(:primitive, Array)),
87
- s(:map,
88
- s(:block,
89
- s(:guard, s(:primitive, Hash)),
90
- s(:hash_transform,
91
- s(:key_symbolize, :repo_uri, s(:guard, s(:primitive, String))),
92
- s(:key_symbolize, :repo_ref, s(:guard, s(:primitive, String))),
93
- s(:key_symbolize, :name, s(:guard, s(:primitive, String))),
94
- s(:key_symbolize, :exclude, s(:map, s(:guard, s(:primitive, String))))),
95
- s(:load_attribute_hash,
96
- # NOTE: The domain param has no DSL currently!
97
- Morpher::Evaluator::Transformer::Domain::Param.new(
98
- Project,
99
- [:repo_uri, :repo_ref, :name, :exclude]
100
- )))))
101
- end
84
+ transform = Mutant::Transform
85
+ string = transform::Primitive.new(String)
86
+ string_array = transform::Array.new(string)
87
+ path = ROOT.join('spec', 'integrations.yml')
88
+
89
+ loader =
90
+ transform::Named.new(
91
+ path.to_s,
92
+ transform::Sequence.new(
93
+ [
94
+ transform::Exception.new(SystemCallError, :read.to_proc),
95
+ transform::Exception.new(YAML::SyntaxError, YAML.method(:safe_load)),
96
+ transform::Array.new(
97
+ transform::Sequence.new(
98
+ [
99
+ transform::Hash.new(
100
+ optional: [],
101
+ required: [
102
+ transform::Hash::Key.new('exclude', string_array),
103
+ transform::Hash::Key.new('name', string),
104
+ transform::Hash::Key.new('repo_ref', string),
105
+ transform::Hash::Key.new('repo_uri', string)
106
+ ]
107
+ ),
108
+ transform::Hash::Symbolize.new,
109
+ transform::Exception.new(Anima::Error, Project.public_method(:new))
110
+ ]
111
+ )
112
+ )
113
+ ]
114
+ )
115
+ )
102
116
 
103
- ALL = LOADER.call(YAML.load_file(ROOT.join('spec', 'integrations.yml')))
117
+ ALL = loader.apply(path).lmap(&:compact_message).from_right
104
118
  end
105
119
 
106
120
  Project::ALL.each do |project|
@@ -18,7 +18,7 @@
18
18
  - name: rubyspec
19
19
  repo_uri: 'https://github.com/ruby/spec.git'
20
20
  # Revision of rubyspec on the last CI build of unparser that passed
21
- repo_ref: 'origin/master'
21
+ repo_ref: 'b40189b88'
22
22
  exclude:
23
23
  - command_line/fixtures/bad_syntax.rb
24
24
  - core/array/pack/shared/float.rb
@@ -40,10 +40,12 @@
40
40
  - core/env/element_reference_spec.rb
41
41
  - core/io/readpartial_spec.rb
42
42
  - core/io/shared/gets_ascii.rb
43
+ - core/kernel/shared/sprintf_encoding.rb
43
44
  - core/marshal/dump_spec.rb
44
45
  - core/marshal/fixtures/marshal_data.rb
45
46
  - core/marshal/shared/load.rb
46
47
  - core/random/bytes_spec.rb
48
+ - core/regexp/shared/new.rb
47
49
  - core/regexp/shared/new_ascii.rb
48
50
  - core/regexp/shared/new_ascii_8bit.rb
49
51
  - core/regexp/shared/quote.rb
@@ -51,6 +53,8 @@
51
53
  - core/string/casecmp_spec.rb
52
54
  - core/string/codepoints_spec.rb
53
55
  - core/string/count_spec.rb
56
+ - core/string/encode_spec.rb
57
+ - core/string/inspect_spec.rb
54
58
  - core/string/shared/codepoints.rb
55
59
  - core/string/shared/each_codepoint_without_block.rb
56
60
  - core/string/shared/eql.rb
@@ -71,6 +75,7 @@
71
75
  - language/regexp/escapes_spec.rb
72
76
  - language/source_encoding_spec.rb
73
77
  - language/string_spec.rb
78
+ - library/base64/decode64_spec.rb
74
79
  - library/digest/md5/shared/constants.rb
75
80
  - library/digest/md5/shared/sample.rb
76
81
  - library/digest/sha1/shared/constants.rb
@@ -1,12 +1,34 @@
1
- require 'yaml'
1
+ require 'anima'
2
+ require 'mutant'
2
3
  require 'pathname'
4
+ require 'timeout'
3
5
  require 'unparser'
4
- require 'anima'
5
- require 'morpher'
6
- require 'devtools/spec_helper'
6
+ require 'yaml'
7
7
 
8
8
  require 'parser/current'
9
9
 
10
+ RSpec.configuration.around(file_path: %r{spec/unit}) do |example|
11
+ Timeout.timeout(0.1, &example)
12
+ end
13
+
14
+ RSpec.shared_examples_for 'a command method' do
15
+ it 'returns self' do
16
+ should equal(object)
17
+ end
18
+ end
19
+
20
+ RSpec.shared_examples_for 'an idempotent method' do
21
+ it 'is idempotent' do
22
+ first = subject
23
+ fail 'RSpec not configured for threadsafety' unless RSpec.configuration.threadsafe?
24
+ mutex = __memoized.instance_variable_get(:@mutex)
25
+ memoized = __memoized.instance_variable_get(:@memoized)
26
+
27
+ mutex.synchronize { memoized.delete(:subject) }
28
+ should equal(first)
29
+ end
30
+ end
31
+
10
32
  module SpecHelper
11
33
  def s(type, *children)
12
34
  Parser::AST::Node.new(type, children)
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Unparser::Color do
4
+ shared_examples 'actual color' do |code|
5
+ describe '#format' do
6
+
7
+ it 'returns formatted string' do
8
+ expect(apply).to eql("\e[#{code}mexample-string\e[0m")
9
+ end
10
+ end
11
+ end
12
+
13
+ describe '#format' do
14
+ let(:input) { 'example-string' }
15
+
16
+ def apply
17
+ object.format(input)
18
+ end
19
+
20
+ context 'RED' do
21
+ let(:object) { described_class::RED }
22
+
23
+ include_examples 'actual color', 31
24
+ end
25
+
26
+ context 'GREEN' do
27
+ let(:object) { described_class::GREEN }
28
+
29
+ include_examples 'actual color', 32
30
+ end
31
+
32
+ context 'NONE' do
33
+ let(:object) { described_class::NONE }
34
+
35
+ it 'returns original input' do
36
+ expect(apply).to be(input)
37
+ end
38
+ end
39
+ end
40
+ end