rubocop-ast 0.0.2 → 0.4.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -4
  3. data/lib/rubocop/ast.rb +12 -7
  4. data/lib/rubocop/ast/builder.rb +8 -1
  5. data/lib/rubocop/ast/ext/range.rb +28 -0
  6. data/lib/rubocop/ast/ext/set.rb +12 -0
  7. data/lib/rubocop/ast/node.rb +81 -10
  8. data/lib/rubocop/ast/node/array_node.rb +2 -8
  9. data/lib/rubocop/ast/node/block_node.rb +1 -1
  10. data/lib/rubocop/ast/node/break_node.rb +1 -6
  11. data/lib/rubocop/ast/node/case_match_node.rb +3 -9
  12. data/lib/rubocop/ast/node/case_node.rb +13 -9
  13. data/lib/rubocop/ast/node/const_node.rb +63 -0
  14. data/lib/rubocop/ast/node/def_node.rb +5 -24
  15. data/lib/rubocop/ast/node/defined_node.rb +2 -0
  16. data/lib/rubocop/ast/node/float_node.rb +1 -0
  17. data/lib/rubocop/ast/node/forward_args_node.rb +15 -0
  18. data/lib/rubocop/ast/node/hash_node.rb +21 -8
  19. data/lib/rubocop/ast/node/if_node.rb +7 -14
  20. data/lib/rubocop/ast/node/index_node.rb +48 -0
  21. data/lib/rubocop/ast/node/indexasgn_node.rb +50 -0
  22. data/lib/rubocop/ast/node/int_node.rb +1 -0
  23. data/lib/rubocop/ast/node/lambda_node.rb +65 -0
  24. data/lib/rubocop/ast/node/mixin/method_dispatch_node.rb +2 -8
  25. data/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb +99 -3
  26. data/lib/rubocop/ast/node/mixin/parameterized_node.rb +56 -0
  27. data/lib/rubocop/ast/node/next_node.rb +12 -0
  28. data/lib/rubocop/ast/node/pair_node.rb +2 -2
  29. data/lib/rubocop/ast/node/regexp_node.rb +56 -0
  30. data/lib/rubocop/ast/node/resbody_node.rb +21 -0
  31. data/lib/rubocop/ast/node/rescue_node.rb +49 -0
  32. data/lib/rubocop/ast/node/return_node.rb +1 -13
  33. data/lib/rubocop/ast/node/send_node.rb +9 -2
  34. data/lib/rubocop/ast/node/super_node.rb +2 -0
  35. data/lib/rubocop/ast/node/when_node.rb +3 -9
  36. data/lib/rubocop/ast/node/yield_node.rb +2 -0
  37. data/lib/rubocop/ast/node_pattern.rb +952 -0
  38. data/lib/rubocop/ast/processed_source.rb +285 -0
  39. data/lib/rubocop/ast/token.rb +116 -0
  40. data/lib/rubocop/ast/traversal.rb +6 -4
  41. data/lib/rubocop/ast/version.rb +1 -1
  42. metadata +19 -13
  43. data/lib/rubocop/ast/node/retry_node.rb +0 -17
  44. data/lib/rubocop/error.rb +0 -34
  45. data/lib/rubocop/node_pattern.rb +0 -881
  46. data/lib/rubocop/processed_source.rb +0 -211
  47. data/lib/rubocop/token.rb +0 -114
