unparser 0.1.7 → 0.1.8

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