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.
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fastererer
4
+ VERSION = '0.12.0'
5
+ end
data/lib/fastererer.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fastererer/version'
4
+ require 'fastererer/analyzer'
5
+ require 'fastererer/method_definition'
6
+
7
+ module Fastererer
8
+ # Your code goes here...
9
+ end
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: []