rubocop-ast 0.0.3 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +16 -3
- data/lib/rubocop/ast.rb +4 -0
- data/lib/rubocop/ast/builder.rb +3 -0
- data/lib/rubocop/ast/node.rb +18 -2
- data/lib/rubocop/ast/node/def_node.rb +1 -1
- data/lib/rubocop/ast/node/forward_args_node.rb +15 -0
- data/lib/rubocop/ast/node/index_node.rb +46 -0
- data/lib/rubocop/ast/node/indexasgn_node.rb +48 -0
- data/lib/rubocop/ast/node/lambda_node.rb +58 -0
- data/lib/rubocop/ast/node/mixin/method_dispatch_node.rb +2 -1
- data/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb +99 -3
- data/lib/rubocop/ast/node/mixin/parameterized_node.rb +1 -0
- data/lib/rubocop/ast/node/regexp_node.rb +56 -0
- data/lib/rubocop/ast/node/send_node.rb +2 -1
- data/lib/rubocop/ast/node_pattern.rb +119 -63
- data/lib/rubocop/ast/processed_source.rb +5 -0
- data/lib/rubocop/ast/traversal.rb +5 -3
- data/lib/rubocop/ast/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4108494066548a429e972d3f2afcaa9adfd39072a929308cdb20e4a602fef66d
|
4
|
+
data.tar.gz: 413b4382539bffed1760d0599bc093ad40ffef64992f821484994150903812da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97af9dd864e8f6867614a79b2aac596e18e147b9a55bcc67b8755e47ef762730ec69612dcc6858ee12017b33beae0b89e008e1267034f1730682015c4efa4255
|
7
|
+
data.tar.gz: 807305ab6dc90885fff96d1387d787debf8ce8f27a0d150ad4bf86a76d4afb2d288f0771b2399cf3c3cfb1472c72cd750e4df9e97ad07467da4615a97c062f79
|
data/README.md
CHANGED
@@ -2,12 +2,15 @@
|
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/rubocop-ast)
|
4
4
|
[](https://github.com/rubocop-hq/rubocop-ast/actions?query=workflow%3ACI)
|
5
|
+
[](https://codeclimate.com/github/rubocop-hq/rubocop-ast/test_coverage)
|
6
|
+
[](https://codeclimate.com/github/rubocop-hq/rubocop-ast/maintainability)
|
5
7
|
|
6
8
|
Contains the classes needed by [RuboCop](https://github.com/rubocop-hq/rubocop) to deal with Ruby's AST, in particular:
|
7
9
|
* `RuboCop::AST::Node`
|
8
|
-
* `RuboCop::AST::NodePattern` ([doc](
|
10
|
+
* `RuboCop::AST::NodePattern` ([doc](docs/modules/ROOT/pages/node_pattern.adoc))
|
9
11
|
|
10
|
-
This gem may be used independently from the main RuboCop gem.
|
12
|
+
This gem may be used independently from the main RuboCop gem. It was extracted from RuboCop in version 0.84 and its only
|
13
|
+
dependency is the `parser` gem, which `rubocop-ast` extends.
|
11
14
|
|
12
15
|
## Installation
|
13
16
|
|
@@ -25,7 +28,17 @@ gem 'rubocop-ast'
|
|
25
28
|
|
26
29
|
## Usage
|
27
30
|
|
28
|
-
Refer to the documentation of `RuboCop::AST::Node` and [`RuboCop::AST::NodePattern`](
|
31
|
+
Refer to the documentation of `RuboCop::AST::Node` and [`RuboCop::AST::NodePattern`](docs/modules/ROOT/pages/node_pattern.adoc)
|
32
|
+
|
33
|
+
### Parser compatibility switches
|
34
|
+
|
35
|
+
The main `RuboCop` gem uses [legacy AST output from parser](https://github.com/whitequark/parser/#usage).
|
36
|
+
This gem is meant to be compatible with all settings. For example, to have `-> { ... }` emitted
|
37
|
+
as `LambdaNode` instead of `SendNode`:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
RuboCop::AST::Builder.emit_lambda = true
|
41
|
+
```
|
29
42
|
|
30
43
|
## Contributing
|
31
44
|
|
data/lib/rubocop/ast.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'parser'
|
4
4
|
require 'forwardable'
|
5
|
+
require 'set'
|
5
6
|
|
6
7
|
require_relative 'ast/node_pattern'
|
7
8
|
require_relative 'ast/sexp'
|
@@ -34,8 +35,11 @@ require_relative 'ast/node/forward_args_node'
|
|
34
35
|
require_relative 'ast/node/float_node'
|
35
36
|
require_relative 'ast/node/hash_node'
|
36
37
|
require_relative 'ast/node/if_node'
|
38
|
+
require_relative 'ast/node/index_node'
|
39
|
+
require_relative 'ast/node/indexasgn_node'
|
37
40
|
require_relative 'ast/node/int_node'
|
38
41
|
require_relative 'ast/node/keyword_splat_node'
|
42
|
+
require_relative 'ast/node/lambda_node'
|
39
43
|
require_relative 'ast/node/module_node'
|
40
44
|
require_relative 'ast/node/or_node'
|
41
45
|
require_relative 'ast/node/pair_node'
|
data/lib/rubocop/ast/builder.rb
CHANGED
@@ -35,9 +35,12 @@ module RuboCop
|
|
35
35
|
hash: HashNode,
|
36
36
|
if: IfNode,
|
37
37
|
int: IntNode,
|
38
|
+
index: IndexNode,
|
39
|
+
indexasgn: IndexasgnNode,
|
38
40
|
irange: RangeNode,
|
39
41
|
erange: RangeNode,
|
40
42
|
kwsplat: KeywordSplatNode,
|
43
|
+
lambda: LambdaNode,
|
41
44
|
module: ModuleNode,
|
42
45
|
or: OrNode,
|
43
46
|
pair: PairNode,
|
data/lib/rubocop/ast/node.rb
CHANGED
@@ -44,6 +44,8 @@ module RuboCop
|
|
44
44
|
|
45
45
|
BASIC_CONDITIONALS = %i[if while until].freeze
|
46
46
|
CONDITIONALS = [*BASIC_CONDITIONALS, :case].freeze
|
47
|
+
POST_CONDITION_LOOP_TYPES = %i[while_post until_post].freeze
|
48
|
+
LOOP_TYPES = (POST_CONDITION_LOOP_TYPES + %i[while until for]).freeze
|
47
49
|
VARIABLES = %i[ivar gvar cvar lvar].freeze
|
48
50
|
REFERENCES = %i[nth_ref back_ref].freeze
|
49
51
|
KEYWORDS = %i[alias and break case class def defs defined?
|
@@ -53,6 +55,7 @@ module RuboCop
|
|
53
55
|
yield].freeze
|
54
56
|
OPERATOR_KEYWORDS = %i[and or].freeze
|
55
57
|
SPECIAL_KEYWORDS = %w[__FILE__ __LINE__ __ENCODING__].freeze
|
58
|
+
ARGUMENT_TYPES = %i[arg optarg restarg kwarg kwoptarg kwrestarg blockarg].freeze
|
56
59
|
|
57
60
|
# @see https://www.rubydoc.info/gems/ast/AST/Node:initialize
|
58
61
|
def initialize(type, children = [], properties = {})
|
@@ -425,6 +428,15 @@ module RuboCop
|
|
425
428
|
CONDITIONALS.include?(type)
|
426
429
|
end
|
427
430
|
|
431
|
+
def post_condition_loop?
|
432
|
+
POST_CONDITION_LOOP_TYPES.include?(type)
|
433
|
+
end
|
434
|
+
|
435
|
+
# Note: `loop { }` is a normal method call and thus not a loop keyword.
|
436
|
+
def loop_keyword?
|
437
|
+
LOOP_TYPES.include?(type)
|
438
|
+
end
|
439
|
+
|
428
440
|
def keyword?
|
429
441
|
return true if special_keyword? || send_type? && prefix_not?
|
430
442
|
return false unless KEYWORDS.include?(type)
|
@@ -456,6 +468,10 @@ module RuboCop
|
|
456
468
|
parent&.send_type? && parent.arguments.include?(self)
|
457
469
|
end
|
458
470
|
|
471
|
+
def argument_type?
|
472
|
+
ARGUMENT_TYPES.include?(type)
|
473
|
+
end
|
474
|
+
|
459
475
|
def boolean_type?
|
460
476
|
true_type? || false_type?
|
461
477
|
end
|
@@ -500,7 +516,7 @@ module RuboCop
|
|
500
516
|
# So, does the return value of this node matter? If we changed it to
|
501
517
|
# `(...; nil)`, might that affect anything?
|
502
518
|
#
|
503
|
-
# rubocop:disable Metrics/MethodLength
|
519
|
+
# rubocop:disable Metrics/MethodLength
|
504
520
|
def value_used?
|
505
521
|
# Be conservative and return true if we're not sure.
|
506
522
|
return false if parent.nil?
|
@@ -522,7 +538,7 @@ module RuboCop
|
|
522
538
|
true
|
523
539
|
end
|
524
540
|
end
|
525
|
-
# rubocop:enable Metrics/MethodLength
|
541
|
+
# rubocop:enable Metrics/MethodLength
|
526
542
|
|
527
543
|
# Some expressions are evaluated for their value, some for their side
|
528
544
|
# effects, and some for both.
|
@@ -24,7 +24,7 @@ module RuboCop
|
|
24
24
|
#
|
25
25
|
# @return [Boolean] whether the `def` node uses argument forwarding
|
26
26
|
def argument_forwarding?
|
27
|
-
arguments.any?(&:forward_args_type?)
|
27
|
+
arguments.any?(&:forward_args_type?) || arguments.any?(&:forward_arg_type?)
|
28
28
|
end
|
29
29
|
|
30
30
|
# The name of the defined method as a symbol.
|
@@ -5,6 +5,21 @@ module RuboCop
|
|
5
5
|
# A node extension for `forward-args` nodes. This will be used in place
|
6
6
|
# of a plain node when the builder constructs the AST, making its methods
|
7
7
|
# available to all `forward-args` nodes within RuboCop.
|
8
|
+
#
|
9
|
+
# Not used with modern emitters:
|
10
|
+
#
|
11
|
+
# $ ruby-parse -e "def foo(...); end"
|
12
|
+
# (def :foo
|
13
|
+
# (args
|
14
|
+
# (forward-arg)) nil)
|
15
|
+
# $ ruby-parse --legacy -e "->(foo) { bar }"
|
16
|
+
# (def :foo
|
17
|
+
# (forward-args) nil)
|
18
|
+
#
|
19
|
+
# Note the extra 's' with legacy form.
|
20
|
+
#
|
21
|
+
# The main RuboCop runs in legacy mode; this node is only used
|
22
|
+
# if user `AST::Builder.modernize` or `AST::Builder.emit_lambda=true`
|
8
23
|
class ForwardArgsNode < Node
|
9
24
|
include CollectionNode
|
10
25
|
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module AST
|
5
|
+
# Used for modern support only!
|
6
|
+
# Not as thoroughly tested as legacy equivalent
|
7
|
+
#
|
8
|
+
# $ ruby-parse -e "foo[:bar]"
|
9
|
+
# (index
|
10
|
+
# (send nil :foo)
|
11
|
+
# (sym :bar))
|
12
|
+
# $ ruby-parse --legacy -e "foo[:bar]"
|
13
|
+
# (send
|
14
|
+
# (send nil :foo) :[]
|
15
|
+
# (sym :bar))
|
16
|
+
#
|
17
|
+
# The main RuboCop runs in legacy mode; this node is only used
|
18
|
+
# if user `AST::Builder.modernize` or `AST::Builder.emit_index=true`
|
19
|
+
class IndexNode < Node
|
20
|
+
include ParameterizedNode
|
21
|
+
include MethodDispatchNode
|
22
|
+
|
23
|
+
# For similarity with legacy mode
|
24
|
+
def attribute_accessor?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
# For similarity with legacy mode
|
29
|
+
def assignment_method?
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
# For similarity with legacy mode
|
34
|
+
def method_name
|
35
|
+
:[]
|
36
|
+
end
|
37
|
+
|
38
|
+
# An array containing the arguments of the dispatched method.
|
39
|
+
#
|
40
|
+
# @return [Array<Node>] the arguments of the dispatched method
|
41
|
+
def arguments
|
42
|
+
node_parts[1..-1]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module AST
|
5
|
+
# Used for modern support only!
|
6
|
+
# Not as thoroughly tested as legacy equivalent
|
7
|
+
#
|
8
|
+
# $ ruby-parse -e "foo[:bar] = :baz"
|
9
|
+
# (indexasgn
|
10
|
+
# (send nil :foo)
|
11
|
+
# (sym :bar)
|
12
|
+
# (sym :baz))
|
13
|
+
# $ ruby-parse --legacy -e "foo[:bar] = :baz"
|
14
|
+
# (send
|
15
|
+
# (send nil :foo) :[]=
|
16
|
+
# (sym :bar)
|
17
|
+
# (sym :baz))
|
18
|
+
#
|
19
|
+
# The main RuboCop runs in legacy mode; this node is only used
|
20
|
+
# if user `AST::Builder.modernize` or `AST::Builder.emit_index=true`
|
21
|
+
class IndexasgnNode < Node
|
22
|
+
include ParameterizedNode
|
23
|
+
include MethodDispatchNode
|
24
|
+
|
25
|
+
# For similarity with legacy mode
|
26
|
+
def attribute_accessor?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
# For similarity with legacy mode
|
31
|
+
def assignment_method?
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
# For similarity with legacy mode
|
36
|
+
def method_name
|
37
|
+
:[]=
|
38
|
+
end
|
39
|
+
|
40
|
+
# An array containing the arguments of the dispatched method.
|
41
|
+
#
|
42
|
+
# @return [Array<Node>] the arguments of the dispatched method
|
43
|
+
def arguments
|
44
|
+
node_parts[1..-1]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module AST
|
5
|
+
# Used for modern support only:
|
6
|
+
# Not as thoroughly tested as legacy equivalent
|
7
|
+
#
|
8
|
+
# $ ruby-parse -e "->(foo) { bar }"
|
9
|
+
# (block
|
10
|
+
# (lambda)
|
11
|
+
# (args
|
12
|
+
# (arg :foo))
|
13
|
+
# (send nil :bar))
|
14
|
+
# $ ruby-parse --legacy -e "->(foo) { bar }"
|
15
|
+
# (block
|
16
|
+
# (send nil :lambda)
|
17
|
+
# (args
|
18
|
+
# (arg :foo))
|
19
|
+
# (send nil :bar))
|
20
|
+
#
|
21
|
+
# The main RuboCop runs in legacy mode; this node is only used
|
22
|
+
# if user `AST::Builder.modernize` or `AST::Builder.emit_lambda=true`
|
23
|
+
class LambdaNode < Node
|
24
|
+
include ParameterizedNode
|
25
|
+
include MethodDispatchNode
|
26
|
+
|
27
|
+
# For similarity with legacy mode
|
28
|
+
def lambda?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
# For similarity with legacy mode
|
33
|
+
def lambda_literal?
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
# For similarity with legacy mode
|
38
|
+
def attribute_accessor?
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
# For similarity with legacy mode
|
43
|
+
def assignment_method?
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
# For similarity with legacy mode
|
48
|
+
def method_name
|
49
|
+
:lambda
|
50
|
+
end
|
51
|
+
|
52
|
+
# For similarity with legacy mode
|
53
|
+
def arguments
|
54
|
+
[]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -3,7 +3,8 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module AST
|
5
5
|
# Common functionality for nodes that are a kind of method dispatch:
|
6
|
-
# `send`, `csend`, `super`, `zsuper`, `yield`, `defined
|
6
|
+
# `send`, `csend`, `super`, `zsuper`, `yield`, `defined?`,
|
7
|
+
# and (modern only): `index`, `indexasgn`, `lambda`
|
7
8
|
module MethodDispatchNode
|
8
9
|
extend NodePattern::Macros
|
9
10
|
include MethodIdentifierPredicates
|
@@ -6,15 +6,62 @@ module RuboCop
|
|
6
6
|
# `send`, `csend`, `def`, `defs`, `super`, `zsuper`
|
7
7
|
#
|
8
8
|
# @note this mixin expects `#method_name` and `#receiver` to be implemented
|
9
|
-
module MethodIdentifierPredicates
|
9
|
+
module MethodIdentifierPredicates # rubocop:disable Metrics/ModuleLength
|
10
10
|
ENUMERATOR_METHODS = %i[collect collect_concat detect downto each
|
11
11
|
find find_all find_index inject loop map!
|
12
12
|
map reduce reject reject! reverse_each select
|
13
|
-
select! times upto].freeze
|
13
|
+
select! times upto].to_set.freeze
|
14
|
+
|
15
|
+
ENUMERABLE_METHODS = (Enumerable.instance_methods + [:each]).to_set.freeze
|
14
16
|
|
15
17
|
# http://phrogz.net/programmingruby/language.html#table_18.4
|
16
18
|
OPERATOR_METHODS = %i[| ^ & <=> == === =~ > >= < <= << >> + - * /
|
17
|
-
% ** ~ +@ -@ !@ ~@ [] []= ! != !~ `].freeze
|
19
|
+
% ** ~ +@ -@ !@ ~@ [] []= ! != !~ `].to_set.freeze
|
20
|
+
|
21
|
+
NONMUTATING_BINARY_OPERATOR_METHODS = %i[* / % + - == === != < > <= >= <=>].to_set.freeze
|
22
|
+
NONMUTATING_UNARY_OPERATOR_METHODS = %i[+@ -@ ~ !].to_set.freeze
|
23
|
+
NONMUTATING_OPERATOR_METHODS = (NONMUTATING_BINARY_OPERATOR_METHODS +
|
24
|
+
NONMUTATING_UNARY_OPERATOR_METHODS).freeze
|
25
|
+
|
26
|
+
NONMUTATING_ARRAY_METHODS = %i[
|
27
|
+
all? any? assoc at bsearch bsearch_index collect
|
28
|
+
combination compact count cycle deconstruct difference
|
29
|
+
dig drop drop_while each each_index empty? eql?
|
30
|
+
fetch filter find_index first flatten hash
|
31
|
+
include? index inspect intersection join
|
32
|
+
last length map max min minmax none? one? pack
|
33
|
+
permutation product rassoc reject
|
34
|
+
repeated_combination repeated_permutation reverse
|
35
|
+
reverse_each rindex rotate sample select shuffle
|
36
|
+
size slice sort sum take take_while
|
37
|
+
to_a to_ary to_h to_s transpose union uniq
|
38
|
+
values_at zip |
|
39
|
+
].to_set.freeze
|
40
|
+
|
41
|
+
NONMUTATING_HASH_METHODS = %i[
|
42
|
+
any? assoc compact dig each each_key each_pair
|
43
|
+
each_value empty? eql? fetch fetch_values filter
|
44
|
+
flatten has_key? has_value? hash include? inspect
|
45
|
+
invert key key? keys? length member? merge rassoc
|
46
|
+
rehash reject select size slice to_a to_h to_hash
|
47
|
+
to_proc to_s transform_keys transform_values value?
|
48
|
+
values values_at
|
49
|
+
].to_set.freeze
|
50
|
+
|
51
|
+
NONMUTATING_STRING_METHODS = %i[
|
52
|
+
ascii_only? b bytes bytesize byteslice capitalize
|
53
|
+
casecmp casecmp? center chars chomp chop chr codepoints
|
54
|
+
count crypt delete delete_prefix delete_suffix
|
55
|
+
downcase dump each_byte each_char each_codepoint
|
56
|
+
each_grapheme_cluster each_line empty? encode encoding
|
57
|
+
end_with? eql? getbyte grapheme_clusters gsub hash
|
58
|
+
hex include index inspect intern length lines ljust lstrip
|
59
|
+
match match? next oct ord partition reverse rindex rjust
|
60
|
+
rpartition rstrip scan scrub size slice squeeze start_with?
|
61
|
+
strip sub succ sum swapcase to_a to_c to_f to_i to_r to_s
|
62
|
+
to_str to_sym tr tr_s unicode_normalize unicode_normalized?
|
63
|
+
unpack unpack1 upcase upto valid_encoding?
|
64
|
+
].to_set.freeze
|
18
65
|
|
19
66
|
# Checks whether the method name matches the argument.
|
20
67
|
#
|
@@ -31,6 +78,48 @@ module RuboCop
|
|
31
78
|
OPERATOR_METHODS.include?(method_name)
|
32
79
|
end
|
33
80
|
|
81
|
+
# Checks whether the method is a nonmutating binary operator method.
|
82
|
+
#
|
83
|
+
# @return [Boolean] whether the method is a nonmutating binary operator method
|
84
|
+
def nonmutating_binary_operator_method?
|
85
|
+
NONMUTATING_BINARY_OPERATOR_METHODS.include?(method_name)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Checks whether the method is a nonmutating unary operator method.
|
89
|
+
#
|
90
|
+
# @return [Boolean] whether the method is a nonmutating unary operator method
|
91
|
+
def nonmutating_unary_operator_method?
|
92
|
+
NONMUTATING_UNARY_OPERATOR_METHODS.include?(method_name)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Checks whether the method is a nonmutating operator method.
|
96
|
+
#
|
97
|
+
# @return [Boolean] whether the method is a nonmutating operator method
|
98
|
+
def nonmutating_operator_method?
|
99
|
+
NONMUTATING_OPERATOR_METHODS.include?(method_name)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Checks whether the method is a nonmutating Array method.
|
103
|
+
#
|
104
|
+
# @return [Boolean] whether the method is a nonmutating Array method
|
105
|
+
def nonmutating_array_method?
|
106
|
+
NONMUTATING_ARRAY_METHODS.include?(method_name)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Checks whether the method is a nonmutating Hash method.
|
110
|
+
#
|
111
|
+
# @return [Boolean] whether the method is a nonmutating Hash method
|
112
|
+
def nonmutating_hash_method?
|
113
|
+
NONMUTATING_HASH_METHODS.include?(method_name)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Checks whether the method is a nonmutating String method.
|
117
|
+
#
|
118
|
+
# @return [Boolean] whether the method is a nonmutating String method
|
119
|
+
def nonmutating_string_method?
|
120
|
+
NONMUTATING_STRING_METHODS.include?(method_name)
|
121
|
+
end
|
122
|
+
|
34
123
|
# Checks whether the method is a comparison method.
|
35
124
|
#
|
36
125
|
# @return [Boolean] whether the method is a comparison
|
@@ -53,6 +142,13 @@ module RuboCop
|
|
53
142
|
method_name.to_s.start_with?('each_')
|
54
143
|
end
|
55
144
|
|
145
|
+
# Checks whether the method is an Enumerable method.
|
146
|
+
#
|
147
|
+
# @return [Boolean] whether the method is an Enumerable method
|
148
|
+
def enumerable_method?
|
149
|
+
ENUMERABLE_METHODS.include?(method_name)
|
150
|
+
end
|
151
|
+
|
56
152
|
# Checks whether the method is a predicate method.
|
57
153
|
#
|
58
154
|
# @return [Boolean] whether the method is a predicate method
|
@@ -4,6 +4,7 @@ module RuboCop
|
|
4
4
|
module AST
|
5
5
|
# Common functionality for nodes that are parameterized:
|
6
6
|
# `send`, `super`, `zsuper`, `def`, `defs`
|
7
|
+
# and (modern only): `index`, `indexasgn`, `lambda`
|
7
8
|
module ParameterizedNode
|
8
9
|
# Checks whether this node's arguments are wrapped in parentheses.
|
9
10
|
#
|
@@ -31,6 +31,62 @@ module RuboCop
|
|
31
31
|
def content
|
32
32
|
children.select(&:str_type?).map(&:str_content).join
|
33
33
|
end
|
34
|
+
|
35
|
+
# @return [Bool] if the regexp is a /.../ literal
|
36
|
+
def slash_literal?
|
37
|
+
loc.begin.source == '/'
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Bool] if the regexp is a %r{...} literal (using any delimiters)
|
41
|
+
def percent_r_literal?
|
42
|
+
!slash_literal?
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [String] the regexp delimiters (without %r)
|
46
|
+
def delimiters
|
47
|
+
[loc.begin.source[-1], loc.end.source[0]]
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [Bool] if char is one of the delimiters
|
51
|
+
def delimiter?(char)
|
52
|
+
delimiters.include?(char)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [Bool] if regexp contains interpolation
|
56
|
+
def interpolation?
|
57
|
+
children.any?(&:begin_type?)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Bool] if regexp uses the multiline regopt
|
61
|
+
def multiline_mode?
|
62
|
+
regopt_include?(:m)
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [Bool] if regexp uses the extended regopt
|
66
|
+
def extended?
|
67
|
+
regopt_include?(:x)
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [Bool] if regexp uses the ignore-case regopt
|
71
|
+
def ignore_case?
|
72
|
+
regopt_include?(:i)
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [Bool] if regexp uses the single-interpolation regopt
|
76
|
+
def single_interpolation?
|
77
|
+
regopt_include?(:o)
|
78
|
+
end
|
79
|
+
|
80
|
+
# @return [Bool] if regexp uses the no-encoding regopt
|
81
|
+
def no_encoding?
|
82
|
+
regopt_include?(:n)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def regopt_include?(option)
|
88
|
+
regopt.children.include?(option)
|
89
|
+
end
|
34
90
|
end
|
35
91
|
end
|
36
92
|
end
|
@@ -10,7 +10,8 @@ module RuboCop
|
|
10
10
|
include MethodDispatchNode
|
11
11
|
|
12
12
|
def_node_matcher :attribute_accessor?, <<~PATTERN
|
13
|
-
(send nil? ${:attr_reader :attr_writer :attr_accessor :attr} $...)
|
13
|
+
[(send nil? ${:attr_reader :attr_writer :attr_accessor :attr} $...)
|
14
|
+
(_ _ _ _ ...)]
|
14
15
|
PATTERN
|
15
16
|
end
|
16
17
|
end
|
@@ -70,13 +70,23 @@ module RuboCop
|
|
70
70
|
# '(send %1 _)' # % stands for a parameter which must be supplied to
|
71
71
|
# # #match at matching time
|
72
72
|
# # it will be compared to the corresponding value in
|
73
|
-
# # the AST using
|
73
|
+
# # the AST using #=== so you can pass Procs, Regexp,
|
74
|
+
# # etc. in addition to Nodes or literals.
|
75
|
+
# # `Array#===` will never match a node element, but
|
76
|
+
# # `Set#===` is an alias to `Set#include?` (Ruby 2.5+
|
77
|
+
# # only), and so can be very useful to match within
|
78
|
+
# # many possible literals / Nodes.
|
74
79
|
# # a bare '%' is the same as '%1'
|
75
80
|
# # the number of extra parameters passed to #match
|
76
81
|
# # must equal the highest % value in the pattern
|
77
82
|
# # for consistency, %0 is the 'root node' which is
|
78
83
|
# # passed as the 1st argument to #match, where the
|
79
84
|
# # matching process starts
|
85
|
+
# '(send _ %named)' # arguments can also be passed as named
|
86
|
+
# # parameters (see `%1`)
|
87
|
+
# # Note that the macros `def_node_pattern` and
|
88
|
+
# # `def_node_search` accept default values for these.
|
89
|
+
# '(send _ %CONST)' # the named constant will act like `%1` and `%named`.
|
80
90
|
# '^^send' # each ^ ascends one level in the AST
|
81
91
|
# # so this matches against the grandparent node
|
82
92
|
# '`send' # descends any number of level in the AST
|
@@ -119,11 +129,13 @@ module RuboCop
|
|
119
129
|
).freeze
|
120
130
|
NUMBER = /-?\d+(?:\.\d+)?/.freeze
|
121
131
|
STRING = /".+?"/.freeze
|
122
|
-
METHOD_NAME = /\#?#{IDENTIFIER}[
|
132
|
+
METHOD_NAME = /\#?#{IDENTIFIER}[!?]?\(?/.freeze
|
133
|
+
PARAM_CONST = /%[A-Z:][a-zA-Z_:]+/.freeze
|
134
|
+
KEYWORD_NAME = /%[a-z_]+/.freeze
|
123
135
|
PARAM_NUMBER = /%\d*/.freeze
|
124
136
|
|
125
|
-
SEPARATORS =
|
126
|
-
TOKENS = Regexp.union(META, PARAM_NUMBER, NUMBER,
|
137
|
+
SEPARATORS = /\s+/.freeze
|
138
|
+
TOKENS = Regexp.union(META, PARAM_CONST, KEYWORD_NAME, PARAM_NUMBER, NUMBER,
|
127
139
|
METHOD_NAME, SYMBOL, STRING)
|
128
140
|
|
129
141
|
TOKEN = /\G(?:#{SEPARATORS}|#{TOKENS}|.)/.freeze
|
@@ -135,6 +147,8 @@ module RuboCop
|
|
135
147
|
FUNCALL = /\A\##{METHOD_NAME}/.freeze
|
136
148
|
LITERAL = /\A(?:#{SYMBOL}|#{NUMBER}|#{STRING})\Z/.freeze
|
137
149
|
PARAM = /\A#{PARAM_NUMBER}\Z/.freeze
|
150
|
+
CONST = /\A#{PARAM_CONST}\Z/.freeze
|
151
|
+
KEYWORD = /\A#{KEYWORD_NAME}\Z/.freeze
|
138
152
|
CLOSING = /\A(?:\)|\}|\])\Z/.freeze
|
139
153
|
|
140
154
|
REST = '...'
|
@@ -193,6 +207,7 @@ module RuboCop
|
|
193
207
|
@captures = 0 # number of captures seen
|
194
208
|
@unify = {} # named wildcard -> temp variable
|
195
209
|
@params = 0 # highest % (param) number seen
|
210
|
+
@keywords = Set[] # keyword parameters seen
|
196
211
|
run(node_var)
|
197
212
|
end
|
198
213
|
|
@@ -232,6 +247,8 @@ module RuboCop
|
|
232
247
|
when LITERAL then compile_literal(token)
|
233
248
|
when PREDICATE then compile_predicate(token)
|
234
249
|
when NODE then compile_nodetype(token)
|
250
|
+
when KEYWORD then compile_keyword(token[1..-1])
|
251
|
+
when CONST then compile_const(token[1..-1])
|
235
252
|
when PARAM then compile_param(token[1..-1])
|
236
253
|
when CLOSING then fail_due_to("#{token} in invalid position")
|
237
254
|
when nil then fail_due_to('pattern ended prematurely')
|
@@ -612,7 +629,15 @@ module RuboCop
|
|
612
629
|
end
|
613
630
|
|
614
631
|
def compile_param(number)
|
615
|
-
"#{
|
632
|
+
"#{get_param(number)} === #{CUR_ELEMENT}"
|
633
|
+
end
|
634
|
+
|
635
|
+
def compile_const(const)
|
636
|
+
"#{get_const(const)} === #{CUR_ELEMENT}"
|
637
|
+
end
|
638
|
+
|
639
|
+
def compile_keyword(keyword)
|
640
|
+
"#{get_keyword(keyword)} === #{CUR_ELEMENT}"
|
616
641
|
end
|
617
642
|
|
618
643
|
def compile_args(tokens)
|
@@ -626,12 +651,14 @@ module RuboCop
|
|
626
651
|
end
|
627
652
|
|
628
653
|
def compile_arg(token)
|
654
|
+
name = token[1..-1]
|
629
655
|
case token
|
630
|
-
when WILDCARD
|
631
|
-
name = token[1..-1]
|
656
|
+
when WILDCARD
|
632
657
|
access_unify(name) || fail_due_to('invalid in arglist: ' + token)
|
633
658
|
when LITERAL then token
|
634
|
-
when
|
659
|
+
when KEYWORD then get_keyword(name)
|
660
|
+
when CONST then get_const(name)
|
661
|
+
when PARAM then get_param(name)
|
635
662
|
when CLOSING then fail_due_to("#{token} in invalid position")
|
636
663
|
when nil then fail_due_to('pattern ended prematurely')
|
637
664
|
else fail_due_to("invalid token in arglist: #{token.inspect}")
|
@@ -650,6 +677,15 @@ module RuboCop
|
|
650
677
|
number.zero? ? @root : "param#{number}"
|
651
678
|
end
|
652
679
|
|
680
|
+
def get_keyword(name)
|
681
|
+
@keywords << name
|
682
|
+
name
|
683
|
+
end
|
684
|
+
|
685
|
+
def get_const(const)
|
686
|
+
const # Output the constant exactly as given
|
687
|
+
end
|
688
|
+
|
653
689
|
def emit_yield_capture(when_no_capture = '')
|
654
690
|
yield_val = if @captures.zero?
|
655
691
|
when_no_capture
|
@@ -675,9 +711,15 @@ module RuboCop
|
|
675
711
|
(1..@params).map { |n| "param#{n}" }.join(',')
|
676
712
|
end
|
677
713
|
|
678
|
-
def
|
714
|
+
def emit_keyword_list(forwarding: false)
|
715
|
+
pattern = "%<keyword>s: #{'%<keyword>s' if forwarding}"
|
716
|
+
@keywords.map { |k| format(pattern, keyword: k) }.join(',')
|
717
|
+
end
|
718
|
+
|
719
|
+
def emit_trailing_params(forwarding: false)
|
679
720
|
params = emit_param_list
|
680
|
-
|
721
|
+
keywords = emit_keyword_list(forwarding: forwarding)
|
722
|
+
[params, keywords].reject(&:empty?).map { |p| ", #{p}" }.join
|
681
723
|
end
|
682
724
|
|
683
725
|
def emit_method_code
|
@@ -753,68 +795,51 @@ module RuboCop
|
|
753
795
|
def self.tokens(pattern)
|
754
796
|
pattern.scan(TOKEN).reject { |token| token =~ /\A#{SEPARATORS}\Z/ }
|
755
797
|
end
|
756
|
-
end
|
757
|
-
private_constant :Compiler
|
758
|
-
|
759
|
-
# Helpers for defining methods based on a pattern string
|
760
|
-
module Macros
|
761
|
-
# Define a method which applies a pattern to an AST node
|
762
|
-
#
|
763
|
-
# The new method will return nil if the node does not match
|
764
|
-
# If the node matches, and a block is provided, the new method will
|
765
|
-
# yield to the block (passing any captures as block arguments).
|
766
|
-
# If the node matches, and no block is provided, the new method will
|
767
|
-
# return the captures, or `true` if there were none.
|
768
|
-
def def_node_matcher(method_name, pattern_str)
|
769
|
-
compiler = Compiler.new(pattern_str, 'node')
|
770
|
-
src = "def #{method_name}(node = self" \
|
771
|
-
"#{compiler.emit_trailing_params});" \
|
772
|
-
"#{compiler.emit_method_code};end"
|
773
|
-
|
774
|
-
location = caller_locations(1, 1).first
|
775
|
-
class_eval(src, location.path, location.lineno)
|
776
|
-
end
|
777
798
|
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
compiler = Compiler.new(pattern_str, 'node')
|
786
|
-
called_from = caller(1..1).first.split(':')
|
787
|
-
|
788
|
-
if method_name.to_s.end_with?('?')
|
789
|
-
node_search_first(method_name, compiler, called_from)
|
790
|
-
else
|
791
|
-
node_search_all(method_name, compiler, called_from)
|
799
|
+
def def_helper(base, method_name, **defaults)
|
800
|
+
location = caller_locations(3, 1).first
|
801
|
+
unless defaults.empty?
|
802
|
+
base.send :define_method, method_name do |*args, **values|
|
803
|
+
send method_name, *args, **defaults, **values
|
804
|
+
end
|
805
|
+
method_name = :"without_defaults_#{method_name}"
|
792
806
|
end
|
807
|
+
src = yield method_name
|
808
|
+
base.class_eval(src, location.path, location.lineno)
|
793
809
|
end
|
794
810
|
|
795
|
-
def
|
796
|
-
|
811
|
+
def def_node_matcher(base, method_name, **defaults)
|
812
|
+
def_helper(base, method_name, **defaults) do |name|
|
813
|
+
<<~RUBY
|
814
|
+
def #{name}(node = self#{emit_trailing_params})
|
815
|
+
#{emit_method_code}
|
816
|
+
end
|
817
|
+
RUBY
|
818
|
+
end
|
797
819
|
end
|
798
820
|
|
799
|
-
def
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
node_search(method_name, compiler, yield_code, prelude, called_from)
|
821
|
+
def def_node_search(base, method_name, **defaults)
|
822
|
+
def_helper(base, method_name, **defaults) do |name|
|
823
|
+
emit_node_search(name)
|
824
|
+
end
|
805
825
|
end
|
806
826
|
|
807
|
-
def
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
827
|
+
def emit_node_search(method_name)
|
828
|
+
if method_name.to_s.end_with?('?')
|
829
|
+
on_match = 'return true'
|
830
|
+
else
|
831
|
+
prelude = <<~RUBY
|
832
|
+
return enum_for(:#{method_name},
|
833
|
+
node0#{emit_trailing_params(forwarding: true)}) unless block_given?
|
834
|
+
RUBY
|
835
|
+
on_match = emit_yield_capture('node')
|
836
|
+
end
|
837
|
+
emit_node_search_body(method_name, prelude: prelude, on_match: on_match)
|
812
838
|
end
|
813
839
|
|
814
|
-
def
|
815
|
-
on_match)
|
840
|
+
def emit_node_search_body(method_name, prelude:, on_match:)
|
816
841
|
<<~RUBY
|
817
|
-
def #{method_name}(node0#{
|
842
|
+
def #{method_name}(node0#{emit_trailing_params})
|
818
843
|
#{prelude}
|
819
844
|
node0.each_node do |node|
|
820
845
|
if #{match_code}
|
@@ -826,6 +851,33 @@ module RuboCop
|
|
826
851
|
RUBY
|
827
852
|
end
|
828
853
|
end
|
854
|
+
private_constant :Compiler
|
855
|
+
|
856
|
+
# Helpers for defining methods based on a pattern string
|
857
|
+
module Macros
|
858
|
+
# Define a method which applies a pattern to an AST node
|
859
|
+
#
|
860
|
+
# The new method will return nil if the node does not match
|
861
|
+
# If the node matches, and a block is provided, the new method will
|
862
|
+
# yield to the block (passing any captures as block arguments).
|
863
|
+
# If the node matches, and no block is provided, the new method will
|
864
|
+
# return the captures, or `true` if there were none.
|
865
|
+
def def_node_matcher(method_name, pattern_str, **keyword_defaults)
|
866
|
+
Compiler.new(pattern_str, 'node')
|
867
|
+
.def_node_matcher(self, method_name, **keyword_defaults)
|
868
|
+
end
|
869
|
+
|
870
|
+
# Define a method which recurses over the descendants of an AST node,
|
871
|
+
# checking whether any of them match the provided pattern
|
872
|
+
#
|
873
|
+
# If the method name ends with '?', the new method will return `true`
|
874
|
+
# as soon as it finds a descendant which matches. Otherwise, it will
|
875
|
+
# yield all descendants which match.
|
876
|
+
def def_node_search(method_name, pattern_str, **keyword_defaults)
|
877
|
+
Compiler.new(pattern_str, 'node')
|
878
|
+
.def_node_search(self, method_name, **keyword_defaults)
|
879
|
+
end
|
880
|
+
end
|
829
881
|
|
830
882
|
attr_reader :pattern
|
831
883
|
|
@@ -837,11 +889,15 @@ module RuboCop
|
|
837
889
|
instance_eval(src, __FILE__, __LINE__ + 1)
|
838
890
|
end
|
839
891
|
|
840
|
-
def match(*args)
|
892
|
+
def match(*args, **rest)
|
841
893
|
# If we're here, it's because the singleton method has not been defined,
|
842
894
|
# either because we've been dup'ed or serialized through YAML
|
843
895
|
initialize(pattern)
|
844
|
-
|
896
|
+
if rest.empty?
|
897
|
+
match(*args)
|
898
|
+
else
|
899
|
+
match(*args, **rest)
|
900
|
+
end
|
845
901
|
end
|
846
902
|
|
847
903
|
def marshal_load(pattern)
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'digest/sha1'
|
4
4
|
|
5
|
+
# rubocop:disable Metrics/ClassLength
|
5
6
|
module RuboCop
|
6
7
|
module AST
|
7
8
|
# ProcessedSource contains objects which are generated by Parser
|
@@ -176,6 +177,9 @@ module RuboCop
|
|
176
177
|
when 2.7
|
177
178
|
require 'parser/ruby27'
|
178
179
|
Parser::Ruby27
|
180
|
+
when 2.8
|
181
|
+
require 'parser/ruby28'
|
182
|
+
Parser::Ruby28
|
179
183
|
else
|
180
184
|
raise ArgumentError,
|
181
185
|
"RuboCop found unknown Ruby version: #{ruby_version.inspect}"
|
@@ -201,3 +205,4 @@ module RuboCop
|
|
201
205
|
end
|
202
206
|
end
|
203
207
|
end
|
208
|
+
# rubocop:enable Metrics/ClassLength
|
@@ -19,9 +19,10 @@ 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
|
22
|
+
kwrestarg zsuper redo retry
|
23
23
|
forward_args forwarded_args
|
24
|
-
match_var match_nil_pattern empty_else
|
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
|
@@ -33,7 +34,8 @@ module RuboCop
|
|
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
|
37
|
+
hash_pattern const_pattern
|
38
|
+
index indexasgn].freeze
|
37
39
|
SECOND_CHILD_ONLY = %i[lvasgn ivasgn cvasgn gvasgn optarg kwarg
|
38
40
|
kwoptarg].freeze
|
39
41
|
|
data/lib/rubocop/ast/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubocop-ast
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bozhidar Batsov
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2020-
|
13
|
+
date: 2020-06-26 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: parser
|
@@ -77,8 +77,11 @@ files:
|
|
77
77
|
- lib/rubocop/ast/node/forward_args_node.rb
|
78
78
|
- lib/rubocop/ast/node/hash_node.rb
|
79
79
|
- lib/rubocop/ast/node/if_node.rb
|
80
|
+
- lib/rubocop/ast/node/index_node.rb
|
81
|
+
- lib/rubocop/ast/node/indexasgn_node.rb
|
80
82
|
- lib/rubocop/ast/node/int_node.rb
|
81
83
|
- lib/rubocop/ast/node/keyword_splat_node.rb
|
84
|
+
- lib/rubocop/ast/node/lambda_node.rb
|
82
85
|
- lib/rubocop/ast/node/mixin/basic_literal_node.rb
|
83
86
|
- lib/rubocop/ast/node/mixin/binary_operator_node.rb
|
84
87
|
- lib/rubocop/ast/node/mixin/collection_node.rb
|
@@ -120,7 +123,7 @@ metadata:
|
|
120
123
|
homepage_uri: https://www.rubocop.org/
|
121
124
|
changelog_uri: https://github.com/rubocop-hq/rubocop-ast/blob/master/CHANGELOG.md
|
122
125
|
source_code_uri: https://github.com/rubocop-hq/rubocop-ast/
|
123
|
-
documentation_uri: https://docs.rubocop.org/
|
126
|
+
documentation_uri: https://docs.rubocop.org/rubocop-ast/
|
124
127
|
bug_tracker_uri: https://github.com/rubocop-hq/rubocop-ast/issues
|
125
128
|
post_install_message:
|
126
129
|
rdoc_options: []
|