From 7b7124747c243631421e50ca13f874c05f53856f Mon Sep 17 00:00:00 2001 From: moznion Date: Thu, 22 Aug 2024 23:29:08 +0900 Subject: [PATCH] Add `Rails/HttpUrl` cop to enforce passing a string literal as a URL to http method calls For the test cases; to make the actual URL obvious and to facilitate tracking endpoint path changes. Original discussion: https://github.com/rubocop/rails-style-guide/issues/328 Signed-off-by: moznion --- config/default.yml | 8 ++++++ lib/rubocop/cop/rails/http_url.rb | 34 +++++++++++++++++++++++++ lib/rubocop/cop/rails_cops.rb | 1 + spec/rubocop/cop/rails/http_url_spec.rb | 19 ++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 lib/rubocop/cop/rails/http_url.rb create mode 100644 spec/rubocop/cop/rails/http_url_spec.rb diff --git a/config/default.yml b/config/default.yml index 1f3b5376dc..e0e6ab6cc2 100644 --- a/config/default.yml +++ b/config/default.yml @@ -565,6 +565,14 @@ Rails/HttpStatus: - numeric - symbolic +Rails/HttpUrl: + Description: 'Enforces passing a string literal as a URL to http method calls.' + Enabled: true + VersionAdded: '2.26' + Include: + - 'spec/**/*' + - 'test/**/*' + Rails/I18nLazyLookup: Description: 'Checks for places where I18n "lazy" lookup can be used.' StyleGuide: 'https://rails.rubystyle.guide/#lazy-lookup' diff --git a/lib/rubocop/cop/rails/http_url.rb b/lib/rubocop/cop/rails/http_url.rb new file mode 100644 index 0000000000..5a5383c674 --- /dev/null +++ b/lib/rubocop/cop/rails/http_url.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Rails + # Enforces passing a string literal as a URL to http method calls. + # + # @example + # # bad + # get photos_path + # put photo_path(id) + # post edit_photo_path(id) + # + # # good + # get "/photos" + # put "/photos/#{id}" + # post "/photos/#{id}/edit" + class HttpUrl < Base + MSG = 'The first argument to `%s` should be a string.' + RESTRICT_ON_SEND = %i[get post put patch delete head].freeze + + def_node_matcher :request_method?, <<-PATTERN + (send nil? {#{RESTRICT_ON_SEND.map(&:inspect).join(' ')}} $!str ...) + PATTERN + + def on_send(node) + request_method?(node) do |arg| + add_offense(arg, message: format(MSG, method: node.method_name)) + end + end + end + end + end +end diff --git a/lib/rubocop/cop/rails_cops.rb b/lib/rubocop/cop/rails_cops.rb index e5173baeb0..92b1d9c5e1 100644 --- a/lib/rubocop/cop/rails_cops.rb +++ b/lib/rubocop/cop/rails_cops.rb @@ -61,6 +61,7 @@ require_relative 'rails/has_many_or_has_one_dependent' require_relative 'rails/helper_instance_variable' require_relative 'rails/http_positional_arguments' +require_relative 'rails/http_url' require_relative 'rails/http_status' require_relative 'rails/i18n_lazy_lookup' require_relative 'rails/i18n_locale_assignment' diff --git a/spec/rubocop/cop/rails/http_url_spec.rb b/spec/rubocop/cop/rails/http_url_spec.rb new file mode 100644 index 0000000000..4c74222f02 --- /dev/null +++ b/spec/rubocop/cop/rails/http_url_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Rails::HttpUrl, :config do + %i[get post put delete patch head].each do |http_method| + it "registers an offense when first argument to `#{http_method}` is not a string" do + padding = " #{' ' * http_method.length}" + expect_offense(<<~RUBY, http_method: http_method) + #{http_method} :resource + #{padding}^^^^^^^^^ The first argument to `#{http_method}` should be a string. + RUBY + end + + it "does not register an offense when the first argument to #{http_method} is a string" do + expect_no_offenses(<<~RUBY) + #{http_method} '/resource' + RUBY + end + end +end