rubocop-graphql 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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