rubocop-graphql 0.1.1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75ede967be105c3771fc0c6c648632bf3c8fc49c1e81bb1f89d8b475c0403555
4
- data.tar.gz: ec1ced3e202cac94d70c24ecf890c63c0c7cca819edd623476bf7e831083bd1b
3
+ metadata.gz: 1c659875c5f0ff36b73bb44becbdbf2b0db912979041ff09f090fa3a265dfe9a
4
+ data.tar.gz: a76e1b6a142e23ec20a0d1936e28dc1e626436691700d2a704c275f1f4d7b134
5
5
  SHA512:
6
- metadata.gz: ccca643eb811b6c25a4b8f2918b7c094a943b8c3a477d9c4b17c8e0bb4e75fa0d11b167c652790619e356716f6bc6c501446334c6f4d65f4ea75ffa4da2dfa3a
7
- data.tar.gz: 62d6ec1ae491c25c1c35b3c8a2d90fbec5a938034bbbc8d4abb7a38c3a7a50f2f359433f845e308f2e5257d9614ff7d3c43a403d56a34c61e575738eee2433ff
6
+ metadata.gz: 1ee99d3d6d3876effb01e6f4f67db2a9f1a700dd991957705f95c6608ffde97709069af1a75417baebaefa16f32c83f6fec157a649d469cd3ab9aa4e99554f7a
7
+ data.tar.gz: 1d2ceb8a083f36bb141addbc43a22b273048dc3abbeacd2891bbb4709d4558ac3f4a524e80fc07bd56609281d734c35f386f3e28c2255868fb9876f0ad95231f
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Rubocop::GraphQL
1
+ # RuboCop::GraphQL
2
2
 
