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
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
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -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
|
+
[](https://github.com/ExtractableMedia/fastererer/actions/workflows/ci.yml)
|
|
4
|
+
[](https://badge.fury.io/rb/fastererer)
|
|
5
|
+
[](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
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,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
|