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.
@@ -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