rubocop-ast 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/rubocop-ast.svg)](https://badge.fury.io/rb/rubocop-ast)
|
4
4
|
[![CI](https://github.com/rubocop-hq/rubocop-ast/workflows/CI/badge.svg)](https://github.com/rubocop-hq/rubocop-ast/actions?query=workflow%3ACI)
|
5
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/a29666e6373bc41bc0a9/test_coverage)](https://codeclimate.com/github/rubocop-hq/rubocop-ast/test_coverage)
|
6
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/a29666e6373bc41bc0a9/maintainability)](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: []
|