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