@@ -0,0 +1,285 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/sha1'
4
+
5
+ # rubocop:disable Metrics/ClassLength
6
+ module RuboCop
7
+ module AST
8
+ # ProcessedSource contains objects which are generated by Parser
9
+ # and other information such as disabled lines for cops.
10
+ # It also provides a convenient way to access source lines.
11
+ class ProcessedSource
12
+ STRING_SOURCE_NAME = '(string)'
13
+
14
+ attr_reader :path, :buffer, :ast, :comments, :tokens, :diagnostics,
15
+ :parser_error, :raw_source, :ruby_version
16
+
17
+ def self.from_file(path, ruby_version)
18
+ file = File.read(path, mode: 'rb')
19
+ new(file, ruby_version, path)
20
+ end
21
+
22
+ def initialize(source, ruby_version, path = nil)
23
+ # Defaults source encoding to UTF-8, regardless of the encoding it has
24
+ # been read with, which could be non-utf8 depending on the default
25
+ # external encoding.
26
+ source.force_encoding(Encoding::UTF_8) unless source.encoding == Encoding::UTF_8
27
+
28
+ @raw_source = source
29
+ @path = path
30
+ @diagnostics = []
31
+ @ruby_version = ruby_version
32
+ @parser_error = nil
33
+
34
+ parse(source, ruby_version)
35
+ end
36
+
37
+ def ast_with_comments
38
+ return if !ast || !comments
39
+
40
+ @ast_with_comments ||= Parser::Source::Comment.associate(ast, comments)
41
+ end
42
+
43
+ # Returns the source lines, line break characters removed, excluding a
44
+ # possible __END__ and everything that comes after.
45
+ def lines
46
+ @lines ||= begin
47
+ all_lines = @buffer.source_lines
48
+ last_token_line = tokens.any? ? tokens.last.line : all_lines.size
49
+ result = []
50
+ all_lines.each_with_index do |line, ix|
51
+ break if ix >= last_token_line && line == '__END__'
52
+
53
+ result << line
54
+ end
55
+ result
56
+ end
57
+ end
58
+
59
+ def [](*args)
60
+ lines[*args]
61
+ end
62
+
63
+ def valid_syntax?
64
+ return false if @parser_error
65
+
66
+ @diagnostics.none? { |d| %i[error fatal].include?(d.level) }
67
+ end
68
+
69
+ # Raw source checksum for tracking infinite loops.
70
+ def checksum
71
+ Digest::SHA1.hexdigest(@raw_source)
72
+ end
73
+
74
+ # @deprecated Use `comments.each`
75
+ def each_comment(&block)
76
+ comments.each(&block)
77
+ end
78
+
79
+ # @deprecated Use `comment_at_line`, `each_comment_in_lines`, or `comments.find`
80
+ def find_comment(&block)
81
+ comments.find(&block)
82
+ end
83
+
84
+ # @deprecated Use `tokens.each`
85
+ def each_token(&block)
86
+ tokens.each(&block)
87
+ end
88
+
89
+ # @deprecated Use `tokens.find`
90
+ def find_token(&block)
91
+ tokens.find(&block)
92
+ end
93
+
94
+ def file_path
95
+ buffer.name
96
+ end
97
+
98
+ def blank?
99
+ ast.nil?
100
+ end
101
+
102
+ # @return [Comment, nil] the comment at that line, if any.
103
+ def comment_at_line(line)
104
+ comment_index[line]
105
+ end
106
+
107
+ # @return [Boolean] if the given line number has a comment.
108
+ def line_with_comment?(line)
109
+ comment_index.include?(line)
110
+ end
111
+
112
+ # Enumerates on the comments contained with the given `line_range`
113
+ def each_comment_in_lines(line_range)
114
+ return to_enum(:each_comment_in_lines, line_range) unless block_given?
115
+
116
+ line_range.each do |line|
117
+ if (comment = comment_index[line])
118
+ yield comment
119
+ end
120
+ end
121
+ end
122
+
123
+ # @return [Boolean] if any of the lines in the given `source_range` has a comment.
124
+ # Consider using `each_comment_in_lines` instead
125
+ def contains_comment?(source_range)
126
+ each_comment_in_lines(source_range.line..source_range.last_line).any?
127
+ end
128
+ # @deprecated use contains_comment?
129
+ alias commented? contains_comment?
130
+
131
+ # @deprecated Use `each_comment_in_lines`
132
+ # Should have been called `comments_before_or_at_line`. Doubtful it has of any valid use.
133
+ def comments_before_line(line)
134
+ each_comment_in_lines(0..line).to_a
135
+ end
136
+
137
+ def start_with?(string)
138
+ return false if self[0].nil?
139
+
140
+ self[0].start_with?(string)
141
+ end
142
+
143
+ def preceding_line(token)
144
+ lines[token.line - 2]
145
+ end
146
+
147
+ def current_line(token)
148
+ lines[token.line - 1]
149
+ end
150
+
151
+ def following_line(token)
152
+ lines[token.line]
153
+ end
154
+
155
+ def line_indentation(line_number)
156
+ lines[line_number - 1]
157
+ .match(/^(\s*)/)[1]
158
+ .to_s
159
+ .length
160
+ end
161
+
162
+ def tokens_within(range_or_node)
163
+ begin_index = first_token_index(range_or_node)
164
+ end_index = last_token_index(range_or_node)
165
+ sorted_tokens[begin_index..end_index]
166
+ end
167
+
168
+ def first_token_of(range_or_node)
169
+ sorted_tokens[first_token_index(range_or_node)]
170
+ end
171
+
172
+ def last_token_of(range_or_node)
173
+ sorted_tokens[last_token_index(range_or_node)]
174
+ end
175
+
176
+ private
177
+
178
+ def comment_index
179
+ @comment_index ||= {}.tap do |hash|
180
+ comments.each { |c| hash[c.location.line] = c }
181
+ end
182
+ end
183
+
184
+ def parse(source, ruby_version)
185
+ buffer_name = @path || STRING_SOURCE_NAME
186
+ @buffer = Parser::Source::Buffer.new(buffer_name, 1)
187
+
188
+ begin
189
+ @buffer.source = source
190
+ rescue EncodingError => e
191
+ @parser_error = e
192
+ @ast = nil
193
+ @comments = []
194
+ @tokens = []
195
+ return
196
+ end
197
+
198
+ @ast, @comments, @tokens = tokenize(create_parser(ruby_version))
199
+ end
200
+
201
+ def tokenize(parser)
202
+ begin
203
+ ast, comments, tokens = parser.tokenize(@buffer)
204
+ ast ||= nil # force `false` to `nil`, see https://github.com/whitequark/parser/pull/722
205
+ rescue Parser::SyntaxError
206
+ # All errors are in diagnostics. No need to handle exception.
207
+ comments = []
208
+ tokens = []
209
+ end
210
+
211
+ ast&.complete!
212
+ tokens.map! { |t| Token.from_parser_token(t) }
213
+
214
+ [ast, comments, tokens]
215
+ end
216
+
217
+ # rubocop:disable Metrics/MethodLength
218
+ def parser_class(ruby_version)
219
+ case ruby_version
220
+ when 2.4
221
+ require 'parser/ruby24'
222
+ Parser::Ruby24
223
+ when 2.5
224
+ require 'parser/ruby25'
225
+ Parser::Ruby25
226
+ when 2.6
227
+ require 'parser/ruby26'
228
+ Parser::Ruby26
229
+ when 2.7
230
+ require 'parser/ruby27'
231
+ Parser::Ruby27
232
+ when 2.8
233
+ require 'parser/ruby28'
234
+ Parser::Ruby28
235
+ else
236
+ raise ArgumentError,
237
+ "RuboCop found unknown Ruby version: #{ruby_version.inspect}"
238
+ end
239
+ end
240
+ # rubocop:enable Metrics/MethodLength
241
+
242
+ def create_parser(ruby_version)
243
+ builder = RuboCop::AST::Builder.new
244
+
245
+ parser_class(ruby_version).new(builder).tap do |parser|
246
+ # On JRuby there's a risk that we hang in tokenize() if we
247
+ # don't set the all errors as fatal flag. The problem is caused by a bug
248
+ # in Racc that is discussed in issue #93 of the whitequark/parser
249
+ # project on GitHub.
250
+ parser.diagnostics.all_errors_are_fatal = (RUBY_ENGINE != 'ruby')
251
+ parser.diagnostics.ignore_warnings = false
252
+ parser.diagnostics.consumer = lambda do |diagnostic|
253
+ @diagnostics << diagnostic
254
+ end
255
+ end
256
+ end
257
+
258
+ def first_token_index(range_or_node)
259
+ begin_pos = source_range(range_or_node).begin_pos
260
+ sorted_tokens.bsearch_index { |token| token.begin_pos >= begin_pos }
261
+ end
262
+
263
+ def last_token_index(range_or_node)
264
+ end_pos = source_range(range_or_node).end_pos
265
+ sorted_tokens.bsearch_index { |token| token.end_pos >= end_pos }
266
+ end
267
+
268
+ # The tokens list is always sorted by token position, except for cases when heredoc
269
+ # is passed as a method argument. In this case tokens are interleaved by
270
+ # heredoc contents' tokens.
271
+ def sorted_tokens
272
+ @sorted_tokens ||= tokens.sort_by(&:begin_pos)
273
+ end
274
+
275
+ def source_range(range_or_node)
276
+ if range_or_node.respond_to?(:source_range)
277
+ range_or_node.source_range
278
+ else
279
+ range_or_node
280
+ end
281
+ end
282
+ end
283
+ end
284
+ end
285
+ # rubocop:enable Metrics/ClassLength
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module AST
5
+ # A basic wrapper around Parser's tokens.
6
+ class Token
7
+ attr_reader :pos, :type, :text
8
+
9
+ def self.from_parser_token(parser_token)
10
+ type, details = parser_token
11
+ text, range = details
12
+ new(range, type, text)
13
+ end
14
+
15
+ def initialize(pos, type, text)
16
+ @pos = pos
17
+ @type = type
18
+ # Parser token "text" may be an Integer
19
+ @text = text.to_s
20
+ end
21
+
22
+ def line
23
+ @pos.line
24
+ end
25
+
26
+ def column
27
+ @pos.column
28
+ end
29
+
30
+ def begin_pos
31
+ @pos.begin_pos
32
+ end
33
+
34
+ def end_pos
35
+ @pos.end_pos
36
+ end
37
+
38
+ def to_s
39
+ "[[#{line}, #{column}], #{type}, #{text.inspect}]"
40
+ end
41
+
42
+ # Checks if there is whitespace after token
43
+ def space_after?
44
+ pos.source_buffer.source.match(/\G\s/, end_pos)
45
+ end
46
+
47
+ # Checks if there is whitespace before token
48
+ def space_before?
49
+ position = begin_pos.zero? ? begin_pos : begin_pos - 1
50
+ pos.source_buffer.source.match(/\G\s/, position)
51
+ end
52
+
53
+ ## Type Predicates
54
+
55
+ def comment?
56
+ type == :tCOMMENT
57
+ end
58
+
59
+ def semicolon?
60
+ type == :tSEMI
61
+ end
62
+
63
+ def left_array_bracket?
64
+ type == :tLBRACK
65
+ end
66
+
67
+ def left_ref_bracket?
68
+ type == :tLBRACK2
69
+ end
70
+
71
+ def left_bracket?
72
+ %i[tLBRACK tLBRACK2].include?(type)
73
+ end
74
+
75
+ def right_bracket?
76
+ type == :tRBRACK
77
+ end
78
+
79
+ def left_brace?
80
+ type == :tLBRACE
81
+ end
82
+
83
+ def left_curly_brace?
84
+ type == :tLCURLY
85
+ end
86
+
87
+ def right_curly_brace?
88
+ type == :tRCURLY
89
+ end
90
+
91
+ def left_parens?
92
+ %i[tLPAREN tLPAREN2].include?(type)
93
+ end
94
+
95
+ def right_parens?
96
+ type == :tRPAREN
97
+ end
98
+
99
+ def comma?
100
+ type == :tCOMMA
101
+ end
102
+
103
+ def rescue_modifier?
104
+ type == :kRESCUE_MOD
105
+ end
106
+
107
+ def end?
108
+ type == :kEND
109
+ end
110
+
111
+ def equal_sign?
112
+ %i[tEQL tOP_ASGN].include?(type)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -19,21 +19,23 @@ module RuboCop
19
19
  rational str sym regopt self lvar
