rubocop-graphql 0.2.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97a32c7f3e6a945161380f4d71afe27ad379af7ed6f4657cea6b73d42dae5213
4
- data.tar.gz: a039ae1c957f91d596f5b834035401ad031f2cf8723701e708d192f29dfb6784
3
+ metadata.gz: a3be1c838260c7be6902b6c06cf411ee768c39dab704b078d0204d8d1fa24fc5
4
+ data.tar.gz: 1061e5a30067f55254d5a89e87ff6a1597ac8df321e6be74b06a937ddd8da1e1
5
5
  SHA512:
6
- metadata.gz: a17ab56af1141ef437dff2e908845c3c9a7d95b86f7e97c329787f43da0aef67be691a1d4179486d4580f40bb4fdbf423354ef619f7135651c0dcbe683a756b2
7
- data.tar.gz: d9fb15417d019a952424942e5d860387da2bb1cd61abbeec1626ca9c7b9c2dec485cbe26b9cba28cec88128a828bb3cd1d776a9f3bc333bfc019d2ef8989b4e6
6
+ metadata.gz: 32e42d5a360bf522df3408bcfbc906e830eb7dcbc46aef53e967567c73f3634908a9cb6e9fb51967e79d1e64be24074067eca0e2870d45137c97b66f2b6354f4
7
+ data.tar.gz: 83a9f4756bec98ff082561a1cc1b81e64335a914bd343ba9353ccb865f38714219619594b8c6844b020fa04b3202412b5533a6fb01e2cbfb59b2a352d6465f57
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:
@@ -35,6 +35,11 @@ GraphQL/FieldDescription:
35
35
  VersionAdded: '0.80'
36
36
  Description: 'Ensures all fields have a description'
37
37
 
38
+ GraphQL/FieldHashKey:
39
+ Enabled: true
40
+ VersionAdded: '0.80'
41
+ Description: 'Checks :hash_key option is used for appropriate fields'
42
+
38
43
  GraphQL/FieldMethod:
39
44
  Enabled: true
40
45
  VersionAdded: '0.80'
@@ -62,3 +67,11 @@ GraphQL/ExtractType:
62
67
  - avg
63
68
  - min
64
69
  - max
70
+
71
+ GraphQL/ObjectDescription:
72
+ Enabled: true
73
+ VersionAdded: '0.80'
74
+ Description: 'Ensures all types have a description'
75
+ Exclude:
76
+ - '**/*_schema.rb'
77
+ - '**/base_*.rb'
@@ -9,13 +9,13 @@ module RuboCop
9
9
  # # good
10
10
  #
11
11
  # class BanUser < BaseMutation
12
- # argument :uuid, ID, required: true
12
+ # argument :uuid, ID, required: true, description: "UUID of the user to ban"
13
13
  # end
14
14
  #
15
15
  # # bad
16
16
  #
17
17
  # class BanUser < BaseMutation
18
- # argument :uuid, ID, required: true, description: "UUID of the user to ban"
18
+ # argument :uuid, ID, required: true
19
19
  # end
20
20
  #
21
21
  class ArgumentDescription < Cop
@@ -12,8 +12,7 @@ module RuboCop
12
12
  # class will invoke the inherited hook instead
13
13
  class << self
14
14
  undef inherited
15
- def inherited(*)
16
- end
15
+ def inherited(*); end
17
16
  end
18
17
 
19
18
  # Special case `Module#<` so that the rspec support rubocop exports
@@ -45,7 +45,8 @@ module RuboCop
45
45
 
46
46
  private
47
47
 
48
- MSG = "Consider moving %<field_names>s to a new type and adding the `%<prefix>s` field instead"
48
+ MSG = "Consider moving %<field_names>s to a new type and " \
49
+ "adding the `%<prefix>s` field instead"
49
50
 
50
51
  def check_fields_prefixes(body)
51
52
  sorted_prefixes = fractured(body).sort_by { |k, _| k.size }.reverse
@@ -105,9 +105,9 @@ module RuboCop
105
105
  def group_field_declarations(corrector, node)
106
106
  field = RuboCop::GraphQL::Field.new(node)
107
107
 
108
- first_field = field.schema_member.body.find { |node|
108
+ first_field = field.schema_member.body.find do |node|
109
109
  field_definition?(node) || field_definition_with_body?(node)
110
- }
110
+ end
111
111
 
112
112
  source_to_insert = "\n" + indent(node) + node.source
113
113
  corrector.insert_after(first_field.loc.expression, source_to_insert)
