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 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