rubocop-graphql 0.19.0 → 1.5.2

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 +14 -3
  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 -6
  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 +2 -2
  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 +1 -1
  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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 80b3609f1de18b1e5db7198fdf29f7deed28cfe0e505c9485203dd38172ff4f9
4
- data.tar.gz: 0d894dad1bc37192b76372d235d10fab4181e171574f471091e9bad0033212d2
3
+ metadata.gz: 54137ed6106e3d8c0d2c675ea02e41989aef1d4480edf875071c9c7918a96fba
4
+ data.tar.gz: a1a0b7d307854a39d4b4761d77cc829569dd9e97453a329d027886331407aae0
5
5
  SHA512:
6
- metadata.gz: 3c2b2d1bcbae2e53e28567c0f13397f77427cc0b9d87a96171cd27fec5d33649568366a623c6ea7c6960f452709d02081c04e3dae1368b465da4fad304ae9ce5
7
- data.tar.gz: b6b5588432b38eaa545bff20b2f52d966859ba1fac6fcad5004f22405fd4d1dab56c04238124f99f3ff1df860b385773e539981221b94d0e8c4c27266d4a7c9e
6
+ metadata.gz: 9e1fc8711b226bd656ca33d8409949a40667e9eabf31ca55a3536ae5f4f54f3af4e7254cbaa589e406172c724afdeb6772ffe5dd689909ebeb09608c7c94b848
7
+ data.tar.gz: 392b99c409b0e2b8247fff232d35fab529c1abf5eb3f7b648f40eb9020b07a5c56f074d88538626e564872a4a13f1cb59ee20a5ddc0c982099a2aa3edc596289
data/config/default.yml CHANGED
@@ -8,123 +8,168 @@ GraphQL:
8
8
 
9
9
  GraphQL/ArgumentDescription:
10
10
  Enabled: true
11
- VersionAdded: '0.80'
11
+ VersionAdded: '0.2.0'
12
12
  Description: 'Ensures all arguments have a description'
13
13
 
14
14
  GraphQL/ArgumentName:
15
15
  Enabled: true
16
- VersionAdded: '0.80'
16
+ VersionAdded: '0.2.0'
17
17
  Description: 'This cop checks whether argument names are snake_case'
18
18
 
19
19
  GraphQL/ArgumentUniqueness:
20
20
  Enabled: true
21
- VersionAdded: '0.80'
21
+ VersionAdded: '0.11.0'
22
22
  Description: 'This cop enforces arguments to be defined once per block'
23
23
 
24
- GraphQL/ResolverMethodLength:
24
+ GraphQL/ExtractInputType:
25
25
  Enabled: true
26
- VersionAdded: '0.80'
27
- Description: 'Checks resolver methods are not too long'
28
- Max: 10
29
- CountComments: false
30
- ExcludedMethods: []
26
+ VersionAdded: '0.2.0'
27
+ Description: 'Suggests using input type instead of many arguments'
28
+ MaxArguments: 2
29
+ Include:
30
+ - '**/graphql/mutations/**/*.rb'
31
+
32
+ GraphQL/ExtractType:
33
+ Enabled: true
34
+ VersionAdded: '0.2.0'
35
+ Description: 'Suggests extracting fields with common prefixes to the separate type'
36
+ MaxFields: 2
37
+ Prefixes:
38
+ - is
39
+ - has
40
+ - with
41
+ - avg
42
+ - min
43
+ - max
31
44
 
32
45
  GraphQL/FieldDefinitions:
33
46
  Enabled: true
34
- VersionAdded: '0.80'
47
+ VersionAdded: '0.1.0'
35
48
  Description: 'Checks consistency of field definitions'
36
49
  EnforcedStyle: group_definitions
37
50
  SupportedStyles:
38
51
  - group_definitions
39
52
  - define_resolver_after_definition
40
53
 
41
- GraphQL/MultipleFieldDefinitions:
42
- Enabled: true
43
- Description: 'Ensures that fields with multiple definitions are grouped together'
44
-
45
54
  GraphQL/FieldDescription:
46
55
  Enabled: true
47
- VersionAdded: '0.80'
56
+ VersionAdded: '0.1.0'
48
57
  Description: 'Ensures all fields have a description'