@@ -124,11 +124,12 @@ module RuboCop
124
124
  method_definition = field.schema_member.find_method_definition(field.resolver_method_name)
125
125
  return unless method_definition
126
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
127
+ field_sibling_index =
128
+ if field_definition_with_body?(field.parent)
129
+ field.parent.sibling_index
130
+ else
131
+ field.sibling_index
132
+ end
132
133
 
133
134
  return if method_definition.sibling_index - field_sibling_index == 1
134
135
 
@@ -146,7 +147,9 @@ module RuboCop
146
147
  method_range = range_by_whole_lines(method_definition.loc.expression)
147
148
  corrector.insert_before(method_range, source_to_insert)
148
149
 
149
- range_to_remove = range_with_surrounding_space(range: field_definition.loc.expression, side: :left)
150
+ range_to_remove = range_with_surrounding_space(
151
+ range: field_definition.loc.expression, side: :left
152
+ )
150
153
  corrector.remove(range_to_remove)
151
154
  end
152
155
 
@@ -0,0 +1,85 @@
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 :hash_key option can be used
8
+ #
9
+ # @example
10
+ # # good
11
+ #
12
+ # class Types::UserType < Types::BaseObject
13
+ # field :phone, String, null: true, hash_key: :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 FieldHashKey < Cop
27
+ include RuboCop::GraphQL::NodePattern
28
+ include RuboCop::Cop::RangeHelp
29
+
30
+ def_node_matcher :hash_key_to_use, <<~PATTERN
31
+ (def
32
+ _
33
+ (args)
34
+ (send
35
+ (send nil? :object) :[]
36
+ (_type $_)
37
+ )
38
+ )
39
+ PATTERN
40
+
41
+ MSG = "Use hash_key: %<hash_key>p"
42
+
43
+ def on_send(node)
44
+ return unless field_definition?(node)
45
+
46
+ field = RuboCop::GraphQL::Field.new(node)
47
+ method_definition = resolver_method_definition_for(field)
48
+
49
+ if (suggested_hash_key_name = hash_key_to_use(method_definition))
50
+ add_offense(node, message: message(suggested_hash_key_name))
51
+ end
52
+ end
53
+
54
+ def autocorrect(node)
55
+ lambda do |corrector|
56
+ field = RuboCop::GraphQL::Field.new(node)
57
+ method_definition = resolver_method_definition_for(field)
58
+ suggested_hash_key_name = hash_key_to_use(method_definition)
59
+
60
+ corrector.insert_after(
61
+ node.loc.expression, ", hash_key: #{suggested_hash_key_name.inspect}"
62
+ )
63
+
64
+ range = range_with_surrounding_space(
65
+ range: method_definition.loc.expression, side: :left
66
+ )
67
+
68
+ corrector.remove(range)
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def message(hash_key)
75
+ format(MSG, hash_key: hash_key)
76
+ end
77
+
78
+ def resolver_method_definition_for(field)
79
+ method_name = field.resolver_method_name
80
+ field.schema_member.find_method_definition(method_name)
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -58,7 +58,9 @@ module RuboCop
58
58
 
59
59
  corrector.insert_after(node.loc.expression, ", method: :#{suggested_method_name}")
60
60
 
61
- range = range_with_surrounding_space(range: method_definition.loc.expression, side: :left)
61
+ range = range_with_surrounding_space(
62
+ range: method_definition.loc.expression, side: :left
63
+ )
62
64
  corrector.remove(range)
63
65
  end
64
66
  end
