Skip to content

Commit

Permalink
Add a cop to enforce prepare argument as a Symbol or String (#150)
Browse files Browse the repository at this point in the history
  • Loading branch information
DougEdey authored Dec 21, 2023
1 parent 48e7d1f commit 18e9a4b
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## master

- [PR#149](https://github.com/DmitryTsepelev/rubocop-graphql/pull/149) Fix false-positives for GraphQL/ArgumentDescription when there's additional nodes in the Argument init block ([@JELaVallee][])
- [PR#]() Add cop to enforce prepare: argument style as symbol or string. ([@DougEdey][])

## 1.4.0 (2023-06-31)

Expand Down
95 changes: 95 additions & 0 deletions lib/rubocop/cop/graphql/prepare_method.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# frozen_string_literal: true

require "rubocop"

module RuboCop
module Cop
module GraphQL
# Checks that GraphQL Argument definitions prepare arguments use String or constants
# instead of `prepare: CONST_REF`
# This allows better Sorbet typing.
#
# Preference is given to an input object declaring a `def prepare(...); end` method
#
# @example
# # bad
# PREPARE = ->(input, context) { ... }
#
# argument :input, prepare: PREPARE
#
# # good
# def input_prepare(input); ...; end
#
# argument :input, prepare: :input_prepare
#
class PrepareMethod < RuboCop::Cop::Base
extend AutoCorrector
GENERAL_MSG = "Avoid using prepare lambdas, use prepare: :method_name or "\
"prepare: \"method_name\" instead."
STRING_MSG = "Avoid using prepare lambdas, use prepare: \"method_name\" instead."
PREFER_STRING_MSG = "Avoid using prepare symbols, use prepare: \"%s\" instead."
SYMBOL_MSG = "Avoid using prepare lambdas, use prepare: :method_name instead."
PREFER_SYMBOL_MSG = "Avoid using prepare string, use prepare: :%s instead."

ARGUMENT_METHODS = Set[:argument, :public_argument].freeze

# @!method graphql_argument_with_prepare_block?(node)
def_node_matcher :graphql_argument_with_prepare_block?, <<~PATTERN
(send nil? ARGUMENT_METHODS ... (hash ... (pair (sym :prepare) $({ const | block } ...) )))
PATTERN

# @!method prepare_method_string_name?(node)
def_node_matcher :prepare_method_string_name?, <<~PATTERN
(send nil? ARGUMENT_METHODS ... (hash ... (pair (sym :prepare) $(str ...) )))
PATTERN

# @!method prepare_method_symbol_name?(node)
def_node_matcher :prepare_method_symbol_name?, <<~PATTERN
(send nil? ARGUMENT_METHODS ... (hash ... (pair (sym :prepare) $(sym ...) )))
PATTERN

def on_send(node)
type = cop_config["EnforcedStyle"]

message = case type
when "symbol"
SYMBOL_MSG
when "string"
STRING_MSG
else
GENERAL_MSG
end
graphql_argument_with_prepare_block?(node) do |prepare_definition|
add_offense(prepare_definition, message: message)
end

if type == "symbol"
prepare_method_string_name?(node) do |prepare_definition|
method_name = prepare_definition.value
add_offense(prepare_definition,
message: format(PREFER_SYMBOL_MSG, method_name)) do |corrector|
autocorrect(corrector, node, "\"#{method_name}\"", ":#{method_name}")
end
end
elsif type == "string"
prepare_method_symbol_name?(node) do |prepare_definition|
method_name = prepare_definition.value
add_offense(prepare_definition,
message: format(PREFER_STRING_MSG, method_name)) do |corrector|
autocorrect(corrector, node, ":#{method_name}", "\"#{method_name}\"")
end
end
end
end

private

def autocorrect(corrector, node, original_method_name, new_method_name)
new_source = node.source.sub("prepare: #{original_method_name}",
"prepare: #{new_method_name}")
corrector.replace(node, new_source)
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/graphql_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
require_relative "graphql/object_description"
require_relative "graphql/ordered_arguments"
require_relative "graphql/ordered_fields"
require_relative "graphql/prepare_method"
require_relative "graphql/unused_argument"
require_relative "graphql/unnecessary_argument_camelize"
require_relative "graphql/unnecessary_field_alias"
Expand Down
187 changes: 187 additions & 0 deletions spec/rubocop/cop/graphql/prepare_method_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::GraphQL::PrepareMethod, :config do
let(:config) do
RuboCop::Config.new(
"GraphQL/PrepareMethod" => {
"EnforcedStyle" => nil
}
)
end

context "when there is a block" do
it "registers an offense when the block is declared directly" do
expect_offense(<<~RUBY)
class CheckAlertSourceUsageCreate
public_argument :input,
Types::CheckAlertSourceUsageCreateInput,
Types.t("check_alert_source_usage_create_input", "self"),
required: true,
prepare: ->(input, context, type: check_type_from_enum) do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid using prepare lambdas, use prepare: :method_name or prepare: "method_name" instead.
PREPARE.call(input, context, type)
end
end
RUBY
end

it "registers offense when prepare block has a lambda defined via a constant" do
expect_offense(<<~RUBY)
class CheckAlertSourceUsageCreate
public_argument :input,
Types::CheckAlertSourceUsageCreateInput,
Types.t("check_alert_source_usage_create_input", "self"),
required: true,
prepare: PREPARE
^^^^^^^ Avoid using prepare lambdas, use prepare: :method_name or prepare: "method_name" instead.
end
RUBY
end
end

context "when the prepare argument uses a method name" do
it "no offense when prepare block has a method defined as a symbol" do
expect_no_offenses(<<~RUBY)
class CheckAlertSourceUsageCreate
public_argument :input,
Types::CheckAlertSourceUsageCreateInput,
Types.t("check_alert_source_usage_create_input", "self"),
required: true,
prepare: :prepare_method
end
RUBY
end

it "no offense when prepare block has a method defined as a string" do
expect_no_offenses(<<~RUBY)
class CheckAlertSourceUsageCreate
public_argument :input,
Types::CheckAlertSourceUsageCreateInput,
Types.t("check_alert_source_usage_create_input", "self"),
required: true,
prepare: "prepare_method"
end
RUBY
end
end

context "when the EnforcedStyle is string" do
let(:config) do
RuboCop::Config.new(
"GraphQL/PrepareMethod" => {
"EnforcedStyle" => "string"
}
)
end

it "registers an offense when the block is declared directly" do
expect_offense(<<~RUBY)
class CheckAlertSourceUsageCreate
public_argument :input,
Types::CheckAlertSourceUsageCreateInput,
Types.t("check_alert_source_usage_create_input", "self"),
required: true,
prepare: ->(input, context, type: check_type_from_enum) do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid using prepare lambdas, use prepare: "method_name" instead.
PREPARE.call(input, context, type)
end
end
RUBY
end

it "registers offense when prepare argument has a method defined as a symbol" do
expect_offense(<<~RUBY)
class CheckAlertSourceUsageCreate
public_argument :input,
Types::CheckAlertSourceUsageCreateInput,
Types.t("check_alert_source_usage_create_input", "self"),
required: true,
prepare: :prepare_method
^^^^^^^^^^^^^^^ Avoid using prepare symbols, use prepare: "prepare_method" instead.
end
RUBY

expect_correction(<<~RUBY)
class CheckAlertSourceUsageCreate
public_argument :input,
Types::CheckAlertSourceUsageCreateInput,
Types.t("check_alert_source_usage_create_input", "self"),
required: true,
prepare: "prepare_method"
end
RUBY
end

it "no offense when prepare block has a method defined as a string" do
expect_no_offenses(<<~RUBY)
class CheckAlertSourceUsageCreate
public_argument :input,
Types::CheckAlertSourceUsageCreateInput,
Types.t("check_alert_source_usage_create_input", "self"),
required: true,
prepare: "prepare_method"
end
RUBY
end
end

context "when the EnforcedStyle is symbol" do
let(:config) do
RuboCop::Config.new(
"GraphQL/PrepareMethod" => {
"EnforcedStyle" => "symbol"
}
)
end

it "registers an offense when the block is declared directly" do
expect_offense(<<~RUBY)
class CheckAlertSourceUsageCreate
public_argument :input,
Types::CheckAlertSourceUsageCreateInput,
Types.t("check_alert_source_usage_create_input", "self"),
required: true,
prepare: ->(input, context, type: check_type_from_enum) do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid using prepare lambdas, use prepare: :method_name instead.
PREPARE.call(input, context, type)
end
end
RUBY
end

it "registers offense when prepare block has a method defined as a string" do
expect_offense(<<~RUBY)
class CheckAlertSourceUsageCreate
public_argument :input,
Types::CheckAlertSourceUsageCreateInput,
Types.t("check_alert_source_usage_create_input", "self"),
required: true,
prepare: "prepare_method"
^^^^^^^^^^^^^^^^ Avoid using prepare string, use prepare: :prepare_method instead.
end
RUBY

expect_correction(<<~RUBY)
class CheckAlertSourceUsageCreate
public_argument :input,
Types::CheckAlertSourceUsageCreateInput,
Types.t("check_alert_source_usage_create_input", "self"),
required: true,
prepare: :prepare_method
end
RUBY
end

it "no offense when prepare block has a method defined as a symbol" do
expect_no_offenses(<<~RUBY)
class CheckAlertSourceUsageCreate
public_argument :input,
Types::CheckAlertSourceUsageCreateInput,
Types.t("check_alert_source_usage_create_input", "self"),
required: true,
prepare: prepare_method
end
RUBY
end
end
end

0 comments on commit 18e9a4b

Please sign in to comment.