rubocop-graphql 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -1
- data/config/default.yml +35 -0
- data/lib/rubocop/cop/graphql/default_for_optional_argument.rb +187 -0
- data/lib/rubocop/cop/graphql/disallowed_types.rb +114 -0
- data/lib/rubocop/cop/graphql/field_description.rb +11 -0
- data/lib/rubocop/cop/graphql/method_shadowed_by_resolver_method.rb +122 -0
- data/lib/rubocop/cop/graphql/nullability_mismatch.rb +137 -0
- data/lib/rubocop/cop/graphql/object_description.rb +171 -17
- data/lib/rubocop/cop/graphql/ordered_fields.rb +32 -3
- data/lib/rubocop/cop/graphql/unused_argument.rb +1 -21
- data/lib/rubocop/cop/graphql/useless_method_option.rb +100 -0
- data/lib/rubocop/cop/graphql.rb +44 -0
- data/lib/rubocop/cop/graphql_cops.rb +4 -27
- data/lib/rubocop/graphql/argument/kwargs.rb +18 -0
- data/lib/rubocop/graphql/argument.rb +45 -0
- data/lib/rubocop/graphql/field/kwargs.rb +24 -0
- data/lib/rubocop/graphql/version.rb +1 -1
- data/lib/rubocop-graphql.rb +1 -1
- metadata +10 -4
|
@@ -6,6 +6,19 @@ module RuboCop
|
|
|
6
6
|
# This cop checks if a type (object, input, interface, scalar, union,
|
|
7
7
|
# mutation, subscription, and resolver) has a description.
|
|
8
8
|
#
|
|
9
|
+
# Only classes that actually declare a GraphQL type are checked: those whose
|
|
10
|
+
# superclass resolves to a GraphQL base (`Object`, `InputObject`, `Union`,
|
|
11
|
+
# `Enum`, `Scalar`, or anything ending in `Mutation`, `Subscription` or
|
|
12
|
+
# `Resolver`), plus modules that `include` an `*Interface` base. Plain Ruby
|
|
13
|
+
# classes that happen to live alongside types - error classes, analyzers,
|
|
14
|
+
# validators, loaders, generators - are skipped, so they no longer need to be
|
|
15
|
+
# silenced one by one.
|
|
16
|
+
#
|
|
17
|
+
# Two kinds of real type declarations are also skipped, because neither
|
|
18
|
+
# surfaces a description in the schema: abstract `Base*` types that other
|
|
19
|
+
# types inherit from, and (unless `IgnoreRootTypes` is disabled) the root
|
|
20
|
+
# operation types `Query`, `Mutation` and `Subscription`.
|
|
21
|
+
#
|
|
9
22
|
# @example
|
|
10
23
|
# # good
|
|
11
24
|
#
|
|
@@ -20,44 +33,185 @@ module RuboCop
|
|
|
20
33
|
# # ...
|
|
21
34
|
# end
|
|
22
35
|
#
|
|
36
|
+
# @example
|
|
37
|
+
# # good - not a GraphQL type, so no description is expected
|
|
38
|
+
#
|
|
39
|
+
# class TrackingInfoNotAvailable < StandardError; end
|
|
40
|
+
# class UserLoader < GraphQL::Batch::Loader; end
|
|
41
|
+
#
|
|
42
|
+
# @example
|
|
43
|
+
# # good - abstract base and root operation types carry no description
|
|
44
|
+
#
|
|
45
|
+
# class Types::BaseObject < GraphQL::Schema::Object; end
|
|
46
|
+
# class Types::Query < Types::BaseObject; end
|
|
47
|
+
#
|
|
48
|
+
# @example AdditionalTypeBases: [] (default)
|
|
49
|
+
# # good - `ApplicationType` is not a recognized GraphQL base, so this
|
|
50
|
+
# # class is not checked
|
|
51
|
+
#
|
|
52
|
+
# class Types::UserType < ApplicationType
|
|
53
|
+
# end
|
|
54
|
+
#
|
|
55
|
+
# @example AdditionalTypeBases: ['ApplicationType']
|
|
56
|
+
# # bad - `ApplicationType` is now treated as a GraphQL base
|
|
57
|
+
#
|
|
58
|
+
# class Types::UserType < ApplicationType
|
|
59
|
+
# end
|
|
60
|
+
#
|
|
23
61
|
class ObjectDescription < Base
|
|
24
|
-
include RuboCop::GraphQL::NodePattern
|
|
25
62
|
include RuboCop::GraphQL::DescriptionMethod
|
|
26
63
|
|
|
27
64
|
MSG = "Missing type description"
|
|
28
65
|
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
66
|
+
# Base class names that mark a GraphQL type when they match exactly, or with
|
|
67
|
+
# a `Base` prefix (`GraphQL::Schema::Object`, `Types::BaseObject`).
|
|
68
|
+
# Ambiguous words are deliberately not matched as suffixes, so a plain Ruby
|
|
69
|
+
# `ValueObject` base is not mistaken for a GraphQL one. `Interface` is absent
|
|
70
|
+
# on purpose: graphql-ruby interfaces are modules, so a *class* inheriting an
|
|
71
|
+
# `*::Interface` constant is always some other abstract base.
|
|
72
|
+
EXACT_BASES = %w[Object InputObject Union Enum Scalar].freeze
|
|
73
|
+
|
|
74
|
+
# These read unambiguously as GraphQL even inside a longer name, so they are
|
|
75
|
+
# matched as suffixes (`RelayClassicMutation`, `Base::PermissionedMutation`).
|
|
76
|
+
SUFFIX_BASES = %w[Mutation Subscription Resolver].freeze
|
|
77
|
+
|
|
78
|
+
# A base named exactly `Base` (`Resolvers::Base`) is only a GraphQL base when
|
|
79
|
+
# it sits in a namespace that says so.
|
|
80
|
+
GRAPHQL_NAMESPACES = %w[
|
|
81
|
+
Types Mutations Subscriptions Resolvers Inputs Interfaces Unions Enums Scalars
|
|
82
|
+
].freeze
|
|
83
|
+
|
|
84
|
+
# GraphQL reserves these names for the root operation types.
|
|
85
|
+
ROOT_TYPE_NAMES = %w[Query Mutation Subscription].freeze
|
|
86
|
+
|
|
87
|
+
# `Base`, or `Base` followed by another word - the naming convention for the
|
|
88
|
+
# abstract types that other types inherit from (`BaseObject`, `BaseInterface`).
|
|
89
|
+
ABSTRACT_BASE_NAME = /\ABase(?:[A-Z]|\z)/
|
|
90
|
+
|
|
91
|
+
# Sorbet's `T` namespace is reserved, and `T::Enum` / `T::Struct` collide with
|
|
92
|
+
# the base names above.
|
|
93
|
+
SORBET_NAMESPACE = :T
|
|
33
94
|
|
|
34
95
|
def on_class(node)
|
|
35
|
-
return
|
|
96
|
+
return unless graphql_type_class?(node)
|
|
97
|
+
return if exempt_type?(node)
|
|
98
|
+
return if described_or_root_named?(node)
|
|
36
99
|
|
|
37
100
|
add_offense(node.identifier)
|
|
38
101
|
end
|
|
39
102
|
|
|
40
103
|
def on_module(node)
|
|
41
|
-
return if
|
|
104
|
+
return if abstract_base?(node)
|
|
105
|
+
return unless undescribed_interface_module?(node)
|
|
42
106
|
|
|
43
|
-
|
|
44
|
-
add_offense(node.identifier)
|
|
45
|
-
end
|
|
107
|
+
add_offense(node.identifier)
|
|
46
108
|
end
|
|
47
109
|
|
|
48
110
|
private
|
|
49
111
|
|
|
50
|
-
|
|
51
|
-
|
|
112
|
+
# Abstract bases and the root operation types are real GraphQL declarations,
|
|
113
|
+
# but neither surfaces a description in the schema.
|
|
114
|
+
def exempt_type?(node)
|
|
115
|
+
return true if abstract_base?(node)
|
|
116
|
+
|
|
117
|
+
ignore_root_types? && ROOT_TYPE_NAMES.include?(type_name(node))
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def abstract_base?(node)
|
|
121
|
+
ABSTRACT_BASE_NAME.match?(type_name(node))
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def type_name(node)
|
|
125
|
+
node.identifier.short_name.to_s
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def graphql_type_class?(node)
|
|
129
|
+
superclass = node.parent_class
|
|
130
|
+
return false if superclass.nil? || !superclass.const_type?
|
|
131
|
+
return false if superclass.namespace&.short_name == SORBET_NAMESPACE
|
|
132
|
+
|
|
133
|
+
graphql_base?(superclass)
|
|
52
134
|
end
|
|
53
135
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
136
|
+
def graphql_base?(const_node)
|
|
137
|
+
name = const_node.short_name.to_s
|
|
138
|
+
return true if additional_type_bases.include?(name)
|
|
139
|
+
return graphql_namespace?(const_node) if name == "Base"
|
|
140
|
+
return true if SUFFIX_BASES.any? { |suffix| name.end_with?(suffix) }
|
|
141
|
+
|
|
142
|
+
EXACT_BASES.include?(name.delete_prefix("Base"))
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def graphql_namespace?(const_node)
|
|
146
|
+
namespace = const_node.namespace
|
|
147
|
+
return false if namespace.nil? || !namespace.const_type?
|
|
148
|
+
|
|
149
|
+
GRAPHQL_NAMESPACES.include?(namespace.short_name.to_s)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# A method call with no explicit receiver (e.g. `description "..."`,
|
|
153
|
+
# `include Foo`) - the shape of a GraphQL type DSL declaration.
|
|
154
|
+
def bare_call?(node)
|
|
155
|
+
node.send_type? && node.receiver.nil?
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Single pass over the module body: an interface module both `include`s an
|
|
159
|
+
# `*Interface` base and (when compliant) declares a `description`.
|
|
160
|
+
def undescribed_interface_module?(node)
|
|
161
|
+
interface = false
|
|
162
|
+
described = false
|
|
163
|
+
|
|
164
|
+
body_nodes(node).each do |child|
|
|
165
|
+
described ||= description_method_call?(child)
|
|
166
|
+
interface ||= interface_include?(child)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
interface && !described
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def interface_include?(node)
|
|
173
|
+
return false unless bare_call?(node) && node.method?(:include)
|
|
174
|
+
|
|
175
|
+
arg = node.first_argument
|
|
176
|
+
return false unless arg&.const_type?
|
|
177
|
+
|
|
178
|
+
name = arg.short_name.to_s
|
|
179
|
+
name.end_with?("Interface") || additional_type_bases.include?(name)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Single pass over the class body: a `description` satisfies the cop, and a
|
|
183
|
+
# `graphql_name "Query"` marks a root operation type declared under some
|
|
184
|
+
# other class name, which is exempt like a class named `Query` itself.
|
|
185
|
+
def described_or_root_named?(node)
|
|
186
|
+
body_nodes(node).any? do |child|
|
|
187
|
+
description_method_call?(child) || root_graphql_name?(child)
|
|
59
188
|
end
|
|
60
189
|
end
|
|
190
|
+
|
|
191
|
+
def root_graphql_name?(node)
|
|
192
|
+
return false unless ignore_root_types?
|
|
193
|
+
return false unless bare_call?(node) && node.method?(:graphql_name)
|
|
194
|
+
|
|
195
|
+
arg = node.first_argument
|
|
196
|
+
return false unless arg&.str_type?
|
|
197
|
+
|
|
198
|
+
ROOT_TYPE_NAMES.include?(arg.value)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def body_nodes(node)
|
|
202
|
+
body = node.body
|
|
203
|
+
return [] if body.nil?
|
|
204
|
+
|
|
205
|
+
body.begin_type? ? body.child_nodes : [body]
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def ignore_root_types?
|
|
209
|
+
cop_config.fetch("IgnoreRootTypes", true)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def additional_type_bases
|
|
213
|
+
@additional_type_bases ||= Array(cop_config["AdditionalTypeBases"])
|
|
214
|
+
end
|
|
61
215
|
end
|
|
62
216
|
end
|
|
63
217
|
end
|
|
@@ -50,7 +50,26 @@ module RuboCop
|
|
|
50
50
|
PATTERN
|
|
51
51
|
|
|
52
52
|
def on_class(node)
|
|
53
|
-
|
|
53
|
+
check_field_ordering(direct_field_declarations(node))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
alias on_module on_class
|
|
57
|
+
|
|
58
|
+
def on_block(node)
|
|
59
|
+
return if node.method?(:field)
|
|
60
|
+
return unless node.each_ancestor(:class, :module).any?
|
|
61
|
+
|
|
62
|
+
fields = field_declarations(node).select { |f| f.each_ancestor(:block).first == node }
|
|
63
|
+
check_field_ordering(fields)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
alias on_itblock on_block
|
|
67
|
+
alias on_numblock on_block
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def check_field_ordering(fields)
|
|
72
|
+
fields.each_cons(2) do |previous, current|
|
|
54
73
|
next unless consecutive_fields(previous, current)
|
|
55
74
|
next if correct_order?(field_name(previous), field_name(current))
|
|
56
75
|
|
|
@@ -58,9 +77,19 @@ module RuboCop
|
|
|
58
77
|
end
|
|
59
78
|
end
|
|
60
79
|
|
|
61
|
-
|
|
80
|
+
def direct_field_declarations(node)
|
|
81
|
+
field_declarations(node).select { |field| direct_child_field?(field, node) }
|
|
82
|
+
end
|
|
62
83
|
|
|
63
|
-
|
|
84
|
+
def direct_child_field?(field_node, class_node)
|
|
85
|
+
return false unless field_node.each_ancestor(:class, :module).first == class_node
|
|
86
|
+
|
|
87
|
+
field_node.each_ancestor(:block).none? do |block_ancestor|
|
|
88
|
+
next false if block_ancestor.each_ancestor(:class, :module).first != class_node
|
|
89
|
+
|
|
90
|
+
!block_ancestor.method?(:field)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
64
93
|
|
|
65
94
|
def consecutive_fields(previous, current)
|
|
66
95
|
return true if cop_config["Groups"] == false
|
|
@@ -109,7 +109,7 @@ module RuboCop
|
|
|
109
109
|
node.node_parts[0]
|
|
110
110
|
end.to_set
|
|
111
111
|
declared_args = declared_arg_nodes.map { |node| RuboCop::GraphQL::Argument.new(node) }
|
|
112
|
-
declared_args.map(
|
|
112
|
+
declared_args.map(&:keyword).uniq.reject do |declared_arg_name|
|
|
113
113
|
resolve_method_kwargs_names.include?(declared_arg_name)
|
|
114
114
|
end
|
|
115
115
|
end
|
|
@@ -147,26 +147,6 @@ module RuboCop
|
|
|
147
147
|
node.source_range.end
|
|
148
148
|
end
|
|
149
149
|
|
|
150
|
-
def inferred_arg_name(name_as_string)
|
|
151
|
-
case name_as_string
|
|
152
|
-
when /_id$/
|
|
153
|
-
name_as_string.sub(/_id$/, "").to_sym
|
|
154
|
-
when /_ids$/
|
|
155
|
-
name_as_string.sub(/_ids$/, "")
|
|
156
|
-
.sub(/([^s])$/, "\\1s")
|
|
157
|
-
.to_sym
|
|
158
|
-
else
|
|
159
|
-
name_as_string.to_sym
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def arg_name(declared_arg)
|
|
164
|
-
return declared_arg.as if declared_arg.kwargs.as
|
|
165
|
-
return inferred_arg_name(declared_arg.name.to_s) if declared_arg.kwargs.loads
|
|
166
|
-
|
|
167
|
-
declared_arg.name
|
|
168
|
-
end
|
|
169
|
-
|
|
170
150
|
def scoped_node?(node)
|
|
171
151
|
scope_changing_syntax?(node) || block_or_lambda?(node)
|
|
172
152
|
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module GraphQL
|
|
6
|
+
# This cop detects `resolver_method:` or `method:` options that have no
|
|
7
|
+
# effect, either because:
|
|
8
|
+
#
|
|
9
|
+
# - the same field also sets `resolver:` -- graphql-ruby always calls
|
|
10
|
+
# the resolver class's own resolver_method once `resolver:` is set,
|
|
11
|
+
# silently ignoring both `resolver_method:` and `method:` regardless
|
|
12
|
+
# of whether a method by either name exists; or
|
|
13
|
+
# - a method matching the field's own plain name is also defined on the
|
|
14
|
+
# type -- that method is checked (and wins) before `method:` is ever
|
|
15
|
+
# considered, since `method:` is only consulted as a fallback when no
|
|
16
|
+
# such method exists. This only applies to `method:`: `resolver_method:`
|
|
17
|
+
# redirects which name is checked instead of competing with it, so it
|
|
18
|
+
# can't be shadowed by a same-named `def` the way `method:` can.
|
|
19
|
+
#
|
|
20
|
+
# @example
|
|
21
|
+
# # good
|
|
22
|
+
#
|
|
23
|
+
# class Types::PostType < Types::BaseObject
|
|
24
|
+
# field :author, resolver: Resolvers::AuthorResolver
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# class Types::PostType < Types::BaseObject
|
|
28
|
+
# field :author, String, null: true, method: :ghostwriter
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# # bad
|
|
32
|
+
#
|
|
33
|
+
# class Types::PostType < Types::BaseObject
|
|
34
|
+
# field :author, resolver: Resolvers::AuthorResolver, resolver_method: :fetch_author
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# class Types::PostType < Types::BaseObject
|
|
38
|
+
# field :author, resolver: Resolvers::AuthorResolver, method: :ghostwriter
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# class Types::PostType < Types::BaseObject
|
|
42
|
+
# field :author, String, null: true, method: :ghostwriter
|
|
43
|
+
#
|
|
44
|
+
# def author
|
|
45
|
+
# object.author
|
|
46
|
+
# end
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
class UselessMethodOption < Base
|
|
50
|
+
include RuboCop::GraphQL::NodePattern
|
|
51
|
+
|
|
52
|
+
RESOLVER_MSG = "Remove `%<option>s:`, it has no effect: `resolver: %<resolver>s` " \
|
|
53
|
+
"always takes precedence."
|
|
54
|
+
DEF_MSG = "Remove `method:`, it has no effect: `def %<field_name>s` on the type " \
|
|
55
|
+
"always takes precedence."
|
|
56
|
+
RESTRICT_ON_SEND = %i[field].freeze
|
|
57
|
+
|
|
58
|
+
# @!method resolver_method_pair(node)
|
|
59
|
+
def_node_search :resolver_method_pair, "(pair (sym :resolver_method) ...)"
|
|
60
|
+
|
|
61
|
+
# @!method method_pair(node)
|
|
62
|
+
def_node_search :method_pair, "(pair (sym :method) ...)"
|
|
63
|
+
|
|
64
|
+
def on_send(node)
|
|
65
|
+
return unless field_definition?(node)
|
|
66
|
+
|
|
67
|
+
field = RuboCop::GraphQL::Field.new(node)
|
|
68
|
+
|
|
69
|
+
if field.kwargs.resolver
|
|
70
|
+
register_resolver_offenses(node, field)
|
|
71
|
+
else
|
|
72
|
+
register_method_shadowed_by_def_offense(node, field)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def register_resolver_offenses(node, field)
|
|
79
|
+
resolver_source = field.kwargs.resolver.value.source
|
|
80
|
+
|
|
81
|
+
[resolver_method_pair(node).first, method_pair(node).first].compact.each do |pair|
|
|
82
|
+
message = format(RESOLVER_MSG, option: pair.key.value, resolver: resolver_source)
|
|
83
|
+
add_offense(pair, message: message)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def register_method_shadowed_by_def_offense(node, field)
|
|
88
|
+
pair = method_pair(node).first
|
|
89
|
+
return unless pair
|
|
90
|
+
|
|
91
|
+
shadowed_def = field.schema_member.find_method_definition(field.name)
|
|
92
|
+
return unless shadowed_def
|
|
93
|
+
|
|
94
|
+
message = format(DEF_MSG, field_name: field.name)
|
|
95
|
+
add_offense(pair, message: message)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
# Cops for the `GraphQL` department. The department's cops are
|
|
6
|
+
# registered for lazy loading and their files are loaded on demand.
|
|
7
|
+
module GraphQL
|
|
8
|
+
extend LazyLoader
|
|
9
|
+
|
|
10
|
+
register_cop :ArgumentDescription, "#{__dir__}/graphql/argument_description"
|
|
11
|
+
register_cop :ArgumentName, "#{__dir__}/graphql/argument_name"
|
|
12
|
+
register_cop :ArgumentUniqueness, "#{__dir__}/graphql/argument_uniqueness"
|
|
13
|
+
register_cop :ContextWriteInType, "#{__dir__}/graphql/context_write_in_type"
|
|
14
|
+
register_cop :DefaultForOptionalArgument, "#{__dir__}/graphql/default_for_optional_argument"
|
|
15
|
+
register_cop :DisallowedTypes, "#{__dir__}/graphql/disallowed_types"
|
|
16
|
+
register_cop :ExtractInputType, "#{__dir__}/graphql/extract_input_type"
|
|
17
|
+
register_cop :ExtractType, "#{__dir__}/graphql/extract_type"
|
|
18
|
+
register_cop :FieldDefinitions, "#{__dir__}/graphql/field_definitions"
|
|
19
|
+
register_cop :FieldDescription, "#{__dir__}/graphql/field_description"
|
|
20
|
+
register_cop :FieldHashKey, "#{__dir__}/graphql/field_hash_key"
|
|
21
|
+
register_cop :FieldMethod, "#{__dir__}/graphql/field_method"
|
|
22
|
+
register_cop :FieldName, "#{__dir__}/graphql/field_name"
|
|
23
|
+
register_cop :FieldUniqueness, "#{__dir__}/graphql/field_uniqueness"
|
|
24
|
+
register_cop :GraphqlName, "#{__dir__}/graphql/graphql_name"
|
|
25
|
+
register_cop :LegacyDsl, "#{__dir__}/graphql/legacy_dsl"
|
|
26
|
+
register_cop :MaxComplexitySchema, "#{__dir__}/graphql/max_complexity_schema"
|
|
27
|
+
register_cop :MaxDepthSchema, "#{__dir__}/graphql/max_depth_schema"
|
|
28
|
+
register_cop :MethodShadowedByResolverMethod, "#{__dir__}/graphql/method_shadowed_by_resolver_method"
|
|
29
|
+
register_cop :MultipleFieldDefinitions, "#{__dir__}/graphql/multiple_field_definitions"
|
|
30
|
+
register_cop :NotAuthorizedNodeType, "#{__dir__}/graphql/not_authorized_node_type"
|
|
31
|
+
register_cop :NullabilityMismatch, "#{__dir__}/graphql/nullability_mismatch"
|
|
32
|
+
register_cop :ResolverMethodLength, "#{__dir__}/graphql/resolver_method_length"
|
|
33
|
+
register_cop :ObjectDescription, "#{__dir__}/graphql/object_description"
|
|
34
|
+
register_cop :OrderedArguments, "#{__dir__}/graphql/ordered_arguments"
|
|
35
|
+
register_cop :OrderedFields, "#{__dir__}/graphql/ordered_fields"
|
|
36
|
+
register_cop :PrepareMethod, "#{__dir__}/graphql/prepare_method"
|
|
37
|
+
register_cop :UnusedArgument, "#{__dir__}/graphql/unused_argument"
|
|
38
|
+
register_cop :UnnecessaryArgumentCamelize, "#{__dir__}/graphql/unnecessary_argument_camelize"
|
|
39
|
+
register_cop :UnnecessaryFieldAlias, "#{__dir__}/graphql/unnecessary_field_alias"
|
|
40
|
+
register_cop :UnnecessaryFieldCamelize, "#{__dir__}/graphql/unnecessary_field_camelize"
|
|
41
|
+
register_cop :UselessMethodOption, "#{__dir__}/graphql/useless_method_option"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -1,29 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
require_relative "graphql
|
|
7
|
-
require_relative "graphql/extract_input_type"
|
|
8
|
-
require_relative "graphql/extract_type"
|
|
9
|
-
require_relative "graphql/field_definitions"
|
|
10
|
-
require_relative "graphql/field_description"
|
|
11
|
-
require_relative "graphql/field_hash_key"
|
|
12
|
-
require_relative "graphql/field_method"
|
|
13
|
-
require_relative "graphql/field_name"
|
|
14
|
-
require_relative "graphql/field_uniqueness"
|
|
15
|
-
require_relative "graphql/graphql_name"
|
|
16
|
-
require_relative "graphql/legacy_dsl"
|
|
17
|
-
require_relative "graphql/max_complexity_schema"
|
|
18
|
-
require_relative "graphql/max_depth_schema"
|
|
19
|
-
require_relative "graphql/multiple_field_definitions"
|
|
20
|
-
require_relative "graphql/not_authorized_node_type"
|
|
21
|
-
require_relative "graphql/resolver_method_length"
|
|
22
|
-
require_relative "graphql/object_description"
|
|
23
|
-
require_relative "graphql/ordered_arguments"
|
|
24
|
-
require_relative "graphql/ordered_fields"
|
|
25
|
-
require_relative "graphql/prepare_method"
|
|
26
|
-
require_relative "graphql/unused_argument"
|
|
27
|
-
require_relative "graphql/unnecessary_argument_camelize"
|
|
28
|
-
require_relative "graphql/unnecessary_field_alias"
|
|
29
|
-
require_relative "graphql/unnecessary_field_camelize"
|
|
3
|
+
# @deprecated This file is deprecated. Cops are registered for lazy loading in
|
|
4
|
+
# `rubocop/cop/graphql`; this file is kept for compatibility with code
|
|
5
|
+
# that requires it directly.
|
|
6
|
+
require_relative "graphql"
|
|
@@ -36,6 +36,16 @@ module RuboCop
|
|
|
36
36
|
(pair (sym :camelize) ...)
|
|
37
37
|
PATTERN
|
|
38
38
|
|
|
39
|
+
# @!method required_kwarg?(node)
|
|
40
|
+
def_node_matcher :required_kwarg?, <<~PATTERN
|
|
41
|
+
(pair (sym :required) ...)
|
|
42
|
+
PATTERN
|
|
43
|
+
|
|
44
|
+
# @!method default_value_kwarg?(node)
|
|
45
|
+
def_node_matcher :default_value_kwarg?, <<~PATTERN
|
|
46
|
+
(pair (sym :default_value) ...)
|
|
47
|
+
PATTERN
|
|
48
|
+
|
|
39
49
|
def initialize(argument_node)
|
|
40
50
|
@nodes = argument_kwargs(argument_node) || []
|
|
41
51
|
end
|
|
@@ -55,6 +65,14 @@ module RuboCop
|
|
|
55
65
|
def as
|
|
56
66
|
@nodes.find { |kwarg| as_kwarg?(kwarg) }
|
|
57
67
|
end
|
|
68
|
+
|
|
69
|
+
def required
|
|
70
|
+
@nodes.find { |kwarg| required_kwarg?(kwarg) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def default_value
|
|
74
|
+
@nodes.find { |kwarg| default_value_kwarg?(kwarg) }
|
|
75
|
+
end
|
|
58
76
|
end
|
|
59
77
|
end
|
|
60
78
|
end
|
|
@@ -20,6 +20,11 @@ module RuboCop
|
|
|
20
20
|
(pair (sym :as) (sym $_))
|
|
21
21
|
PATTERN
|
|
22
22
|
|
|
23
|
+
# @!method argument_required(node)
|
|
24
|
+
def_node_matcher :argument_required, <<~PATTERN
|
|
25
|
+
(pair (sym :required) $_)
|
|
26
|
+
PATTERN
|
|
27
|
+
|
|
23
28
|
attr_reader :node
|
|
24
29
|
|
|
25
30
|
def initialize(node)
|
|
@@ -34,6 +39,29 @@ module RuboCop
|
|
|
34
39
|
@as ||= argument_as(kwargs.as)
|
|
35
40
|
end
|
|
36
41
|
|
|
42
|
+
# The keyword this argument is passed as to #resolve and friends. `as:` wins when both
|
|
43
|
+
# are given, matching graphql-ruby's `kwargs[:as] ||= inferred_arg_name`.
|
|
44
|
+
def keyword
|
|
45
|
+
return as if kwargs.as
|
|
46
|
+
return inferred_keyword if kwargs.loads
|
|
47
|
+
|
|
48
|
+
name
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# `required: false` is the only value that lets graphql-ruby omit the keyword:
|
|
52
|
+
# `required: :nullable` still demands the argument be present, though it may be null.
|
|
53
|
+
def optional?
|
|
54
|
+
required_node = kwargs.required
|
|
55
|
+
return false unless required_node
|
|
56
|
+
|
|
57
|
+
argument_required(required_node)&.false_type? || false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# A configured default is always passed, even when the client omits the argument.
|
|
61
|
+
def default_value?
|
|
62
|
+
!kwargs.default_value.nil?
|
|
63
|
+
end
|
|
64
|
+
|
|
37
65
|
def description
|
|
38
66
|
@description ||= argument_description(@node) || kwargs.description || block.description
|
|
39
67
|
end
|
|
@@ -45,6 +73,23 @@ module RuboCop
|
|
|
45
73
|
def block
|
|
46
74
|
@block ||= Argument::Block.new(@node.parent)
|
|
47
75
|
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
# Mirrors graphql-ruby: a `loads:` argument named `foo_id` arrives as `foo:`, and one
|
|
80
|
+
# named `foo_ids` arrives as `foos:`.
|
|
81
|
+
def inferred_keyword
|
|
82
|
+
name_as_string = name.to_s
|
|
83
|
+
|
|
84
|
+
case name_as_string
|
|
85
|
+
when /_id$/
|
|
86
|
+
name_as_string.sub(/_id$/, "").to_sym
|
|
87
|
+
when /_ids$/
|
|
88
|
+
name_as_string.sub(/_ids$/, "").sub(/([^s])$/, "\\1s").to_sym
|
|
89
|
+
else
|
|
90
|
+
name
|
|
91
|
+
end
|
|
92
|
+
end
|
|
48
93
|
end
|
|
49
94
|
end
|
|
50
95
|
end
|
|
@@ -21,6 +21,16 @@ module RuboCop
|
|
|
21
21
|
(pair (sym :resolver) ...)
|
|
22
22
|
PATTERN
|
|
23
23
|
|
|
24
|
+
# @!method mutation_kwarg?(node)
|
|
25
|
+
def_node_matcher :mutation_kwarg?, <<~PATTERN
|
|
26
|
+
(pair (sym :mutation) ...)
|
|
27
|
+
PATTERN
|
|
28
|
+
|
|
29
|
+
# @!method subscription_kwarg?(node)
|
|
30
|
+
def_node_matcher :subscription_kwarg?, <<~PATTERN
|
|
31
|
+
(pair (sym :subscription) ...)
|
|
32
|
+
PATTERN
|
|
33
|
+
|
|
24
34
|
# @!method method_kwarg?(node)
|
|
25
35
|
def_node_matcher :method_kwarg?, <<~PATTERN
|
|
26
36
|
(pair (sym :method) ...)
|
|
@@ -59,6 +69,20 @@ module RuboCop
|
|
|
59
69
|
@nodes.find { |kwarg| resolver_kwarg?(kwarg) }
|
|
60
70
|
end
|
|
61
71
|
|
|
72
|
+
def mutation
|
|
73
|
+
@nodes.find { |kwarg| mutation_kwarg?(kwarg) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def subscription
|
|
77
|
+
@nodes.find { |kwarg| subscription_kwarg?(kwarg) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# graphql-ruby builds the field's resolver class out of any one of these
|
|
81
|
+
# options, and the field then inherits configuration from that class.
|
|
82
|
+
def resolver_class
|
|
83
|
+
resolver || mutation || subscription
|
|
84
|
+
end
|
|
85
|
+
|
|
62
86
|
def method
|
|
63
87
|
@nodes.find { |kwarg| method_kwarg?(kwarg) }
|
|
64
88
|
end
|
data/lib/rubocop-graphql.rb
CHANGED