rubocop-graphql 0.19.0 → 1.5.4

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +86 -41
  3. data/lib/rubocop/cop/graphql/argument_description.rb +1 -0
  4. data/lib/rubocop/cop/graphql/argument_name.rb +1 -0
  5. data/lib/rubocop/cop/graphql/field_definitions.rb +21 -5
  6. data/lib/rubocop/cop/graphql/field_description.rb +1 -0
  7. data/lib/rubocop/cop/graphql/field_hash_key.rb +3 -2
  8. data/lib/rubocop/cop/graphql/field_method.rb +8 -2
  9. data/lib/rubocop/cop/graphql/field_name.rb +1 -0
  10. data/lib/rubocop/cop/graphql/field_uniqueness.rb +8 -1
  11. data/lib/rubocop/cop/graphql/graphql_name.rb +70 -0
  12. data/lib/rubocop/cop/graphql/legacy_dsl.rb +1 -0
  13. data/lib/rubocop/cop/graphql/max_complexity_schema.rb +31 -0
  14. data/lib/rubocop/cop/graphql/max_depth_schema.rb +31 -0
  15. data/lib/rubocop/cop/graphql/multiple_field_definitions.rb +2 -0
  16. data/lib/rubocop/cop/graphql/not_authorized_node_type.rb +134 -0
  17. data/lib/rubocop/cop/graphql/ordered_arguments.rb +15 -21
  18. data/lib/rubocop/cop/graphql/ordered_fields.rb +20 -15
  19. data/lib/rubocop/cop/graphql/prepare_method.rb +95 -0
  20. data/lib/rubocop/cop/graphql/resolver_method_length.rb +4 -7
  21. data/lib/rubocop/cop/graphql/unnecessary_argument_camelize.rb +11 -4
  22. data/lib/rubocop/cop/graphql/unnecessary_field_alias.rb +33 -9
  23. data/lib/rubocop/cop/graphql/unnecessary_field_camelize.rb +1 -0
  24. data/lib/rubocop/cop/graphql/unused_argument.rb +4 -5
  25. data/lib/rubocop/cop/graphql_cops.rb +5 -0
  26. data/lib/rubocop/graphql/argument/block.rb +1 -1
  27. data/lib/rubocop/graphql/compare_order.rb +51 -0
  28. data/lib/rubocop/graphql/ext/snake_case.rb +1 -1
  29. data/lib/rubocop/graphql/field.rb +5 -2
  30. data/lib/rubocop/graphql/heredoc.rb +12 -5
  31. data/lib/rubocop/graphql/schema_member.rb +1 -1
  32. data/lib/rubocop/graphql/sorbet.rb +2 -10
  33. data/lib/rubocop/graphql/version.rb +1 -1
  34. data/lib/rubocop-graphql.rb +1 -0
  35. metadata +11 -5
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module GraphQL
6
+ # Detects types that implement Node interface and not have `.authorized?` check.
7
+ # Such types can be fetched by ID and therefore should have type level check to
8
+ # avoid accidental information exposure.
9
+ #
10
+ # If `.authorized?` is defined in a parent class, you can add parent to the "SafeBaseClasses"
11
+ # to avoid offenses in children.
12
+ #
13
+ # This cop also checks the `can_can_action` or `pundit_role` methods that
14
+ # can be used as part of the Ruby GraphQL Pro.
15
+ #
16
+ # @example
17
+ # # good
18
+ #
19
+ # class UserType < BaseType
20
+ # implements GraphQL::Types::Relay::Node
21
+ #
22
+ # field :uuid, ID, null: false
23
+ #
24
+ # def self.authorized?(object, context)
25
+ # super && object.owner == context[:viewer]
26
+ # end
27
+ # end
28
+ #
29
+ # # good
30
+ #
31
+ # class UserType < BaseType
32
+ # implements GraphQL::Types::Relay::Node
33
+ #
34
+ # field :uuid, ID, null: false
35
+ #
36
+ # class << self
37
+ # def authorized?(object, context)
38
+ # super && object.owner == context[:viewer]
39
+ # end
40
+ # end
41
+ # end
42
+ #
43
+ # # good
44
+ #
45
+ # class UserType < BaseType
46
+ # implements GraphQL::Types::Relay::Node
47
+ #
48
+ # pundit_role :staff
49
+ #
50
+ # field :uuid, ID, null: false
51
+ # end
52
+ #
53
+ # # good
54
+ #
55
+ # class UserType < BaseType
56
+ # implements GraphQL::Types::Relay::Node
57
+ #
58
+ # can_can_action :staff
59
+ #
60
+ # field :uuid, ID, null: false
61
+ # end
62
+ #
63
+ # # bad
64
+ #
65
+ # class UserType < BaseType
66
+ # implements GraphQL::Types::Relay::Node
67
+ #
68
+ # field :uuid, ID, null: false
69
+ # end
70
+ #
71
+ class NotAuthorizedNodeType < Base
72
+ MSG = ".authorized? should be defined for types implementing Node interface."
73
+
74
+ # @!method implements_node_type?(node)
75
+ def_node_matcher :implements_node_type?, <<~PATTERN
76
+ `(send nil? :implements
77
+ (const
78
+ (const
79
+ (const
80
+ (const nil? :GraphQL) :Types) :Relay) :Node))
81
+ PATTERN
82
+
83
+ # @!method has_can_can_action?(node)
84
+ def_node_matcher :has_can_can_action?, <<~PATTERN
85
+ `(send nil? :can_can_action {nil_type? sym_type?})
86
+ PATTERN
87
+
88
+ # @!method has_pundit_role?(node)
89
+ def_node_matcher :has_pundit_role?, <<~PATTERN
90
+ `(send nil? :pundit_role {nil_type? sym_type?})
91
+ PATTERN
92
+
93
+ # @!method has_authorized_method?(node)
94
+ def_node_matcher :has_authorized_method?, <<~PATTERN
95
+ {`(:defs (:self) :authorized? ...) | `(:sclass (:self) `(:def :authorized? ...))}
96
+ PATTERN
97
+
98
+ def on_module(node)
99
+ @parent_modules ||= []
100
+ @parent_modules << node.child_nodes[0].const_name
101
+ end
102
+
103
+ def on_class(node)
104
+ @parent_modules ||= []
105
+ return if possible_parent_classes(node).any? { |klass| ignored_class?(klass) }
106
+
107
+ @parent_modules << node.child_nodes[0].const_name
108
+
109
+ add_offense(node) if implements_node_type?(node) && !implements_authorization?(node)
110
+ end
111
+
112
+ private
113
+
114
+ def implements_authorization?(node)
115
+ has_authorized_method?(node) || has_can_can_action?(node) || has_pundit_role?(node)
116
+ end
117
+
118
+ def possible_parent_classes(node)
119
+ klass = node.child_nodes[1].const_name
120
+
121
+ return [] if klass.nil?
122
+ return [klass] if node.child_nodes[1].absolute?
123
+
124
+ parent_module = "#{@parent_modules.join('::')}::"
125
+ [klass, parent_module + klass]
126
+ end
127
+
128
+ def ignored_class?(klass)
129
+ cop_config["SafeBaseClasses"].include?(klass)
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -52,26 +52,25 @@ module RuboCop
52
52
  extend AutoCorrector
