routes_coverage 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/lib/routes_coverage/auditor.rb +156 -0
- data/lib/routes_coverage/result.rb +3 -1
- data/lib/routes_coverage/version.rb +1 -1
- data/lib/routes_coverage.rb +4 -4
- data/routes_coverage.gemspec +1 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4f992227e3172a9ee4ea678d302b2d6655cdf16feed78020947375cc627a0254
|
4
|
+
data.tar.gz: 4f5dae5790bac97b3675d085d771a17175e8744f00fed659625592dc471c8a27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97e2aaf45f30572215e26fe51370257f00ee92129c38404050f569d5faf3d290690ddd1f5b5022c366edbb09b3b3fe5b16e9c6a50814ce86c2a4d6fc313d8be0
|
7
|
+
data.tar.gz: 432330b7f4a1641f03bb9bcfed7dc2a85868a2b6637cc2ba3015454471f1bc7a33889730feb7fdb8257b0456a536defbceeff320c88a899d4dd19500882fe2f4
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## Unreleased
|
4
|
+
|
5
|
+
-
|
6
|
+
|
7
|
+
## 0.7.0
|
8
|
+
|
9
|
+
- Support for Rails 7 (added tests, main code was already working)
|
10
|
+
- Fixed: errors on Rails 3 and ruby 2.3
|
11
|
+
- New feature: `require 'routes_coverage/auditor'; RoutesCoverage::Auditor.new.print_missing_actions` detects actions present in routes, but not present in controllers.
|
12
|
+
`print_unused_actions` - the other direction. Useful for routes cleanup.
|
13
|
+
|
14
|
+
- Known bug: collecting coverage data from engines is still not supported :(
|
15
|
+
|
16
|
+
## <= 0.6.0
|
17
|
+
|
18
|
+
In commit history, sorry
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RoutesCoverage
|
4
|
+
class Auditor
|
5
|
+
def logger
|
6
|
+
@logger ||= Logger.new($stdout).tap do |log|
|
7
|
+
log.formatter = ->(_severity, _datetime, _progname, msg) { "#{msg}\n" }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def controllers
|
12
|
+
@controllers ||= begin
|
13
|
+
logger.info "Eager-loading app to collect controllers"
|
14
|
+
Rails.application.eager_load!
|
15
|
+
|
16
|
+
logger.info "Collecting controllers"
|
17
|
+
if defined?(ActionController::API)
|
18
|
+
ActionController::Base.descendants + ActionController::API.descendants
|
19
|
+
else
|
20
|
+
# older rails
|
21
|
+
ActionController::Base.descendants
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def controllers_hash
|
27
|
+
@controllers_hash ||= controllers.index_by { |controller| controller.name.sub(/Controller$/, "").underscore }
|
28
|
+
end
|
29
|
+
|
30
|
+
def controller_class_by_name(controller_name)
|
31
|
+
controller = controllers_hash[controller_name]
|
32
|
+
return controller if controller
|
33
|
+
|
34
|
+
@missing_controllers ||= Set.new
|
35
|
+
return if @missing_controllers.include?(controller_name)
|
36
|
+
|
37
|
+
controllers_hash[controller_name] ||= "#{controller_name}_controller".classify.constantize
|
38
|
+
logger.warn "Controller #{controller_name} was not collected, but exists"
|
39
|
+
controllers_hash[controller_name]
|
40
|
+
rescue ArgumentError => e
|
41
|
+
@missing_controllers << controller_name
|
42
|
+
logger.warn "Controller #{controller_name} failed to load: #{e}"
|
43
|
+
nil
|
44
|
+
rescue NameError
|
45
|
+
@missing_controllers << controller_name
|
46
|
+
logger.warn "Controller #{controller_name} looks not existing"
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def existing_actions_usage_hash
|
51
|
+
@existing_actions_usage_hash ||= begin
|
52
|
+
logger.info "Collecting actions"
|
53
|
+
controller_actions = controllers.map do |controller|
|
54
|
+
# cannot use controller.controller_name - it has no namespace, same thing without demodulize:
|
55
|
+
controller_name = controller.name.sub(/Controller$/, "").underscore
|
56
|
+
controller.action_methods.map { |action| "#{controller_name}##{action}" }
|
57
|
+
end
|
58
|
+
controller_actions.flatten.map { |action| [action, 0] }.to_h
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def all_routes
|
63
|
+
# NB: there're no engines
|
64
|
+
@all_routes ||= RoutesCoverage._collect_all_routes
|
65
|
+
end
|
66
|
+
|
67
|
+
def perform
|
68
|
+
require 'routes_coverage'
|
69
|
+
routes = all_routes
|
70
|
+
|
71
|
+
@missing_actions = Hash.new(0)
|
72
|
+
@existing_actions_usage_hash = nil
|
73
|
+
routes.each do |route|
|
74
|
+
next unless route.respond_to?(:requirements) && route.requirements[:controller]
|
75
|
+
|
76
|
+
action = "#{route.requirements[:controller]}##{route.requirements[:action]}"
|
77
|
+
if existing_actions_usage_hash[action]
|
78
|
+
existing_actions_usage_hash[action] += 1
|
79
|
+
else
|
80
|
+
# there may be inheritance or implicit renders
|
81
|
+
controller_instance = controller_class_by_name(route.requirements[:controller])&.new
|
82
|
+
unless controller_instance&.available_action?(route.requirements[:action])
|
83
|
+
if controller_instance.respond_to?(route.requirements[:action])
|
84
|
+
logger.warn "No action, but responds: #{action}"
|
85
|
+
end
|
86
|
+
@missing_actions[action] += 1
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def missing_actions
|
93
|
+
perform unless @missing_actions
|
94
|
+
@missing_actions
|
95
|
+
end
|
96
|
+
|
97
|
+
def unused_actions
|
98
|
+
perform unless @existing_actions_usage_hash
|
99
|
+
|
100
|
+
root = "#{Rails.root}/" # rubocop:disable Rails/FilePath
|
101
|
+
@unused_actions ||= begin
|
102
|
+
# methods with special suffixes are obviously not actions, reduce noise:
|
103
|
+
unused_actions_from_hash = existing_actions_usage_hash.reject do |action, count|
|
104
|
+
count.positive? || action.end_with?('?') || action.end_with?('!') || action.end_with?('=')
|
105
|
+
end
|
106
|
+
|
107
|
+
unused_actions_from_hash.keys.map do |action|
|
108
|
+
controller_name, action_name = action.split('#', 2)
|
109
|
+
controller = controller_class_by_name(controller_name)&.new
|
110
|
+
method = controller.method(action_name.to_sym)
|
111
|
+
if method&.source_location && method.source_location.first.start_with?(root)
|
112
|
+
"#{method.source_location.first.sub(root, '')}:#{method.source_location.second} - #{action}"
|
113
|
+
else
|
114
|
+
action
|
115
|
+
end
|
116
|
+
end.uniq.sort
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def print_missing_actions
|
121
|
+
logger.info "\nMissing #{missing_actions.count} actions:"
|
122
|
+
|
123
|
+
# NB: for singular `resource` there may be unnecessary `index` in suggestions
|
124
|
+
restful_actions = %w[index new create show edit update destroy].freeze
|
125
|
+
|
126
|
+
declared_restful = all_routes.select { |route|
|
127
|
+
route.respond_to?(:requirements) && route.requirements[:controller] &&
|
128
|
+
restful_actions.include?(route.requirements[:action])
|
129
|
+
}.group_by{ |route| route.requirements[:controller]}
|
130
|
+
|
131
|
+
missing_actions.keys.map { |action| action.split('#', 2) }.group_by(&:first).each do |(controller, actions)|
|
132
|
+
missing = actions.map(&:last)
|
133
|
+
next if missing.empty?
|
134
|
+
|
135
|
+
undeclared_restful = restful_actions - declared_restful[controller].map{|r| r.requirements[:action] }
|
136
|
+
logger.info([
|
137
|
+
"#{controller}:",
|
138
|
+
(if (restful_actions & missing).any?
|
139
|
+
"#{(missing & restful_actions).join(', ')}"\
|
140
|
+
", except: %i[#{(restful_actions & (missing + undeclared_restful)).join(' ')}]"\
|
141
|
+
", only: %i[#{(restful_actions - (missing + undeclared_restful)).join(' ')}]"
|
142
|
+
end),
|
143
|
+
(if (missing - restful_actions).any?
|
144
|
+
", Missing custom: #{(missing - restful_actions).join(', ')}"
|
145
|
+
end)
|
146
|
+
|
147
|
+
].compact.join(' '))
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def print_unused_actions
|
152
|
+
logger.info "Unused #{unused_actions.count} actions:"
|
153
|
+
unused_actions.each { |action| logger.info action }
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -95,8 +95,10 @@ module RoutesCoverage
|
|
95
95
|
namespaces_regex = Regexp.union(@settings.exclude_namespaces.map { |n| %r{^/#{n}} })
|
96
96
|
|
97
97
|
routes_groups = all_routes.group_by do |r|
|
98
|
+
# rails <=4 has regex in verb
|
99
|
+
verb = r.verb.is_a?(Regexp) && r.verb.inspect.gsub(/[^\w]/, '') || r.verb
|
98
100
|
(
|
99
|
-
("#{
|
101
|
+
("#{verb} #{r.path.spec}".strip =~ filter_regex) ||
|
100
102
|
(r.path.spec.to_s =~ namespaces_regex)
|
101
103
|
).present?
|
102
104
|
end
|
data/lib/routes_coverage.rb
CHANGED
@@ -170,7 +170,7 @@ module RoutesCoverage
|
|
170
170
|
when :constraints
|
171
171
|
value.all? do |constraint_name, constraint_value|
|
172
172
|
if constraint_value.present?
|
173
|
-
route.constraints[constraint_name] && route.constraints[constraint_name].match
|
173
|
+
route.constraints[constraint_name] && route.constraints[constraint_name].match(constraint_value)
|
174
174
|
else
|
175
175
|
route.constraints[constraint_name].blank?
|
176
176
|
end
|
@@ -178,7 +178,7 @@ module RoutesCoverage
|
|
178
178
|
end
|
179
179
|
end
|
180
180
|
else
|
181
|
-
route.path.spec.to_s.match
|
181
|
+
route.path.spec.to_s.match(matcher)
|
182
182
|
end
|
183
183
|
end
|
184
184
|
|
@@ -197,9 +197,9 @@ module RoutesCoverage
|
|
197
197
|
else # rails < 4.2
|
198
198
|
dispatcher = route.app
|
199
199
|
req.env['action_dispatch.request.path_parameters'] =
|
200
|
-
(env['action_dispatch.request.path_parameters'] || {}).merge(parameters)
|
200
|
+
(req.env['action_dispatch.request.path_parameters'] || {}).merge(parameters)
|
201
201
|
while dispatcher.is_a?(ActionDispatch::Routing::Mapper::Constraints)
|
202
|
-
dispatcher = (dispatcher.app if dispatcher.matches?(env))
|
202
|
+
dispatcher = (dispatcher.app if dispatcher.matches?(req.env))
|
203
203
|
end
|
204
204
|
end
|
205
205
|
next unless dispatcher
|
data/routes_coverage.gemspec
CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.require_paths = ["lib"]
|
27
27
|
|
28
28
|
spec.add_development_dependency 'appraisal'
|
29
|
-
spec.add_development_dependency "bundler"
|
29
|
+
spec.add_development_dependency "bundler" #, ">= 2.2.10"
|
30
30
|
spec.add_development_dependency "minitest"
|
31
31
|
spec.add_development_dependency "rake", ">= 12.3.3"
|
32
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: routes_coverage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vasily Fedoseyev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-04-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: appraisal
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: minitest
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -74,6 +74,7 @@ executables: []
|
|
74
74
|
extensions: []
|
75
75
|
extra_rdoc_files: []
|
76
76
|
files:
|
77
|
+
- CHANGELOG.md
|
77
78
|
- LICENSE.txt
|
78
79
|
- README.md
|
79
80
|
- compiled_assets/routes.css
|
@@ -82,6 +83,7 @@ files:
|
|
82
83
|
- lib/routes_coverage/adapters/atexit.rb
|
83
84
|
- lib/routes_coverage/adapters/rspec.rb
|
84
85
|
- lib/routes_coverage/adapters/simplecov.rb
|
86
|
+
- lib/routes_coverage/auditor.rb
|
85
87
|
- lib/routes_coverage/formatters/base.rb
|
86
88
|
- lib/routes_coverage/formatters/full_text.rb
|
87
89
|
- lib/routes_coverage/formatters/html.rb
|
@@ -111,7 +113,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
113
|
- !ruby/object:Gem::Version
|
112
114
|
version: '0'
|
113
115
|
requirements: []
|
114
|
-
rubygems_version: 3.
|
116
|
+
rubygems_version: 3.3.3
|
115
117
|
signing_key:
|
116
118
|
specification_version: 4
|
117
119
|
summary: Provides coverage report for your rails routes
|