unparser 0.1.7 → 0.1.8

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -0
  3. data/.travis.yml +3 -0
  4. data/Changelog.md +4 -0
  5. data/README.md +4 -2
  6. data/config/flay.yml +1 -1
  7. data/config/flog.yml +1 -1
  8. data/config/reek.yml +24 -19
  9. data/config/rubocop.yml +2 -3
  10. data/lib/unparser.rb +8 -22
  11. data/lib/unparser/ast.rb +232 -0
  12. data/lib/unparser/ast/local_variable_scope.rb +198 -0
  13. data/lib/unparser/cli.rb +41 -24
  14. data/lib/unparser/cli/differ.rb +38 -16
  15. data/lib/unparser/cli/source.rb +46 -17
  16. data/lib/unparser/constants.rb +23 -6
  17. data/lib/unparser/emitter.rb +32 -0
  18. data/lib/unparser/emitter/argument.rb +30 -4
  19. data/lib/unparser/emitter/assignment.rb +12 -1
  20. data/lib/unparser/emitter/begin.rb +23 -2
  21. data/lib/unparser/emitter/case.rb +1 -1
  22. data/lib/unparser/emitter/class.rb +1 -0
  23. data/lib/unparser/emitter/def.rb +28 -1
  24. data/lib/unparser/emitter/defined.rb +3 -1
  25. data/lib/unparser/emitter/flow_modifier.rb +63 -0
  26. data/lib/unparser/emitter/if.rb +44 -0
  27. data/lib/unparser/emitter/literal/dynamic.rb +25 -1
  28. data/lib/unparser/emitter/literal/hash.rb +3 -3
  29. data/lib/unparser/emitter/literal/primitive.rb +9 -47
  30. data/lib/unparser/emitter/literal/regexp.rb +5 -16
  31. data/lib/unparser/emitter/module.rb +1 -0
  32. data/lib/unparser/emitter/repetition.rb +52 -0
  33. data/lib/unparser/emitter/resbody.rb +4 -2
  34. data/lib/unparser/emitter/rescue.rb +12 -2
  35. data/lib/unparser/emitter/root.rb +2 -11
  36. data/lib/unparser/emitter/send.rb +19 -2
  37. data/lib/unparser/emitter/send/index.rb +42 -4
  38. data/lib/unparser/emitter/send/unary.rb +4 -0
  39. data/lib/unparser/emitter/undef.rb +1 -3
  40. data/lib/unparser/node_helpers.rb +13 -1
  41. data/lib/unparser/preprocessor.rb +226 -0
  42. data/lib/unparser/strip_helper.rb +23 -0
  43. data/rubyspec.sh +20 -0
  44. data/spec/spec_helper.rb +2 -0
  45. data/spec/unit/unparser_spec.rb +390 -151
  46. data/unparser.gemspec +1 -1
  47. metadata +27 -24
  48. data/lib/unparser/cli/preprocessor.rb +0 -197
  49. data/lib/unparser/emitter/break.rb +0 -27
  50. data/lib/unparser/emitter/next.rb +0 -28
  51. data/lib/unparser/emitter/return.rb +0 -41