53
53
 
54
54
  include RuboCop::GraphQL::SwapRange
55
+ include RuboCop::GraphQL::CompareOrder
55
56
 
56
57
  MSG = "Arguments should be sorted in an alphabetical order within their section. " \
57
58
  "Field `%<current>s` should appear before `%<previous>s`."
58
59
 
59
60
  def on_class(node)
60
- declarations_with_blocks = argument_declarations_with_blocks(node)
61
- declarations_without_blocks = argument_declarations_without_blocks(node)
62
-
63
- argument_declarations = declarations_without_blocks.map do |node|
64
- arg_name = argument_name(node)
65
- same_arg_with_block_declaration = declarations_with_blocks.find do |dec|
66
- argument_name(dec) == arg_name
61
+ # Do a single pass over descendants to get argument declarations
62
+ # with and without a block.
63
+ argument_declarations = argument_declaration(node).map do |declaration|
64
+ if argument_declaration_with_block?(declaration)
65
+ declaration.parent
66
+ else
67
+ declaration
67
68
  end
68
-
69
- same_arg_with_block_declaration || node
70
69
  end
71
70
 
72
71
  argument_declarations.each_cons(2) do |previous, current|
73
72
  next unless consecutive_lines(previous, current)
74
- next if argument_name(current) >= argument_name(previous)
73
+ next if correct_order?(argument_name(previous), argument_name(current))
75
74
 
