rubocop-graphql 0.2.0 → 0.5.0

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: 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
  - - ">="