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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ee0a8fc9f430360ccef546a4f391fdb3112801f6fb2c56204900f0dca8214063
4
+ data.tar.gz: a477c6a7b4b9f19011ea89bee00aa193d562f91981dd38e5136cd88fd7ebca14
5
+ SHA512:
6
+ metadata.gz: 4d239a4233228c45cc0c0ea28ef00110a8d2ea6476c258ef8f68d3f5b443a66cc48724f47529bb6eb787857843a3c7a4d84aed4aeda09c3a2266880c65ccb14a
7
+ data.tar.gz: 6ac88eca14ca854edc0e549b4ddbd6cd9b39796fb9658c10e3c16399b65a7341cea039c0d18fbb9e243406fe8503072ec25ba1a9671dba35d77f0549995bc7ea
data/.fastererer.yml ADDED
@@ -0,0 +1,27 @@
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 ADDED
@@ -0,0 +1 @@
1
+ 4.0.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,113 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog] and this project adheres to [Semantic Versioning].
6
+
7
+ Versions 0.11.0 and earlier were released as [`fasterer`](https://github.com/DamirSvrtan/fasterer), prior to the fork. From 0.12.0 onward, this is `fastererer`, maintained by ExtractableMedia.
8
+
9
+ ## [Unreleased]
10
+
11
+ ## [0.12.0] - 2026-05-18
12
+
13
+ ### Added
14
+
15
+ - [#2]: Explicit support for Ruby 4.0.
16
+ - [#42]: `NO_COLOR` env var and `--no-color` flag to suppress ANSI color output. Color also
17
+ auto-disables when STDOUT isn't a TTY (piped output, CI logs, editor integrations).
18
+ - [#42]: `--help`/`-h` and `--version`/`-v` CLI flags.
19
+
20
+ ### Changed
21
+
22
+ - [#1]: Rename the gem from `fasterer` to `fastererer`:
23
+ - Config filename: `.fasterer.yml` → `.fastererer.yml`.
24
+ - Executable: `fasterer` → `fastererer`.
25
+ - Top-level module: `Fasterer` → `Fastererer`.
26
+
27
+ ### Removed
28
+
29
+ - [#2]: Support for EOL Ruby versions (2.x, 3.0, 3.1, 3.2). Minimum required Ruby is now 3.3.
30
+
31
+ ### Fixed
32
+
33
+ - `select_last_vs_reverse_detect` no longer reports a false positive when `.last` is called with
34
+ arguments. `.last(n)` can return multiple elements, so `detect` (which only returns the first
35
+ match) isn't an equivalent replacement. Mirrors the same fix for `select_first_vs_detect`
36
+ shipped in 0.10.1.
37
+
38
+ ## 0.11.0
39
+
40
+ - There have been multiple issues filed with the colorize gem, such as licencing, gem versions etc. Due to the easy implementation, fasterer will now leverage an internal implementation of this so we don't need to get issues tracked bc of colorize. There might be other issues that arise (perhaps somebody leveraging Windows might have issues), but that's okay, we'll solve it.
41
+
42
+ - There has been a [bug report #102](https://github.com/DamirSvrtan/fasterer/issues/102) that the Redis `#keys` method shouldn't trigger `Hash#each_key` recommendation. It's not possible to detect that what is the receiver of the keys method call, but the keys method called on the Hash doesn't accept arguments while the redis one does. So not an ideal solution, but should fix any issues for now.
43
+
44
+
45
+ ## 0.10.1
46
+
47
+ - There has been a [bug report #99](https://github.com/DamirSvrtan/fasterer/issues/99) that the `select_first_vs_detect` reports false positives when first gets an argument passed in. If there is an argument passed in, the detect method is not suitable, since it always returns the first element matching (can't return multiple items).
48
+
49
+ ## 0.10.0
50
+
51
+ - Due to issues while setting up builds with Github Actions, I have dropped Ruby 2.2
52
+ support. It's EOL date was 2018-03-31, and I don't have the bandwidth to support
53
+ deprecated Ruby versions. The only reason at this point why Ruby versions 2.3, 2.4
54
+ and 2.5 are supported is because they still work with other dependencies, so it's
55
+ no effort currently to support them. Once they become issues, they'll probably be
56
+ dropped.
57
+ - Upgrade to ruby_parser 3.19.1 that fully supports Ruby 3.1
58
+
59
+ ## 0.9.0
60
+
61
+ - Ruby 3 support! Merged in [#85](https://github.com/DamirSvrtan/fasterer/pull/85), a PR that relaxed Ruby version constraints and added a minor change to support Ruby 3.0. Thanks to [swiknaba](https://github.com/swiknaba).
62
+
63
+ ## 0.8.3
64
+
65
+ - Merged in [#79](https://github.com/DamirSvrtan/fasterer/pull/79), a PR that makes sure the output is green when there are no failures.
66
+
67
+ ## 0.8.2
68
+
69
+ - Fixes [#77](https://github.com/DamirSvrtan/fasterer/issues/77). An error occurs on the symbol to proc check when somebody invokes a method with no arguments on an array or range inside of the inspected block. Seems like a bug in the inspected code, but nevertheless it is a code path we need to support.
70
+
71
+ ## 0.8.1
72
+
73
+ - Ignore lambda literals when checking symbol to proc. Thanks to [kiyot](https://github.com/kiyot) for his fix in PR [#74](https://github.com/DamirSvrtan/fasterer/pull/74).
74
+
75
+ ## 0.8.0
76
+
77
+ - Dropped support for ruby versions below 2.2.0 and locks ruby_parser to be above or equal to 1.14.1. The new ruby_parser version 1.14.1 explicitly [requires ruby versions above 2.2.0](https://github.com/seattlerb/ruby_parser/issues/298#issuecomment-539795933), so this affects fasterer as well.
78
+
79
+ ## 0.7.1
80
+
81
+ - Fix `check_symbol_to_proc` rule from crashing when there is no receiver [#67](https://github.com/DamirSvrtan/fasterer/pull/67)
82
+
83
+ ## 0.7.0
84
+
85
+ - More inclusive `check_symbol_to_proc` rule - don't only look at #each but any method that could leverage the symbol to proc rule. Merged through [#41](https://github.com/DamirSvrtan/fasterer/pull/41)
86
+
87
+ ## 0.6.0
88
+
89
+ - Enable placing the `.fasterer.yml` file not just in the current dir, but in any parent directory as well.
90
+
91
+ ## 0.5.1
92
+
93
+ - Upgrade to ruby_parser 3.13.0 that fully supports Ruby 2.6
94
+
95
+ ## 0.5.0
96
+
97
+ - New style of outputting offenses: `spec/support/output/sample_code.rb:1 For loop is slower than using each.`
98
+
99
+ ## 0.4.1
100
+ - Upgrade ruby parser version to 3.11.0 (to stop warnings)
101
+
102
+ ## 0.4.0
103
+ - Better error message on each_key
104
+ - Update Ruby Parser to ~> 3.9
105
+ - Support Ruby 2.3
106
+
107
+ [Keep a Changelog]: https://keepachangelog.com
108
+ [Semantic Versioning]: https://semver.org/spec/v2.0.0.html
109
+ [Unreleased]: https://github.com/ExtractableMedia/fastererer/compare/v0.12.0...HEAD
110
+ [0.12.0]: https://github.com/ExtractableMedia/fastererer/compare/v0.11.0...v0.12.0
111
+ [#1]: https://github.com/ExtractableMedia/fastererer/pull/1
112
+ [#2]: https://github.com/ExtractableMedia/fastererer/pull/2
113
+ [#42]: https://github.com/ExtractableMedia/fastererer/pull/42
@@ -0,0 +1,18 @@
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 ADDED
@@ -0,0 +1,63 @@
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/LICENSE.txt ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2015 Damir Svrtan
2
+ Copyright (c) 2026 ExtractableMedia (fork modifications)
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # Fastererer
2
+
3
+ [![CI](https://github.com/ExtractableMedia/fastererer/actions/workflows/ci.yml/badge.svg)](https://github.com/ExtractableMedia/fastererer/actions/workflows/ci.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/fastererer.svg)](https://badge.fury.io/rb/fastererer)
5
+ [![Gem Downloads](https://img.shields.io/gem/dt/fastererer.svg)](https://rubygems.org/gems/fastererer)
6
+
7
+ [Roadmap][roadmap-project] |
8
+ [Changelog](./CHANGELOG.md) |
9
+ [Contributing](./CONTRIBUTING.md)
10
+
11
+ `fastererer` is a static analyzer that suggests speed improvements for Ruby code, inspired by
12
+ [fast-ruby][fast-ruby] and [Sferik's talk at Baruco Conf][sferik-talk]. It's a maintained fork of
13
+ [fasterer][fasterer] with Ruby 3.3+ support and native [Prism][prism] parsing.
14
+
15
+ Suggestions aren't gospel — many trade clarity for marginal speed gains. Use judgment,
16
+ especially in non-performance-critical Rails code; the wins matter most in hot paths like web
17
+ frameworks and request middleware.
18
+
19
+ ## Installation
20
+
21
+ Add to your `Gemfile`:
22
+
23
+ ```ruby
24
+ group :development do
25
+ gem 'fastererer', require: false
26
+ end
27
+ ```
28
+
29
+ Then run `bundle install`. Or install directly:
30
+
31
+ ```shell
32
+ gem install fastererer
33
+ ```
34
+
35
+ Fastererer requires Ruby 3.3 or higher.
36
+
37
+ ## Usage
38
+
39
+ Run from the root of your project to scan everything:
40
+
41
+ ```shell
42
+ bundle exec fastererer
43
+ ```
44
+
45
+ Pass a path to scan a specific file or directory:
46
+
47
+ ```shell
48
+ bundle exec fastererer app/models
49
+ bundle exec fastererer app/models/post.rb
50
+ ```
51
+
52
+ Fastererer exits with status `1` when offenses are found, making it suitable for CI.
53
+
54
+ ## Example output
55
+
56
+ ```text
57
+ app/models/post.rb:57 Array#select.first is slower than Array#detect.
58
+ app/models/post.rb:61 Array#select.first is slower than Array#detect.
59
+
60
+ db/seeds/cities.rb:15 Hash#keys.each is slower than Hash#each_key.
61
+ db/seeds/cities.rb:33 Hash#keys.each is slower than Hash#each_key.
62
+
63
+ test/options_test.rb:84 Hash#merge! with one argument is slower than Hash#[].
64
+
65
+ test/module_test.rb:272 Don't rescue NoMethodError, rather check with respond_to?.
66
+
67
+ spec/cache/mem_cache_store_spec.rb:161 Using tr is faster than gsub when replacing a single character in a string with another single character.
68
+ ```
69
+
70
+ ## Configuration
71
+
72
+ Configuration lives in a `.fastererer.yml` file at the root of your project (or any ancestor
73
+ directory). It supports two options:
74
+
75
+ * Turn individual speedup checks off
76
+ * Exclude files or directories
77
+
78
+ Example:
79
+
80
+ ```yaml
81
+ speedups:
82
+ rescue_vs_respond_to: true
83
+ module_eval: true
84
+ shuffle_first_vs_sample: true
85
+ for_loop_vs_each: true
86
+ each_with_index_vs_while: false
87
+ map_flatten_vs_flat_map: true
88
+ reverse_each_vs_reverse_each: true
89
+ select_first_vs_detect: true
90
+ sort_vs_sort_by: true
91
+ fetch_with_argument_vs_block: true
92
+ keys_each_vs_each_key: true
93
+ hash_merge_bang_vs_hash_brackets: true
94
+ block_vs_symbol_to_proc: true
95
+ proc_call_vs_yield: true
96
+ gsub_vs_tr: true
97
+ select_last_vs_reverse_detect: true
98
+ getter_vs_attr_reader: true
99
+ setter_vs_attr_writer: true
100
+ include_vs_cover_on_range: true
101
+
102
+ exclude_paths:
103
+ - 'vendor/**/*.rb'
104
+ - 'db/schema.rb'
105
+ ```
106
+
107
+ ## CI integration
108
+
109
+ Fastererer's non-zero exit status on offenses makes it drop-in for CI. A minimal GitHub Actions
110
+ step:
111
+
112
+ ```yaml
113
+ - name: Run fastererer
114
+ run: bundle exec fastererer
115
+ ```
116
+
117
+ Color output is auto-disabled when STDOUT isn't a TTY, when `NO_COLOR` is set (see
118
+ [no-color.org](https://no-color.org/)), or when `--no-color` is passed — so CI logs, piped output
119
+ (`fastererer | grep`), and editor integrations get plain text without configuration.
120
+
121
+ ## Migrating from fasterer
122
+
123
+ `fastererer` is a hard fork of [fasterer][fasterer] at v0.11.0. To migrate an existing project:
124
+
125
+ 1. Replace `gem 'fasterer'` with `gem 'fastererer'` in your `Gemfile`
126
+ 2. Rename `.fasterer.yml` to `.fastererer.yml`
127
+ 3. Update CI commands from `fasterer` to `fastererer`
128
+
129
+ ## Roadmap
130
+
131
+ Roadmap items are tracked in the [Fastererer Roadmap][roadmap-project] project.
132
+
133
+ ## Questions?
134
+
135
+ Have a question? Start a [discussion][discussions] — questions, ideas, and show-and-tell are all
136
+ welcome there.
137
+
138
+ ## Bugs?
139
+
140
+ Found a bug? [Open an issue][issues] or send a pull request.
141
+
142
+ ## Development
143
+
144
+ Clone the repo and run `bin/setup` to install dependencies. Run tests with `bin/rspec`. See
145
+ [CONTRIBUTING.md](./CONTRIBUTING.md) for the full development workflow.
146
+
147
+ ## License
148
+
149
+ Fastererer is released under the [MIT License](./LICENSE.txt).
150
+
151
+ ## Code of Conduct
152
+
153
+ Everyone interacting in this project's codebases, issue trackers, and discussions is expected to
154
+ follow the [Code of Conduct](./CODE_OF_CONDUCT.md).
155
+
156
+ ## Special Thanks
157
+
158
+ Fastererer carries forward [Damir Svrtan][damir-svrtan]'s [fasterer][fasterer] (v0.11.0 was the
159
+ fork point). Thanks to Damir for the original work, and to the [fast-ruby][fast-ruby] community
160
+ for the idiom catalog that drives the speed checks.
161
+
162
+ [damir-svrtan]: https://github.com/DamirSvrtan
163
+ [discussions]: https://github.com/ExtractableMedia/fastererer/discussions
164
+ [fast-ruby]: https://github.com/fastruby/fast-ruby
165
+ [fasterer]: https://github.com/DamirSvrtan/fasterer
166
+ [issues]: https://github.com/ExtractableMedia/fastererer/issues
167
+ [prism]: https://github.com/ruby/prism
168
+ [roadmap-project]: https://github.com/orgs/ExtractableMedia/projects/1
169
+ [sferik-talk]: https://speakerdeck.com/sferik/writing-fast-ruby
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
6
+
7
+ desc 'Run all RSpec tests'
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/SECURITY.md ADDED
@@ -0,0 +1,20 @@
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
data/exe/fastererer ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH << File.expand_path('../lib', __dir__)
5
+ require 'fastererer/cli'
6
+
7
+ Fastererer::CLI.execute
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fastererer/method_definition'
4
+ require 'fastererer/method_call'
5
+ require 'fastererer/rescue_call'
6
+ require 'fastererer/offense_collector'
7
+ require 'fastererer/parser'
8
+ require 'fastererer/scanners/method_call_scanner'
9
+ require 'fastererer/scanners/rescue_call_scanner'
10
+ require 'fastererer/scanners/method_definition_scanner'
11
+
12
+ module Fastererer
13
+ class Analyzer
14
+ attr_reader :file_path
15
+ alias path file_path
16
+
17
+ def initialize(file_path)
18
+ @file_path = file_path.to_s
19
+ @file_content = File.read(file_path)
20
+ end
21
+
22
+ def scan
23
+ sexp_tree = Fastererer::Parser.parse(@file_content)
24
+ traverse_sexp_tree(sexp_tree)
25
+ end
26
+
27
+ def errors
28
+ @errors ||= Fastererer::OffenseCollector.new
29
+ end
30
+
31
+ private
32
+
33
+ def traverse_sexp_tree(sexp_tree)
34
+ return unless sexp_tree.is_a?(Sexp)
35
+
36
+ token = sexp_tree.first
37
+ scan_by_token(token, sexp_tree)
38
+
39
+ if %i[call iter].include?(token)
40
+ descend_into_call(sexp_tree)
41
+ else
42
+ sexp_tree.each { |element| traverse_sexp_tree(element) }
43
+ end
44
+ end
45
+
46
+ def descend_into_call(sexp_tree)
47
+ method_call = MethodCall.new(sexp_tree)
48
+ traverse_sexp_tree(method_call.receiver_element) if method_call.receiver_element
49
+ traverse_sexp_tree(method_call.arguments_element)
50
+ traverse_sexp_tree(method_call.block_body) if method_call.block?
51
+ end
52
+
53
+ def scan_by_token(token, element)
54
+ case token
55
+ when :defn
56
+ scan_method_definitions(element)
57
+ when :call, :iter
58
+ scan_method_calls(element)
59
+ when :for
60
+ scan_for_loop(element)
61
+ when :resbody
62
+ scan_rescue(element)
63
+ end
64
+ end
65
+
66
+ def scan_method_definitions(element)
67
+ method_definition_scanner = MethodDefinitionScanner.new(element)
68
+
69
+ return unless method_definition_scanner.offense_detected?
70
+
71
+ errors.push(method_definition_scanner.offense)
72
+ end
73
+
74
+ def scan_method_calls(element)
75
+ method_call_scanner = MethodCallScanner.new(element)
76
+
77
+ return unless method_call_scanner.offense_detected?
78
+
79
+ errors.push(method_call_scanner.offense)
80
+ end
81
+
82
+ def scan_for_loop(element)
83
+ errors.push(Fastererer::Offense.new(:for_loop_vs_each, element.line))
84
+ end
85
+
86
+ def scan_rescue(element)
87
+ rescue_call_scanner = RescueCallScanner.new(element)
88
+
89
+ return unless rescue_call_scanner.offense_detected?
90
+
91
+ errors.push(rescue_call_scanner.offense)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require_relative 'file_traverser'
5
+ require_relative 'painter'
6
+ require_relative 'version'
7
+
8
+ module Fastererer
9
+ class CLI
10
+ def self.execute
11
+ options = parse_options(ARGV.dup)
12
+ Painter.disable! if options[:no_color]
13
+ file_traverser = Fastererer::FileTraverser.new(options[:path])
14
+ file_traverser.traverse
15
+ abort if file_traverser.offenses_found?
16
+ end
17
+
18
+ def self.parse_options(argv)
19
+ options = {}
20
+ options[:path] = build_parser(options).parse(argv).first
21
+ options
22
+ end
23
+
24
+ def self.build_parser(options)
25
+ OptionParser.new do |opts|
26
+ opts.banner = 'Usage: fastererer [options] [path]'
27
+ opts.on('--no-color', 'Disable ANSI color in output') { options[:no_color] = true }
28
+ opts.on('-h', '--help', 'Show this help') { show_help_and_exit(opts) }
29
+ opts.on('-v', '--version', 'Show version') { show_version_and_exit }
30
+ end
31
+ end
32
+
33
+ def self.show_help_and_exit(opts)
34
+ puts opts
35
+ exit
36
+ end
37
+
38
+ def self.show_version_and_exit
39
+ puts Fastererer::VERSION
40
+ exit
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'pathname'
5
+
6
+ module Fastererer
7
+ class Config
8
+ FILE_NAME = '.fastererer.yml'
9
+ SPEEDUPS_KEY = 'speedups'
10
+ EXCLUDE_PATHS_KEY = 'exclude_paths'
11
+
12
+ def ignored_speedups
13
+ @ignored_speedups ||=
14
+ file[SPEEDUPS_KEY].select { |_, value| value == false }.keys.map(&:to_sym)
15
+ end
16
+
17
+ def ignored_files
18
+ @ignored_files ||=
19
+ file[EXCLUDE_PATHS_KEY].flat_map { |path| Dir[path] }
20
+ end
21
+
22
+ def file
23
+ return @file if defined?(@file)
24
+
25
+ @file = load_file
26
+ end
27
+
28
+ def file_location
29
+ @file_location ||=
30
+ Pathname(Dir.pwd)
31
+ .enum_for(:ascend)
32
+ .map { |dir| File.join(dir.to_s, FILE_NAME) }
33
+ .find { |f| File.exist?(f) }
34
+ end
35
+
36
+ def nil_file
37
+ { SPEEDUPS_KEY => {}, EXCLUDE_PATHS_KEY => [] }
38
+ end
39
+
40
+ private
41
+
42
+ def load_file
43
+ return nil_file if file_location.nil?
44
+
45
+ # YAML.load_file returns false if the content is blank, so coerce to nil_file.
46
+ loaded = YAML.load_file(file_location) || nil_file
47
+ loaded.merge!(nil_file) { |_k, v1, v2| v1 || v2 }
48
+ end
49
+ end
50
+ end