Skip to content

Commit b1fbd49

Browse files
authored
Merge pull request #1407 from koic/add_new_rails_multiple_route_paths_cop
Add new `Rails/MultipleRoutePaths` cop
2 parents 574e4ef + f305297 commit b1fbd49

File tree

7 files changed

+192
-9
lines changed

7 files changed

+192
-9
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#1407](https://github.com/rubocop/rubocop-rails/pull/1407): Add new `Rails/MultipleRoutePaths` cop. ([@koic][])

config/default.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,12 @@ Rails/MigrationClassName:
698698
Include:
699699
- db/**/*.rb
700700

701+
Rails/MultipleRoutePaths:
702+
Description: 'Checks for mapping a route with multiple paths, which is deprecated and will be removed in Rails 8.1.'
703+
Enabled: pending
704+
Severity: warning
705+
VersionAdded: '<<next>>'
706+
701707
Rails/NegateInclude:
702708
Description: 'Prefer `collection.exclude?(obj)` over `!collection.include?(obj)`.'
703709
StyleGuide: 'https://rails.rubystyle.guide#exclude'
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
# Common functionality for cops working with routes.
6+
module RoutesHelper
7+
extend NodePattern::Macros
8+
9+
HTTP_METHODS = %i[get post put patch delete].freeze
10+
11+
def_node_matcher :routes_draw?, <<~PATTERN
12+
(send (send _ :routes) :draw)
13+
PATTERN
14+
15+
def within_routes?(node)
16+
node.each_ancestor(:block).any? { |block| routes_draw?(block.send_node) }
17+
end
18+
end
19+
end
20+
end

lib/rubocop/cop/rails/match_route.rb

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ module Rails
2121
# match 'photos/:id', to: 'photos#show', via: :all
2222
#
2323
class MatchRoute < Base
24+
include RoutesHelper
2425
extend AutoCorrector
2526

2627
MSG = 'Use `%<http_method>s` instead of `match` to define a route.'
2728
RESTRICT_ON_SEND = %i[match].freeze
28-
HTTP_METHODS = %i[get post put patch delete].freeze
2929

3030
def_node_matcher :match_method_call?, <<~PATTERN
3131
(send nil? :match $_ $(hash ...) ?)
@@ -60,14 +60,6 @@ def register_offense(node, http_method)
6060
end
6161
end
6262

63-
def_node_matcher :routes_draw?, <<~PATTERN
64-
(send (send _ :routes) :draw)
65-
PATTERN
66-
67-
def within_routes?(node)
68-
node.each_ancestor(:block).any? { |a| routes_draw?(a.send_node) }
69-
end
70-
7163
def extract_via(node)
7264
via_pair = via_pair(node)
7365
return %i[get] unless via_pair
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Rails
6+
# Checks for mapping a route with multiple paths, which is deprecated and will be removed in Rails 8.1.
7+
#
8+
# @example
9+
#
10+
# # bad
11+
# get '/users', '/other_path', to: 'users#index'
12+
#
13+
# # good
14+
# get '/users', to: 'users#index'
15+
# get '/other_path', to: 'users#index'
16+
#
17+
class MultipleRoutePaths < Base
18+
include RoutesHelper
19+
extend AutoCorrector
20+
21+
MSG = 'Use separate routes instead of combining multiple route paths in a single route.'
22+
RESTRICT_ON_SEND = HTTP_METHODS
23+
24+
IGNORED_ARGUMENT_TYPES = %i[array hash].freeze
25+
26+
def on_send(node)
27+
return unless within_routes?(node)
28+
29+
route_paths = node.arguments.reject { |argument| IGNORED_ARGUMENT_TYPES.include?(argument.type) }
30+
return if route_paths.count < 2
31+
32+
add_offense(node) do |corrector|
33+
corrector.replace(node, migrate_to_multiple_routes(node, route_paths))
34+
end
35+
end
36+
37+
private
38+
39+
def migrate_to_multiple_routes(node, route_paths)
40+
rest = route_paths.last.source_range.end.join(node.source_range.end).source
41+
indentation = ' ' * node.source_range.column
42+
43+
route_paths.map do |route_path|
44+
"#{node.method_name} #{route_path.source}#{rest}"
45+
end.join("\n#{indentation}")
46+
end
47+
end
48+
end
49+
end
50+
end