49
58
 
50
59
  GraphQL/FieldHashKey:
51
60
  Enabled: true
52
- VersionAdded: '0.80'
61
+ VersionAdded: '0.4.0'
53
62
  Description: 'Checks :hash_key option is used for appropriate fields'
54
63
 
55
64
  GraphQL/FieldMethod:
56
65
  Enabled: true
57
- VersionAdded: '0.80'
66
+ VersionAdded: '0.2.0'
58
67
  Description: 'Checks :method option is used for appropriate fields'
59
68
 
60
69
  GraphQL/FieldName:
61
70
  Enabled: true
62
- VersionAdded: '0.80'
71
+ VersionAdded: '0.2.0'
63
72
  Description: 'This cop checks whether field names are snake_case'
64
73
  SafeAutoCorrect: false
65
74
 
66
75
  GraphQL/FieldUniqueness:
67
76
  Enabled: true
68
- VersionAdded: '0.80'
77
+ VersionAdded: '0.11.0'
69
78
  Description: 'This cop enforces fields to be defined once'
70
79
 
71
- GraphQL/ExtractInputType:
80
+ GraphQL/GraphqlName:
72
81
  Enabled: true
73
- VersionAdded: '0.80'
74
- Description: 'Suggests using input type instead of many arguments'
75
- MaxArguments: 2
82
+ VersionAdded: '1.0.0'
83
+ Description: 'This cop check proper configuration of graphql_name'
76
84
  Include:
77
- - '**/graphql/mutations/**/*.rb'
78
-
79
- GraphQL/ExtractType:
80
- Enabled: true
81
- VersionAdded: '0.80'
82
- Description: 'Suggests extracting fields with common prefixes to the separate type'
83
- MaxFields: 2
84
- Prefixes:
85
- - is
86
- - has
87
- - with
88
- - avg
89
- - min
90
- - max
85
+ - "**/graphql/types/**/*"
86
+ - "**/graphql/mutations/**/*"
87
+ EnforcedStyle: only_override
88
+ SupportedStyles:
89
+ - required
90
+ - only_override
91
91
 
92
92
  GraphQL/LegacyDsl:
93
93
  Enabled: true
94
- VersionAdded: '0.80'
94
+ VersionAdded: '0.9.0'
95
95
  Description: 'Checks that types are defined with class-based API'
96
96
 
97
+ GraphQL/MaxComplexitySchema:
98
+ Enabled: true
99
+ VersionAdded: '1.0.0'
100
+ Description: 'Enforces max_complexity configuration in schema'
101
+ Include:
102
+ - '**/graphql/**/*_schema.rb'
103
+
104
+ GraphQL/MaxDepthSchema:
105
+ Enabled: true
106
+ VersionAdded: '1.0.0'
107
+ Description: 'Enforces max_depth configuration in schema'
108
+ Include:
109
+ - '**/graphql/**/*_schema.rb'
110
+
111
+ GraphQL/MultipleFieldDefinitions:
112
+ Enabled: true
113
+ VersionAdded: '0.15.0'
114
+ Description: 'Ensures that fields with multiple definitions are grouped together'
115
+
116
+ GraphQL/NotAuthorizedNodeType:
117
+ Enabled: true
118
+ VersionAdded: '1.0.0'
119
+ Description: 'Detects types that implement Node interface and not have `.authorized?` check'
120
+ Include:
121
+ - '**/graphql/types/**/*'
122
+ SafeBaseClasses: []
123
+
124
+ GraphQL/ResolverMethodLength:
125
+ Enabled: true
126
+ VersionAdded: '0.1.0'
127
+ Description: 'Checks resolver methods are not too long'
128
+ Max: 10
129
+ CountComments: false
130
+ ExcludedMethods: []
131
+ CountAsOne: []
132
+
97
133
  GraphQL/ObjectDescription:
98
134
  Enabled: true
99
- VersionAdded: '0.80'
135
+ VersionAdded: '0.3.0'
100
136
  Description: 'Ensures all types have a description'
101
137
  Exclude:
138
+ - "spec/**/*"
139
+ - "test/**/*"
102
140
  - '**/*_schema.rb'
103
141
  - '**/base_*.rb'
