route_guard 0.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 +7 -0
- data/.gitignore +13 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +173 -0
- data/README.md +57 -0
- data/Rakefile +10 -0
- data/bin/route_guard +14 -0
- data/docs/publishing.md +76 -0
- data/docs/usage.md +81 -0
- data/lib/route_guard/analyzer.rb +69 -0
- data/lib/route_guard/cli.rb +123 -0
- data/lib/route_guard/configuration.rb +38 -0
- data/lib/route_guard/formatter/ci.rb +20 -0
- data/lib/route_guard/formatter/html.rb +297 -0
- data/lib/route_guard/formatter/json.rb +61 -0
- data/lib/route_guard/formatter/terminal.rb +95 -0
- data/lib/route_guard/inspector.rb +58 -0
- data/lib/route_guard/models/issue.rb +30 -0
- data/lib/route_guard/models/report.rb +29 -0
- data/lib/route_guard/models/route.rb +42 -0
- data/lib/route_guard/railtie.rb +12 -0
- data/lib/route_guard/route_loader.rb +110 -0
- data/lib/route_guard/route_tracker.rb +141 -0
- data/lib/route_guard/rules/base.rb +163 -0
- data/lib/route_guard/rules/complexity.rb +62 -0
- data/lib/route_guard/rules/duplicate_helpers.rb +43 -0
- data/lib/route_guard/rules/duplicate_resources.rb +56 -0
- data/lib/route_guard/rules/duplicate_routes.rb +40 -0
- data/lib/route_guard/rules/shadowed_routes.rb +78 -0
- data/lib/route_guard/rules/statistics.rb +81 -0
- data/lib/route_guard/rules/unreachable_routes.rb +42 -0
- data/lib/route_guard/rules/unused_routes.rb +66 -0
- data/lib/route_guard/version.rb +5 -0
- data/lib/route_guard.rb +14 -0
- data/lib/tasks/route_guard.rake +30 -0
- metadata +195 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c095ab5dfc78ae856774803b38cd9d4cceb6073d623908014e73f2d48752d2db
|
|
4
|
+
data.tar.gz: edcb632d5f8b621477d8d3f8b612b3c581877308db4807e3e57e8ad835f4bf78
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 2dbdfaf0cb426abccbe8e9258742ae121923e635e1f1b6041ebed0d6085fc86409c888e05686c947ccdb757c871ea547a9675a193d0171cfe1c85b9d94ccc6e0
|
|
7
|
+
data.tar.gz: dd07ec99fb9ad444cc5851db746f36eb73f66017ae9c276dafca239e78f90d7df6b19717bfaf0a8c22b5b27171d4e4daa5b346263ed44eef8b796fc8fcf4d554
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
route_guard (0.1.0)
|
|
5
|
+
actionpack (>= 6.1)
|
|
6
|
+
railties (>= 6.1)
|
|
7
|
+
rainbow (~> 3.1)
|
|
8
|
+
thor (~> 1.2)
|
|
9
|
+
|
|
10
|
+
GEM
|
|
11
|
+
remote: https://rubygems.org/
|
|
12
|
+
specs:
|
|
13
|
+
actionpack (8.1.3.1)
|
|
14
|
+
actionview (= 8.1.3.1)
|
|
15
|
+
activesupport (= 8.1.3.1)
|
|
16
|
+
nokogiri (>= 1.8.5)
|
|
17
|
+
rack (>= 2.2.4)
|
|
18
|
+
rack-session (>= 1.0.1)
|
|
19
|
+
rack-test (>= 0.6.3)
|
|
20
|
+
rails-dom-testing (~> 2.2)
|
|
21
|
+
rails-html-sanitizer (~> 1.6)
|
|
22
|
+
useragent (~> 0.16)
|
|
23
|
+
actionview (8.1.3.1)
|
|
24
|
+
activesupport (= 8.1.3.1)
|
|
25
|
+
builder (~> 3.1)
|
|
26
|
+
erubi (~> 1.11)
|
|
27
|
+
rails-dom-testing (~> 2.2)
|
|
28
|
+
rails-html-sanitizer (~> 1.6)
|
|
29
|
+
activesupport (8.1.3.1)
|
|
30
|
+
base64
|
|
31
|
+
bigdecimal
|
|
32
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
33
|
+
connection_pool (>= 2.2.5)
|
|
34
|
+
drb
|
|
35
|
+
i18n (>= 1.6, < 2)
|
|
36
|
+
json
|
|
37
|
+
logger (>= 1.4.2)
|
|
38
|
+
minitest (>= 5.1)
|
|
39
|
+
securerandom (>= 0.3)
|
|
40
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
41
|
+
uri (>= 0.13.1)
|
|
42
|
+
base64 (0.3.0)
|
|
43
|
+
bigdecimal (4.1.2)
|
|
44
|
+
builder (3.3.0)
|
|
45
|
+
concurrent-ruby (1.3.8)
|
|
46
|
+
connection_pool (3.0.2)
|
|
47
|
+
crass (1.0.7)
|
|
48
|
+
diff-lcs (1.6.2)
|
|
49
|
+
docile (1.4.1)
|
|
50
|
+
drb (2.2.3)
|
|
51
|
+
erb (6.0.6)
|
|
52
|
+
erubi (1.13.1)
|
|
53
|
+
i18n (1.15.2)
|
|
54
|
+
concurrent-ruby (~> 1.0)
|
|
55
|
+
io-console (0.8.2)
|
|
56
|
+
irb (1.18.0)
|
|
57
|
+
pp (>= 0.6.0)
|
|
58
|
+
prism (>= 1.3.0)
|
|
59
|
+
rdoc (>= 4.0.0)
|
|
60
|
+
reline (>= 0.4.2)
|
|
61
|
+
json (2.21.2)
|
|
62
|
+
logger (1.7.0)
|
|
63
|
+
loofah (2.25.2)
|
|
64
|
+
crass (~> 1.0.2)
|
|
65
|
+
nokogiri (>= 1.12.0)
|
|
66
|
+
minitest (6.0.6)
|
|
67
|
+
drb (~> 2.0)
|
|
68
|
+
prism (~> 1.5)
|
|
69
|
+
nokogiri (1.19.4-aarch64-linux-gnu)
|
|
70
|
+
racc (~> 1.4)
|
|
71
|
+
nokogiri (1.19.4-aarch64-linux-musl)
|
|
72
|
+
racc (~> 1.4)
|
|
73
|
+
nokogiri (1.19.4-arm-linux-gnu)
|
|
74
|
+
racc (~> 1.4)
|
|
75
|
+
nokogiri (1.19.4-arm-linux-musl)
|
|
76
|
+
racc (~> 1.4)
|
|
77
|
+
nokogiri (1.19.4-arm64-darwin)
|
|
78
|
+
racc (~> 1.4)
|
|
79
|
+
nokogiri (1.19.4-x86_64-darwin)
|
|
80
|
+
racc (~> 1.4)
|
|
81
|
+
nokogiri (1.19.4-x86_64-linux-gnu)
|
|
82
|
+
racc (~> 1.4)
|
|
83
|
+
nokogiri (1.19.4-x86_64-linux-musl)
|
|
84
|
+
racc (~> 1.4)
|
|
85
|
+
pp (0.6.4)
|
|
86
|
+
prettyprint
|
|
87
|
+
prettyprint (0.2.0)
|
|
88
|
+
prism (1.9.0)
|
|
89
|
+
racc (1.8.1)
|
|
90
|
+
rack (3.2.6)
|
|
91
|
+
rack-session (2.1.2)
|
|
92
|
+
base64 (>= 0.1.0)
|
|
93
|
+
rack (>= 3.0.0)
|
|
94
|
+
rack-test (2.2.0)
|
|
95
|
+
rack (>= 1.3)
|
|
96
|
+
rackup (2.3.1)
|
|
97
|
+
rack (>= 3)
|
|
98
|
+
rails-dom-testing (2.3.0)
|
|
99
|
+
activesupport (>= 5.0.0)
|
|
100
|
+
minitest
|
|
101
|
+
nokogiri (>= 1.6)
|
|
102
|
+
rails-html-sanitizer (1.7.1)
|
|
103
|
+
loofah (~> 2.25, >= 2.25.2)
|
|
104
|
+
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
|
105
|
+
railties (8.1.3.1)
|
|
106
|
+
actionpack (= 8.1.3.1)
|
|
107
|
+
activesupport (= 8.1.3.1)
|
|
108
|
+
irb (~> 1.13)
|
|
109
|
+
rackup (>= 1.0.0)
|
|
110
|
+
rake (>= 12.2)
|
|
111
|
+
thor (~> 1.0, >= 1.2.2)
|
|
112
|
+
tsort (>= 0.2)
|
|
113
|
+
zeitwerk (~> 2.6)
|
|
114
|
+
rainbow (3.1.1)
|
|
115
|
+
rake (13.4.2)
|
|
116
|
+
rbs (4.1.2)
|
|
117
|
+
logger
|
|
118
|
+
prism (>= 1.6.0)
|
|
119
|
+
tsort
|
|
120
|
+
rdoc (8.0.0)
|
|
121
|
+
erb
|
|
122
|
+
prism (>= 1.6.0)
|
|
123
|
+
rbs (>= 4.0.0)
|
|
124
|
+
tsort
|
|
125
|
+
reline (0.6.3)
|
|
126
|
+
io-console (~> 0.5)
|
|
127
|
+
rspec (3.13.2)
|
|
128
|
+
rspec-core (~> 3.13.0)
|
|
129
|
+
rspec-expectations (~> 3.13.0)
|
|
130
|
+
rspec-mocks (~> 3.13.0)
|
|
131
|
+
rspec-core (3.13.6)
|
|
132
|
+
rspec-support (~> 3.13.0)
|
|
133
|
+
rspec-expectations (3.13.5)
|
|
134
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
135
|
+
rspec-support (~> 3.13.0)
|
|
136
|
+
rspec-mocks (3.13.8)
|
|
137
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
138
|
+
rspec-support (~> 3.13.0)
|
|
139
|
+
rspec-support (3.13.7)
|
|
140
|
+
securerandom (0.4.1)
|
|
141
|
+
simplecov (0.22.0)
|
|
142
|
+
docile (~> 1.1)
|
|
143
|
+
simplecov-html (~> 0.11)
|
|
144
|
+
simplecov_json_formatter (~> 0.1)
|
|
145
|
+
simplecov-html (0.13.2)
|
|
146
|
+
simplecov_json_formatter (0.1.4)
|
|
147
|
+
thor (1.5.0)
|
|
148
|
+
tsort (0.2.0)
|
|
149
|
+
tzinfo (2.0.6)
|
|
150
|
+
concurrent-ruby (~> 1.0)
|
|
151
|
+
uri (1.1.1)
|
|
152
|
+
useragent (0.16.11)
|
|
153
|
+
zeitwerk (2.8.3)
|
|
154
|
+
|
|
155
|
+
PLATFORMS
|
|
156
|
+
aarch64-linux-gnu
|
|
157
|
+
aarch64-linux-musl
|
|
158
|
+
arm-linux-gnu
|
|
159
|
+
arm-linux-musl
|
|
160
|
+
arm64-darwin
|
|
161
|
+
x86_64-darwin
|
|
162
|
+
x86_64-linux-gnu
|
|
163
|
+
x86_64-linux-musl
|
|
164
|
+
|
|
165
|
+
DEPENDENCIES
|
|
166
|
+
bundler (~> 2.4)
|
|
167
|
+
rake (~> 13.0)
|
|
168
|
+
route_guard!
|
|
169
|
+
rspec (~> 3.12)
|
|
170
|
+
simplecov (~> 0.22)
|
|
171
|
+
|
|
172
|
+
BUNDLED WITH
|
|
173
|
+
2.5.3
|
data/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# RouteGuard
|
|
2
|
+
|
|
3
|
+
RouteGuard is a production-ready static analysis tool for Rails routing. It inspects your Rails routes to catch issues Rails itself doesn't report—like shadowing, duplicates, unreachable catch-alls, and duplicate resource blocks.
|
|
4
|
+
|
|
5
|
+
## Core Features
|
|
6
|
+
|
|
7
|
+
* **Duplicate Route Detection**: Detects routes sharing the same HTTP verb and path pattern.
|
|
8
|
+
* **Route Shadowing**: Flags routes that can never be matched because an earlier route matches first (e.g. `/users/:id` shadowing `/users/new`).
|
|
9
|
+
* **Unreachable Routes**: Detects routes defined after catch-all wildcard routes (e.g. `match "*path"`).
|
|
10
|
+
* **Duplicate Named Helpers**: Identifies duplicate helper names (e.g., duplicate `auth_path` helpers pointing to different paths).
|
|
11
|
+
* **Duplicate Resources**: Warns when identical resources are declared multiple times in the same namespace scope.
|
|
12
|
+
* **Route Statistics**: Computes REST resources count, nested scopes, and wildcards.
|
|
13
|
+
* **Health Score**: Measures a 0-100 maintainability score based on issues and path nesting levels.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### Installation
|
|
20
|
+
|
|
21
|
+
Add this gem to your group `:development, :test` block:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
group :development, :test do
|
|
25
|
+
gem "route_guard", path: "path/to/route_guard"
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Run `bundle install`.
|
|
30
|
+
|
|
31
|
+
### Run RouteGuard
|
|
32
|
+
|
|
33
|
+
Run check from the terminal:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bundle exec route_guard check
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or execute via Rake tasks:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
bundle exec rails route_guard
|
|
43
|
+
bundle exec rails routes:doctor
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Documentation
|
|
49
|
+
|
|
50
|
+
For detailed configurations, options, and workflows, please see:
|
|
51
|
+
|
|
52
|
+
* [Usage & Integration Guide](docs/usage.md)
|
|
53
|
+
* [Publishing Guide](docs/publishing.md)
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
|
|
57
|
+
MIT License.
|
data/Rakefile
ADDED
data/bin/route_guard
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../lib/route_guard"
|
|
5
|
+
|
|
6
|
+
begin
|
|
7
|
+
RouteGuard::CLI.start(ARGV)
|
|
8
|
+
rescue SystemExit => e
|
|
9
|
+
# Propagate CLI exits
|
|
10
|
+
exit(e.status)
|
|
11
|
+
rescue => e
|
|
12
|
+
warn "Error: #{e.message}"
|
|
13
|
+
exit(1)
|
|
14
|
+
end
|
data/docs/publishing.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# RouteGuard: Gem Publishing Guide
|
|
2
|
+
|
|
3
|
+
This guide details how to build, package, and publish the **RouteGuard** gem to the RubyGems community.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Preparing for Release
|
|
8
|
+
|
|
9
|
+
Before building and publishing, ensure that:
|
|
10
|
+
|
|
11
|
+
1. All tests pass:
|
|
12
|
+
```bash
|
|
13
|
+
bundle exec rspec
|
|
14
|
+
```
|
|
15
|
+
2. Your version in `lib/route_guard/version.rb` is bumped according to Semantic Versioning (SemVer) rules:
|
|
16
|
+
```ruby
|
|
17
|
+
module RouteGuard
|
|
18
|
+
VERSION = "0.1.0" # Major.Minor.Patch
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
3. The metadata in `route_guard.gemspec` (homepage, authors, email, etc.) is correct.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 2. Building the Gem
|
|
26
|
+
|
|
27
|
+
To package the gem code into a distributable `.gem` file, run the `gem build` command from the root of the project:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
gem build route_guard.gemspec
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This generates a file named `route_guard-0.1.0.gem` (matching your current version) in the root folder.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 3. Authentication with RubyGems
|
|
38
|
+
|
|
39
|
+
To publish a gem, you need an account on [RubyGems.org](https://rubygems.org/).
|
|
40
|
+
|
|
41
|
+
Once registered, sign in via your terminal using:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
gem signin
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Enter your RubyGems email and password when prompted. This securely stores your API key locally in `~/.gem/credentials`.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 4. Publishing to RubyGems
|
|
52
|
+
|
|
53
|
+
Publish the built gem package using the `gem push` command:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
gem push route_guard-0.1.0.gem
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Once pushed, your gem will be immediately indexable, and developers around the world can install it via:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
gem install route_guard
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 5. Version Control Best Practices
|
|
68
|
+
|
|
69
|
+
After publishing a release:
|
|
70
|
+
|
|
71
|
+
1. **Tag the release** in git:
|
|
72
|
+
```bash
|
|
73
|
+
git tag -a v0.1.0 -m "Release version 0.1.0"
|
|
74
|
+
git push origin v0.1.0
|
|
75
|
+
```
|
|
76
|
+
2. **Document changes** in a `CHANGELOG.md` file so developers know what changed.
|
data/docs/usage.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# RouteGuard: Usage Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to install, integrate, and run **RouteGuard** in your Rails application.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Installation
|
|
8
|
+
|
|
9
|
+
Add `route_guard` to your application's `Gemfile`. Since RouteGuard has zero runtime overhead in production, it is highly recommended to group it under `:development` and `:test`:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
group :development, :test do
|
|
13
|
+
gem "route_guard", path: "path/to/route_guard" # During local development
|
|
14
|
+
# Or once published:
|
|
15
|
+
# gem "route_guard"
|
|
16
|
+
end
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then execute:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bundle install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. Command Line Interface (CLI)
|
|
28
|
+
|
|
29
|
+
You can run RouteGuard directly from your terminal. Since it automatically boot-loads your Rails environment, make sure to execute it from your Rails application root directory:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bundle exec route_guard
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Commands
|
|
36
|
+
|
|
37
|
+
* `check` (default): Run full inspection rules on routes.
|
|
38
|
+
* `stats`: Generate key route complexity metrics and statistics.
|
|
39
|
+
* `doctor`: Run in strict mode (fails on warnings/errors). Perfect for CI pipelines.
|
|
40
|
+
* `json`: Generate a machine-readable JSON report.
|
|
41
|
+
* `html`: Generate a premium interactive HTML report dashboard.
|
|
42
|
+
|
|
43
|
+
### CLI Options
|
|
44
|
+
|
|
45
|
+
| Option | Type | Description |
|
|
46
|
+
| :--- | :--- | :--- |
|
|
47
|
+
| `--strict` | Boolean | Treat all warnings as errors. |
|
|
48
|
+
| `--format` | String | Output format (`terminal`, `json`, `html`, `ci`). |
|
|
49
|
+
| `--only` | Array | Only run specific inspections (e.g. `duplicate_routes`). |
|
|
50
|
+
| `--except` | Array | Exclude specific inspections from running. |
|
|
51
|
+
| `--fail-on-warning`| Boolean | Exit with status code `1` if warnings are found. |
|
|
52
|
+
| `--output` | String | Path to write the JSON/HTML output. |
|
|
53
|
+
| `--verbose` | Boolean | Display detailed debug information. |
|
|
54
|
+
|
|
55
|
+
#### Examples
|
|
56
|
+
|
|
57
|
+
**Generate a self-contained HTML dashboard report:**
|
|
58
|
+
```bash
|
|
59
|
+
bundle exec route_guard html --output=coverage/route_guard_report.html
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Run only duplicate route checking:**
|
|
63
|
+
```bash
|
|
64
|
+
bundle exec route_guard check --only duplicate_routes
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Strict checking in CI, failing on warnings:**
|
|
68
|
+
```bash
|
|
69
|
+
bundle exec route_guard check --fail-on-warning
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 3. Rails / Rake Integration
|
|
75
|
+
|
|
76
|
+
RouteGuard automatically registers Rake tasks when loaded in your Rails application.
|
|
77
|
+
|
|
78
|
+
### Available Tasks
|
|
79
|
+
|
|
80
|
+
* **`bundle exec rails route_guard`** (or `bundle exec rails routes:lint`): Analyzes your routes and prints findings to your terminal.
|
|
81
|
+
* **`bundle exec rails routes:doctor`**: Runs strict verification. Exits with non-zero code if any issue (warning or error) is detected. Ideal for Git pre-commit hooks or CI steps.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "models/report"
|
|
4
|
+
require_relative "rules/duplicate_routes"
|
|
5
|
+
require_relative "rules/shadowed_routes"
|
|
6
|
+
require_relative "rules/unreachable_routes"
|
|
7
|
+
require_relative "rules/duplicate_helpers"
|
|
8
|
+
require_relative "rules/duplicate_resources"
|
|
9
|
+
require_relative "rules/statistics"
|
|
10
|
+
require_relative "rules/complexity"
|
|
11
|
+
require_relative "rules/unused_routes"
|
|
12
|
+
|
|
13
|
+
module RouteGuard
|
|
14
|
+
class Analyzer
|
|
15
|
+
RULE_MAPPING = {
|
|
16
|
+
duplicate_routes: Rules::DuplicateRoutes,
|
|
17
|
+
shadowed_routes: Rules::ShadowedRoutes,
|
|
18
|
+
unreachable_routes: Rules::UnreachableRoutes,
|
|
19
|
+
duplicate_helpers: Rules::DuplicateHelpers,
|
|
20
|
+
duplicate_resources: Rules::DuplicateResources,
|
|
21
|
+
statistics: Rules::Statistics,
|
|
22
|
+
complexity: Rules::Complexity,
|
|
23
|
+
unused_routes: Rules::UnusedRoutes
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
def self.analyze(routes, enabled_rules, options = {})
|
|
27
|
+
report = Models::Report.new(routes)
|
|
28
|
+
start_time = Time.now
|
|
29
|
+
|
|
30
|
+
# 1. Run Statistics first (if enabled)
|
|
31
|
+
if enabled_rules.include?(:statistics)
|
|
32
|
+
stat_rule = Rules::Statistics.new(options)
|
|
33
|
+
stat_rule.analyze(routes, report)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# 2. Run other lint rules
|
|
37
|
+
issues = []
|
|
38
|
+
lint_rules = %i[duplicate_routes shadowed_routes unreachable_routes duplicate_helpers duplicate_resources unused_routes]
|
|
39
|
+
(lint_rules & enabled_rules).each do |rule_sym|
|
|
40
|
+
rule_class = RULE_MAPPING[rule_sym]
|
|
41
|
+
next unless rule_class
|
|
42
|
+
|
|
43
|
+
rule = rule_class.new(options)
|
|
44
|
+
rule_issues = rule.analyze(routes, report)
|
|
45
|
+
issues.concat(rule_issues) if rule_issues.is_a?(Array)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Expose intermediate issues on report for Complexity to analyze
|
|
49
|
+
report.issues = issues
|
|
50
|
+
|
|
51
|
+
# Update stats with issue metrics
|
|
52
|
+
if report.stats && report.stats.any?
|
|
53
|
+
report.stats[:duplicate_paths] = issues.count { |i| i.rule_name == :duplicate_routes }
|
|
54
|
+
report.stats[:shadowed_routes] = issues.count { |i| i.rule_name == :shadowed_routes }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# 3. Run Complexity last (if enabled)
|
|
58
|
+
if enabled_rules.include?(:complexity)
|
|
59
|
+
comp_rule = Rules::Complexity.new(options)
|
|
60
|
+
comp_issues = comp_rule.analyze(routes, report)
|
|
61
|
+
issues.concat(comp_issues) if comp_issues.is_a?(Array)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
report.issues = issues
|
|
65
|
+
report.duration = Time.now - start_time
|
|
66
|
+
report
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
require_relative "../route_guard"
|
|
5
|
+
|
|
6
|
+
module RouteGuard
|
|
7
|
+
class CLI < Thor
|
|
8
|
+
class_option :strict, type: :boolean, default: false, desc: "Treat warnings as errors"
|
|
9
|
+
class_option :format, type: :string, default: "terminal", desc: "Output format: terminal, json, html, ci"
|
|
10
|
+
class_option :only, type: :array, default: [], desc: "Run only specified rules"
|
|
11
|
+
class_option :except, type: :array, default: [], desc: "Exclude specified rules"
|
|
12
|
+
class_option :fail_on_warning, type: :boolean, default: false, desc: "Fail if warnings are found"
|
|
13
|
+
class_option :verbose, type: :boolean, default: false, desc: "Print verbose output"
|
|
14
|
+
class_option :output, type: :string, desc: "Output file path"
|
|
15
|
+
|
|
16
|
+
desc "check", "Analyze Rails routes for issues"
|
|
17
|
+
def check
|
|
18
|
+
config = build_config
|
|
19
|
+
inspector = Inspector.new(config)
|
|
20
|
+
report = inspector.run
|
|
21
|
+
|
|
22
|
+
fmt = (options[:format] || "terminal").to_sym
|
|
23
|
+
io = if options[:output]
|
|
24
|
+
File.open(options[:output], "w")
|
|
25
|
+
else
|
|
26
|
+
$stdout
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
inspector.format(report, fmt, io)
|
|
30
|
+
io.close if options[:output]
|
|
31
|
+
|
|
32
|
+
exit_status(report, config)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
default_task :check
|
|
36
|
+
|
|
37
|
+
desc "stats", "Calculate routing statistics and metrics"
|
|
38
|
+
def stats
|
|
39
|
+
config = build_config
|
|
40
|
+
config.rules = [:statistics]
|
|
41
|
+
inspector = Inspector.new(config)
|
|
42
|
+
report = inspector.run
|
|
43
|
+
|
|
44
|
+
fmt = (options[:format] || "terminal").to_sym
|
|
45
|
+
if fmt == :json
|
|
46
|
+
inspector.format(report, :json)
|
|
47
|
+
else
|
|
48
|
+
inspector.format(report, :terminal)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
desc "doctor", "Thoroughly inspect and report on route health"
|
|
53
|
+
def doctor
|
|
54
|
+
config = build_config
|
|
55
|
+
config.strict = true
|
|
56
|
+
config.fail_on_warning = true
|
|
57
|
+
inspector = Inspector.new(config)
|
|
58
|
+
report = inspector.run
|
|
59
|
+
inspector.format(report, :terminal)
|
|
60
|
+
exit_status(report, config)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
desc "json", "Output results in JSON format"
|
|
64
|
+
def json
|
|
65
|
+
config = build_config
|
|
66
|
+
config.formatters = [:json]
|
|
67
|
+
inspector = Inspector.new(config)
|
|
68
|
+
report = inspector.run
|
|
69
|
+
inspector.format(report, :json)
|
|
70
|
+
exit_status(report, config)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
desc "html", "Generate a beautiful HTML report"
|
|
74
|
+
def html
|
|
75
|
+
config = build_config
|
|
76
|
+
config.formatters = [:html]
|
|
77
|
+
inspector = Inspector.new(config)
|
|
78
|
+
report = inspector.run
|
|
79
|
+
|
|
80
|
+
output_file = options[:output] || "route_guard_report.html"
|
|
81
|
+
File.open(output_file, "w") do |f|
|
|
82
|
+
inspector.format(report, :html, f)
|
|
83
|
+
end
|
|
84
|
+
puts "HTML report generated at #{output_file}"
|
|
85
|
+
exit_status(report, config)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Disable Thor's exit-on-failure behavior by default to control exit statuses
|
|
89
|
+
def self.exit_on_failure?
|
|
90
|
+
true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def build_config
|
|
96
|
+
config = Configuration.new
|
|
97
|
+
config.strict = options[:strict]
|
|
98
|
+
config.fail_on_warning = options[:fail_on_warning]
|
|
99
|
+
config.only = options[:only] || []
|
|
100
|
+
config.except = options[:except] || []
|
|
101
|
+
config.verbose = options[:verbose]
|
|
102
|
+
|
|
103
|
+
fmt = (options[:format] || "terminal").to_sym
|
|
104
|
+
config.formatters = [fmt]
|
|
105
|
+
config
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def exit_status(report, config = nil)
|
|
109
|
+
strict = config ? config.strict : options[:strict]
|
|
110
|
+
fail_on_warning = config ? config.fail_on_warning : options[:fail_on_warning]
|
|
111
|
+
|
|
112
|
+
if report.errors.any?
|
|
113
|
+
exit(1)
|
|
114
|
+
elsif fail_on_warning && report.warnings.any?
|
|
115
|
+
exit(1)
|
|
116
|
+
elsif strict && report.warnings.any?
|
|
117
|
+
exit(1)
|
|
118
|
+
else
|
|
119
|
+
exit(0)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RouteGuard
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :rules, :formatters, :strict, :fail_on_warning, :only, :except, :verbose
|
|
6
|
+
|
|
7
|
+
ALL_RULES = %i[
|
|
8
|
+
duplicate_routes
|
|
9
|
+
shadowed_routes
|
|
10
|
+
unreachable_routes
|
|
11
|
+
duplicate_helpers
|
|
12
|
+
duplicate_resources
|
|
13
|
+
unused_routes
|
|
14
|
+
statistics
|
|
15
|
+
complexity
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
@rules = ALL_RULES.dup
|
|
20
|
+
@formatters = [:terminal]
|
|
21
|
+
@strict = false
|
|
22
|
+
@fail_on_warning = false
|
|
23
|
+
@only = []
|
|
24
|
+
@except = []
|
|
25
|
+
@verbose = false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def enabled_rules
|
|
29
|
+
active_rules = if only.any?
|
|
30
|
+
rules & only.map(&:to_sym)
|
|
31
|
+
else
|
|
32
|
+
rules
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
active_rules - except.map(&:to_sym)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RouteGuard
|
|
4
|
+
module Formatter
|
|
5
|
+
class Ci
|
|
6
|
+
def format(report, io = $stdout)
|
|
7
|
+
report.issues.each do |issue|
|
|
8
|
+
severity = issue.severity == :error ? "error" : "warning"
|
|
9
|
+
|
|
10
|
+
if issue.file && issue.line
|
|
11
|
+
rel_file = issue.file.sub(/\A#{Regexp.escape(Dir.pwd)}\//, "")
|
|
12
|
+
io.puts "::#{severity} file=#{rel_file},line=#{issue.line}::[RouteGuard] #{issue.message}"
|
|
13
|
+
else
|
|
14
|
+
io.puts "::#{severity}::[RouteGuard] #{issue.message}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|