76
75
  register_offense(previous, current)
77
76
  end
@@ -79,6 +78,10 @@ module RuboCop
79
78
 
80
79
  private
81
80
 
81
+ def argument_declaration_with_block?(node)
82
+ node.parent&.block_type? && node.parent.send_node == node
83
+ end
84
+
82
85
  def register_offense(previous, current)
83
86
  message = format(
84
87
  self.class::MSG,
@@ -101,19 +104,10 @@ module RuboCop
101
104
  previous.source_range.last_line == current.source_range.first_line - 1
102
105
  end
103
106
 
104
- # @!method argument_declarations_without_blocks(node)
105
- def_node_search :argument_declarations_without_blocks, <<~PATTERN
107
+ # @!method argument_declaration(node)
108
+ def_node_search :argument_declaration, <<~PATTERN
106
109
  (send nil? :argument (:sym _) ...)
107
110
  PATTERN
108
-
109
- # @!method argument_declarations_with_blocks(node)
110
- def_node_search :argument_declarations_with_blocks, <<~PATTERN
111
- (block
112
- (send nil? :argument
113
- (:sym _)
114
- ...)
115
- ...)
116
- PATTERN
117
111
  end
118
112
  end
119
113
  end
@@ -34,15 +34,25 @@ module RuboCop
34
34
  extend AutoCorrector
35
35
 
36
36
  include RuboCop::GraphQL::SwapRange
37
+ include RuboCop::GraphQL::CompareOrder
37
38
 
38
39
  MSG = "Fields should be sorted in an alphabetical order within their "\
39
40
  "section. "\
40
41
  "Field `%<current>s` should appear before `%<previous>s`."
41
42
 
43
+ # @!method field_declarations(node)
44
+ def_node_search :field_declarations, <<~PATTERN
45
+ {
46
+ (send nil? :field (:sym _) ...)
47
+ (block
48
+ (send nil? :field (:sym _) ...) ...)
49
+ }
50
+ PATTERN
51
+
42
52
  def on_class(node)
43
53
  field_declarations(node).each_cons(2) do |previous, current|
44
- next unless consecutive_lines(previous, current)
45
- next if field_name(current) >= field_name(previous)
54
+ next unless consecutive_fields(previous, current)
55
+ next if correct_order?(field_name(previous), field_name(current))
46
56
 
47
57
  register_offense(previous, current)
48
58
  end
@@ -50,6 +60,14 @@ module RuboCop
50
60
 
51
61
  private
52
62
 
63
+ def consecutive_fields(previous, current)
64
+ return true if cop_config["Groups"] == false
65
+
66
+ (previous.source_range.last_line == current.source_range.first_line - 1) ||
67
+ (previous.parent.block_type? &&
68
+ previous.parent.last_line == current.source_range.first_line - 1)
69
+ end
70
+
53
71
  def register_offense(previous, current)
54
72
  message = format(
55
73
  self.class::MSG,
@@ -69,19 +87,6 @@ module RuboCop
69
87
  node.first_argument.value.to_s
70
88
  end
71
89
  end
72
-
73
- def consecutive_lines(previous, current)
74
- previous.source_range.last_line == current.source_range.first_line - 1
75
- end
76
-
77
- # @!method field_declarations(node)
78
- def_node_search :field_declarations, <<~PATTERN
79
- {
80
- (send nil? :field (:sym _) ...)
81
- (block
82
- (send nil? :field (:sym _) ...) ...)
83
- }
84
- PATTERN
85
90
  end
86
91
  end
87
92
  end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop"
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module GraphQL
8
+ # Checks that GraphQL Argument definitions prepare arguments use String or constants
9
+ # instead of `prepare: CONST_REF`
10
+ # This allows better Sorbet typing.
11
+ #
12
+ # Preference is given to an input object declaring a `def prepare(...); end` method
13
+ #
14
+ # @example
15
+ # # bad
16
+ # PREPARE = ->(input, context) { ... }
17
+ #
18
+ # argument :input, prepare: PREPARE
19
+ #
20
+ # # good
21
+ # def input_prepare(input); ...; end
22
+ #
23
+ # argument :input, prepare: :input_prepare
24
+ #
25
+ class PrepareMethod < RuboCop::Cop::Base
26
+ extend AutoCorrector
27
+ GENERAL_MSG = "Avoid using prepare lambdas, use prepare: :method_name or "\
28
+ "prepare: \"method_name\" instead."
29
+ STRING_MSG = "Avoid using prepare lambdas, use prepare: \"method_name\" instead."
30
+ PREFER_STRING_MSG = "Avoid using prepare symbols, use prepare: \"%s\" instead."
31
+ SYMBOL_MSG = "Avoid using prepare lambdas, use prepare: :method_name instead."
32
+ PREFER_SYMBOL_MSG = "Avoid using prepare string, use prepare: :%s instead."
33
+
34
+ ARGUMENT_METHODS = Set[:argument, :public_argument].freeze
35
+
36
+ # @!method graphql_argument_with_prepare_block?(node)
37
+ def_node_matcher :graphql_argument_with_prepare_block?, <<~PATTERN
38
+ (send nil? ARGUMENT_METHODS ... (hash ... (pair (sym :prepare) $({ const | block } ...) )))
39
+ PATTERN
40
+
41
+ # @!method prepare_method_string_name?(node)
42
+ def_node_matcher :prepare_method_string_name?, <<~PATTERN
43
+ (send nil? ARGUMENT_METHODS ... (hash ... (pair (sym :prepare) $(str ...) )))
44
+ PATTERN
45
+
46
+ # @!method prepare_method_symbol_name?(node)
47
+ def_node_matcher :prepare_method_symbol_name?, <<~PATTERN
48
+ (send nil? ARGUMENT_METHODS ... (hash ... (pair (sym :prepare) $(sym ...) )))
49
+ PATTERN
50
+
51
+ def on_send(node)
52
+ type = cop_config["EnforcedStyle"]
53
+
54
+ message = case type
55
+ when "symbol"
56
+ SYMBOL_MSG
57
+ when "string"
58
+ STRING_MSG
59
+ else
60
+ GENERAL_MSG
61
+ end
62
+ graphql_argument_with_prepare_block?(node) do |prepare_definition|
63
+ add_offense(prepare_definition, message: message)
64
+ end
65
+
66
+ if type == "symbol"
67
+ prepare_method_string_name?(node) do |prepare_definition|
68
+ method_name = prepare_definition.value
69
+ add_offense(prepare_definition,
70
+ message: format(PREFER_SYMBOL_MSG, method_name)) do |corrector|
71
+ autocorrect(corrector, node, "\"#{method_name}\"", ":#{method_name}")
72
+ end
73
+ end
74
+ elsif type == "string"
75
+ prepare_method_symbol_name?(node) do |prepare_definition|
76
+ method_name = prepare_definition.value
77
+ add_offense(prepare_definition,
78
+ message: format(PREFER_STRING_MSG, method_name)) do |corrector|
79
+ autocorrect(corrector, node, ":#{method_name}", "\"#{method_name}\"")
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def autocorrect(corrector, node, original_method_name, new_method_name)
88
+ new_source = node.source.sub("prepare: #{original_method_name}",
89
+ "prepare: #{new_method_name}")
90
+ corrector.replace(node, new_source)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -8,7 +8,6 @@ module RuboCop
8
8
  #
9
9
  # The maximum allowed length is configurable using the Max option.
10
10
  class ResolverMethodLength < Base
11
- include RuboCop::Cop::ConfigurableMax
12
11
  include RuboCop::Cop::CodeLength
13
12
 
14
13
  MSG = "ResolverMethod has too many lines. [%<total>d/%<max>d]"
@@ -23,9 +22,11 @@ module RuboCop
23
22
  return if excluded_methods.include?(String(node.method_name))
24
23
 
25
24
  if field_is_defined?(node)
26
- length = code_length(node)
25
+ return if node.line_count <= max_length
27
26
 
28
- return unless length > max_length
27
+ calculator = build_code_length_calculator(node)
28
+ length = calculator.calculate
29
+ return if length <= max_length
29
30
 
30
31
  add_offense(node, message: message(length))
31
32
  end
@@ -34,10 +35,6 @@ module RuboCop
34
35
 
35
36
  private
36
37
 
37
- def code_length(node)
38
- node.source.lines[1..-2].count { |line| !irrelevant_line(line) }
39
- end
40
-
41
38
  def field_is_defined?(node)
42
39
  node.parent
43
40
  .children
@@ -10,27 +10,33 @@ module RuboCop
10
10
  #
11
11
  # class UserType < BaseType
12
12
  # field :name, String, "Name of the user", null: true do
13
- # argument :filter, String, required: false, camelize: false
13
+ # argument :filter, String, required: false
14
14
  # end
15
15
  # end
16
16
  #
17
17
  # # good
18
18
  #
19
19
  # class UserType < BaseType
20
- # argument :filter, String, required: false, camelize: false
20
+ # argument :filter, String, required: false
21
+ # end
22
+ #
23
+ # # good
24
+ #
25
+ # class UserType < BaseType
26
+ # argument :email_filter, String, required: false, camelize: true
21
27
  # end
22
28
  #
23
29
  # # bad
24
30
  #
25
31
  # class UserType < BaseType
26
- # argument :filter, String, required: false
32
+ # argument :filter, String, required: false, camelize: false
27
33
  # end
28
34
  #
29
35
  # # bad
30
36
  #
31
37
  # class UserType < BaseType
32
38
  # field :name, String, "Name of the user", null: true do
33
- # argument :filter, String, required: false
39
+ # argument :filter, String, required: false, camelize: false
34
40
  # end
35
41
  # end
36
42
  #
@@ -38,6 +44,7 @@ module RuboCop
38
44
  include RuboCop::GraphQL::NodePattern
39
45
 
40
46
  MSG = "Unnecessary argument camelize"
47
+ RESTRICT_ON_SEND = %i[argument].freeze
41
48
 
42
49
  def on_send(node)
43
50
  return unless argument?(node)
@@ -3,32 +3,56 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module GraphQL
6
- # This cop checks if a field has an unnecessary alias.
6
+ # This cop prevents defining an unnecessary alias, method, or resolver_method.
7
7
  #
8
8
  # @example
9
9
  # # good
10
10
  #
11
- # class UserType < BaseType
12
- # field :name, String, "Name of the user", null: true, alias: :real_name
13
- # end
11
+ # field :name, String, "Name of the user", null: true, alias: :real_name
12
+ # field :name, String, "Name of the user", null: true, method: :real_name
13
+ # field :name, String, "Name of the user", null: true, resolver_method: :real_name
14
+ # field :name, String, "Name of the user", null: true, hash_key: :real_name
14
15
  #
15
16
  # # bad
16
17
  #
17
- # class UserType < BaseType
18
- # field :name, "Name of the user" String, null: true, alias: :name
19
- # end
18
+ # field :name, "Name of the user" String, null: true, alias: :name
19
+ # field :name, String, "Name of the user", null: true, method: :name
20
+ # field :name, String, "Name of the user", null: true, resolver_method: :name
21
+ # field :name, String, "Name of the user", null: true, hash_key: :name
20
22
  #
21
23
  class UnnecessaryFieldAlias < Base
24
+ extend AutoCorrector
22
25
  include RuboCop::GraphQL::NodePattern
23
26
 
24
- MSG = "Unnecessary field alias"
27
+ MSG = "Unnecessary :%<kwarg>s configured"
28
+ RESTRICT_ON_SEND = %i[field].freeze
25
29
 
26
30
  def on_send(node)
27
31
  return unless field_definition?(node)
28
32
 
29
33
  field = RuboCop::GraphQL::Field.new(node)
30
34
 
31
- add_offense(node) if field.name == field.kwargs.alias
35
+ if (unnecessary_kwarg = validate_kwargs(field))
36
+ message = format(self.class::MSG, kwarg: unnecessary_kwarg)
37
+ add_offense(node, message: message) do |corrector|
38
+ kwarg_node = node.last_argument.pairs.find do |pair|
39
+ pair.key.value == unnecessary_kwarg.to_sym
40
+ end
41
+ corrector.remove_preceding(kwarg_node, 2)
42
+ corrector.remove(kwarg_node)
43
+ end
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def validate_kwargs(field) # rubocop:disable Metrics/CyclomaticComplexity
50
+ case field.name
51
+ when field.kwargs.alias then "alias"
52
+ when field.kwargs.method&.value&.value then "method"
53
+ when field.kwargs.resolver_method_name then "resolver_method"
54
+ when field.kwargs.hash_key&.value&.value then "hash_key"
55
+ end
32
56
  end
33
57
  end
34
58
  end
@@ -22,6 +22,7 @@ module RuboCop
22
22
  include RuboCop::GraphQL::NodePattern
23
23
 
24
24
  MSG = "Unnecessary field camelize"
25
+ RESTRICT_ON_SEND = %i[field].freeze
25
26
 
26
27
  def on_send(node)
27
28
  return unless field_definition?(node)
@@ -132,7 +132,7 @@ module RuboCop
132
132
 
133
133
  add_offense(node, message: message) do |corrector|
134
134
  if node.arguments?
135
- corrector.insert_after(arg_end(node.arguments.last), ", #{unresolved_args_source}")
135
+ corrector.insert_after(arg_end(node.last_argument), ", #{unresolved_args_source}")
136
136
  else
137
137
  corrector.insert_after(method_name(node), "(#{unresolved_args_source})")
138
138
  end
@@ -144,7 +144,7 @@ module RuboCop
144
144
  end
145
145
 
146
146
  def arg_end(node)
147
- node.loc.expression.end
147
+ node.source_range.end
148
148
  end
149
149
 
150
150
  def inferred_arg_name(name_as_string)
@@ -156,7 +156,7 @@ module RuboCop
156
156
  .sub(/([^s])$/, "\\1s")
157
157
  .to_sym
158
158
  else
159
- name
159
+ name_as_string.to_sym
160
160
  end
161
161
  end
162
162
 
@@ -186,8 +186,7 @@ module RuboCop
186
186
 
187
187
  # @!method resolve_method_definition(node)
188
188
  def_node_search :resolve_method_definition, <<~PATTERN
189
- (def {:resolve | :authorized?}
190
- (args ...) ...)
189
+ (def {:resolve | :authorized?} (args ...) ...)
191
190
  PATTERN
192
191
  end
193
192
  end
@@ -11,12 +11,17 @@ require_relative "graphql/field_hash_key"
11
11
  require_relative "graphql/field_method"
12
12
  require_relative "graphql/field_name"
13
13
  require_relative "graphql/field_uniqueness"
14
+ require_relative "graphql/graphql_name"
14
15
  require_relative "graphql/legacy_dsl"
16
+ require_relative "graphql/max_complexity_schema"
17
+ require_relative "graphql/max_depth_schema"
15
18
  require_relative "graphql/multiple_field_definitions"
19
+ require_relative "graphql/not_authorized_node_type"
16
20
  require_relative "graphql/resolver_method_length"
17
21
  require_relative "graphql/object_description"
18
22
  require_relative "graphql/ordered_arguments"
19
23
  require_relative "graphql/ordered_fields"
24
+ require_relative "graphql/prepare_method"
20
25
  require_relative "graphql/unused_argument"
21
26
  require_relative "graphql/unnecessary_argument_camelize"
22
27
  require_relative "graphql/unnecessary_field_alias"
@@ -12,7 +12,7 @@ module RuboCop
12
12
  (block
13
13
  (send nil? :argument ...)
14
14
  (args ...)
15
- $...
15
+ {(begin $...)|$...}
16
16
  )
17
17
  PATTERN
18
18
 
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module GraphQL
5
+ module CompareOrder
6
+ def correct_order?(previous, current)
7
+ # If Order config is provided, we should use it to determine the order
8
+ # Else, we should use alphabetical order
9
+ # e.g. "Order" => [
10
+ # "id",
11
+ # "/^id_.*$/",
12
+ # "/^.*_id$/",
13
+ # "everything-else",
14
+ # "/^(created|updated)_at$/"
15
+ # ]
16
+ if (order = cop_config["Order"])
17
+ # For each of previous and current, we should find the first matching order,
18
+ # checking 'everything-else' last
19
+ # If the order is the same, we should use alphabetical order
20
+ # If the order is different, we should use the order
21
+ previous_order = order_index(previous, order)
22
+ current_order = order_index(current, order)
23
+
24
+ if previous_order == current_order
25
+ previous <= current
26
+ else
27
+ previous_order < current_order
28
+ end
29
+ else
30
+ previous <= current
31
+ end
32
+ end
33
+
34
+ def order_index(field, order)
35
+ everything_else_index = order.length
36
+
37
+ order.each_with_index do |order_item, index|
38
+ if order_item == "everything-else"
39
+ everything_else_index = index
40
+ elsif order_item.start_with?("/") && order_item.end_with?("/") # is regex-like?
41
+ return index if field.match?(order_item[1..-2])
42
+ elsif field == order_item
43
+ return index
44
+ end
45
+ end
46
+
47
+ everything_else_index
48
+ end
49
+ end
50
+ end
51
+ end
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module GraphQL
5
5
  module Ext
6
6
  module SnakeCase
7
- SNAKE_CASE = /^[\da-z_]+[!?=]?$/.freeze
7
+ SNAKE_CASE = /^[\da-z_]+[!?=]?$/
8
8
 
9
9
  refine Symbol do
10
10
  def snake_case?
@@ -33,7 +33,7 @@ module RuboCop
33
33
 
34
34
  # @!method field_description(node)
35
35
  def_node_matcher :field_description, <<~PATTERN
36
- (send nil? :field _ _ (:str $_) ...)
36
+ (send nil? :field _ _ {(:str $_)|(:dstr $...)} ...)
37
37
  PATTERN
38
38
 
39
39
  attr_reader :node
@@ -84,7 +84,10 @@ module RuboCop
84
84
  end
85
85
 
86
86
  def root_node?(node)
87
- node.parent.nil? || node.parent.module_type? || root_with_siblings?(node.parent)
87
+ node.parent.nil? ||
88
+ node.module_type? && node.children.none?(&:module_type?) ||
89
+ node.class_type? ||
90
+ root_with_siblings?(node.parent)
88
91
  end
89
92
 
90
93
  def root_with_siblings?(node)