fastererer 0.12.0 → 1.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/CHANGELOG.md +75 -2
- data/README.md +97 -8
- data/config/locales/en.yml +60 -0
- data/lib/fastererer/analyzer.rb +28 -53
- data/lib/fastererer/cli.rb +21 -4
- data/lib/fastererer/explanation.rb +44 -0
- data/lib/fastererer/file_traverser.rb +43 -112
- data/lib/fastererer/finding.rb +18 -0
- data/lib/fastererer/formatters/base.rb +32 -0
- data/lib/fastererer/formatters/json_formatter.rb +48 -0
- data/lib/fastererer/formatters/rdjsonl_formatter.rb +34 -0
- data/lib/fastererer/formatters/text_formatter.rb +63 -0
- data/lib/fastererer/formatters/text_statistics.rb +45 -0
- data/lib/fastererer/formatters.rb +24 -0
- data/lib/fastererer/method_call.rb +139 -75
- data/lib/fastererer/method_definition.rb +61 -48
- data/lib/fastererer/offense.rb +5 -63
- data/lib/fastererer/offense_collector.rb +1 -1
- data/lib/fastererer/painter.rb +2 -1
- data/lib/fastererer/parser.rb +8 -4
- data/lib/fastererer/receiver.rb +14 -0
- data/lib/fastererer/report.rb +10 -0
- data/lib/fastererer/rescue_call.rb +10 -12
- data/lib/fastererer/rule_catalog.rb +83 -0
- data/lib/fastererer/rule_name.rb +17 -0
- data/lib/fastererer/scanners/for_loop_scanner.rb +23 -0
- data/lib/fastererer/scanners/method_call_scanner.rb +13 -17
- data/lib/fastererer/scanners/method_definition_scanner.rb +57 -29
- data/lib/fastererer/scanners/offensive.rb +2 -6
- data/lib/fastererer/scanners/rescue_call_scanner.rb +1 -1
- data/lib/fastererer/scanners/symbol_to_proc_check.rb +21 -8
- data/lib/fastererer/version.rb +1 -1
- metadata +19 -11
- data/.fastererer.yml +0 -27
- data/.ruby-version +0 -1
- data/CODE_OF_CONDUCT.md +0 -18
- data/CONTRIBUTING.md +0 -63
- data/Rakefile +0 -12
- data/SECURITY.md +0 -20
|
@@ -1,141 +1,205 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'prism'
|
|
4
|
+
require 'fastererer/receiver'
|
|
5
|
+
|
|
3
6
|
module Fastererer
|
|
4
7
|
class MethodCall
|
|
5
|
-
|
|
8
|
+
include Receiver
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
attr_reader :element
|
|
8
11
|
|
|
9
|
-
def
|
|
10
|
-
|
|
11
|
-
set_call_element
|
|
12
|
-
set_receiver
|
|
13
|
-
set_method_name
|
|
14
|
-
set_arguments
|
|
15
|
-
set_block_presence
|
|
16
|
-
set_block_body
|
|
17
|
-
set_block_argument_names
|
|
12
|
+
def self.build(node)
|
|
13
|
+
node.is_a?(Prism::LambdaNode) ? LambdaCall.new(node) : new(node)
|
|
18
14
|
end
|
|
19
15
|
|
|
20
|
-
def
|
|
21
|
-
@
|
|
16
|
+
def initialize(node)
|
|
17
|
+
@element = node
|
|
22
18
|
end
|
|
23
19
|
|
|
24
|
-
def
|
|
25
|
-
|
|
20
|
+
def receiver
|
|
21
|
+
@receiver ||= ReceiverFactory.build(element.receiver)
|
|
26
22
|
end
|
|
27
23
|
|
|
28
|
-
def
|
|
29
|
-
|
|
24
|
+
def method_name
|
|
25
|
+
element.name
|
|
30
26
|
end
|
|
31
27
|
|
|
32
|
-
def
|
|
33
|
-
|
|
28
|
+
def name
|
|
29
|
+
method_name
|
|
34
30
|
end
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
def call_name = method_name
|
|
37
33
|
|
|
38
|
-
|
|
34
|
+
def arguments
|
|
35
|
+
@arguments ||= argument_nodes.map { |argument| Argument.new(argument) }
|
|
36
|
+
end
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@call_element = case element.sexp_type
|
|
43
|
-
when :call
|
|
44
|
-
@element
|
|
45
|
-
when :iter
|
|
46
|
-
@element[1]
|
|
47
|
-
end
|
|
38
|
+
def block_body
|
|
39
|
+
@block_body ||= block_statements
|
|
48
40
|
end
|
|
49
41
|
|
|
50
|
-
def
|
|
51
|
-
@
|
|
42
|
+
def block_argument_names
|
|
43
|
+
@block_argument_names ||= positional_block_parameter_names
|
|
52
44
|
end
|
|
53
45
|
|
|
54
|
-
def
|
|
55
|
-
|
|
46
|
+
def block?
|
|
47
|
+
!block_node.nil?
|
|
56
48
|
end
|
|
57
49
|
|
|
58
|
-
def
|
|
59
|
-
|
|
50
|
+
def lambda_literal?
|
|
51
|
+
false
|
|
60
52
|
end
|
|
61
53
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
54
|
+
# Provably a Hash: bare `Hash.new`/`Hash[...]` (any args — every `Hash[...]` yields a Hash),
|
|
55
|
+
# not qualified `Foo::Hash`. The is_a?(ConstantReadNode) check is load-bearing:
|
|
56
|
+
# ConstantPathNode#name is :Hash too, so the type (not the name) excludes qualified constants.
|
|
57
|
+
# unwrap_parentheses lets `(Hash).new` match, as the receiver factory normalizes its input.
|
|
58
|
+
def hash?
|
|
59
|
+
return false unless %i[new []].include?(method_name)
|
|
60
|
+
|
|
61
|
+
constant = ReceiverFactory.unwrap_parentheses(element.receiver)
|
|
62
|
+
constant.is_a?(Prism::ConstantReadNode) && constant.name == :Hash
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def block_node
|
|
68
|
+
@block_node ||= element.block
|
|
66
69
|
end
|
|
67
70
|
|
|
68
|
-
def
|
|
69
|
-
|
|
71
|
+
def argument_nodes
|
|
72
|
+
element.arguments&.arguments || []
|
|
70
73
|
end
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
def block_statements
|
|
76
|
+
return unless block_node.is_a?(Prism::BlockNode)
|
|
77
|
+
|
|
78
|
+
body = block_node.body
|
|
79
|
+
body.body if body.is_a?(Prism::StatementsNode)
|
|
77
80
|
end
|
|
81
|
+
|
|
82
|
+
# Only single positional params convert to &:sym, so a splat or keyword param must not count
|
|
83
|
+
# toward the single-name check
|
|
84
|
+
def positional_block_parameter_names
|
|
85
|
+
return [] unless block_node.is_a?(Prism::BlockNode)
|
|
86
|
+
|
|
87
|
+
params = block_node.parameters
|
|
88
|
+
return [] unless params.is_a?(Prism::BlockParametersNode) && params.parameters
|
|
89
|
+
|
|
90
|
+
positional = params.parameters.requireds + params.parameters.optionals
|
|
91
|
+
positional.map { |param| param.is_a?(Prism::MultiTargetNode) ? nil : param.name }
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# A `-> {}` lambda literal. Checks ignore it, so its call attributes are inert
|
|
96
|
+
class LambdaCall < MethodCall
|
|
97
|
+
def receiver = nil
|
|
98
|
+
def method_name = :lambda
|
|
99
|
+
def arguments = []
|
|
100
|
+
def block_body = nil
|
|
101
|
+
def block_argument_names = []
|
|
102
|
+
def block? = true
|
|
103
|
+
def lambda_literal? = true
|
|
78
104
|
end
|
|
79
105
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
106
|
+
module ReceiverFactory
|
|
107
|
+
PRIMITIVE_NODE_TYPES = [Prism::ArrayNode, Prism::RangeNode, Prism::IntegerNode,
|
|
108
|
+
Prism::FloatNode, Prism::SymbolNode, Prism::StringNode,
|
|
109
|
+
Prism::HashNode].freeze
|
|
110
|
+
|
|
111
|
+
def self.build(node)
|
|
112
|
+
return unless node
|
|
113
|
+
|
|
114
|
+
node = unwrap_parentheses(node)
|
|
115
|
+
case node
|
|
116
|
+
# A ConstantPathNode's #name is the unqualified tail only (Foo::Bar => :Bar), which is all
|
|
117
|
+
# the symbol-to-proc check needs; revisit if a check ever needs the fully-qualified path.
|
|
118
|
+
when Prism::LocalVariableReadNode, Prism::ConstantReadNode,
|
|
119
|
+
Prism::ConstantPathNode then VariableReference.new(node)
|
|
120
|
+
when Prism::CallNode then MethodCall.build(node)
|
|
121
|
+
when *PRIMITIVE_NODE_TYPES then Primitive.new(node)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Peels a single parenthesis level, so `((x))` stays wrapped — matches how callers normalize
|
|
126
|
+
def self.unwrap_parentheses(node)
|
|
127
|
+
if node.is_a?(Prism::ParenthesesNode) &&
|
|
128
|
+
node.body.is_a?(Prism::StatementsNode) &&
|
|
129
|
+
node.body.body.size == 1
|
|
130
|
+
node.body.body.first
|
|
131
|
+
else
|
|
132
|
+
node
|
|
93
133
|
end
|
|
94
134
|
end
|
|
95
135
|
end
|
|
96
136
|
|
|
97
137
|
class VariableReference
|
|
138
|
+
include Receiver
|
|
139
|
+
|
|
98
140
|
attr_reader :name
|
|
99
141
|
|
|
100
|
-
def initialize(
|
|
101
|
-
@
|
|
102
|
-
@name = reference_info[1]
|
|
142
|
+
def initialize(node)
|
|
143
|
+
@name = node.name
|
|
103
144
|
end
|
|
104
145
|
end
|
|
105
146
|
|
|
106
147
|
class Argument
|
|
107
148
|
attr_reader :element
|
|
108
149
|
|
|
109
|
-
def initialize(
|
|
110
|
-
@element =
|
|
111
|
-
end
|
|
150
|
+
def initialize(node)
|
|
151
|
+
@element = node
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
TYPE_BY_NODE_CLASS = {
|
|
155
|
+
Prism::KeywordHashNode => :hash,
|
|
156
|
+
Prism::HashNode => :hash,
|
|
157
|
+
Prism::StringNode => :string,
|
|
158
|
+
Prism::IntegerNode => :integer,
|
|
159
|
+
Prism::SymbolNode => :symbol,
|
|
160
|
+
Prism::FloatNode => :float,
|
|
161
|
+
Prism::RegularExpressionNode => :regexp,
|
|
162
|
+
Prism::NilNode => :nil,
|
|
163
|
+
Prism::TrueNode => :boolean,
|
|
164
|
+
Prism::FalseNode => :boolean,
|
|
165
|
+
Prism::LocalVariableReadNode => :variable,
|
|
166
|
+
Prism::CallNode => :method_call
|
|
167
|
+
}.freeze
|
|
112
168
|
|
|
113
169
|
def type
|
|
114
|
-
@type ||=
|
|
170
|
+
@type ||= TYPE_BY_NODE_CLASS[element.class] || :unknown
|
|
115
171
|
end
|
|
116
172
|
|
|
117
173
|
def value
|
|
118
|
-
@value
|
|
174
|
+
return @value if defined?(@value)
|
|
175
|
+
|
|
176
|
+
@value = case element
|
|
177
|
+
when Prism::StringNode then element.unescaped
|
|
178
|
+
when Prism::IntegerNode, Prism::FloatNode then element.value
|
|
179
|
+
when Prism::SymbolNode then element.unescaped.to_sym
|
|
180
|
+
end
|
|
119
181
|
end
|
|
120
182
|
end
|
|
121
183
|
|
|
122
184
|
class Primitive
|
|
123
|
-
|
|
185
|
+
include Receiver
|
|
124
186
|
|
|
125
|
-
|
|
126
|
-
@element = element
|
|
127
|
-
end
|
|
187
|
+
attr_reader :element
|
|
128
188
|
|
|
129
|
-
def
|
|
130
|
-
@
|
|
189
|
+
def initialize(node)
|
|
190
|
+
@element = node
|
|
131
191
|
end
|
|
132
192
|
|
|
133
193
|
def range?
|
|
134
|
-
|
|
194
|
+
element.is_a?(Prism::RangeNode)
|
|
135
195
|
end
|
|
136
196
|
|
|
137
197
|
def array?
|
|
138
|
-
|
|
198
|
+
element.is_a?(Prism::ArrayNode)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def hash?
|
|
202
|
+
element.is_a?(Prism::HashNode)
|
|
139
203
|
end
|
|
140
204
|
end
|
|
141
205
|
end
|
|
@@ -1,22 +1,37 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'prism'
|
|
4
|
+
|
|
3
5
|
module Fastererer
|
|
4
6
|
class MethodDefinition
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
PARAMETER_CATEGORIES = %i[requireds optionals rest posts keywords keyword_rest block].freeze
|
|
8
|
+
private_constant :PARAMETER_CATEGORIES
|
|
9
|
+
|
|
10
|
+
attr_reader :element
|
|
7
11
|
|
|
12
|
+
def initialize(node)
|
|
13
|
+
@element = node
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def method_name
|
|
17
|
+
element.name
|
|
18
|
+
end
|
|
8
19
|
alias name method_name
|
|
9
20
|
|
|
10
|
-
def
|
|
11
|
-
@
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
21
|
+
def body
|
|
22
|
+
@body ||= statement_body
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def arguments
|
|
26
|
+
@arguments ||= parameter_nodes.map { |node| MethodDefinitionArgument.new(node) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def block_argument_name
|
|
30
|
+
block_parameter&.name
|
|
16
31
|
end
|
|
17
32
|
|
|
18
33
|
def block?
|
|
19
|
-
|
|
34
|
+
!block_parameter.nil?
|
|
20
35
|
end
|
|
21
36
|
|
|
22
37
|
def setter?
|
|
@@ -25,70 +40,68 @@ module Fastererer
|
|
|
25
40
|
|
|
26
41
|
private
|
|
27
42
|
|
|
28
|
-
def
|
|
29
|
-
element
|
|
43
|
+
def statement_body
|
|
44
|
+
body_node = element.body
|
|
45
|
+
# A rescue/ensure body is a BeginNode wrapping the statements; #statements may be nil
|
|
46
|
+
body_node = body_node.statements if body_node.is_a?(Prism::BeginNode)
|
|
47
|
+
body_node.is_a?(Prism::StatementsNode) ? body_node.body : []
|
|
30
48
|
end
|
|
31
49
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
|
|
50
|
+
def parameter_nodes
|
|
51
|
+
params = element.parameters
|
|
52
|
+
return [] unless params
|
|
35
53
|
|
|
36
|
-
|
|
37
|
-
@arguments = arguments_element.map do |argument_element|
|
|
38
|
-
MethodDefinitionArgument.new(argument_element)
|
|
39
|
-
end
|
|
54
|
+
PARAMETER_CATEGORIES.flat_map { |category| Array(params.public_send(category)) }
|
|
40
55
|
end
|
|
41
56
|
|
|
42
|
-
def
|
|
43
|
-
|
|
57
|
+
def block_parameter
|
|
58
|
+
params = element.parameters
|
|
59
|
+
return unless params&.block.is_a?(Prism::BlockParameterNode)
|
|
60
|
+
|
|
61
|
+
params.block
|
|
44
62
|
end
|
|
63
|
+
end
|
|
45
64
|
|
|
46
|
-
|
|
47
|
-
|
|
65
|
+
class MethodDefinitionArgument
|
|
66
|
+
attr_reader :element
|
|
48
67
|
|
|
49
|
-
|
|
68
|
+
def initialize(node)
|
|
69
|
+
@element = node
|
|
50
70
|
end
|
|
51
71
|
|
|
52
|
-
|
|
53
|
-
|
|
72
|
+
# Prism param nodes expose #name (nil when anonymous); MultiTargetNode has none
|
|
73
|
+
def name
|
|
74
|
+
element.respond_to?(:name) ? element.name : nil
|
|
54
75
|
end
|
|
55
|
-
end
|
|
56
76
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def initialize(element)
|
|
61
|
-
@element = element
|
|
62
|
-
set_name
|
|
63
|
-
set_argument_type
|
|
77
|
+
def type
|
|
78
|
+
@type ||= argument_type
|
|
64
79
|
end
|
|
65
80
|
|
|
66
81
|
def regular_argument?
|
|
67
|
-
|
|
82
|
+
type == :regular_argument
|
|
68
83
|
end
|
|
69
84
|
|
|
70
85
|
def default_argument?
|
|
71
|
-
|
|
86
|
+
type == :default_argument
|
|
72
87
|
end
|
|
73
88
|
|
|
74
89
|
def keyword_argument?
|
|
75
|
-
|
|
90
|
+
type == :keyword_argument
|
|
76
91
|
end
|
|
77
92
|
|
|
78
93
|
private
|
|
79
94
|
|
|
80
|
-
def
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
:keyword_argument
|
|
91
|
-
end
|
|
95
|
+
def argument_type
|
|
96
|
+
case element
|
|
97
|
+
when Prism::RequiredParameterNode
|
|
98
|
+
:regular_argument
|
|
99
|
+
when Prism::OptionalParameterNode
|
|
100
|
+
:default_argument
|
|
101
|
+
when Prism::RequiredKeywordParameterNode,
|
|
102
|
+
Prism::OptionalKeywordParameterNode
|
|
103
|
+
:keyword_argument
|
|
104
|
+
end
|
|
92
105
|
end
|
|
93
106
|
end
|
|
94
107
|
end
|
data/lib/fastererer/offense.rb
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'explanation'
|
|
4
|
+
require_relative 'rule_catalog'
|
|
5
|
+
|
|
3
6
|
module Fastererer
|
|
4
7
|
class Offense
|
|
5
8
|
attr_reader :offense_name, :line_number
|
|
@@ -10,72 +13,11 @@ module Fastererer
|
|
|
10
13
|
def initialize(offense_name, line_number)
|
|
11
14
|
@offense_name = offense_name
|
|
12
15
|
@line_number = line_number
|
|
13
|
-
|
|
16
|
+
RuleCatalog.validate!(offense_name)
|
|
14
17
|
end
|
|
15
18
|
|
|
16
19
|
def explanation
|
|
17
|
-
@explanation ||=
|
|
20
|
+
@explanation ||= Explanation.for(offense_name)
|
|
18
21
|
end
|
|
19
|
-
|
|
20
|
-
EXPLANATIONS = {
|
|
21
|
-
rescue_vs_respond_to:
|
|
22
|
-
'Don\'t rescue NoMethodError, rather check with respond_to?',
|
|
23
|
-
|
|
24
|
-
module_eval:
|
|
25
|
-
'Using module_eval is slower than define_method',
|
|
26
|
-
|
|
27
|
-
shuffle_first_vs_sample:
|
|
28
|
-
'Array#shuffle.first is slower than Array#sample',
|
|
29
|
-
|
|
30
|
-
for_loop_vs_each:
|
|
31
|
-
'For loop is slower than using each',
|
|
32
|
-
|
|
33
|
-
each_with_index_vs_while:
|
|
34
|
-
'Using each_with_index is slower than while loop',
|
|
35
|
-
|
|
36
|
-
map_flatten_vs_flat_map:
|
|
37
|
-
'Array#map.flatten(1) is slower than Array#flat_map',
|
|
38
|
-
|
|
39
|
-
reverse_each_vs_reverse_each:
|
|
40
|
-
'Array#reverse.each is slower than Array#reverse_each',
|
|
41
|
-
|
|
42
|
-
select_first_vs_detect:
|
|
43
|
-
'Array#select.first is slower than Array#detect',
|
|
44
|
-
|
|
45
|
-
sort_vs_sort_by:
|
|
46
|
-
'Enumerable#sort is slower than Enumerable#sort_by',
|
|
47
|
-
|
|
48
|
-
fetch_with_argument_vs_block:
|
|
49
|
-
'Hash#fetch with second argument is slower than Hash#fetch with block',
|
|
50
|
-
|
|
51
|
-
keys_each_vs_each_key:
|
|
52
|
-
'Hash#keys.each is slower than Hash#each_key. N.B. Hash#each_key cannot be used if ' \
|
|
53
|
-
'the hash is modified during the each block',
|
|
54
|
-
|
|
55
|
-
hash_merge_bang_vs_hash_brackets:
|
|
56
|
-
'Hash#merge! with one argument is slower than Hash#[]',
|
|
57
|
-
|
|
58
|
-
block_vs_symbol_to_proc:
|
|
59
|
-
'Calling argumentless methods within blocks is slower than using symbol to proc',
|
|
60
|
-
|
|
61
|
-
proc_call_vs_yield:
|
|
62
|
-
'Calling blocks with call is slower than yielding',
|
|
63
|
-
|
|
64
|
-
gsub_vs_tr:
|
|
65
|
-
'Using tr is faster than gsub when replacing a single character in a string with ' \
|
|
66
|
-
'another single character',
|
|
67
|
-
|
|
68
|
-
select_last_vs_reverse_detect:
|
|
69
|
-
'Array#select.last is slower than Array#reverse.detect',
|
|
70
|
-
|
|
71
|
-
getter_vs_attr_reader:
|
|
72
|
-
'Use attr_reader for reading ivars',
|
|
73
|
-
|
|
74
|
-
setter_vs_attr_writer:
|
|
75
|
-
'Use attr_writer for writing to ivars',
|
|
76
|
-
|
|
77
|
-
include_vs_cover_on_range:
|
|
78
|
-
'Use #cover? instead of #include? on ranges'
|
|
79
|
-
}.freeze
|
|
80
22
|
end
|
|
81
23
|
end
|
data/lib/fastererer/painter.rb
CHANGED
data/lib/fastererer/parser.rb
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'prism'
|
|
4
4
|
|
|
5
5
|
module Fastererer
|
|
6
|
-
class
|
|
7
|
-
PARSER_CLASS = RubyParser
|
|
6
|
+
class ParseError < StandardError; end
|
|
8
7
|
|
|
8
|
+
# Single seam around Prism.parse: returns the AST root or raises ParseError
|
|
9
|
+
class Parser
|
|
9
10
|
def self.parse(ruby_code)
|
|
10
|
-
|
|
11
|
+
result = Prism.parse(ruby_code)
|
|
12
|
+
raise ParseError, result.errors.map(&:message).join('; ') if result.failure?
|
|
13
|
+
|
|
14
|
+
result.value
|
|
11
15
|
end
|
|
12
16
|
end
|
|
13
17
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fastererer
|
|
4
|
+
# Query surface every receiver answers; includers override only what is true for their type
|
|
5
|
+
module Receiver
|
|
6
|
+
def call_name = nil
|
|
7
|
+
def name = nil
|
|
8
|
+
def block? = false
|
|
9
|
+
def arguments = []
|
|
10
|
+
def range? = false
|
|
11
|
+
def array? = false
|
|
12
|
+
def hash? = false
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fastererer
|
|
4
|
+
# Findings arrive already filtered by ignored speedups; formatters must not filter again
|
|
5
|
+
Report = Data.define(:findings, :files_inspected_count, :unparsable_files, :missing_path) do
|
|
6
|
+
def offenses_detected_count
|
|
7
|
+
findings.count
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'prism'
|
|
4
|
+
|
|
3
5
|
module Fastererer
|
|
4
6
|
class RescueCall
|
|
5
|
-
attr_reader :element
|
|
7
|
+
attr_reader :element
|
|
6
8
|
|
|
7
|
-
def initialize(
|
|
8
|
-
@element =
|
|
9
|
-
@rescue_classes = []
|
|
10
|
-
set_rescue_classes
|
|
9
|
+
def initialize(node)
|
|
10
|
+
@element = node
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@rescue_classes = element[1].drop(1).filter_map do |rescue_reference|
|
|
19
|
-
rescue_reference[1] if rescue_reference.sexp_type == :const
|
|
13
|
+
# Only unqualified constants: a namespaced `Foo::NoMethodError` is not the core NoMethodError,
|
|
14
|
+
# so it must not match the rescue_vs_respond_to check.
|
|
15
|
+
def rescue_classes
|
|
16
|
+
@rescue_classes ||= element.exceptions.filter_map do |exception|
|
|
17
|
+
exception.name if exception.is_a?(Prism::ConstantReadNode)
|
|
20
18
|
end
|
|
21
19
|
end
|
|
22
20
|
end
|