@@ -0,0 +1,198 @@
1
+ # encoding: UTF-8
2
+
3
+ module Unparser
4
+ module AST
5
+ # Local variable scope enumerator
6
+ class LocalVariableScope
7
+ include Enumerable
8
+
9
+ RESET_NODES = [:module, :class, :sclass, :def, :defs].freeze
10
+ INHERIT_NODES = [:block].freeze
11
+ CLOSE_NODES = (RESET_NODES + INHERIT_NODES).freeze
12
+
13
+ # Nodes that assign a local variable
14
+ #
15
+ # FIXME: Kwargs are missing.
16
+ #
17
+ ASSIGN_NODES = [:lvasgn, :arg, :optarg, :restarg].freeze
18
+
19
+ # Initialize object
20
+ #
21
+ # @return [undefined]
22
+ #
23
+ # @api private
24
+ #
25
+ def initialize
26
+ @stack = [Set.new]
27
+ end
28
+
29
+ # Enumerate each node with its local variable scope
30
+ #
31
+ # @param [Parser::AST::Node] node
32
+ #
33
+ # @return [self]
34
+ #
35
+ # @api private
36
+ #
37
+ def self.each(node, &block)
38
+ new.each(node, &block)
39
+ self
40
+ end
41
+
42
+ # Test for local variable inherited scope reset
43
+ #
44
+ # @param [Parser::AST::Node] node
45
+ #
46
+ # @return [true]
47
+ # if local variable scope must NOT be reset
48
+ #
49
+ # @return [false]
50
+ # otherwise
51
+ #
52
+ # @api private
53
+ #
54
+ def self.not_close_scope?(node)
55
+ !CLOSE_NODES.include?(node.type)
56
+ end
57
+
58
+ # Test for local variable scope reset
59
+ #
60
+ # @param [Parser::AST::Node] node
61
+ #
62
+ # @return [true]
63
+ # if local variable scope must NOT be reset
64
+ #
65
+ # @return [false]
66
+ # otherwise
67
+ #
68
+ # @api private
69
+ #
70
+ def self.not_reset_scope?(node)
71
+ !RESET_NODES.include?(node.type)
72
+ end
73
+
74
+ # Enumerate local variable scope scope
75
+ #
76
+ # @return [self]
77
+ # if block given
78
+ #
79
+ # @return [Enumerator<Array<Symbol>>>]
80
+ # otherwise
81
+ #
82
+ # @api private
83
+ #
84
+ def each(node, &block)
85
+ return to_enum(__method__, node) unless block_given?
86
+ visit(node, &block)
87
+ end
88
+
89
+ private
90
+
91
+ # Return current set of local variables
92
+ #
93
+ # @return [Set<Symbol>]
94
+ #
95
+ # @api private
96
+ #
97
+ def current
98
+ @stack.last
99
+ end
100
+
101
+ # Visit node and record local variable state
102
+ #
103
+ # @param [Parser::AST::Node]
104
+ #
105
+ # @return [undefined]
106
+ #
107
+ # @api private
108
+ #
109
+ def visit(node, &block)
110
+ before = current.dup
111
+ enter(node)
112
+ yield node, current, before
113
+ node.children.each do |child|
114
+ if child.kind_of?(Parser::AST::Node)
115
+ visit(child, &block)
116
+ end
117
+ end
118
+ leave(node)
119
+ end
120
+
121
+ # Record local variable state
122
+ #
123
+ # @param [Parser::AST::Node]
124
+ #
125
+ # @return [undefined]
126
+ #
127
+ # @api private
128
+ #
129
+ def enter(node)
130
+ case node.type
131
+ when *RESET_NODES
132
+ push_reset
133
+ when *ASSIGN_NODES
134
+ define(node.children.first)
135
+ when *INHERIT_NODES
136
+ push_inherit
137
+ end
138
+ end
139
+
140
+ # Pop from local variable state
141
+ #
142
+ # @param [Parser::AST::Node] node
143
+ #
144
+ # @return [undefined]
145
+ #
146
+ # @api private
147
+ #
148
+ def leave(node)
149
+ if CLOSE_NODES.include?(node.type)
150
+ pop
151
+ end
152
+ end
153
+
154
+ # Define a local variable on current stack
155
+ #
156
+ # @param [Symbol] name
157
+ #
158
+ # @return [undefined]
159
+ #
160
+ # @api private
161
+ #
162
+ def define(name)
163
+ current << name
164
+ end
165
+
166
+ # Push reset scope on stack
167
+ #
168
+ # @return [undefined]
169
+ #
170
+ # @api private
171
+ #
172
+ def push_reset
173
+ @stack << Set.new
174
+ end
175
+
176
+ # Push inherited lvar scope on stack
177
+ #
178
+ # @return [undefined]
179
+ #
180
+ # @api private
181
+ #
182
+ def push_inherit
183
+ @stack << current.dup
184
+ end
185
+
186
+ # Pop lvar scope from stack
187
+ #
188
+ # @return [undefined]
189
+ #
190
+ # @api private
191
+ #
192
+ def pop
193
+ @stack.pop
194
+ end
195
+
196
+ end # LocalVariableScope
197
+ end # AST
198
+ end # Unparser
data/lib/unparser/cli.rb CHANGED
@@ -5,7 +5,6 @@ require 'optparse'
5
5
  require 'diff/lcs'
6
6
  require 'diff/lcs/hunk'
7
7
 
8
- require 'unparser/cli/preprocessor'
9
8
  require 'unparser/cli/source'
10
9
  require 'unparser/cli/differ'
11
10
  require 'unparser/cli/color'
@@ -39,7 +38,7 @@ module Unparser
39
38
  # @api private
40
39
  #
41
40
  def initialize(arguments)
42
- @sources = []
41
+ @sources, @ignore = [], Set.new
43
42
 