104
142
  - '**/graphql/query_context.rb'
105
143
 
106
144
  GraphQL/OrderedArguments:
107
145
  Enabled: true
108
- VersionAdded: '0.80'
146
+ VersionAdded: '0.7.0'
109
147
  Description: 'Arguments should be alphabetically sorted within groups'
148
+ Order: null
110
149
 
111
150
  GraphQL/OrderedFields:
112
151
  Enabled: true
113
- VersionAdded: '0.80'
152
+ VersionAdded: '0.5.0'
114
153
  Description: 'Fields should be alphabetically sorted within groups'
154
+ Groups: true
155
+ Order: null
115
156
 
116
157
  GraphQL/UnusedArgument:
117
158
  Enabled: true
159
+ VersionAdded: '0.12.0'
118
160
  Description: 'Arguments should either be listed explicitly or **rest should be in the resolve signature'
119
161
 
120
162
  GraphQL/UnnecessaryFieldAlias:
121
163
  Enabled: true
164
+ VersionAdded: '0.18.0'
122
165
  Description: 'Field aliases should be different than their field names'
123
166
 
124
167
  GraphQL/UnnecessaryArgumentCamelize:
125
168
  Enabled: true
169
+ VersionAdded: '0.18.0'
126
170
  Description: "Camelize isn't necessary if the argument name doesn't contain underscores"
127
171
 
128
172
  GraphQL/UnnecessaryFieldCamelize:
129
173
  Enabled: true
174
+ VersionAdded: '0.18.0'
130
175
  Description: "Camelize isn't necessary if the field name doesn't contain underscores"
@@ -22,6 +22,7 @@ module RuboCop
22
22
  include RuboCop::GraphQL::NodePattern
23
23
 
24
24
  MSG = "Missing argument description"
25
+ RESTRICT_ON_SEND = %i[argument].freeze
25
26
 
26
27
  def on_send(node)
27
28
  return unless argument?(node)
@@ -20,6 +20,7 @@ module RuboCop
20
20
  #
21
21
  class ArgumentName < Base
22
22
  include RuboCop::GraphQL::NodePattern
23
+ RESTRICT_ON_SEND = %i[argument].freeze
23
24
 
24
25
  using RuboCop::GraphQL::Ext::SnakeCase
25
26
 
@@ -52,6 +52,8 @@ module RuboCop
52
52
  include RuboCop::GraphQL::Sorbet
53
53
  include RuboCop::GraphQL::Heredoc
54
54
 
55
+ RESTRICT_ON_SEND = %i[field].freeze
56
+
55
57
  # @!method field_kwargs(node)
56
58
  def_node_matcher :field_kwargs, <<~PATTERN
