routes_coverage 0.6.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b697ab555d0362fa18dae5161266efbeb99eff4c1f276f7d06039952a6fc2f7c
4
- data.tar.gz: 5e0fbeb4d54958bfb46f307c42ef5a54ceec49af49f699943881c6a665cca251
3
+ metadata.gz: 4f992227e3172a9ee4ea678d302b2d6655cdf16feed78020947375cc627a0254
4
+ data.tar.gz: 4f5dae5790bac97b3675d085d771a17175e8744f00fed659625592dc471c8a27
5
5
  SHA512:
6
- metadata.gz: c810351185cd998b8370c357d28bb06736e1c3de4202e12aab641b7ab22ed182f9e9f2b31a4cefe54dd9cdd52fe6a20a0187b088649e6652107b9edfce6bad39
7
- data.tar.gz: e7af918230387e897334154962115d6d6f9b6e38d1e870bd5070529cf88d8fc2708cf6c57baf1d9f1f66b14beacd62f5eb1ae21d33de41ed279e671e4dcf7449
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
- ("#{r.verb.to_s[8..-3]} #{r.path.spec}".strip =~ filter_regex) ||
101
+ ("#{verb} #{r.path.spec}".strip =~ filter_regex) ||
100
102
  (r.path.spec.to_s =~ namespaces_regex)
101
103
  ).present?
102
104
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RoutesCoverage
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
@@ -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?(constraint_value)
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?(matcher)
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
@@ -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", ">= 2.2.10"
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.6.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: 2021-11-15 00:00:00.000000000 Z
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: 2.2.10
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: 2.2.10
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.0.9
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