lib/rubocop/cop/rails_cops.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
require_relative 'mixin/enforce_superclass'
88
require_relative 'mixin/index_method'
99
require_relative 'mixin/migrations_helper'
10+
require_relative 'mixin/routes_helper'
1011
require_relative 'mixin/target_rails_version'
1112

1213
require_relative 'rails/action_controller_flash_before_render'
@@ -77,6 +78,7 @@
7778
require_relative 'rails/mailer_name'
7879
require_relative 'rails/match_route'
7980
require_relative 'rails/migration_class_name'
81+
require_relative 'rails/multiple_route_paths'
8082
require_relative 'rails/negate_include'
8183
require_relative 'rails/not_null_column'
8284
require_relative 'rails/order_by_id'
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Rails::MultipleRoutePaths, :config do
4+
it 'registers an offense when using a route with multiple string route paths' do
5+
expect_offense(<<~RUBY)
6+
Rails.application.routes.draw do
7+
get '/users', '/other_path/users', '/another_path/users'
8+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use separate routes instead of combining multiple route paths in a single route.
9+
end
10+
RUBY
11+
12+
expect_correction(<<~RUBY)
13+
Rails.application.routes.draw do
14+
get '/users'
15+
get '/other_path/users'
16+
get '/another_path/users'
17+
end
18+
RUBY
19+
end
20+
21+
it 'registers an offense when using a route with multiple route paths with option' do
22+
expect_offense(<<~RUBY)
23+
Rails.application.routes.draw do
24+
get '/users', '/other_path/users', '/another_path/users', to: 'users#index'
25+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use separate routes instead of combining multiple route paths in a single route.
26+
end
27+
RUBY
28+
29+
expect_correction(<<~RUBY)
30+
Rails.application.routes.draw do
31+
get '/users', to: 'users#index'
32+
get '/other_path/users', to: 'users#index'
33+
get '/another_path/users', to: 'users#index'
34+
end
35+
RUBY
36+
end
37+
38+
it 'registers an offense when using a route with multiple route paths with splat option' do
39+
expect_offense(<<~RUBY)
40+
Rails.application.routes.draw do
41+
get '/users', '/other_path/users', '/another_path/users', **options
42+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use separate routes instead of combining multiple route paths in a single route.
43+
end
44+
RUBY
45+
46+
expect_correction(<<~RUBY)
47+
Rails.application.routes.draw do
48+
get '/users', **options
49+
get '/other_path/users', **options
50+
get '/another_path/users', **options
51+
end
52+
RUBY
53+
end
54+
55+
it 'registers an offense when using a route with multiple symbol route paths' do
56+
expect_offense(<<~RUBY)
57+
Rails.application.routes.draw do
58+
get :resend, :generate_new_password
59+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use separate routes instead of combining multiple route paths in a single route.
60+
end
61+
RUBY
62+
63+
expect_correction(<<~RUBY)
64+
Rails.application.routes.draw do
65+
get :resend
66+
get :generate_new_password
67+
end
68+
RUBY
69+
end
70+
71+
it 'does not register an offense when using single string path method calls' do
72+
expect_no_offenses(<<~RUBY)
73+
Rails.application.routes.draw do
74+
get '/users'
75+
get '/other_path/users'
76+
get '/another_path/users'
77+
end
78+
RUBY
79+
end
80+
81+
it 'does not register an offense when using single string path with option method calls' do
82+
expect_no_offenses(<<~RUBY)
83+
Rails.application.routes.draw do
84+
get '/users', to: 'users#index'
85+
get '/other_path/users', to: 'users#index'
86+
get '/another_path/users', to: 'users#index'
87+
end
88+
RUBY
89+
end
90+
91+
it 'does not register an offense when using single string path with array literal' do
92+
expect_no_offenses(<<~RUBY)
93+
Rails.application.routes.draw do
94+
get '/other_path/users', []
95+
end
96+
RUBY
97+
end
98+
99+
it 'does not register an offense when using single route path with no arguments' do
100+
expect_no_offenses(<<~RUBY)
101+
Rails.application.routes.draw do
102+
get
103+
end
104+
RUBY
105+
end
106+
107+
it 'does not register an offense when not within routes' do
108+
expect_no_offenses(<<~RUBY)
109+
get '/users', '/other_path/users', '/another_path/users'
110+
RUBY
111+
end
112+
end

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy