fastererer 0.12.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 +7 -0
- data/.fastererer.yml +27 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +113 -0
- data/CODE_OF_CONDUCT.md +18 -0
- data/CONTRIBUTING.md +63 -0
- data/LICENSE.txt +23 -0
- data/README.md +169 -0
- data/Rakefile +12 -0
- data/SECURITY.md +20 -0
- data/exe/fastererer +7 -0
- data/lib/fastererer/analyzer.rb +94 -0
- data/lib/fastererer/cli.rb +43 -0
- data/lib/fastererer/config.rb +50 -0
- data/lib/fastererer/file_traverser.rb +186 -0
- data/lib/fastererer/method_call.rb +141 -0
- data/lib/fastererer/method_definition.rb +94 -0
- data/lib/fastererer/offense.rb +81 -0
- data/lib/fastererer/offense_collector.rb +19 -0
- data/lib/fastererer/painter.rb +45 -0
- data/lib/fastererer/parser.rb +13 -0
- data/lib/fastererer/rescue_call.rb +23 -0
- data/lib/fastererer/scanners/method_call_scanner.rb +139 -0
- data/lib/fastererer/scanners/method_definition_scanner.rb +94 -0
- data/lib/fastererer/scanners/offensive.rb +25 -0
- data/lib/fastererer/scanners/rescue_call_scanner.rb +30 -0
- data/lib/fastererer/scanners/symbol_to_proc_check.rb +35 -0
- data/lib/fastererer/version.rb +5 -0
- data/lib/fastererer.rb +9 -0
- metadata +92 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fastererer/method_call'
|
|
4
|
+
require 'fastererer/offense'
|
|
5
|
+
require 'fastererer/scanners/offensive'
|
|
6
|
+
require 'fastererer/scanners/symbol_to_proc_check'
|
|
7
|
+
|
|
8
|
+
module Fastererer
|
|
9
|
+
class MethodCallScanner
|
|
10
|
+
include Fastererer::Offensive
|
|
11
|
+
include Fastererer::SymbolToProcCheck
|
|
12
|
+
|
|
13
|
+
CHECKERS = {
|
|
14
|
+
module_eval: :check_module_eval_offense,
|
|
15
|
+
gsub: :check_gsub_offense,
|
|
16
|
+
sort: :check_sort_offense,
|
|
17
|
+
each_with_index: :check_each_with_index_offense,
|
|
18
|
+
first: :check_first_offense,
|
|
19
|
+
each: :check_each_offense,
|
|
20
|
+
flatten: :check_flatten_offense,
|
|
21
|
+
fetch: :check_fetch_offense,
|
|
22
|
+
merge!: :check_merge_bang_offense,
|
|
23
|
+
last: :check_last_offense,
|
|
24
|
+
include?: :check_range_include_offense
|
|
25
|
+
}.freeze
|
|
26
|
+
private_constant :CHECKERS
|
|
27
|
+
|
|
28
|
+
attr_reader :element
|
|
29
|
+
|
|
30
|
+
def initialize(element)
|
|
31
|
+
@element = element
|
|
32
|
+
check_offense
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def method_call
|
|
36
|
+
@method_call ||= MethodCall.new(element)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def check_offense
|
|
42
|
+
checker = CHECKERS[method_call.method_name]
|
|
43
|
+
send(checker) if checker
|
|
44
|
+
check_symbol_to_proc
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def check_module_eval_offense
|
|
48
|
+
first_argument = method_call.arguments.first
|
|
49
|
+
return unless first_argument && first_argument.value.is_a?(String)
|
|
50
|
+
|
|
51
|
+
add_offense(:module_eval) if first_argument.value.include?('def')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def check_gsub_offense
|
|
55
|
+
first_argument = method_call.arguments[0]
|
|
56
|
+
second_argument = method_call.arguments[1]
|
|
57
|
+
return if first_argument.nil? || second_argument.nil?
|
|
58
|
+
|
|
59
|
+
add_offense(:gsub_vs_tr) if both_single_char_strings?(first_argument, second_argument)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def both_single_char_strings?(first, second)
|
|
63
|
+
[first, second].all? { |arg| arg.value.is_a?(String) && arg.value.size == 1 }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def check_sort_offense
|
|
67
|
+
return unless method_call.arguments.any? || method_call.block?
|
|
68
|
+
|
|
69
|
+
add_offense(:sort_vs_sort_by)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def check_each_with_index_offense
|
|
73
|
+
add_offense(:each_with_index_vs_while)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def check_first_offense
|
|
77
|
+
return unless method_call.receiver.is_a?(MethodCall)
|
|
78
|
+
|
|
79
|
+
case method_call.receiver.name
|
|
80
|
+
when :shuffle
|
|
81
|
+
add_offense(:shuffle_first_vs_sample)
|
|
82
|
+
when :select
|
|
83
|
+
return unless method_call.receiver.block?
|
|
84
|
+
return if method_call.arguments.any?
|
|
85
|
+
|
|
86
|
+
add_offense(:select_first_vs_detect)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def check_each_offense
|
|
91
|
+
return unless method_call.receiver.is_a?(MethodCall)
|
|
92
|
+
|
|
93
|
+
case method_call.receiver.name
|
|
94
|
+
when :reverse
|
|
95
|
+
add_offense(:reverse_each_vs_reverse_each)
|
|
96
|
+
when :keys
|
|
97
|
+
add_offense(:keys_each_vs_each_key) if method_call.receiver.arguments.none?
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def check_flatten_offense
|
|
102
|
+
return unless method_call.receiver.is_a?(MethodCall)
|
|
103
|
+
return unless method_call.receiver.name == :map && method_call.arguments.one?
|
|
104
|
+
|
|
105
|
+
add_offense(:map_flatten_vs_flat_map) if method_call.arguments.first.value == 1
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def check_fetch_offense
|
|
109
|
+
return unless method_call.arguments.count == 2 && !method_call.block?
|
|
110
|
+
|
|
111
|
+
add_offense(:fetch_with_argument_vs_block)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def check_merge_bang_offense
|
|
115
|
+
return unless method_call.arguments.one?
|
|
116
|
+
|
|
117
|
+
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
|
|
121
|
+
|
|
122
|
+
add_offense(:hash_merge_bang_vs_hash_brackets)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def check_last_offense
|
|
126
|
+
return unless method_call.receiver.is_a?(MethodCall)
|
|
127
|
+
return unless method_call.receiver.name == :select
|
|
128
|
+
return if method_call.arguments.any?
|
|
129
|
+
|
|
130
|
+
add_offense(:select_last_vs_reverse_detect)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def check_range_include_offense
|
|
134
|
+
return unless method_call.receiver.is_a?(Primitive) && method_call.receiver.range?
|
|
135
|
+
|
|
136
|
+
add_offense(:include_vs_cover_on_range)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fastererer/method_definition'
|
|
4
|
+
require 'fastererer/method_call'
|
|
5
|
+
require 'fastererer/offense'
|
|
6
|
+
require 'fastererer/scanners/offensive'
|
|
7
|
+
|
|
8
|
+
module Fastererer
|
|
9
|
+
class MethodDefinitionScanner
|
|
10
|
+
include Fastererer::Offensive
|
|
11
|
+
|
|
12
|
+
attr_reader :element
|
|
13
|
+
|
|
14
|
+
def initialize(element)
|
|
15
|
+
@element = element
|
|
16
|
+
check_offense
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def check_offense
|
|
22
|
+
if method_definition.block?
|
|
23
|
+
scan_block_call_offense
|
|
24
|
+
else
|
|
25
|
+
scan_getter_and_setter_offense
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def scan_block_call_offense
|
|
30
|
+
traverse_tree(method_definition.body) do |element|
|
|
31
|
+
next unless element.sexp_type == :call
|
|
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
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def method_definition
|
|
45
|
+
@method_definition ||= MethodDefinition.new(element)
|
|
46
|
+
end
|
|
47
|
+
|
|
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
|
+
def scan_getter_and_setter_offense
|
|
58
|
+
method_definition.setter? ? scan_setter_offense : scan_getter_offense
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def scan_setter_offense
|
|
62
|
+
return if method_definition.arguments.size != 1
|
|
63
|
+
return if method_definition.body.size != 1
|
|
64
|
+
|
|
65
|
+
first_argument = method_definition.arguments.first
|
|
66
|
+
return if first_argument.type != :regular_argument
|
|
67
|
+
|
|
68
|
+
add_offense(:setter_vs_attr_writer) if trivial_setter?(first_argument)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def trivial_setter?(first_argument)
|
|
72
|
+
body_first = method_definition.body.first
|
|
73
|
+
expected_ivar = "@#{method_definition.name.to_s.delete_suffix('=')}"
|
|
74
|
+
|
|
75
|
+
body_first.sexp_type == :iasgn &&
|
|
76
|
+
body_first[1].to_s == expected_ivar &&
|
|
77
|
+
body_first[2][1] == first_argument.name
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def scan_getter_offense
|
|
81
|
+
return if method_definition.arguments.size.positive?
|
|
82
|
+
return if method_definition.body.size != 1
|
|
83
|
+
|
|
84
|
+
add_offense(:getter_vs_attr_reader) if trivial_getter?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def trivial_getter?
|
|
88
|
+
body_first = method_definition.body.first
|
|
89
|
+
|
|
90
|
+
body_first.sexp_type == :ivar &&
|
|
91
|
+
body_first[1].to_s == "@#{method_definition.name}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fastererer/offense'
|
|
4
|
+
|
|
5
|
+
module Fastererer
|
|
6
|
+
module Offensive
|
|
7
|
+
attr_accessor :offense
|
|
8
|
+
|
|
9
|
+
def offensive?
|
|
10
|
+
!!offense
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
alias offense_detected? offensive?
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def add_offense(offense_name, element_line_number = element.line)
|
|
18
|
+
self.offense = Fastererer::Offense.new(offense_name, element_line_number)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def check_offense
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fastererer/rescue_call'
|
|
4
|
+
require 'fastererer/offense'
|
|
5
|
+
require 'fastererer/scanners/offensive'
|
|
6
|
+
|
|
7
|
+
module Fastererer
|
|
8
|
+
class RescueCallScanner
|
|
9
|
+
include Fastererer::Offensive
|
|
10
|
+
|
|
11
|
+
attr_reader :element
|
|
12
|
+
|
|
13
|
+
def initialize(element)
|
|
14
|
+
@element = element
|
|
15
|
+
check_offense
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def check_offense
|
|
21
|
+
return unless rescue_call.rescue_classes.include? :NoMethodError
|
|
22
|
+
|
|
23
|
+
add_offense(:rescue_vs_respond_to)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def rescue_call
|
|
27
|
+
@rescue_call ||= RescueCall.new(element)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fastererer/method_call'
|
|
4
|
+
|
|
5
|
+
module Fastererer
|
|
6
|
+
# Includer must respond to #method_call (returning a MethodCall) and #add_offense(symbol).
|
|
7
|
+
module SymbolToProcCheck
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def check_symbol_to_proc
|
|
11
|
+
return unless symbol_to_proc_candidate?
|
|
12
|
+
|
|
13
|
+
body_call = MethodCall.new(method_call.block_body)
|
|
14
|
+
return unless symbol_to_proc_body?(body_call)
|
|
15
|
+
|
|
16
|
+
add_offense(:block_vs_symbol_to_proc)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def symbol_to_proc_candidate?
|
|
20
|
+
method_call.block_argument_names.one? &&
|
|
21
|
+
!method_call.block_body.nil? &&
|
|
22
|
+
method_call.block_body.sexp_type == :call &&
|
|
23
|
+
method_call.arguments.none? &&
|
|
24
|
+
!method_call.lambda_literal?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def symbol_to_proc_body?(body_call)
|
|
28
|
+
body_call.arguments.none? &&
|
|
29
|
+
!body_call.block? &&
|
|
30
|
+
!body_call.receiver.nil? &&
|
|
31
|
+
!body_call.receiver.is_a?(Fastererer::Primitive) &&
|
|
32
|
+
body_call.receiver.name == method_call.block_argument_names.first
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/fastererer.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: fastererer
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.12.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Matt Menefee
|
|
8
|
+
- Damir Svrtan
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: ruby_parser
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 3.22.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 3.22.0
|
|
27
|
+
description: Use Fastererer to check various places in your code that could be faster.
|
|
28
|
+
A fork of fasterer (https://github.com/DamirSvrtan/fasterer) with Ruby 4.0 support
|
|
29
|
+
and native Prism parsing.
|
|
30
|
+
email:
|
|
31
|
+
- matt.menefee@extractablemedia.com
|
|
32
|
+
- damir.svrtan@gmail.com
|
|
33
|
+
executables:
|
|
34
|
+
- fastererer
|
|
35
|
+
extensions: []
|
|
36
|
+
extra_rdoc_files: []
|
|
37
|
+
files:
|
|
38
|
+
- ".fastererer.yml"
|
|
39
|
+
- ".ruby-version"
|
|
40
|
+
- CHANGELOG.md
|
|
41
|
+
- CODE_OF_CONDUCT.md
|
|
42
|
+
- CONTRIBUTING.md
|
|
43
|
+
- LICENSE.txt
|
|
44
|
+
- README.md
|
|
45
|
+
- Rakefile
|
|
46
|
+
- SECURITY.md
|
|
47
|
+
- exe/fastererer
|
|
48
|
+
- lib/fastererer.rb
|
|
49
|
+
- lib/fastererer/analyzer.rb
|
|
50
|
+
- lib/fastererer/cli.rb
|
|
51
|
+
- lib/fastererer/config.rb
|
|
52
|
+
- lib/fastererer/file_traverser.rb
|
|
53
|
+
- lib/fastererer/method_call.rb
|
|
54
|
+
- lib/fastererer/method_definition.rb
|
|
55
|
+
- lib/fastererer/offense.rb
|
|
56
|
+
- lib/fastererer/offense_collector.rb
|
|
57
|
+
- lib/fastererer/painter.rb
|
|
58
|
+
- lib/fastererer/parser.rb
|
|
59
|
+
- lib/fastererer/rescue_call.rb
|
|
60
|
+
- lib/fastererer/scanners/method_call_scanner.rb
|
|
61
|
+
- lib/fastererer/scanners/method_definition_scanner.rb
|
|
62
|
+
- lib/fastererer/scanners/offensive.rb
|
|
63
|
+
- lib/fastererer/scanners/rescue_call_scanner.rb
|
|
64
|
+
- lib/fastererer/scanners/symbol_to_proc_check.rb
|
|
65
|
+
- lib/fastererer/version.rb
|
|
66
|
+
homepage: https://github.com/ExtractableMedia/fastererer
|
|
67
|
+
licenses:
|
|
68
|
+
- MIT
|
|
69
|
+
metadata:
|
|
70
|
+
source_code_uri: https://github.com/ExtractableMedia/fastererer
|
|
71
|
+
bug_tracker_uri: https://github.com/ExtractableMedia/fastererer/issues
|
|
72
|
+
changelog_uri: https://github.com/ExtractableMedia/fastererer/blob/main/CHANGELOG.md
|
|
73
|
+
documentation_uri: https://rubydoc.info/gems/fastererer
|
|
74
|
+
rubygems_mfa_required: 'true'
|
|
75
|
+
rdoc_options: []
|
|
76
|
+
require_paths:
|
|
77
|
+
- lib
|
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '3.3'
|
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '0'
|
|
88
|
+
requirements: []
|
|
89
|
+
rubygems_version: 4.0.10
|
|
90
|
+
specification_version: 4
|
|
91
|
+
summary: Run Ruby more than fast. Fastererer
|
|
92
|
+
test_files: []
|