rubocop-graphql 0.3.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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +78 -0
- data/config/default.yml +72 -0
- data/lib/rubocop-graphql.rb +22 -0
- data/lib/rubocop/cop/graphql/argument_description.rb +36 -0
- data/lib/rubocop/cop/graphql/argument_name.rb +39 -0
- data/lib/rubocop/cop/graphql/cop.rb +92 -0
- data/lib/rubocop/cop/graphql/extract_input_type.rb +41 -0
- data/lib/rubocop/cop/graphql/extract_type.rb +98 -0
- data/lib/rubocop/cop/graphql/field_definitions.rb +159 -0
- data/lib/rubocop/cop/graphql/field_description.rb +36 -0
- data/lib/rubocop/cop/graphql/field_method.rb +79 -0
- data/lib/rubocop/cop/graphql/field_name.rb +39 -0
- data/lib/rubocop/cop/graphql/object_description.rb +67 -0
- data/lib/rubocop/cop/graphql/resolver_method_length.rb +40 -0
- data/lib/rubocop/cop/graphql_cops.rb +14 -0
- data/lib/rubocop/graphql.rb +12 -0
- data/lib/rubocop/graphql/argument.rb +39 -0
- data/lib/rubocop/graphql/argument/block.rb +31 -0
- data/lib/rubocop/graphql/argument/kwargs.rb +32 -0
- data/lib/rubocop/graphql/ext/snake_case.rb +17 -0
- data/lib/rubocop/graphql/field.rb +80 -0
- data/lib/rubocop/graphql/field/block.rb +31 -0
- data/lib/rubocop/graphql/field/kwargs.rb +64 -0
- data/lib/rubocop/graphql/inject.rb +18 -0
- data/lib/rubocop/graphql/node_pattern.rb +28 -0
- data/lib/rubocop/graphql/schema_member.rb +35 -0
- data/lib/rubocop/graphql/version.rb +5 -0
- metadata +130 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module GraphQL
|
6
|
+
# This cop checks consistency of field definitions
|
7
|
+
#
|
8
|
+
# EnforcedStyle supports two modes:
|
9
|
+
#
|
10
|
+
# `group_definitions` : all field definitions should be grouped together.
|
11
|
+
#
|
12
|
+
# `define_resolver_after_definition` : if resolver method exists it should
|
13
|
+
# be defined right after the field definition.
|
14
|
+
#
|
15
|
+
# @example EnforcedStyle: group_definitions (default)
|
16
|
+
# # good
|
17
|
+
#
|
18
|
+
# class UserType < BaseType
|
19
|
+
# field :first_name, String, null: true
|
20
|
+
# field :last_name, String, null: true
|
21
|
+
#
|
22
|
+
# def first_name
|
23
|
+
# object.contact_data.first_name
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# def last_name
|
27
|
+
# object.contact_data.last_name
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# @example EnforcedStyle: define_resolver_after_definition
|
32
|
+
# # good
|
33
|
+
#
|
34
|
+
# class UserType < BaseType
|
35
|
+
# field :first_name, String, null: true
|
36
|
+
#
|
37
|
+
# def first_name
|
38
|
+
# object.contact_data.first_name
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# field :last_name, String, null: true
|
42
|
+
#
|
43
|
+
# def last_name
|
44
|
+
# object.contact_data.last_name
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
class FieldDefinitions < Cop
|
48
|
+
include ConfigurableEnforcedStyle
|
49
|
+
include RuboCop::GraphQL::NodePattern
|
50
|
+
include RuboCop::Cop::RangeHelp
|
51
|
+
|
52
|
+
def_node_matcher :field_kwargs, <<~PATTERN
|
53
|
+
(send nil? :field
|
54
|
+
...
|
55
|
+
(hash
|
56
|
+
$...
|
57
|
+
)
|
58
|
+
)
|
59
|
+
PATTERN
|
60
|
+
|
61
|
+
def on_send(node)
|
62
|
+
return if !field_definition?(node) || style != :define_resolver_after_definition
|
63
|
+
|
64
|
+
field = RuboCop::GraphQL::Field.new(node)
|
65
|
+
check_resolver_is_defined_after_definition(field)
|
66
|
+
end
|
67
|
+
|
68
|
+
def on_class(node)
|
69
|
+
return if style != :group_definitions
|
70
|
+
|
71
|
+
schema_member = RuboCop::GraphQL::SchemaMember.new(node)
|
72
|
+
|
73
|
+
if (body = schema_member.body)
|
74
|
+
check_grouped_field_declarations(body)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def autocorrect(node)
|
79
|
+
lambda do |corrector|
|
80
|
+
case style
|
81
|
+
when :define_resolver_after_definition
|
82
|
+
place_resolver_after_definitions(corrector, node)
|
83
|
+
when :group_definitions
|
84
|
+
group_field_declarations(corrector, node)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
GROUP_DEFS_MSG = "Group all field definitions together."
|
92
|
+
|
93
|
+
def check_grouped_field_declarations(body)
|
94
|
+
fields = body.select { |node| field?(node) }
|
95
|
+
|
96
|
+
first_field = fields.first
|
97
|
+
|
98
|
+
fields.each_with_index do |node, idx|
|
99
|
+
next if node.sibling_index == first_field.sibling_index + idx
|
100
|
+
|
101
|
+
add_offense(node, message: GROUP_DEFS_MSG)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def group_field_declarations(corrector, node)
|
106
|
+
field = RuboCop::GraphQL::Field.new(node)
|
107
|
+
|
108
|
+
first_field = field.schema_member.body.find { |node|
|
109
|
+
field_definition?(node) || field_definition_with_body?(node)
|
110
|
+
}
|
111
|
+
|
112
|
+
source_to_insert = "\n" + indent(node) + node.source
|
113
|
+
corrector.insert_after(first_field.loc.expression, source_to_insert)
|
114
|
+
|
115
|
+
range = range_with_surrounding_space(range: node.loc.expression, side: :left)
|
116
|
+
corrector.remove(range)
|
117
|
+
end
|
118
|
+
|
119
|
+
RESOLVER_AFTER_FIELD_MSG = "Define resolver method after field definition."
|
120
|
+
|
121
|
+
def check_resolver_is_defined_after_definition(field)
|
122
|
+
return if field.kwargs.resolver || field.kwargs.method || field.kwargs.hash_key
|
123
|
+
|
124
|
+
method_definition = field.schema_member.find_method_definition(field.resolver_method_name)
|
125
|
+
return unless method_definition
|
126
|
+
|
127
|
+
field_sibling_index = if field_definition_with_body?(field.parent)
|
128
|
+
field.parent.sibling_index
|
129
|
+
else
|
130
|
+
field.sibling_index
|
131
|
+
end
|
132
|
+
|
133
|
+
return if method_definition.sibling_index - field_sibling_index == 1
|
134
|
+
|
135
|
+
add_offense(field.node, message: RESOLVER_AFTER_FIELD_MSG)
|
136
|
+
end
|
137
|
+
|
138
|
+
def place_resolver_after_definitions(corrector, node)
|
139
|
+
field = RuboCop::GraphQL::Field.new(node)
|
140
|
+
|
141
|
+
method_definition = field.schema_member.find_method_definition(field.resolver_method_name)
|
142
|
+
|
143
|
+
field_definition = field_definition_with_body?(node.parent) ? node.parent : node
|
144
|
+
|
145
|
+
source_to_insert = indent(method_definition) + field_definition.source + "\n\n"
|
146
|
+
method_range = range_by_whole_lines(method_definition.loc.expression)
|
147
|
+
corrector.insert_before(method_range, source_to_insert)
|
148
|
+
|
149
|
+
range_to_remove = range_with_surrounding_space(range: field_definition.loc.expression, side: :left)
|
150
|
+
corrector.remove(range_to_remove)
|
151
|
+
end
|
152
|
+
|
153
|
+
def indent(node)
|
154
|
+
" " * node.location.column
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module GraphQL
|
6
|
+
# This cop checks if each field has a description.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # good
|
10
|
+
#
|
11
|
+
# class UserType < BaseType
|
12
|
+
# field :name, String, "Name of the user", null: true
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# # bad
|
16
|
+
#
|
17
|
+
# class UserType < BaseType
|
18
|
+
# field :name, String, null: true
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
class FieldDescription < Cop
|
22
|
+
include RuboCop::GraphQL::NodePattern
|
23
|
+
|
24
|
+
MSG = "Missing field description"
|
25
|
+
|
26
|
+
def on_send(node)
|
27
|
+
return unless field_definition?(node)
|
28
|
+
|
29
|
+
field = RuboCop::GraphQL::Field.new(node)
|
30
|
+
|
31
|
+
add_offense(node) unless field.description
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module GraphQL
|
6
|
+
# This cop prevents defining unnecessary resolver methods in cases
|
7
|
+
# when :method option can be used
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # good
|
11
|
+
#
|
12
|
+
# class Types::UserType < Types::BaseObject
|
13
|
+
# field :phone, String, null: true, method: :home_phone
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # bad
|
17
|
+
#
|
18
|
+
# class Types::UserType < Types::BaseObject
|
19
|
+
# field :phone, String, null: true
|
20
|
+
#
|
21
|
+
# def phone
|
22
|
+
# object.home_phone
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
class FieldMethod < Cop
|
27
|
+
include RuboCop::GraphQL::NodePattern
|
28
|
+
include RuboCop::Cop::RangeHelp
|
29
|
+
|
30
|
+
def_node_matcher :method_to_use, <<~PATTERN
|
31
|
+
(def
|
32
|
+
_
|
33
|
+
(args)
|
34
|
+
(send
|
35
|
+
(send nil? :object) $_
|
36
|
+
)
|
37
|
+
)
|
38
|
+
PATTERN
|
39
|
+
|
40
|
+
MSG = "Use method: :%<method_name>s"
|
41
|
+
|
42
|
+
def on_send(node)
|
43
|
+
return unless field_definition?(node)
|
44
|
+
|
45
|
+
field = RuboCop::GraphQL::Field.new(node)
|
46
|
+
method_definition = suggest_method_name_for(field)
|
47
|
+
|
48
|
+
if (suggested_method_name = method_to_use(method_definition))
|
49
|
+
add_offense(node, message: message(suggested_method_name))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def autocorrect(node)
|
54
|
+
lambda do |corrector|
|
55
|
+
field = RuboCop::GraphQL::Field.new(node)
|
56
|
+
method_definition = suggest_method_name_for(field)
|
57
|
+
suggested_method_name = method_to_use(method_definition)
|
58
|
+
|
59
|
+
corrector.insert_after(node.loc.expression, ", method: :#{suggested_method_name}")
|
60
|
+
|
61
|
+
range = range_with_surrounding_space(range: method_definition.loc.expression, side: :left)
|
62
|
+
corrector.remove(range)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def message(method_name)
|
69
|
+
format(MSG, method_name: method_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def suggest_method_name_for(field)
|
73
|
+
method_name = field.resolver_method_name
|
74
|
+
field.schema_member.find_method_definition(method_name)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module GraphQL
|
6
|
+
# This cop checks whether field names are snake_case.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # good
|
10
|
+
#
|
11
|
+
# class UserType < BaseType
|
12
|
+
# field :first_name, String, null: true
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# # bad
|
16
|
+
#
|
17
|
+
# class UserType < BaseType
|
18
|
+
# field :firstName, String, null: true
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
class FieldName < Cop
|
22
|
+
include RuboCop::GraphQL::NodePattern
|
23
|
+
|
24
|
+
using RuboCop::GraphQL::Ext::SnakeCase
|
25
|
+
|
26
|
+
MSG = "Use snake_case for field names"
|
27
|
+
|
28
|
+
def on_send(node)
|
29
|
+
return unless field_definition?(node)
|
30
|
+
|
31
|
+
field = RuboCop::GraphQL::Field.new(node)
|
32
|
+
return if field.name.snake_case?
|
33
|
+
|
34
|
+
add_offense(node)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module GraphQL
|
6
|
+
# This cop checks if a type (object, input, interface, scalar, union, mutation, subscription, and resolver) has a description.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # good
|
10
|
+
#
|
11
|
+
# class Types::UserType < Types::BaseObject
|
12
|
+
# description "Represents application user"
|
13
|
+
# # ...
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # bad
|
17
|
+
#
|
18
|
+
# class Types::UserType < Types::BaseObject
|
19
|
+
# # ...
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
class ObjectDescription < Cop
|
23
|
+
include RuboCop::GraphQL::NodePattern
|
24
|
+
|
25
|
+
MSG = "Missing type description"
|
26
|
+
|
27
|
+
def_node_matcher :has_i18n_description?, <<~PATTERN
|
28
|
+
(send nil? :description (send (const nil? :I18n) :t ...))
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
def_node_matcher :has_string_description?, <<~PATTERN
|
32
|
+
(send nil? :description (:str $_))
|
33
|
+
PATTERN
|
34
|
+
|
35
|
+
def_node_matcher :interface?, <<~PATTERN
|
36
|
+
(send nil? :include (const ...))
|
37
|
+
PATTERN
|
38
|
+
|
39
|
+
def on_class(node)
|
40
|
+
return if child_nodes(node).find { |child_node| has_description?(child_node) }
|
41
|
+
|
42
|
+
add_offense(node.identifier)
|
43
|
+
end
|
44
|
+
|
45
|
+
def on_module(node)
|
46
|
+
return if child_nodes(node).none? { |child_node| interface?(child_node) }
|
47
|
+
|
48
|
+
add_offense(node.identifier) if child_nodes(node).none? { |child_node| has_description?(child_node) }
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def has_description?(node)
|
54
|
+
has_i18n_description?(node) || has_string_description?(node)
|
55
|
+
end
|
56
|
+
|
57
|
+
def child_nodes(node)
|
58
|
+
if node.body.instance_of? RuboCop::AST::Node
|
59
|
+
node.body.child_nodes
|
60
|
+
else
|
61
|
+
node.child_nodes
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module GraphQL
|
6
|
+
# This cop checks if the length of a resolver method exceeds some maximum value.
|
7
|
+
# Comment lines can optionally be ignored.
|
8
|
+
#
|
9
|
+
# The maximum allowed length is configurable using the Max option.
|
10
|
+
class ResolverMethodLength < Cop
|
11
|
+
include RuboCop::Cop::ConfigurableMax
|
12
|
+
include RuboCop::Cop::TooManyLines
|
13
|
+
|
14
|
+
LABEL = "ResolverMethod"
|
15
|
+
|
16
|
+
def_node_matcher :field_definition, <<~PATTERN
|
17
|
+
(send nil? :field (sym $...) ...)
|
18
|
+
PATTERN
|
19
|
+
|
20
|
+
def on_def(node)
|
21
|
+
excluded_methods = cop_config["ExcludedMethods"]
|
22
|
+
return if excluded_methods.include?(String(node.method_name))
|
23
|
+
|
24
|
+
check_code_length(node) if field_is_defined?(node)
|
25
|
+
end
|
26
|
+
alias on_defs on_def
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def field_is_defined?(node)
|
31
|
+
node.parent.children.flat_map { |child| field_definition(child) }.include?(node.method_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def cop_label
|
35
|
+
LABEL
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "graphql/cop"
|
4
|
+
|
5
|
+
require_relative "graphql/argument_description"
|
6
|
+
require_relative "graphql/argument_name"
|
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_method"
|
12
|
+
require_relative "graphql/field_name"
|
13
|
+
require_relative "graphql/resolver_method_length"
|
14
|
+
require_relative "graphql/object_description"
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
# RuboCop GraphQL project namespace
|
5
|
+
module GraphQL
|
6
|
+
PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
|
7
|
+
CONFIG_DEFAULT = PROJECT_ROOT.join("config", "default.yml").freeze
|
8
|
+
CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze
|
9
|
+
|
10
|
+
private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
|
11
|
+
end
|
12
|
+
end
|