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
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module Fastererer
|
|
6
|
+
class UnknownRuleError < StandardError; end
|
|
7
|
+
|
|
8
|
+
# Loads, validates, memoizes and looks up the rule catalog from the i18n locale.
|
|
9
|
+
module RuleCatalog
|
|
10
|
+
LOCALE_PATH = File.expand_path('../../config/locales/en.yml', __dir__)
|
|
11
|
+
KEY_SHAPE = /\A[a-z0-9_]+\z/
|
|
12
|
+
private_constant :KEY_SHAPE
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def all
|
|
16
|
+
@all ||= load
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def fetch(offense_name)
|
|
20
|
+
all.fetch(offense_name.to_s) do
|
|
21
|
+
raise UnknownRuleError, "Unknown rule: #{offense_name.inspect}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Guard-only: raises for an unknown rule, returns nil otherwise.
|
|
26
|
+
def validate!(offense_name)
|
|
27
|
+
fetch(offense_name)
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def load
|
|
34
|
+
rows = read_rows
|
|
35
|
+
rows.each { |key, row| validate_row!(key, row) }
|
|
36
|
+
rows.transform_values { |row| row.transform_values(&:freeze).freeze }.freeze
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def read_rows
|
|
40
|
+
loaded = YAML.safe_load_file(LOCALE_PATH)
|
|
41
|
+
rows = loaded.is_a?(Hash) ? loaded.dig('en', 'fastererer', 'rules') : nil
|
|
42
|
+
return rows if rows.is_a?(Hash)
|
|
43
|
+
|
|
44
|
+
raise "Fastererer locale at #{LOCALE_PATH} is missing the 'en.fastererer.rules' section"
|
|
45
|
+
rescue Errno::ENOENT
|
|
46
|
+
raise "Fastererer locale file not found at #{LOCALE_PATH}"
|
|
47
|
+
rescue Psych::SyntaxError => e
|
|
48
|
+
raise "Fastererer locale at #{LOCALE_PATH} is not valid YAML: #{e.message}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def validate_row!(key, row)
|
|
52
|
+
raise "Fastererer rule #{key} is malformed: #{row.inspect}" unless row.is_a?(Hash)
|
|
53
|
+
unless key.is_a?(String) && key.match?(KEY_SHAPE)
|
|
54
|
+
raise "Fastererer rule #{key.inspect} is not a snake_case key"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
validate_url!(key, row['url'])
|
|
58
|
+
validate_description!(key, row['description'])
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def validate_url!(key, url)
|
|
62
|
+
unless url.is_a?(String) && url.start_with?('https://')
|
|
63
|
+
raise "Fastererer rule #{key} has a non-https url: #{url.inspect}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
return if url.match?(/\A[[:print:]]+\z/)
|
|
67
|
+
|
|
68
|
+
raise "Fastererer rule #{key} has a non-printable url: #{url.inspect}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def validate_description!(key, text)
|
|
72
|
+
return if text.is_a?(String) && text.match?(/\A[[:print:][:space:]]+\z/)
|
|
73
|
+
|
|
74
|
+
raise "Fastererer rule #{key} has a non-printable description: #{text.inspect}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Test-only hook: clears the memoized catalog so the YAML can be re-read.
|
|
78
|
+
def reset!
|
|
79
|
+
@all = nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fastererer
|
|
4
|
+
# Derives a rubocop-style "Performance/PascalCase" display name from a snake_case rule key.
|
|
5
|
+
module RuleName
|
|
6
|
+
DEPARTMENT = 'Performance'
|
|
7
|
+
|
|
8
|
+
def self.from(offense_name)
|
|
9
|
+
"#{DEPARTMENT}/#{pascal_case(offense_name)}"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.pascal_case(name)
|
|
13
|
+
name.to_s.split('_').filter_map { |part| part.capitalize unless part.empty? }.join
|
|
14
|
+
end
|
|
15
|
+
private_class_method :pascal_case
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fastererer/offense'
|
|
4
|
+
require 'fastererer/scanners/offensive'
|
|
5
|
+
|
|
6
|
+
module Fastererer
|
|
7
|
+
class ForLoopScanner
|
|
8
|
+
include Fastererer::Offensive
|
|
9
|
+
|
|
10
|
+
attr_reader :element
|
|
11
|
+
|
|
12
|
+
def initialize(element)
|
|
13
|
+
@element = element
|
|
14
|
+
check_offense
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def check_offense
|
|
20
|
+
add_offense(:for_loop_vs_each) # for is always slower than each, so no guard
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -20,6 +20,7 @@ module Fastererer
|
|
|
20
20
|
flatten: :check_flatten_offense,
|
|
21
21
|
fetch: :check_fetch_offense,
|
|
22
22
|
merge!: :check_merge_bang_offense,
|
|
23
|
+
update: :check_update_offense,
|
|
23
24
|
last: :check_last_offense,
|
|
24
25
|
include?: :check_range_include_offense
|
|
25
26
|
}.freeze
|
|
@@ -33,7 +34,7 @@ module Fastererer
|
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
def method_call
|
|
36
|
-
@method_call ||= MethodCall.
|
|
37
|
+
@method_call ||= MethodCall.build(element)
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
private
|
|
@@ -74,9 +75,7 @@ module Fastererer
|
|
|
74
75
|
end
|
|
75
76
|
|
|
76
77
|
def check_first_offense
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
case method_call.receiver.name
|
|
78
|
+
case method_call.receiver&.call_name
|
|
80
79
|
when :shuffle
|
|
81
80
|
add_offense(:shuffle_first_vs_sample)
|
|
82
81
|
when :select
|
|
@@ -88,9 +87,7 @@ module Fastererer
|
|
|
88
87
|
end
|
|
89
88
|
|
|
90
89
|
def check_each_offense
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
case method_call.receiver.name
|
|
90
|
+
case method_call.receiver&.call_name
|
|
94
91
|
when :reverse
|
|
95
92
|
add_offense(:reverse_each_vs_reverse_each)
|
|
96
93
|
when :keys
|
|
@@ -99,8 +96,7 @@ module Fastererer
|
|
|
99
96
|
end
|
|
100
97
|
|
|
101
98
|
def check_flatten_offense
|
|
102
|
-
return unless method_call.receiver.
|
|
103
|
-
return unless method_call.receiver.name == :map && method_call.arguments.one?
|
|
99
|
+
return unless method_call.receiver&.call_name == :map && method_call.arguments.one?
|
|
104
100
|
|
|
105
101
|
add_offense(:map_flatten_vs_flat_map) if method_call.arguments.first.value == 1
|
|
106
102
|
end
|
|
@@ -115,25 +111,25 @@ module Fastererer
|
|
|
115
111
|
return unless method_call.arguments.one?
|
|
116
112
|
|
|
117
113
|
first_argument = method_call.arguments.first
|
|
118
|
-
return unless first_argument.type == :hash
|
|
119
|
-
# each key and value is an item by itself.
|
|
120
|
-
return unless first_argument.element.drop(1).count == 2
|
|
114
|
+
return unless first_argument.type == :hash && first_argument.element.elements.one?
|
|
121
115
|
|
|
122
116
|
add_offense(:hash_merge_bang_vs_hash_brackets)
|
|
123
117
|
end
|
|
124
118
|
|
|
119
|
+
# Hash#update is a merge! alias but heavily overloaded, so require a provably-Hash receiver
|
|
120
|
+
def check_update_offense
|
|
121
|
+
check_merge_bang_offense if method_call.receiver&.hash?
|
|
122
|
+
end
|
|
123
|
+
|
|
125
124
|
def check_last_offense
|
|
126
|
-
return unless method_call.receiver
|
|
127
|
-
return unless method_call.receiver.name == :select
|
|
125
|
+
return unless method_call.receiver&.call_name == :select
|
|
128
126
|
return if method_call.arguments.any?
|
|
129
127
|
|
|
130
128
|
add_offense(:select_last_vs_reverse_detect)
|
|
131
129
|
end
|
|
132
130
|
|
|
133
131
|
def check_range_include_offense
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
add_offense(:include_vs_cover_on_range)
|
|
132
|
+
add_offense(:include_vs_cover_on_range) if method_call.receiver&.range?
|
|
137
133
|
end
|
|
138
134
|
end
|
|
139
135
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'prism'
|
|
3
4
|
require 'fastererer/method_definition'
|
|
4
5
|
require 'fastererer/method_call'
|
|
5
6
|
require 'fastererer/offense'
|
|
@@ -27,33 +28,15 @@ module Fastererer
|
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
def scan_block_call_offense
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
method_call = MethodCall.new(element)
|
|
34
|
-
|
|
35
|
-
if method_call.receiver.is_a?(Fastererer::VariableReference) &&
|
|
36
|
-
method_call.receiver.name == method_definition.block_argument_name &&
|
|
37
|
-
method_call.method_name == :call
|
|
38
|
-
|
|
39
|
-
add_offense(:proc_call_vs_yield) && return
|
|
40
|
-
end
|
|
41
|
-
end
|
|
31
|
+
visitor = ProcCallVisitor.new(method_definition.block_argument_name)
|
|
32
|
+
method_definition.body.each { |node| node.accept(visitor) }
|
|
33
|
+
add_offense(:proc_call_vs_yield) if visitor.proc_call_found?
|
|
42
34
|
end
|
|
43
35
|
|
|
44
36
|
def method_definition
|
|
45
37
|
@method_definition ||= MethodDefinition.new(element)
|
|
46
38
|
end
|
|
47
39
|
|
|
48
|
-
def traverse_tree(sexp_tree, &block)
|
|
49
|
-
sexp_tree.each do |element|
|
|
50
|
-
next unless element.is_a?(Array)
|
|
51
|
-
|
|
52
|
-
yield element
|
|
53
|
-
traverse_tree(element, &block)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
40
|
def scan_getter_and_setter_offense
|
|
58
41
|
method_definition.setter? ? scan_setter_offense : scan_getter_offense
|
|
59
42
|
end
|
|
@@ -69,12 +52,12 @@ module Fastererer
|
|
|
69
52
|
end
|
|
70
53
|
|
|
71
54
|
def trivial_setter?(first_argument)
|
|
72
|
-
|
|
73
|
-
|
|
55
|
+
body_node = method_definition.body.first
|
|
56
|
+
return false unless body_node.is_a?(Prism::InstanceVariableWriteNode)
|
|
57
|
+
return false unless body_node.value.is_a?(Prism::LocalVariableReadNode)
|
|
74
58
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
body_first[2][1] == first_argument.name
|
|
59
|
+
expected_ivar = :"@#{method_definition.name.to_s.delete_suffix('=')}"
|
|
60
|
+
body_node.name == expected_ivar && body_node.value.name == first_argument.name
|
|
78
61
|
end
|
|
79
62
|
|
|
80
63
|
def scan_getter_offense
|
|
@@ -85,10 +68,55 @@ module Fastererer
|
|
|
85
68
|
end
|
|
86
69
|
|
|
87
70
|
def trivial_getter?
|
|
88
|
-
|
|
71
|
+
body_node = method_definition.body.first
|
|
72
|
+
expected_ivar = :"@#{method_definition.name}"
|
|
89
73
|
|
|
90
|
-
|
|
91
|
-
body_first[1].to_s == "@#{method_definition.name}"
|
|
74
|
+
body_node.is_a?(Prism::InstanceVariableReadNode) && body_node.name == expected_ivar
|
|
92
75
|
end
|
|
93
76
|
end
|
|
77
|
+
|
|
78
|
+
# Finds `block_name.call` in a method body, without descending into nested def/class/module scopes
|
|
79
|
+
# where the block parameter is no longer in scope
|
|
80
|
+
class ProcCallVisitor < Prism::Visitor
|
|
81
|
+
def initialize(block_name)
|
|
82
|
+
super()
|
|
83
|
+
@block_name = block_name
|
|
84
|
+
@proc_call_found = false
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def proc_call_found?
|
|
88
|
+
@proc_call_found
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def visit_call_node(node)
|
|
92
|
+
@proc_call_found = true if proc_call?(node)
|
|
93
|
+
super
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# No super on purpose: these open a new scope where the block param is unbound, so we stop
|
|
97
|
+
# descending rather than attribute their inner `name.call`s to the enclosing method.
|
|
98
|
+
def visit_def_node(_node)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def visit_class_node(_node)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def visit_module_node(_node)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def visit_singleton_class_node(_node)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def proc_call?(node)
|
|
113
|
+
return false if @block_name.nil? # an anonymous block param (&) is never invoked by name
|
|
114
|
+
|
|
115
|
+
node.receiver.is_a?(Prism::LocalVariableReadNode) &&
|
|
116
|
+
node.receiver.name == @block_name &&
|
|
117
|
+
node.name == :call
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private_constant :ProcCallVisitor
|
|
94
122
|
end
|
|
@@ -14,12 +14,8 @@ module Fastererer
|
|
|
14
14
|
|
|
15
15
|
private
|
|
16
16
|
|
|
17
|
-
def add_offense(offense_name
|
|
18
|
-
self.offense = Fastererer::Offense.new(offense_name,
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def check_offense
|
|
22
|
-
raise NotImplementedError
|
|
17
|
+
def add_offense(offense_name)
|
|
18
|
+
self.offense = Fastererer::Offense.new(offense_name, element.location.start_line)
|
|
23
19
|
end
|
|
24
20
|
end
|
|
25
21
|
end
|
|
@@ -1,35 +1,48 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'prism'
|
|
3
4
|
require 'fastererer/method_call'
|
|
4
5
|
|
|
5
6
|
module Fastererer
|
|
6
|
-
# Includer must respond to #method_call (returning a MethodCall) and #add_offense(symbol)
|
|
7
|
+
# Includer must respond to #method_call (returning a MethodCall) and #add_offense(symbol)
|
|
8
|
+
# (the latter via the Offensive mixin).
|
|
7
9
|
module SymbolToProcCheck
|
|
8
10
|
private
|
|
9
11
|
|
|
10
12
|
def check_symbol_to_proc
|
|
11
13
|
return unless symbol_to_proc_candidate?
|
|
12
14
|
|
|
13
|
-
body_call = MethodCall.
|
|
15
|
+
body_call = MethodCall.build(method_call.block_body.first)
|
|
14
16
|
return unless symbol_to_proc_body?(body_call)
|
|
15
17
|
|
|
16
18
|
add_offense(:block_vs_symbol_to_proc)
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def symbol_to_proc_candidate?
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
method_call.block_body.sexp_type == :call &&
|
|
22
|
+
single_block_argument_name &&
|
|
23
|
+
single_call_body? &&
|
|
23
24
|
method_call.arguments.none? &&
|
|
24
25
|
!method_call.lambda_literal?
|
|
25
26
|
end
|
|
26
27
|
|
|
28
|
+
# A destructured param has no name, so |(a, b), c| must not read as a single-name block
|
|
29
|
+
def single_block_argument_name
|
|
30
|
+
names = method_call.block_argument_names
|
|
31
|
+
names.first if names.size == 1
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# A safe-nav body (foo&.bar) is excluded: arr.map(&:bar) raises on a nil element, so the rewrite
|
|
35
|
+
# would not preserve behavior
|
|
36
|
+
def single_call_body?
|
|
37
|
+
body = method_call.block_body
|
|
38
|
+
body&.size == 1 && body.first.is_a?(Prism::CallNode) && !body.first.safe_navigation?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# A primitive receiver's name is nil, which never matches the named param the gate requires
|
|
27
42
|
def symbol_to_proc_body?(body_call)
|
|
28
43
|
body_call.arguments.none? &&
|
|
29
44
|
!body_call.block? &&
|
|
30
|
-
|
|
31
|
-
!body_call.receiver.is_a?(Fastererer::Primitive) &&
|
|
32
|
-
body_call.receiver.name == method_call.block_argument_names.first
|
|
45
|
+
body_call.receiver&.name == single_block_argument_name
|
|
33
46
|
end
|
|
34
47
|
end
|
|
35
48
|
end
|
data/lib/fastererer/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fastererer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matt Menefee
|
|
@@ -11,19 +11,19 @@ cert_chain: []
|
|
|
11
11
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: prism
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 3.
|
|
19
|
+
version: 1.3.0
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: 3.
|
|
26
|
+
version: 1.3.0
|
|
27
27
|
description: Use Fastererer to check various places in your code that could be faster.
|
|
28
28
|
A fork of fasterer (https://github.com/DamirSvrtan/fasterer) with Ruby 4.0 support
|
|
29
29
|
and native Prism parsing.
|
|
@@ -35,28 +35,36 @@ executables:
|
|
|
35
35
|
extensions: []
|
|
36
36
|
extra_rdoc_files: []
|
|
37
37
|
files:
|
|
38
|
-
- ".fastererer.yml"
|
|
39
|
-
- ".ruby-version"
|
|
40
38
|
- CHANGELOG.md
|
|
41
|
-
- CODE_OF_CONDUCT.md
|
|
42
|
-
- CONTRIBUTING.md
|
|
43
39
|
- LICENSE.txt
|
|
44
40
|
- README.md
|
|
45
|
-
-
|
|
46
|
-
- SECURITY.md
|
|
41
|
+
- config/locales/en.yml
|
|
47
42
|
- exe/fastererer
|
|
48
43
|
- lib/fastererer.rb
|
|
49
44
|
- lib/fastererer/analyzer.rb
|
|
50
45
|
- lib/fastererer/cli.rb
|
|
51
46
|
- lib/fastererer/config.rb
|
|
47
|
+
- lib/fastererer/explanation.rb
|
|
52
48
|
- lib/fastererer/file_traverser.rb
|
|
49
|
+
- lib/fastererer/finding.rb
|
|
50
|
+
- lib/fastererer/formatters.rb
|
|
51
|
+
- lib/fastererer/formatters/base.rb
|
|
52
|
+
- lib/fastererer/formatters/json_formatter.rb
|
|
53
|
+
- lib/fastererer/formatters/rdjsonl_formatter.rb
|
|
54
|
+
- lib/fastererer/formatters/text_formatter.rb
|
|
55
|
+
- lib/fastererer/formatters/text_statistics.rb
|
|
53
56
|
- lib/fastererer/method_call.rb
|
|
54
57
|
- lib/fastererer/method_definition.rb
|
|
55
58
|
- lib/fastererer/offense.rb
|
|
56
59
|
- lib/fastererer/offense_collector.rb
|
|
57
60
|
- lib/fastererer/painter.rb
|
|
58
61
|
- lib/fastererer/parser.rb
|
|
62
|
+
- lib/fastererer/receiver.rb
|
|
63
|
+
- lib/fastererer/report.rb
|
|
59
64
|
- lib/fastererer/rescue_call.rb
|
|
65
|
+
- lib/fastererer/rule_catalog.rb
|
|
66
|
+
- lib/fastererer/rule_name.rb
|
|
67
|
+
- lib/fastererer/scanners/for_loop_scanner.rb
|
|
60
68
|
- lib/fastererer/scanners/method_call_scanner.rb
|
|
61
69
|
- lib/fastererer/scanners/method_definition_scanner.rb
|
|
62
70
|
- lib/fastererer/scanners/offensive.rb
|
|
@@ -86,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
86
94
|
- !ruby/object:Gem::Version
|
|
87
95
|
version: '0'
|
|
88
96
|
requirements: []
|
|
89
|
-
rubygems_version: 4.0.
|
|
97
|
+
rubygems_version: 4.0.16
|
|
90
98
|
specification_version: 4
|
|
91
99
|
summary: Run Ruby more than fast. Fastererer
|
|
92
100
|
test_files: []
|
data/.fastererer.yml
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
speedups:
|
|
2
|
-
rescue_vs_respond_to: true
|
|
3
|
-
module_eval: true
|
|
4
|
-
shuffle_first_vs_sample: true
|
|
5
|
-
for_loop_vs_each: true
|
|
6
|
-
each_with_index_vs_while: false
|
|
7
|
-
map_flatten_vs_flat_map: true
|
|
8
|
-
reverse_each_vs_reverse_each: true
|
|
9
|
-
select_first_vs_detect: true
|
|
10
|
-
select_last_vs_reverse_detect: true
|
|
11
|
-
sort_vs_sort_by: true
|
|
12
|
-
fetch_with_argument_vs_block: true
|
|
13
|
-
keys_each_vs_each_key: true
|
|
14
|
-
hash_merge_bang_vs_hash_brackets: true
|
|
15
|
-
block_vs_symbol_to_proc: true
|
|
16
|
-
proc_call_vs_yield: true
|
|
17
|
-
gsub_vs_tr: true
|
|
18
|
-
getter_vs_attr_reader: true
|
|
19
|
-
setter_vs_attr_writer: true
|
|
20
|
-
|
|
21
|
-
exclude_paths:
|
|
22
|
-
- 'spec/support/analyzer/*.rb'
|
|
23
|
-
- 'spec/support/binary_call/*.rb'
|
|
24
|
-
- 'spec/support/method_call/*.rb'
|
|
25
|
-
- 'spec/support/method_definition/*.rb'
|
|
26
|
-
- 'spec/support/rescue_call/*.rb'
|
|
27
|
-
- 'vendor/**/*.rb'
|
data/.ruby-version
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
4.0.4
|
data/CODE_OF_CONDUCT.md
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# Code of Conduct
|
|
2
|
-
|
|
3
|
-
Fastererer follows [The Ruby Community Conduct Guideline][ruby-conduct] in all "collaborative
|
|
4
|
-
space", which is defined as community communications channels (such as mailing lists, submitted
|
|
5
|
-
patches, commit comments, etc.):
|
|
6
|
-
|
|
7
|
-
* Participants will be tolerant of opposing views.
|
|
8
|
-
* Participants must ensure that their language and actions are free of personal attacks and
|
|
9
|
-
disparaging personal remarks.
|
|
10
|
-
* When interpreting the words and actions of others, participants should always assume good
|
|
11
|
-
intentions.
|
|
12
|
-
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
13
|
-
|
|
14
|
-
If you have any concerns about behaviour within this project, please contact us at
|
|
15
|
-
[matt.menefee@extractablemedia.com][email].
|
|
16
|
-
|
|
17
|
-
[email]: mailto:matt.menefee@extractablemedia.com
|
|
18
|
-
[ruby-conduct]: https://www.ruby-lang.org/en/conduct
|
data/CONTRIBUTING.md
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# Contributing to fastererer
|
|
2
|
-
|
|
3
|
-
Thanks for your interest in improving `fastererer`. This document explains how to report issues
|
|
4
|
-
and submit code.
|
|
5
|
-
|
|
6
|
-
## Reporting Issues
|
|
7
|
-
|
|
8
|
-
Before opening a new issue:
|
|
9
|
-
|
|
10
|
-
1. Check the [existing issues][issues] to avoid duplicates.
|
|
11
|
-
2. Confirm the bug reproduces on the latest release.
|
|
12
|
-
|
|
13
|
-
When opening an issue, include:
|
|
14
|
-
|
|
15
|
-
- The output of `fastererer --version`
|
|
16
|
-
- Your Ruby version (`ruby --version`)
|
|
17
|
-
- A minimal reproduction — the smallest Ruby snippet that triggers the behavior
|
|
18
|
-
- The expected behavior and the actual behavior
|
|
19
|
-
|
|
20
|
-
## Pull Requests
|
|
21
|
-
|
|
22
|
-
1. Fork the repository and create a topic branch off `main`.
|
|
23
|
-
2. Run `bin/setup` to install dependencies (or `bundle install` directly).
|
|
24
|
-
3. Add tests that cover the change. Bug fixes should include a regression spec.
|
|
25
|
-
4. Run the test suite with `bin/rspec` and make sure it passes.
|
|
26
|
-
5. Update [CHANGELOG.md][changelog] under an unreleased heading describing the change.
|
|
27
|
-
6. Submit the PR with a clear description of what changed and why.
|
|
28
|
-
|
|
29
|
-
For non-trivial changes, consider opening an issue first to discuss the approach.
|
|
30
|
-
|
|
31
|
-
## Local Development
|
|
32
|
-
|
|
33
|
-
After cloning:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
bin/setup # installs dependencies
|
|
37
|
-
bin/console # opens an IRB session with the gem loaded
|
|
38
|
-
bin/rake # runs the test suite and rubocop (the default task)
|
|
39
|
-
bin/rspec # runs just the test suite
|
|
40
|
-
bin/rubocop # runs just the linter
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
To try the executable against a Ruby file:
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
bundle exec exe/fastererer path/to/file.rb
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Code Style
|
|
50
|
-
|
|
51
|
-
- Follow the [Ruby Style Guide][ruby-style-guide].
|
|
52
|
-
- Run `bin/rubocop` before submitting — CI runs the default rake task on every PR.
|
|
53
|
-
- New Ruby files should start with `# frozen_string_literal: true`.
|
|
54
|
-
- Tests use RSpec — see existing specs in `spec/` for examples and conventions.
|
|
55
|
-
|
|
56
|
-
## Code of Conduct
|
|
57
|
-
|
|
58
|
-
By participating in this project, you agree to abide by its [Code of Conduct][code-of-conduct].
|
|
59
|
-
|
|
60
|
-
[changelog]: CHANGELOG.md
|
|
61
|
-
[code-of-conduct]: CODE_OF_CONDUCT.md
|
|
62
|
-
[issues]: https://github.com/ExtractableMedia/fastererer/issues
|
|
63
|
-
[ruby-style-guide]: https://rubystyle.guide
|
data/Rakefile
DELETED
data/SECURITY.md
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# Security Policy
|
|
2
|
-
|
|
3
|
-
## Supported Versions
|
|
4
|
-
|
|
5
|
-
Only the latest released version of `fastererer` receives security updates. Older versions are not
|
|
6
|
-
supported.
|
|
7
|
-
|
|
8
|
-
## Reporting a Vulnerability
|
|
9
|
-
|
|
10
|
-
Please report security vulnerabilities **privately** rather than opening a public GitHub issue.
|
|
11
|
-
|
|
12
|
-
The preferred channel is **GitHub Security Advisories**: [Report a vulnerability][advisory].
|
|
13
|
-
|
|
14
|
-
If GitHub is unavailable, email [matt.menefee@extractablemedia.com][email].
|
|
15
|
-
|
|
16
|
-
We will acknowledge receipt within a few business days and work with you on a coordinated disclosure
|
|
17
|
-
timeline. Credit will be given in the release notes unless you prefer to remain anonymous.
|
|
18
|
-
|
|
19
|
-
[advisory]: https://github.com/ExtractableMedia/fastererer/security/advisories/new
|
|
20
|
-
[email]: mailto:matt.menefee@extractablemedia.com
|