@@ -0,0 +1,70 @@
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,
7
+ # mutation, subscription, and resolver) has a description.
8
+ #
9
+ # @example
10
+ # # good
11
+ #
12
+ # class Types::UserType < Types::BaseObject
13
+ # description "Represents application user"
14
+ # # ...
15
+ # end
16
+ #
17
+ # # bad
18
+ #
19
+ # class Types::UserType < Types::BaseObject
20
+ # # ...
21
+ # end
22
+ #
23
+ class ObjectDescription < Cop
24
+ include RuboCop::GraphQL::NodePattern
25
+
26
+ MSG = "Missing type description"
27
+
28
+ def_node_matcher :has_i18n_description?, <<~PATTERN
29
+ (send nil? :description (send (const nil? :I18n) :t ...))
30
+ PATTERN
31
+
32
+ def_node_matcher :has_string_description?, <<~PATTERN
33
+ (send nil? :description (:str $_))
34
+ PATTERN
35
+
36
+ def_node_matcher :interface?, <<~PATTERN
37
+ (send nil? :include (const ...))
38
+ PATTERN
39
+
40
+ def on_class(node)
41
+ return if child_nodes(node).find { |child_node| has_description?(child_node) }
42
+
43
+ add_offense(node.identifier)
44
+ end
45
+
46
+ def on_module(node)
47
+ return if child_nodes(node).none? { |child_node| interface?(child_node) }
48
+
49
+ if child_nodes(node).none? { |child_node| has_description?(child_node) }
50
+ add_offense(node.identifier)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def has_description?(node)
57
+ has_i18n_description?(node) || has_string_description?(node)
58
+ end
59
+
60
+ def child_nodes(node)
61
+ if node.body.instance_of? RuboCop::AST::Node
62
+ node.body.child_nodes
63
+ else
64
+ node.child_nodes
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module GraphQL
6
+ # Field should be alphabetically sorted within groups.
7
+ #
8
+ # @example
9
+ # # good
10
+ #
11
+ # class UserType < BaseType
12
+ # field :name, String, null: true
13
+ # field :phone, String, null: true do
14
+ # argument :something, String, required: false
15
+ # end
16
+ # end
17
+ #
18
+ # # good
19
+ #
20
+ # class UserType < BaseType
21
+ # field :phone, String, null: true
22
+ #
23
+ # field :name, String, null: true
24
+ # end
25
+ #
26
+ # # bad
27
+ #
28
+ # class UserType < BaseType
29
+ # field :phone, String, null: true
30
+ # field :name, String, null: true
31
+ # end
32
+ #
33
+ class OrderedFields < Cop
34
+ MSG = "Fields should be sorted in an alphabetical order within their "\
35
+ "section. "\
36
+ "Field `%<current>s` should appear before `%<previous>s`."
37
+
38
+ def investigate(processed_source)
39
+ return if processed_source.blank?
40
+
41
+ field_declarations(processed_source.ast)
42
+ .each_cons(2) do |previous, current|
43
+ next unless consecutive_lines(previous, current)
44
+ next if field_name(current) > field_name(previous)
45
+
46
+ register_offense(previous, current)
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def register_offense(previous, current)
53
+ message = format(
54
+ self.class::MSG,
55
+ previous: field_name(previous),
56
+ current: field_name(current)
57
+ )
58
+ add_offense(current, message: message)
59
+ end
60
+
61
+ def field_name(node)
62
+ if node.block_type?
63
+ field_name(node.send_node)
64
+ else
65
+ node.first_argument.value.to_s
66
+ end
67
+ end
68
+
69
+ def consecutive_lines(previous, current)
70
+ previous.source_range.last_line == current.source_range.first_line - 1
71
+ end
72
+
73
+ def_node_search :field_declarations, <<~PATTERN
74
+ {
75
+ (send nil? :field (:sym _) ...)
76
+ (block
77
+ (send nil? :field (:sym _) ...) ...)
78
+ }
79
+ PATTERN
80
+ end
81
+ end
82
+ end
83
+ end
@@ -9,9 +9,9 @@ module RuboCop
9
9
  # The maximum allowed length is configurable using the Max option.
10
10
  class ResolverMethodLength < Cop
11
11
  include RuboCop::Cop::ConfigurableMax
12
- include RuboCop::Cop::TooManyLines
12
+ include RuboCop::Cop::CodeLength
13
13
 
14
- LABEL = "ResolverMethod"
14
+ MSG = "ResolverMethod has too many lines. [%<total>d/%<max>d]"
15
15
 
16
16
  def_node_matcher :field_definition, <<~PATTERN
17
17
  (send nil? :field (sym $...) ...)
@@ -21,18 +21,31 @@ module RuboCop
21
21
  excluded_methods = cop_config["ExcludedMethods"]
22
22
  return if excluded_methods.include?(String(node.method_name))
23
23
 
24
- check_code_length(node) if field_is_defined?(node)
24
+ if field_is_defined?(node)
25
+ length = code_length(node)
26
+
27
+ return unless length > max_length
28
+
29
+ add_offense(node, message: message(length))
30
+ end
25
31
  end
26
32
  alias on_defs on_def
27
33
 
28
34
  private
29
35
 
36
+ def code_length(node)
37
+ node.source.lines[1..-2].count { |line| !irrelevant_line(line) }
38
+ end
39
+
30
40
  def field_is_defined?(node)
31
- node.parent.children.flat_map { |child| field_definition(child) }.include?(node.method_name)
41
+ node.parent
42
+ .children
43
+ .flat_map { |child| field_definition(child) }
44
+ .include?(node.method_name)
32
45
  end
33
46
 