20
20
  ivar cvar gvar nth_ref back_ref cbase
21
21
  arg restarg blockarg shadowarg
22
- kwrestarg zsuper lambda redo retry
22
+ kwrestarg zsuper redo retry
23
23
  forward_args forwarded_args
24
- match_var match_nil_pattern empty_else].freeze
24
+ match_var match_nil_pattern empty_else
25
+ forward_arg lambda procarg0 __ENCODING__].freeze
25
26
  ONE_CHILD_NODE = %i[splat kwsplat block_pass not break next
26
27
  preexe postexe match_current_line defined?
27
28
  arg_expr pin match_rest if_guard unless_guard
28
29
  match_with_trailing_comma].freeze
29
30
  MANY_CHILD_NODES = %i[dstr dsym xstr regexp array hash pair
30
- mlhs masgn or_asgn and_asgn
31
+ mlhs masgn or_asgn and_asgn rasgn mrasgn
31
32
  undef alias args super yield or and
32
33
  while_post until_post iflipflop eflipflop
33
34
  match_with_lvasgn begin kwbegin return
34
35
  in_match match_alt
35
36
  match_as array_pattern array_pattern_with_tail
36
- hash_pattern const_pattern].freeze
37
+ hash_pattern const_pattern find_pattern
38
+ index indexasgn].freeze
37
39
  SECOND_CHILD_ONLY = %i[lvasgn ivasgn cvasgn gvasgn optarg kwarg
38
40
  kwoptarg].freeze