44
43
  @success = true
45
44
  @fail_fast = false
@@ -48,14 +47,8 @@ module Unparser
48
47
  add_options(builder)
49
48
  end
50
49
 
51
- arguments = opts.parse!(arguments)
52
-
53
- arguments.each do |name|
54
- if File.directory?(name)
55
- add_directory(name)
56
- else
57
- add_file(name)
58
- end
50
+ opts.parse!(arguments).each do |name|
51
+ @sources.concat(sources(name))
59
52
  end
60
53
  end
61
54
 
@@ -73,6 +66,12 @@ module Unparser
73
66
  builder.on('-e', '--evaluate SOURCE') do |original_source|
74
67
  @sources << Source::String.new(original_source)
75
68
  end
69
+ builder.on('--start-with FILE') do |file|
70
+ @start_with = sources(file).first
71
+ end
72
+ builder.on('--ignore FILE') do |file|
73
+ @ignore.merge(sources(file))
74
+ end
76
75
  builder.on('--fail-fast') do
77
76
  @fail_fast = true
78
77
  end
@@ -85,7 +84,8 @@ module Unparser
85
84
  # @api private
86
85
  #
87
86
  def exit_status
88
- @sources.each do |source|
87
+ effective_sources.each do |source|
88
+ next if @ignore.include?(source)
89
89
  process_source(source)
90
90
  if @fail_fast
91
91
  break unless @success
@@ -115,30 +115,47 @@ module Unparser
115
115
  end
116
116
  end
117
117
 
118
- # Add file
118
+ # Return effective sources
119
119
  #
120
- # @param [String] file_name
121
- #
122
- # @return [undefined]
120
+ # @return [Enumerable<CLI::Source>]
123
121
  #
124
122
  # @api private
125
123
  #
126
- def add_file(file_name)
127
- @sources << Source::File.new(file_name)
124
+ def effective_sources
125
+ if @start_with
126
+ reject = true
127
+ @sources.reject do |source|
128
+ if reject && source == @start_with
129
+ reject = false
130
+ end
131
+
132
+ reject
133
+ end
134
+ else
135
+ @sources
136
+ end
128
137
  end
129
138
 
130
- # Add directory
139
+ # Return sources for file name
131
140
  #
132
- # @param [String] directory_name
141
+ # @param [String] file_name
133
142
  #
134
- # @return [undefined]
143
+ # @return [Enumerable<CLI::Source>]
135
144
  #
136
145
  # @api private
137
146
  #
138
- def add_directory(directory_name)
139
- Dir.glob(File.join(directory_name, '**/*.rb')).each do |file_name|
140
- add_file(file_name)
141
- end
147
+ def sources(file_name)
148
+ files =
149
+ case
150
+ when File.directory?(file_name)
151
+ Dir.glob(File.join(file_name, '**/*.rb')).sort
152
+ when File.file?(file_name)
153
+ [file_name]
154
+ else
155
+ Dir.glob(file_name).sort
156
+ end
157
+
158
+ files.map(&Source::File.method(:new))
142
159
  end
143
160
 
144
161
  end # CLI
@@ -6,6 +6,41 @@ module Unparser
6
6
  class Differ
7
7
  include Adamantium::Flat, Concord.new(:old, :new), Procto.call(:colorized_diff)
8
8
 
9
+ CONTEXT_LINES = 5
10
+
11
+ # Return hunks
12
+ #
13
+ # @return [Array<Diff::LCS::Hunk>]
14
+ #
15
+ # @api private
16
+ #
17
+ def hunks
18
+ file_length_difference = new.length - old.length
19
+ diffs.map do |piece|
20
+ hunk = Diff::LCS::Hunk.new(old, new, piece, CONTEXT_LINES, file_length_difference)
21
+ file_length_difference = hunk.file_length_difference
22
+ hunk
23
+ end
24
+ end
25
+
26
+ # Return collapsed hunks
27
+ #
28
+ # @return [Enumerable<Diff::LCS::Hunk>]
29
+ #
30
+ # @api private
31
+ #
32
+ def collapsed_hunks
33
+ hunks.each_with_object([]) do |hunk, output|
34
+ last = output.last
35
+
36
+ if last && hunk.merge(last)
37
+ output.pop
38
+ end
39
+
40
+ output << hunk
41
+ end
42
+ end
43
+
9
44
  # Return source diff
10
45
  #
11
46
  # @return [String]
@@ -17,24 +52,11 @@ module Unparser
17
52
  # @api private