34
- def cop_label
35
- LABEL
47
+ def message(length)
48
+ format(MSG, total: length, max: max_length)
36
49
  end
37
50
  end
38
51
  end
@@ -8,6 +8,9 @@ require_relative "graphql/extract_input_type"
8
8
  require_relative "graphql/extract_type"
9
9
  require_relative "graphql/field_definitions"
10
10
  require_relative "graphql/field_description"
11
+ require_relative "graphql/field_hash_key"
11
12
  require_relative "graphql/field_method"
12
13
  require_relative "graphql/field_name"
13
14
  require_relative "graphql/resolver_method_length"
15
+ require_relative "graphql/object_description"
16
+ require_relative "graphql/ordered_fields"
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module GraphQL
5
5
  module Ext
6
6
  module SnakeCase
7
- SNAKE_CASE = /^[\da-z_]+[!?=]?$/
7
+ SNAKE_CASE = /^[\da-z_]+[!?=]?$/.freeze
8
8
 
9
9
  refine Symbol do
10
10
  def snake_case?
@@ -69,7 +69,7 @@ module RuboCop
69
69
  end
70
70
 
71
71
  def root_node?(node)
72
- node.parent.nil? || root_with_siblings?(node.parent)
72
+ node.parent.nil? || node.parent.module_type? || root_with_siblings?(node.parent)
73
73
  end
74
74
 
75
75
  def root_with_siblings?(node)
@@ -36,7 +36,7 @@ module RuboCop
36
36
  PATTERN
37
37
 
38
38
  def initialize(field_node)
39
- @nodes = field_kwargs(field_node)
39
+ @nodes = field_kwargs(field_node) || []
40
40
  end
41
41
 
42
42
  def resolver
@@ -56,7 +56,8 @@ module RuboCop
56
56
  end
57
57
 
58
58
  def resolver_method_name
59
- @resolver_method_name ||= @nodes.flat_map { |kwarg| resolver_method_option(kwarg) }.compact.first
59
+ @resolver_method_name ||=
60
+ @nodes.flat_map { |kwarg| resolver_method_option(kwarg) }.compact.first
60
61
  end
61
62
  end
62
63
  end
@@ -6,18 +6,18 @@ 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
11
 
12
12
  def_node_matcher :field_definition_with_body?, <<~PATTERN
13
13
  (block
14
- (send nil? :field ...)
14
+ (send nil? :field (:sym _) ...)
15
15
  ...
16
16
  )
17
17
  PATTERN
18
18
 
19
19
  def_node_matcher :argument?, <<~PATTERN
20
- (send nil? :argument ...)
20
+ (send nil? :argument (:sym _) ...)
21
21
  PATTERN
22
22
 
23
23
  def field?(node)
@@ -1,5 +1,5 @@
1
1
  module RuboCop
2
2
  module GraphQL
3
- VERSION = "0.2.0"
3
+ VERSION = "0.5.0".freeze
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.2.0
4
+ version: 0.5.0
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-07-02 00:00:00.000000000 Z
11
+ date: 2020-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -39,33 +39,33 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: standard
42
+ name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '='
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.2.0
47
+ version: '3.9'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '='
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.2.0
54
+ version: '3.9'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rubocop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 0.71.0
61
+ version: '0.87'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 0.71.0
68
+ version: '0.87'
69
69
  description: A collection of RuboCop cops to improve GraphQL-related code
70
70
  email:
71
71
  - dmitry.a.tsepelev@gmail.com
@@ -84,8 +84,11 @@ files:
84
84
  - lib/rubocop/cop/graphql/extract_type.rb
85
85
  - lib/rubocop/cop/graphql/field_definitions.rb
86
86
  - lib/rubocop/cop/graphql/field_description.rb
87
+ - lib/rubocop/cop/graphql/field_hash_key.rb
87
88
  - lib/rubocop/cop/graphql/field_method.rb
88
89
  - lib/rubocop/cop/graphql/field_name.rb
90
+ - lib/rubocop/cop/graphql/object_description.rb
91
+ - lib/rubocop/cop/graphql/ordered_fields.rb
89
92
  - lib/rubocop/cop/graphql/resolver_method_length.rb
90
93
  - lib/rubocop/cop/graphql_cops.rb
91
94
  - lib/rubocop/graphql.rb
@@ -115,7 +118,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
115
118
  requirements:
116
119
  - - ">="
117
120
  - !ruby/object:Gem::Version
118
- version: '0'
121
+ version: '2.4'
119
122
  required_rubygems_version: !ruby/object:Gem::Requirement
120
123
  requirements:
121
124
  - - ">="