39
41
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module AST
5
5
  module Version
6
- STRING = '0.0.2'
6
+ STRING = '0.4.0'
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-ast
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
8
8
  - Jonas Arvidsson
9
9
  - Yuji Nakayama
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2020-05-12 00:00:00.000000000 Z
13
+ date: 2020-09-11 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: parser
@@ -18,14 +18,14 @@ dependencies:
18
18
  requirements:
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: 2.7.0.1
21
+ version: 2.7.1.4
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
- version: 2.7.0.1
28
+ version: 2.7.1.4
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: bundler
31
31
  requirement: !ruby/object:Gem::Requirement
@@ -59,6 +59,8 @@ files:
59
59
  - lib/rubocop-ast.rb
60
60
  - lib/rubocop/ast.rb
61
61
  - lib/rubocop/ast/builder.rb
62
+ - lib/rubocop/ast/ext/range.rb
63
+ - lib/rubocop/ast/ext/set.rb
62
64
  - lib/rubocop/ast/node.rb
63
65
  - lib/rubocop/ast/node/alias_node.rb
64
66
  - lib/rubocop/ast/node/and_node.rb
@@ -69,6 +71,7 @@ files:
69
71
  - lib/rubocop/ast/node/case_match_node.rb
70
72
  - lib/rubocop/ast/node/case_node.rb
