-
-
Notifications
You must be signed in to change notification settings - Fork 263
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #275 from fatkodima/match-route-cop
Add new `Rails/MatchRoute` cop
- Loading branch information
Showing
7 changed files
with
273 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module Rails | ||
# This cop identifies places where defining routes with `match` | ||
# can be replaced with a specific HTTP method. | ||
# | ||
# Don't use `match` to define any routes unless there is a need to map multiple request types | ||
# among [:get, :post, :patch, :put, :delete] to a single action using the `:via` option. | ||
# | ||
# @example | ||
# # bad | ||
# match ':controller/:action/:id' | ||
# match 'photos/:id', to: 'photos#show', via: :get | ||
# | ||
# # good | ||
# get ':controller/:action/:id' | ||
# get 'photos/:id', to: 'photos#show' | ||
# match 'photos/:id', to: 'photos#show', via: [:get, :post] | ||
# match 'photos/:id', to: 'photos#show', via: :all | ||
# | ||
class MatchRoute < Cop | ||
MSG = 'Use `%<http_method>s` instead of `match` to define a route.' | ||
HTTP_METHODS = %i[get post put patch delete].freeze | ||
|
||
def_node_matcher :match_method_call?, <<~PATTERN | ||
(send nil? :match $_ $(hash ...) ?) | ||
PATTERN | ||
|
||
def on_send(node) | ||
match_method_call?(node) do |path_node, options_node| | ||
return unless within_routes?(node) | ||
|
||
options_node = path_node.hash_type? ? path_node : options_node.first | ||
|
||
if options_node.nil? | ||
message = format(MSG, http_method: 'get') | ||
add_offense(node, message: message) | ||
else | ||
via = extract_via(options_node) | ||
if via.size == 1 && http_method?(via.first) | ||
message = format(MSG, http_method: via.first) | ||
add_offense(node, message: message) | ||
end | ||
end | ||
end | ||
end | ||
|
||
def autocorrect(node) | ||
match_method_call?(node) do |path_node, options_node| | ||
options_node = options_node.first | ||
|
||
lambda do |corrector| | ||
corrector.replace(node, replacement(path_node, options_node)) | ||
end | ||
end | ||
end | ||
|
||
private | ||
|
||
def_node_matcher :routes_draw?, <<~PATTERN | ||
(send (send _ :routes) :draw) | ||
PATTERN | ||
|
||
def within_routes?(node) | ||
node.each_ancestor(:block).any? { |a| routes_draw?(a.send_node) } | ||
end | ||
|
||
def extract_via(node) | ||
via_pair = via_pair(node) | ||
return %i[get] unless via_pair | ||
|
||
_, via = *via_pair | ||
|
||
if via.basic_literal? | ||
[via.value] | ||
elsif via.array_type? | ||
via.values.map(&:value) | ||
end | ||
end | ||
|
||
def via_pair(node) | ||
node.pairs.find { |p| p.key.value == :via } | ||
end | ||
|
||
def http_method?(method) | ||
HTTP_METHODS.include?(method.to_sym) | ||
end | ||
|
||
def replacement(path_node, options_node) | ||
if path_node.hash_type? | ||
http_method, options = *http_method_and_options(path_node) | ||
"#{http_method} #{options.map(&:source).join(', ')}" | ||
elsif options_node.nil? | ||
"get #{path_node.source}" | ||
else | ||
http_method, options = *http_method_and_options(options_node) | ||
|
||
if options.any? | ||
"#{http_method} #{path_node.source}, #{options.map(&:source).join(', ')}" | ||
else | ||
"#{http_method} #{path_node.source}" | ||
end | ||
end | ||
end | ||
|
||
def http_method_and_options(node) | ||
via_pair = via_pair(node) | ||
http_method = extract_via(node).first | ||
rest_pairs = node.pairs - [via_pair] | ||
[http_method, rest_pairs] | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::Cop::Rails::MatchRoute do | ||
subject(:cop) { described_class.new } | ||
|
||
it 'registers an offense and corrects when using `match` only with path' do | ||
expect_offense(<<~RUBY) | ||
routes.draw do | ||
match ':controller/:action/:id' | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `get` instead of `match` to define a route. | ||
end | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
routes.draw do | ||
get ':controller/:action/:id' | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense and corrects when using `match` with single :via' do | ||
expect_offense(<<~RUBY) | ||
routes.draw do | ||
match 'photos/:id', to: 'photos#show', via: :get | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `get` instead of `match` to define a route. | ||
end | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
routes.draw do | ||
get 'photos/:id', to: 'photos#show' | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense and corrects when using `match` with one item in :via array' do | ||
expect_offense(<<~RUBY) | ||
routes.draw do | ||
match 'photos/:id', to: 'photos#show', via: [:get] | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `get` instead of `match` to define a route. | ||
end | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
routes.draw do | ||
get 'photos/:id', to: 'photos#show' | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense and corrects when using `match` with hash shorthand' do | ||
expect_offense(<<~RUBY) | ||
routes.draw do | ||
match 'photos/:id' => 'photos#show', via: :get | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `get` instead of `match` to define a route. | ||
end | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
routes.draw do | ||
get 'photos/:id' => 'photos#show' | ||
end | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when not within routes' do | ||
expect_no_offenses(<<~RUBY) | ||
match 'photos/:id', to: 'photos#show', via: :get | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when using `match` with `via: :all`' do | ||
expect_no_offenses(<<~RUBY) | ||
routes.draw do | ||
match 'photos/:id', to: 'photos#show', via: :all | ||
end | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when using `match` with multiple verbs in :via array' do | ||
expect_no_offenses(<<~RUBY) | ||
routes.draw do | ||
match 'photos/:id', to: 'photos#update', via: [:put, :patch] | ||
end | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when using `get`' do | ||
expect_no_offenses(<<~RUBY) | ||
routes.draw do | ||
get 'photos/:id', to: 'photos#show' | ||
end | ||
RUBY | ||
end | ||
end |