57
59
  (send nil? :field
@@ -80,6 +82,16 @@ module RuboCop
80
82
  end
81
83
  end
82
84
 
85
+ def on_module(node)
86
+ return if style != :group_definitions
87
+
88
+ schema_member = RuboCop::GraphQL::SchemaMember.new(node)
89
+
90
+ if (body = schema_member.body)
91
+ check_grouped_field_declarations(body)
92
+ end
93
+ end
94
+
83
95
  private
84
96
 
85
97
  GROUP_DEFS_MSG = "Group all field definitions together."
@@ -100,7 +112,6 @@ module RuboCop
100
112
 
101
113
  def group_field_declarations(corrector, node)
102
114
  field = RuboCop::GraphQL::Field.new(node)
103
-
104
115
  first_field = field.schema_member.body.find do |node|
105
116
  field_definition?(node) || field_definition_with_body?(node)
106
117
  end
@@ -211,7 +222,7 @@ module RuboCop
211
222
 
212
223
  def remove_old_resolver(corrector, resolver_definition)
213
224
  range_to_remove = range_with_surrounding_space(
214
- range: resolver_definition.loc.expression, side: :left
225
+ range: resolver_definition.source_range, side: :left
215
226
  )
216
227
  corrector.remove(range_to_remove)
217
228
 
@@ -220,7 +231,7 @@ module RuboCop
220
231
  return unless resolver_signature
221
232
 
222
233
  range_to_remove = range_with_surrounding_space(
223
- range: resolver_signature.loc.expression, side: :left
234
+ range: resolver_signature.source_range, side: :left
224
235
  )
225
236
  corrector.remove(range_to_remove)
226
237
  end
@@ -22,6 +22,7 @@ module RuboCop
22
22
  include RuboCop::GraphQL::NodePattern
23
23
 
24
24
  MSG = "Missing field description"
25
+ RESTRICT_ON_SEND = %i[field].freeze
25
26
 
26
27
  def on_send(node)
27
28
  return unless field_definition?(node)
@@ -41,6 +41,7 @@ module RuboCop
41
41
  PATTERN
42
42
 
43
43
  MSG = "Use hash_key: %<hash_key>p"
44
+ RESTRICT_ON_SEND = %i[field].freeze
44
45
 
45
46
  def on_send(node)
46
47
  return unless field_definition?(node)
@@ -74,11 +75,11 @@ module RuboCop
74
75
  suggested_hash_key_name = hash_key_to_use(method_definition)
75
76
 
76
77
  corrector.insert_after(
77
- node.loc.expression, ", hash_key: #{suggested_hash_key_name.inspect}"
78
+ node, ", hash_key: #{suggested_hash_key_name.inspect}"
78
79
  )
79
80
 
80
81
  range = range_with_surrounding_space(
81
- range: method_definition.loc.expression, side: :left
82
+ range: method_definition.source_range, side: :left
82
83
  )
83
84
 
84
85
  corrector.remove(range)
@@ -40,6 +40,7 @@ module RuboCop
40
40
  PATTERN
41
41
 
42
42
  MSG = "Use method: :%<method_name>s"
43
+ RESTRICT_ON_SEND = %i[field].freeze
43
44
 
44
45
  def on_send(node)
45
46
  return unless field_definition?(node)
@@ -51,6 +52,7 @@ module RuboCop
51
52
 
52
53
  return if suggested_method_name.nil?
53
54
  return if RuboCop::GraphQL::Field::CONFLICT_FIELD_NAMES.include?(suggested_method_name)
55
+ return if method_kwarg_set?(field)
54
56
 
55
57
  add_offense(node, message: message(suggested_method_name)) do |corrector|
56
58
  autocorrect(corrector, node)
@@ -68,10 +70,10 @@ module RuboCop
68
70
  method_definition = suggest_method_name_for(field)
69
71
  suggested_method_name = method_to_use(method_definition)
70
72
 
71
- corrector.insert_after(node.loc.expression, ", method: :#{suggested_method_name}")
73
+ corrector.insert_after(node, ", method: :#{suggested_method_name}")
72
74
 
73
75
  range = range_with_surrounding_space(
74
- range: method_definition.loc.expression, side: :left
76
+ range: method_definition.source_range, side: :left
75
77
  )
76
78
  corrector.remove(range)
77
79
  end
@@ -80,6 +82,10 @@ module RuboCop
80
82
  method_name = field.resolver_method_name
81
83
  field.schema_member.find_method_definition(method_name)
82
84
  end
85
+
86
+ def method_kwarg_set?(field)
87
+ field.kwargs.method != nil
88
+ end
83
89
  end
84
90
  end
85
91
  end
@@ -29,6 +29,7 @@ module RuboCop
29
29
  using RuboCop::GraphQL::Ext::SnakeCase
30
30
 
31
31
  MSG = "Use snake_case for field names"
32
+ RESTRICT_ON_SEND = %i[field].freeze
32
33
 
33
34
  def on_send(node)
34
35
  return unless field_definition?(node)
@@ -63,9 +63,16 @@ module RuboCop
63
63
  end
64
64
 
65
65
  def field_name(node)
66
- node.first_argument.value.to_s
66
+ field = RuboCop::GraphQL::Field.new(node)
67
+
68
+ "#{field.name}#{':non-camelized' if false_value?(field.kwargs.camelize)}"
67
69
  end
68
70
 
71
+ # @!method false_value?(node)
72
+ def_node_matcher :false_value?, <<~PATTERN
73
+ (pair ... false)
74
+ PATTERN
75
+
69
76
  # @!method field_declarations(node)
70
77
  def_node_search :field_declarations, <<~PATTERN
71
78
  {
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module GraphQL
6
+ # Checks consistency of graphql_name usage
7
+ #
8
+ # EnforcedStyle supports two modes:
9
+ #
10
+ # `only_override` : types and mutations should have graphql_name configured only if it's
11
+ # different from the default name
12
+ #
13
+ # `required` : all types and mutations should have graphql_name configured
14
+ #
15
+ # @example EnforcedStyle: only_override (default)
16
+ # # good
17
+ #
18
+ # class UserType < BaseType
19
+ # graphql_name 'Viewer'
20
+ # end
21
+ #
22
+ # # bad
23
+ #
24
+ # class UserType < BaseType
25
+ # graphql_name 'User'
26
+ # end
27
+ #
28
+ # @example EnforcedStyle: required
29
+ # # good
30
+ #
31
+ # class UserType < BaseType
32
+ # graphql_name 'User'
33
+ # end
34
+ #
35
+ # # bad
36
+ #
37
+ # class UserType < BaseType
38
+ # end
39
+ #
40
+ class GraphqlName < Base
41
+ include ConfigurableEnforcedStyle
42
+
43
+ # @!method graphql_name(node)
44
+ def_node_matcher :graphql_name, <<~PATTERN
45
+ `(send nil? :graphql_name (str $_))
46
+ PATTERN
47
+
48
+ # @!method class_name(node)
49
+ def_node_matcher :class_name, <<~PATTERN
50
+ (class (const _ $_) ...)
51
+ PATTERN
52
+
53
+ MISSING_NAME = "graphql_name should be configured."
54
+ UNNEEDED_OVERRIDE = "graphql_name should be specified only for overrides."
55
+
56
+ def on_class(node)
57
+ specified_name = graphql_name(node)
58
+
59
+ case style
60
+ when :required
61
+ add_offense(node, message: MISSING_NAME) if specified_name.nil?
62
+ when :only_override
63
+ default_graphql_name = class_name(node).to_s.sub(/Type\Z/, "")
64
+ add_offense(node, message: UNNEEDED_OVERRIDE) if specified_name == default_graphql_name
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -32,6 +32,7 @@ module RuboCop
32
32
 
33
33
  MSG = "Avoid using legacy based type-based definitions. " \
34
34
  "Use class-based definitions instead."
35
+ RESTRICT_ON_SEND = %i[define].freeze
35
36
 
36
37
  def on_send(node)
37
38
  return unless node.parent.block_type?
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module GraphQL
6
+ # Detects missing max_complexity configuration in schema files
7
+ #
8
+ # @example
9
+ # # good
10
+ #
11
+ # class AppSchema < BaseSchema
12
+ # max_complexity 42
13
+ # end
14
+ #
15
+ class MaxComplexitySchema < Base
16
+ # @!method max_complexity(node)
17
+ def_node_matcher :max_complexity, <<~PATTERN
18
+ `(send nil? :max_complexity ...)
19
+ PATTERN
20
+
21
+ MSG = "max_complexity should be configured for schema."
22
+
23
+ def on_class(node)
24
+ return if ::RuboCop::GraphQL::Class.new(node).nested? || max_complexity(node)
25
+
26
+ add_offense(node)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module GraphQL
6
+ # Detects missing max_depth configuration in schema files
7
+ #
8
+ # @example
9
+ # # good
10
+ #
11
+ # class AppSchema < BaseSchema
12
+ # max_depth 42
13
+ # end
14
+ #
15
+ class MaxDepthSchema < Base
16
+ # @!method max_depth(node)
17
+ def_node_matcher :max_depth, <<~PATTERN
18
+ `(send nil? :max_depth ...)
19
+ PATTERN
20
+
21
+ MSG = "max_depth should be configured for schema."
22
+
23
+ def on_class(node)
24
+ return if ::RuboCop::GraphQL::Class.new(node).nested? || max_depth(node)
25
+
26
+ add_offense(node)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -32,6 +32,8 @@ module RuboCop
32
32
  include RuboCop::Cop::RangeHelp
33
33
  include RuboCop::GraphQL::Heredoc
34
34
 
35
+ RESTRICT_ON_SEND = %i[field].freeze
36
+
35
37
  def on_send(node)
36
38
  return unless field?(node)
37
39
 
@@ -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