18
53
  #
19
54
  def diff
20
- output = ""
21
- lines = 5
22
- hunk = oldhunk = nil
23
- file_length_difference = new.length - old.length
24
- diffs.each do |piece|
25
- begin
26
- hunk = Diff::LCS::Hunk.new(old, new, piece, lines, file_length_difference)
27
- file_length_difference = hunk.file_length_difference
55
+ output = ''
28
56
 
29
- next unless oldhunk
30
- next if (lines > 0) && hunk.merge(oldhunk)
31
-
32
- output << oldhunk.diff(:unified) << "\n"
33
- ensure
34
- oldhunk = hunk
35
- end
57
+ collapsed_hunks.each do |hunk|
58
+ output << hunk.diff(:unified) << "\n"
36
59
  end
37
- output << oldhunk.diff(:unified) << "\n"
38
60
 
39
61
  output
40
62
  end
@@ -26,15 +26,17 @@ module Unparser
26
26
  # @api private
27
27
  #
28
28
  def error_report
29
- if original_ast && generated_ast
29
+ case
30
+ when original_ast && generated_ast
30
31
  error_report_with_ast_diff
31
- else
32
- error_report_with_parser_error
32
+ when !original_ast
33
+ error_report_original
34
+ when !generated_ast
35
+ error_report_generated
33
36
  end
34
37
  end
35
38
  memoize :error_report
36
39
 
37
-
38
40
  private
39
41
 
40
42
  # Return generated source
@@ -48,21 +50,33 @@ module Unparser
48
50
  end
49
51
  memoize :generated_source
50
52
 
51
- # Return error report with parser error
53
+ # Return error report for parsing original
52
54
  #
53
55
  # @return [String]
54
56
  #
55
57
  # @api private
56
58
  #
57
- def error_report_with_parser_error
58
- unless original_ast
59
- return "Parsing of original source failed:\n#{original_source}"
60
- end
61
-
62
- unless generated_ast
63
- return "Parsing of generated source failed:\nOriginal-AST:#{original_ast.inspect}\nSource:\n#{generated_source}"
64
- end
59
+ def error_report_original
60
+ strip(<<-MESSAGE)
61
+ Parsing of original source failed:
62
+ #{original_source}
63
+ MESSAGE
64
+ end
65
65
 
66
+ # Return error report for parsing generated
67
+ #
68
+ # @return [String]
69
+ #
70
+ # @api private
71
+ #
72
+ def error_report_generated
73
+ strip(<<-MESSAGE)
74
+ Parsing of generated source failed:
75
+ Original-AST:
76
+ #{original_ast.inspect}
77
+ Source:
78
+ #{generated_source}
79
+ MESSAGE
66
80
  end
67
81
 
68
82
  # Return error report with AST difference
@@ -72,11 +86,26 @@ module Unparser
72
86
  # @api private
73
87
  #
74
88
  def error_report_with_ast_diff
75
- diff = Differ.call(
89
+ strip(<<-MESSAGE)
90
+ #{diff}
91
+ Original-Source:\n#{original_source}
92
+ Original-AST:\n#{original_ast.inspect}
93
+ Generated-Source:\n#{generated_source}
94
+ Generated-AST:\n#{generated_ast.inspect}
95
+ MESSAGE
96
+ end
97
+
98
+ # Return ast diff
99
+ #
100
+ # @return [String]
101
+ #
102
+ # @api private
103
+ #
104
+ def ast_diff
105
+ Differ.call(
76
106
  original_ast.inspect.lines.map(&:chomp),
77
107
  generated_ast.inspect.lines.map(&:chomp)
78
108
  )
79
- "#{diff}\nOriginal:\n#{original_source}\nGenerated:\n#{generated_source}"
80
109
  end
81
110
 
82
111
  # Return generated AST
@@ -90,7 +119,7 @@ module Unparser
90
119
  # @api private
91
120
  #
92
121
  def generated_ast
93
- Preprocessor.run(parse(generated_source)) || s(:empty)
122
+ Preprocessor.run(parse(generated_source))
94
123
  rescue Parser::SyntaxError
95
124
  nil
96
125
  end
@@ -103,7 +132,7 @@ module Unparser
103
132
  # @api private
104
133
  #
105
134
  def original_ast
106
- Preprocessor.run(parse(original_source)) || s(:empty)
135
+ Preprocessor.run(parse(original_source))
107
136
  rescue Parser::SyntaxError
108
137
  nil
109
138
  end