71
73
  - lib/rubocop/ast/node/class_node.rb
74
+ - lib/rubocop/ast/node/const_node.rb
72
75
  - lib/rubocop/ast/node/def_node.rb
73
76
  - lib/rubocop/ast/node/defined_node.rb
74
77
  - lib/rubocop/ast/node/ensure_node.rb
@@ -77,8 +80,11 @@ files:
77
80
  - lib/rubocop/ast/node/forward_args_node.rb
78
81
  - lib/rubocop/ast/node/hash_node.rb
79
82
  - lib/rubocop/ast/node/if_node.rb
83
+ - lib/rubocop/ast/node/index_node.rb
84
+ - lib/rubocop/ast/node/indexasgn_node.rb
80
85
  - lib/rubocop/ast/node/int_node.rb
81
86
  - lib/rubocop/ast/node/keyword_splat_node.rb
87
+ - lib/rubocop/ast/node/lambda_node.rb
82
88
  - lib/rubocop/ast/node/mixin/basic_literal_node.rb
83
89
  - lib/rubocop/ast/node/mixin/binary_operator_node.rb
84
90
  - lib/rubocop/ast/node/mixin/collection_node.rb
@@ -91,12 +97,13 @@ files:
91
97
  - lib/rubocop/ast/node/mixin/parameterized_node.rb
92
98
  - lib/rubocop/ast/node/mixin/predicate_operator_node.rb
93
99
  - lib/rubocop/ast/node/module_node.rb
100
+ - lib/rubocop/ast/node/next_node.rb
94
101
  - lib/rubocop/ast/node/or_node.rb
95
102
  - lib/rubocop/ast/node/pair_node.rb
96
103
  - lib/rubocop/ast/node/range_node.rb
97
104
  - lib/rubocop/ast/node/regexp_node.rb
98
105
  - lib/rubocop/ast/node/resbody_node.rb
99
- - lib/rubocop/ast/node/retry_node.rb
106
+ - lib/rubocop/ast/node/rescue_node.rb
100
107
  - lib/rubocop/ast/node/return_node.rb
101
108
  - lib/rubocop/ast/node/self_class_node.rb
102
109
  - lib/rubocop/ast/node/send_node.rb
@@ -107,13 +114,12 @@ files:
107
114
  - lib/rubocop/ast/node/when_node.rb
108
115
  - lib/rubocop/ast/node/while_node.rb
109
116
  - lib/rubocop/ast/node/yield_node.rb
117
+ - lib/rubocop/ast/node_pattern.rb
118
+ - lib/rubocop/ast/processed_source.rb
110
119
  - lib/rubocop/ast/sexp.rb
120
+ - lib/rubocop/ast/token.rb
111
121
  - lib/rubocop/ast/traversal.rb
112
122
  - lib/rubocop/ast/version.rb
113
- - lib/rubocop/error.rb
114
- - lib/rubocop/node_pattern.rb
115
- - lib/rubocop/processed_source.rb
116
- - lib/rubocop/token.rb
117
123
  homepage: https://github.com/rubocop-hq/rubocop-ast
118
124
  licenses:
119
125
  - MIT
@@ -121,9 +127,9 @@ metadata:
121
127
  homepage_uri: https://www.rubocop.org/
122
128
  changelog_uri: https://github.com/rubocop-hq/rubocop-ast/blob/master/CHANGELOG.md
123
129
  source_code_uri: https://github.com/rubocop-hq/rubocop-ast/
124
- documentation_uri: https://docs.rubocop.org/
130
+ documentation_uri: https://docs.rubocop.org/rubocop-ast/
125
131
  bug_tracker_uri: https://github.com/rubocop-hq/rubocop-ast/issues
126
- post_install_message:
132
+ post_install_message:
127
133
  rdoc_options: []
128
134
  require_paths:
129
135
  - lib
@@ -139,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
145
  version: '0'
140
146
  requirements: []
141
147
  rubygems_version: 3.1.2
142
- signing_key:
148
+ signing_key:
143
149
  specification_version: 4
144
150
  summary: RuboCop tools to deal with Ruby code AST.
145
151
  test_files: []