3
3
  [Rubocop](https://github.com/rubocop-hq/rubocop) extension for enforcing [graphql-ruby](https://github.com/rmosolgo/graphql-ruby) best practices.
4
4
 
@@ -24,7 +24,7 @@ gem 'rubocop-graphql', require: false
24
24
 
25
25
  ## Usage
26
26
 
27
- You need to tell RuboCop to load the Rails extension. There are three ways to do this:
27
+ You need to tell RuboCop to load the GraphQL extension. There are three ways to do this:
28
28
 
29
29
  ### RuboCop configuration file
30
30
 
@@ -42,7 +42,7 @@ require:
42
42
  - rubocop-graphql
43
43
  ```
44
44
 
45
- Now you can run `rubocop` and it will automatically load the RuboCop Rails cops together with the standard cops.
45
+ Now you can run `rubocop` and it will automatically load the RuboCop GraphQL cops together with the standard cops.
46
46
 
47
47
  ### Command line
48
48
 
@@ -62,7 +62,7 @@ end
62
62
 
63
63
  All cops are located under [`lib/rubocop/cop/graphql`](lib/rubocop/cop/graphql), and contain examples and documentation.
64
64
 
65
- In your `.rubocop.yml`, you may treat the Rails cops just like any other cop. For example:
65
+ In your `.rubocop.yml`, you may treat the GraphQL cops just like any other cop. For example:
66
66
 
67
67
  ```yaml
68
68
  GraphQL/ResolverMethodLength:
@@ -3,10 +3,20 @@ AllCops:
3
3
  Patterns:
4
4
  - "(?:^|/)graphql/"
5
5
 
6
+ GraphQL/ArgumentDescription:
7
+ Enabled: true
8
+ VersionAdded: '0.80'
9
+ Description: 'Ensures all arguments have a description'
10
+
11
+ GraphQL/ArgumentName:
12
+ Enabled: true
13
+ VersionAdded: '0.80'
14
+ Description: 'This cop checks whether argument names are snake_case'
15
+
6
16
  GraphQL/ResolverMethodLength:
7
17
  Enabled: true
8
18
  VersionAdded: '0.80'
9
- Description: 'Avoid resolver methods longer than 10 lines of code.'
19
+ Description: 'Checks resolver methods are not too long'
10
20
  Max: 10
11
21
  CountComments: false
12
22
  ExcludedMethods: []
@@ -23,4 +33,40 @@ GraphQL/FieldDefinitions:
23
33
  GraphQL/FieldDescription:
24
34
  Enabled: true
25
35
  VersionAdded: '0.80'
26
- Description: 'Missing field description'
36
+ Description: 'Ensures all fields have a description'
37
+
38
+ GraphQL/FieldMethod:
39
+ Enabled: true
40
+ VersionAdded: '0.80'
41
+ Description: 'Checks :method option is used for appropriate fields'
42
+
43
+ GraphQL/FieldName:
44
+ Enabled: true
45
+ VersionAdded: '0.80'
46
+ Description: 'This cop checks whether field names are snake_case'
47
+
48
+ GraphQL/ExtractInputType:
49
+ Enabled: true
50
+ VersionAdded: '0.80'
51
+ Description: 'Suggests using input type instead of many arguments'
52
+ MaxArguments: 2
53
+
54
+ GraphQL/ExtractType:
55
+ Enabled: true
56
+ VersionAdded: '0.80'
57
+ Description: 'Suggests extracting fields with common prefixes to the separate type'
58
+ MaxFields: 2
59
+ Prefixes:
60
+ - is
61
+ - with
62
+ - avg
63
+ - min
64
+ - max
65
+
66
+ GraphQL/ObjectDescription:
67
+ Enabled: true
68
+ VersionAdded: '0.80'
69
+ Description: 'Ensures all types have a description'
70
+ Exclude:
71
+ - '**/*_schema.rb'
72
+ - '**/base_*.rb'
@@ -2,11 +2,20 @@
2
2
 
3
3
  require "rubocop"
4
4
 
5
+ require_relative "rubocop/graphql/ext/snake_case"
6
+
5
7
  require_relative "rubocop/graphql"
6
8
  require_relative "rubocop/graphql/version"
7
9
  require_relative "rubocop/graphql/inject"
8
10
  require_relative "rubocop/graphql/node_pattern"
11
+
12
+ require_relative "rubocop/graphql/argument"
13
+ require_relative "rubocop/graphql/argument/block"
14
+ require_relative "rubocop/graphql/argument/kwargs"
9
15
  require_relative "rubocop/graphql/field"
16
+ require_relative "rubocop/graphql/field/block"
17
+ require_relative "rubocop/graphql/field/kwargs"
18
+ require_relative "rubocop/graphql/schema_member"
10
19
 
11
20
  RuboCop::GraphQL::Inject.defaults!
12
21
 
@@ -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 BanUser < BaseMutation
12
+ # argument :uuid, ID, required: true, description: "UUID of the user to ban"
13
+ # end
14
+ #
15
+ # # bad
16
+ #
17
+ # class BanUser < BaseMutation
18
+ # argument :uuid, ID, required: true
19
+ # end
20
+ #
21
+ class ArgumentDescription < Cop
22
+ include RuboCop::GraphQL::NodePattern
23
+
24
+ MSG = "Missing argument description"
25
+
26
+ def on_send(node)
27
+ return unless argument?(node)
28
+
29
+ argument = RuboCop::GraphQL::Argument.new(node)
30
+
31
+ add_offense(node) unless argument.description
32
+ end
33
+ end
34
+ end
35
+ end
36
+ 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 BanUser < BaseMutation
12
+ # argument :user_id, ID, required: true
13
+ # end
14
+ #
15
+ # # bad
16
+ #
17
+ # class BanUser < BaseMutation
18
+ # argument :userId, ID, required: true
19
+ # end
20
+ #
21
+ class ArgumentName < Cop
22
+ include RuboCop::GraphQL::NodePattern
23
+
24
+ using RuboCop::GraphQL::Ext::SnakeCase
25
+
26
+ MSG = "Use snake_case for argument names"
27
+
28
+ def on_send(node)
29
+ return unless argument?(node)
30
+
31
+ argument = RuboCop::GraphQL::Argument.new(node)
32
+ return if argument.name.snake_case?
33
+
34
+ add_offense(node)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module GraphQL
6
+ class ExtractInputType < Cop
7
+ # This cop checks fields on common prefix groups
8
+ #
9
+ # # @example
10
+ # # good
11
+ #
12
+ # class UpdateUser < BaseMutation
13
+ # argument :uuid, ID, required: true
14
+ # argument :user_attributes, UserAttributesInputType
15
+ # end
16
+ #
17
+ # # bad
18
+ #
19
+ # class UpdateUser < BaseMutation
20
+ # argument :uuid, ID, required: true
21
+ # argument :first_name, String, required: true
22
+ # argument :last_name, String, required: true
23
+ # end
24
+ #
25
+ include RuboCop::GraphQL::NodePattern
26
+
27
+ MSG = "Consider moving arguments to a new input type"
28
+
29
+ def on_class(node)
30
+ schema_member = RuboCop::GraphQL::SchemaMember.new(node)
31
+
32
+ if (body = schema_member.body)
33
+ arguments = body.select { |node| argument?(node) }
34
+
35
+ add_offense(arguments.last) if arguments.count > cop_config["MaxArguments"]
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module GraphQL
6
+ class ExtractType < Cop
7
+ # This cop checks fields on common prefix groups
8
+ #
9
+ # # @example
10
+ # # good
11
+ #
12
+ # class Types::UserType < Types::BaseObject
13
+ # field :registered_at, String, null: false
14
+ # field :contact, Types::ContactType, null: false
15
+ #
16
+ # def contact
17
+ # self
18
+ # end
19
+ # end
20
+ #
21
+ # class Types::ContactType < Types::BaseObject
22
+ # field :phone, String, null: false
23
+ # field :first_name, String, null: false
24
+ # field :last_name, String, null: false
25
+ # end
26
+ #
27
+ # # bad
28
+ #
29
+ # class Types::UserType < Types::BaseObject
30
+ # field :registered_at, String, null: false
31
+ # field :contact_phone, String, null: false
32
+ # field :contact_first_name, String, null: false
33
+ # field :contact_last_name, String, null: false
34
+ # end
35
+ #
36
+ include RuboCop::GraphQL::NodePattern
37
+
38
+ def on_class(node)
39
+ schema_member = RuboCop::GraphQL::SchemaMember.new(node)
40
+
41
+ if (body = schema_member.body)
42
+ check_fields_prefixes(body)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ MSG = "Consider moving %<field_names>s to a new type and adding the `%<prefix>s` field instead"
49
+
50
+ def check_fields_prefixes(body)
51
+ sorted_prefixes = fractured(body).sort_by { |k, _| k.size }.reverse
52
+ already_offenced_fields = []
53
+
54
+ sorted_prefixes.each do |prefix, fields|
55
+ fields -= already_offenced_fields
56
+
57
+ next if fields.count < cop_config["MaxFields"]
58
+
59
+ add_offense(
60
+ fields.last.node,
61
+ message: message(prefix, fields.map(&:name).join(", "))
62
+ )
63
+
64
+ already_offenced_fields += fields
65
+ end
66
+ end
67
+
68
+ def fractured(body)
69
+ body.each_with_object({}) do |node, acc|
70
+ next unless field?(node)
71
+
72
+ field = RuboCop::GraphQL::Field.new(node)
73
+ next unless field.underscore_name.include?("_")
74
+
75
+ *prefixes, _ = field.underscore_name.split("_")
76
+
77
+ prefixes.each_with_object([]) do |prefix, prev_prefix|
78
+ prefix = (prev_prefix + [prefix]).join("_")
79
+ break if ignored_prefix?(prefix)
80
+
81
+ (acc[prefix] ||= []) << field
82
+
83
+ prev_prefix << prefix
84
+ end
85
+ end
86
+ end
87
+
88
+ def message(prefix, field_names)
89
+ format(MSG, field_names: field_names, prefix: prefix)
90
+ end
91
+
92
+ def ignored_prefix?(word)
93
+ cop_config["Prefixes"].include?(word)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -47,6 +47,7 @@ module RuboCop
47
47
  class FieldDefinitions < Cop
48
48
  include ConfigurableEnforcedStyle
49
49
  include RuboCop::GraphQL::NodePattern
50
+ include RuboCop::Cop::RangeHelp
50
51
 
51
52
  def_node_matcher :field_kwargs, <<~PATTERN
52
53
  (send nil? :field
@@ -57,19 +58,31 @@ module RuboCop
57
58
  )
58
59
  PATTERN
59
60
 
60
- def_node_matcher :resolver_method_option, <<~PATTERN
61
- (pair (sym :resolver_method) (sym $...))
62
- PATTERN
63
-
64
61
  def on_send(node)
65
- return unless field_definition?(node)
66
-
67
- case style
68
- when :group_definitions
69
- check_grouped_field_declarations(node.parent)
70
- when :define_resolver_after_definition
71
- field = RuboCop::GraphQL::Field.new(node)
72
- check_resolver_is_defined_after_definition(field)
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
73
86
  end
74
87
  end
75
88
 
@@ -77,10 +90,11 @@ module RuboCop
77
90
 
78
91
  GROUP_DEFS_MSG = "Group all field definitions together."
79
92
 
80
- def check_grouped_field_declarations(node)
81
- fields = node.each_child_node.select { |node| field_definition?(node) }
93
+ def check_grouped_field_declarations(body)
94
+ fields = body.select { |node| field?(node) }
82
95
 
83
96
  first_field = fields.first
97
+
84
98
  fields.each_with_index do |node, idx|
85
99
  next if node.sibling_index == first_field.sibling_index + idx
86
100
 
@@ -88,21 +102,56 @@ module RuboCop
88
102
  end
89
103
  end
90
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
+
91
119
  RESOLVER_AFTER_FIELD_MSG = "Define resolver method after field definition."
92
120
 
93
121
  def check_resolver_is_defined_after_definition(field)
94
- return if field.resolver || field.method || field.hash_key
122
+ return if field.kwargs.resolver || field.kwargs.method || field.kwargs.hash_key
95
123
 
96
- resolver_method = field.kwargs.flat_map { |kwarg| resolver_method_option(kwarg) }.compact.first
97
-
98
- method_name = resolver_method || field.name
99
- method_definition = field.parent.each_child_node.find { |node|
100
- node.def_type? && node.method_name == method_name
101
- }
124
+ method_definition = field.schema_member.find_method_definition(field.resolver_method_name)
125
+ return unless method_definition
102
126
 
103
- if method_definition.sibling_index - field.sibling_index > 1
104
- add_offense(field.node, message: RESOLVER_AFTER_FIELD_MSG)
127
+ field_sibling_index = if field_definition_with_body?(field.parent)
128
+ field.parent.sibling_index
129
+ else
130
+ field.sibling_index
105
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
106
155
  end
107
156
  end
108
157
  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
@@ -2,6 +2,13 @@
2
2
 
3
3
  require_relative "graphql/cop"
4
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"
5
9
  require_relative "graphql/field_definitions"
6
10
  require_relative "graphql/field_description"
11
+ require_relative "graphql/field_method"
12
+ require_relative "graphql/field_name"
7
13
  require_relative "graphql/resolver_method_length"
14
+ require_relative "graphql/object_description"
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module GraphQL
5
+ class Argument
6
+ extend RuboCop::NodePattern::Macros
7
+
8
+ def_node_matcher :argument_description, <<~PATTERN
9
+ (send nil? :argument _ _ (:str $_) ...)
10
+ PATTERN
11
+
12
+ def_node_matcher :argument_name, <<~PATTERN
13
+ (send nil? :argument (:sym $_) ...)
14
+ PATTERN
15
+
16
+ attr_reader :node
17
+
18
+ def initialize(node)
19
+ @node = node
20
+ end
21
+
22
+ def name
23
+ @name ||= argument_name(@node)
24
+ end
25
+
26
+ def description
27
+ @description ||= argument_description(@node) || kwargs.description || block.description
28
+ end
29
+
30
+ def kwargs
31
+ @kwargs ||= Argument::Kwargs.new(@node)
32
+ end
33
+
34
+ def block
35
+ @block ||= Argument::Block.new(@node.parent)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module GraphQL
5
+ class Argument
6
+ class Block
7
+ extend RuboCop::NodePattern::Macros
8
+
9
+ def_node_matcher :argument_block, <<~PATTERN
10
+ (block
11
+ (send nil? :argument ...)
12
+ (args)
13
+ $...
14
+ )
15
+ PATTERN
16
+
17
+ def_node_matcher :description_kwarg?, <<~PATTERN
18
+ (send nil? :description (str ...))
19
+ PATTERN
20
+
21
+ def initialize(argument_node)
22
+ @nodes = argument_block(argument_node) || []
23
+ end
24
+
25
+ def description
26
+ @nodes.find { |kwarg| description_kwarg?(kwarg) }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module GraphQL
5
+ class Argument
6
+ class Kwargs
7
+ extend RuboCop::NodePattern::Macros
8
+
9
+ def_node_matcher :argument_kwargs, <<~PATTERN
10
+ (send nil? :argument
11
+ ...
12
+ (hash
13
+ $...
14
+ )
15
+ )
16
+ PATTERN
17
+
18
+ def_node_matcher :description_kwarg?, <<~PATTERN
19
+ (pair (sym :description) ...)
20
+ PATTERN
21
+
22
+ def initialize(argument_node)
23
+ @nodes = argument_kwargs(argument_node)
24
+ end
25
+
26
+ def description
27
+ @nodes.find { |kwarg| description_kwarg?(kwarg) }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module GraphQL
5
+ module Ext
6
+ module SnakeCase
7
+ SNAKE_CASE = /^[\da-z_]+[!?=]?$/
8
+
9
+ refine Symbol do
10
+ def snake_case?
11
+ match?(SNAKE_CASE)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -8,33 +8,17 @@ module RuboCop
8
8
 
9
9
  def_delegators :@node, :sibling_index, :parent
10
10
 
11
- def_node_matcher :field_kwargs, <<~PATTERN
12
- (send nil? :field
13
- ...
14
- (hash
15
- $...
16
- )
17
- )
18
- PATTERN
19
-
20
11
  def_node_matcher :field_name, <<~PATTERN
21
- (send nil? :field (:sym $...) ...)
22
- PATTERN
23
-
24
- def_node_matcher :field_description, <<~PATTERN
25
- (send nil? :field _ _ (:str $...) ...)
12
+ (send nil? :field (:sym $_) ...)
26
13
  PATTERN
27
14
 
28
- def_node_matcher :resolver_kwarg?, <<~PATTERN
29
- (pair (sym :resolver) ...)
15
+ def_node_matcher :field_with_body_name, <<~PATTERN
16
+ (block
17
+ (send nil? :field (:sym $_) ...)...)
30
18
  PATTERN
31
19
 
32
- def_node_matcher :method_kwarg?, <<~PATTERN
33
- (pair (sym :method) ...)
34
- PATTERN
35
-
36
- def_node_matcher :hash_key_kwarg?, <<~PATTERN
37
- (pair (sym :hash_key) ...)
20
+ def_node_matcher :field_description, <<~PATTERN
21
+ (send nil? :field _ _ (:str $_) ...)
38
22
  PATTERN
39
23
 
40
24
  attr_reader :node
@@ -44,27 +28,52 @@ module RuboCop
44
28
  end
45
29
 
46
30
  def name
47
- @name ||= field_name(@node).first
31
+ @name ||= field_name(@node) || field_with_body_name(@node)
32
+ end
33
+
34
+ def underscore_name
35
+ @underscore_name ||= begin
36
+ word = name.to_s.dup
37
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
38
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
39
+ word.tr!("-", "_")
40
+ word.downcase!
41
+ word
42
+ end
48
43
  end
49
44
 
50
45
  def description
51
- @name ||= field_description(@node)
46
+ @description ||= field_description(@node) || kwargs.description || block.description
47
+ end
48
+
49
+ def resolver_method_name
50
+ kwargs.resolver_method_name || name
52
51
  end
53
52
 
54
53
  def kwargs
55
- @kwargs ||= field_kwargs(@node) || []
54
+ @kwargs ||= Field::Kwargs.new(@node)
55
+ end
56
+
57
+ def block
58
+ @block ||= Field::Block.new(@node.parent)
59
+ end
60
+
61
+ def schema_member
62
+ @schema_member ||= SchemaMember.new(root_node)
56
63
  end
57
64
 
58
- def resolver
59
- kwargs.find { |kwarg| resolver_kwarg?(kwarg) }
65
+ private
66
+
67
+ def root_node
68
+ @node.ancestors.find { |parent| root_node?(parent) }
60
69
  end
61
70
 
62
- def method
63
- kwargs.find { |kwarg| method_kwarg?(kwarg) }
71
+ def root_node?(node)
72
+ node.parent.nil? || node.parent.module_type? || root_with_siblings?(node.parent)
64
73
  end
65
74
 
66
- def hash_key
67
- kwargs.find { |kwarg| hash_key_kwarg?(kwarg) }
75
+ def root_with_siblings?(node)
76
+ node.begin_type? && node.parent.nil?
68
77
  end
69
78
  end
70
79
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module GraphQL
5
+ class Field
6
+ class Block
7
+ extend RuboCop::NodePattern::Macros
8
+
9
+ def_node_matcher :field_block, <<~PATTERN
10
+ (block
11
+ (send nil? :field ...)
12
+ (args)
13
+ $...
14
+ )
15
+ PATTERN
16
+
17
+ def_node_matcher :description_kwarg?, <<~PATTERN
18
+ (send nil? :description (str ...))
19
+ PATTERN
20
+
21
+ def initialize(field_node)
22
+ @nodes = field_block(field_node) || []
23
+ end
24
+
25
+ def description
26
+ @nodes.find { |kwarg| description_kwarg?(kwarg) }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module GraphQL
5
+ class Field
6
+ class Kwargs
7
+ extend RuboCop::NodePattern::Macros
8
+
9
+ def_node_matcher :field_kwargs, <<~PATTERN
10
+ (send nil? :field
11
+ ...
12
+ (hash
13
+ $...
14
+ )
15
+ )
16
+ PATTERN
17
+
18
+ def_node_matcher :resolver_kwarg?, <<~PATTERN
19
+ (pair (sym :resolver) ...)
20
+ PATTERN
21
+
22
+ def_node_matcher :method_kwarg?, <<~PATTERN
23
+ (pair (sym :method) ...)
24
+ PATTERN
25
+
26
+ def_node_matcher :hash_key_kwarg?, <<~PATTERN
27
+ (pair (sym :hash_key) ...)
28
+ PATTERN
29
+
30
+ def_node_matcher :description_kwarg?, <<~PATTERN
31
+ (pair (sym :description) ...)
32
+ PATTERN
33
+
34
+ def_node_matcher :resolver_method_option, <<~PATTERN
35
+ (pair (sym :resolver_method) (sym $...))
36
+ PATTERN
37
+
38
+ def initialize(field_node)
39
+ @nodes = field_kwargs(field_node) || []
40
+ end
41
+
42
+ def resolver
43
+ @nodes.find { |kwarg| resolver_kwarg?(kwarg) }
44
+ end
45
+
46
+ def method
47
+ @nodes.find { |kwarg| method_kwarg?(kwarg) }
48
+ end
49
+
50
+ def hash_key
51
+ @nodes.find { |kwarg| hash_key_kwarg?(kwarg) }
52
+ end
53
+
54
+ def description
55
+ @nodes.find { |kwarg| description_kwarg?(kwarg) }
56
+ end
57
+
58
+ def resolver_method_name
59
+ @resolver_method_name ||= @nodes.flat_map { |kwarg| resolver_method_option(kwarg) }.compact.first
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -6,8 +6,23 @@ module RuboCop
6
6
  extend RuboCop::NodePattern::Macros
7
7
 
8
8
  def_node_matcher :field_definition?, <<~PATTERN
9
- (send nil? :field ...)
9
+ (send nil? :field (:sym _) ...)
10
10
  PATTERN
11
+
12
+ def_node_matcher :field_definition_with_body?, <<~PATTERN
13
+ (block
14
+ (send nil? :field (:sym _) ...)
15
+ ...
16
+ )
17
+ PATTERN
18
+
19
+ def_node_matcher :argument?, <<~PATTERN
20
+ (send nil? :argument (:sym _) ...)
21
+ PATTERN
22
+
23
+ def field?(node)
24
+ field_definition?(node) || field_definition_with_body?(node)
25
+ end
11
26
  end
12
27
  end
13
28
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module GraphQL
5
+ class SchemaMember
6
+ extend RuboCop::NodePattern::Macros
7
+
8
+ def_node_matcher :class_contents, <<~PATTERN
9
+ (class _ _ $_)
10
+ PATTERN
11
+
12
+ attr_reader :node
13
+
14
+ def initialize(node)
15
+ @node = node
16
+ end
17
+
18
+ def find_method_definition(method_name)
19
+ body.find { |node| node.def_type? && node.method_name == method_name }
20
+ end
21
+
22
+ def body
23
+ contents = class_contents(@node)
24
+
25
+ if contents.nil?
26
+ []
27
+ elsif contents.begin_type?
28
+ contents.child_nodes
29
+ else
30
+ [contents]
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,5 +1,5 @@
1
1
  module RuboCop
2
2
  module GraphQL
3
- VERSION = "0.1.1"
3
+ VERSION = "0.3.1"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Tsepelev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-25 00:00:00.000000000 Z
11
+ date: 2020-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -77,15 +77,29 @@ files:
77
77
  - README.md
78
78
  - config/default.yml
79
79
  - lib/rubocop-graphql.rb
80
+ - lib/rubocop/cop/graphql/argument_description.rb
81
+ - lib/rubocop/cop/graphql/argument_name.rb
80
82
  - lib/rubocop/cop/graphql/cop.rb
83
+ - lib/rubocop/cop/graphql/extract_input_type.rb
84
+ - lib/rubocop/cop/graphql/extract_type.rb
81
85
  - lib/rubocop/cop/graphql/field_definitions.rb
82
86
  - lib/rubocop/cop/graphql/field_description.rb
87
+ - lib/rubocop/cop/graphql/field_method.rb
88
+ - lib/rubocop/cop/graphql/field_name.rb
89
+ - lib/rubocop/cop/graphql/object_description.rb
83
90
  - lib/rubocop/cop/graphql/resolver_method_length.rb
84
91
  - lib/rubocop/cop/graphql_cops.rb
85
92
  - lib/rubocop/graphql.rb
93
+ - lib/rubocop/graphql/argument.rb
94
+ - lib/rubocop/graphql/argument/block.rb
95
+ - lib/rubocop/graphql/argument/kwargs.rb
96
+ - lib/rubocop/graphql/ext/snake_case.rb
86
97
  - lib/rubocop/graphql/field.rb
98
+ - lib/rubocop/graphql/field/block.rb
99
+ - lib/rubocop/graphql/field/kwargs.rb
87
100
  - lib/rubocop/graphql/inject.rb
88
101
  - lib/rubocop/graphql/node_pattern.rb
102
+ - lib/rubocop/graphql/schema_member.rb
89
103
  - lib/rubocop/graphql/version.rb
90
104
  homepage: https://github.com/DmitryTsepelev/rubocop-graphql
91
105
  licenses: