rubocop-graphql 0.8.2 → 0.10.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22be8928aa9fbce1700f99d21ccddacd9590ab7bd6c6723e4b7458f974e02f66
4
- data.tar.gz: 6e06404047cba621b7493912723fa5eac13c19004d128339e494455758871831
3
+ metadata.gz: d7d2dac11533f09319d23f8770fe5b154c9477632a12d61f845a1c583e653635
4
+ data.tar.gz: 178ae678fdfc54ff8bc46af35160b5b0c0a57d183dff89045e3e790434c01728
5
5
  SHA512:
6
- metadata.gz: 94e9c24116f399d08b0c264e4b1b6c5dc50ac9436833fd8ec9f4916bdae102f5fdd5df5589a31502bca8aa65ccc61448a24b2e57aded363c35e6b0adec1513f6
7
- data.tar.gz: '0855eb683b91a3d5bb6e78b89c7c3089df5832813b1c298b26edf56de2e3c4ee71da837d279bad5ce15fd4dbcf9197fe908f81f77967a7b7345c9a1b3fdcb1f2'
6
+ metadata.gz: 0ea0e2358678578a7b6404c320fc836644fb6fd08cf33dfd13937eba0d991b2724fbd69b8ea9ff1f162ddc4c0b09f384c2344cbd4197e6744fef5931ea10a1da
7
+ data.tar.gz: bf5a8346e275f271c4a5c38cc9ad6c12b5271fa2ff3813853768a2f183530b078e214a7598cc00ebe43edefbfb2b2f79bfcd85a8d71ab664ed3fd43ddc7a87ea
data/config/default.yml CHANGED
@@ -48,6 +48,7 @@ GraphQL/FieldName:
48
48
  Enabled: true
49
49
  VersionAdded: '0.80'
50
50
  Description: 'This cop checks whether field names are snake_case'
51
+ SafeAutoCorrect: false
51
52
 
52
53
  GraphQL/ExtractInputType:
53
54
  Enabled: true
@@ -67,6 +68,11 @@ GraphQL/ExtractType:
67
68
  - min
68
69
  - max
69
70
 
71
+ GraphQL/LegacyDsl:
72
+ Enabled: true
73
+ VersionAdded: '0.80'
74
+ Description: 'Checks that types are defined with class-based API'
75
+
70
76
  GraphQL/ObjectDescription:
71
77
  Enabled: true
72
78
  VersionAdded: '0.80'
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UnderscoreString
4
+ refine String do
5
+ # This method was extracted from activesupport in Rails:
6
+ # https://github.com/rails/rails/blob/8dab534ca81dd32c6a83ac03596a1feb7ddaaca7/activesupport/lib/active_support/inflector/methods.rb#L96
7
+
8
+ def underscore
9
+ camel_cased_word = self
10
+ regex = /(?:(?<=([A-Za-z\d]))|\b)((?=a)b)(?=\b|[^a-z])/
11
+ return camel_cased_word.to_s unless /[A-Z-]|::/.match?(camel_cased_word)
12
+
13
+ word = camel_cased_word.to_s.gsub("::", "/")
14
+ word.gsub!(regex) { "#{Regexp.last_match(1) && '_'}#{Regexp.last_match(2).downcase}" }
15
+ word.gsub!(/([A-Z\d]+)(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) do
16
+ (Regexp.last_match(1) || Regexp.last_match(2)) << "_"
17
+ end
18
+ word.tr!("-", "_")
19
+ word.downcase!
20
+ word
21
+ end
22
+ end
23
+ end
@@ -18,7 +18,7 @@ module RuboCop
18
18
  # argument :uuid, ID, required: true
19
19
  # end
20
20
  #
21
- class ArgumentDescription < Cop
21
+ class ArgumentDescription < Base
22
22
  include RuboCop::GraphQL::NodePattern
23
23
 
24
24
  MSG = "Missing argument description"
@@ -18,7 +18,7 @@ module RuboCop
18
18
  # argument :userId, ID, required: true
19
19
  # end
20
20
  #
21
- class ArgumentName < Cop
21
+ class ArgumentName < Base
22
22
  include RuboCop::GraphQL::NodePattern
23
23
 
24
24
  using RuboCop::GraphQL::Ext::SnakeCase
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module GraphQL
6
- class ExtractInputType < Cop
6
+ class ExtractInputType < Base
7
7
  # This cop checks fields on common prefix groups
8
8
  #
9
9
  # # @example
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module GraphQL
6
- class ExtractType < Cop
6
+ class ExtractType < Base
7
7
  # This cop checks fields on common prefix groups
8
8
  #
9
9
  # # @example
@@ -44,7 +44,8 @@ module RuboCop
44
44
  # object.contact_data.last_name
45
45
  # end
46
46
  # end
47
- class FieldDefinitions < Cop
47
+ class FieldDefinitions < Base
48
+ extend AutoCorrector
48
49
  include ConfigurableEnforcedStyle
49
50
  include RuboCop::GraphQL::NodePattern
50
51
  include RuboCop::Cop::RangeHelp
@@ -75,17 +76,6 @@ module RuboCop
75
76
  end
76
77
  end
77
78
 
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
86
- end
87
- end
88
-
89
79
  private
90
80
 
91
81
  GROUP_DEFS_MSG = "Group all field definitions together."
@@ -98,7 +88,9 @@ module RuboCop
98
88
  fields.each_with_index do |node, idx|
99
89
  next if node.sibling_index == first_field.sibling_index + idx
100
90
 
101
- add_offense(node, message: GROUP_DEFS_MSG)
91
+ add_offense(node, message: GROUP_DEFS_MSG) do |corrector|
92
+ group_field_declarations(corrector, node)
93
+ end
102
94
  end
103
95
  end
104
96
 
@@ -133,7 +125,9 @@ module RuboCop
133
125
 
134
126
  return if method_definition.sibling_index - field_sibling_index == 1
135
127
 
136
- add_offense(field.node, message: RESOLVER_AFTER_FIELD_MSG)
128
+ add_offense(field.node, message: RESOLVER_AFTER_FIELD_MSG) do |corrector|
129
+ place_resolver_after_definitions(corrector, field.node)
130
+ end
137
131
  end
138
132
 
139
133
  def place_resolver_after_definitions(corrector, node)
@@ -18,7 +18,7 @@ module RuboCop
18
18
  # field :name, String, null: true
19
19
  # end
20
20
  #
21
- class FieldDescription < Cop
21
+ class FieldDescription < Base
22
22
  include RuboCop::GraphQL::NodePattern
23
23
 
24
24
  MSG = "Missing field description"
@@ -23,7 +23,8 @@ module RuboCop
23
23
  # end
24
24
  # end
25
25
  #
26
- class FieldHashKey < Cop
26
+ class FieldHashKey < Base
27
+ extend AutoCorrector
27
28
  include RuboCop::GraphQL::NodePattern
28
29
  include RuboCop::Cop::RangeHelp
29
30
 
@@ -33,7 +34,7 @@ module RuboCop
33
34
  (args)
34
35
  (send
35
36
  (send nil? :object) :[]
36
- (_type $_)
37
+ ({sym str} $_)
37
38
  )
38
39
  )
39
40
  PATTERN
@@ -47,32 +48,32 @@ module RuboCop
47
48
  method_definition = resolver_method_definition_for(field)
48
49
 
49
50
  if (suggested_hash_key_name = hash_key_to_use(method_definition))
50
- add_offense(node, message: message(suggested_hash_key_name))
51
+ add_offense(node, message: message(suggested_hash_key_name)) do |corrector|
52
+ autocorrect(corrector, node)
53
+ end
51
54
  end
52
55
  end
53
56
 
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)
57
+ private
59
58
 
60
- corrector.insert_after(
61
- node.loc.expression, ", hash_key: #{suggested_hash_key_name.inspect}"
62
- )
59
+ def message(hash_key)
60
+ format(MSG, hash_key: hash_key)
61
+ end
63
62
 
64
- range = range_with_surrounding_space(
65
- range: method_definition.loc.expression, side: :left
66
- )
63
+ def autocorrect(corrector, node)
64
+ field = RuboCop::GraphQL::Field.new(node)
65
+ method_definition = resolver_method_definition_for(field)
66
+ suggested_hash_key_name = hash_key_to_use(method_definition)
67
67
 
68
- corrector.remove(range)
69
- end
70
- end
68
+ corrector.insert_after(
69
+ node.loc.expression, ", hash_key: #{suggested_hash_key_name.inspect}"
70
+ )
71
71
 
72
- private
72
+ range = range_with_surrounding_space(
73
+ range: method_definition.loc.expression, side: :left
74
+ )
73
75
 
74
- def message(hash_key)
75
- format(MSG, hash_key: hash_key)
76
+ corrector.remove(range)
76
77
  end
77
78
 
78
79
  def resolver_method_definition_for(field)
@@ -23,7 +23,8 @@ module RuboCop
23
23
  # end
24
24
  # end
25
25
  #
26
- class FieldMethod < Cop
26
+ class FieldMethod < Base
27
+ extend AutoCorrector
27
28
  include RuboCop::GraphQL::NodePattern
28
29
  include RuboCop::Cop::RangeHelp
29
30
 
@@ -46,22 +47,9 @@ module RuboCop
46
47
  method_definition = suggest_method_name_for(field)
47
48
 
48
49
  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(
62
- range: method_definition.loc.expression, side: :left
63
- )
64
- corrector.remove(range)
50
+ add_offense(node, message: message(suggested_method_name)) do |corrector|
51
+ autocorrect(corrector, node)
52
+ end
65
53
  end
66
54
  end
67
55
 
@@ -71,6 +59,19 @@ module RuboCop
71
59
  format(MSG, method_name: method_name)
72
60
  end
73
61
 
62
+ def autocorrect(corrector, node)
63
+ field = RuboCop::GraphQL::Field.new(node)
64
+ method_definition = suggest_method_name_for(field)
65
+ suggested_method_name = method_to_use(method_definition)
66
+
67
+ corrector.insert_after(node.loc.expression, ", method: :#{suggested_method_name}")
68
+
69
+ range = range_with_surrounding_space(
70
+ range: method_definition.loc.expression, side: :left
71
+ )
72
+ corrector.remove(range)
73
+ end
74
+
74
75
  def suggest_method_name_for(field)
75
76
  method_name = field.resolver_method_name
76
77
  field.schema_member.find_method_definition(method_name)
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../../../refinements/underscore_string"
4
+
5
+ using UnderscoreString unless String.method_defined?(:underscore)
6
+
3
7
  module RuboCop
4
8
  module Cop
5
9
  module GraphQL
@@ -18,7 +22,8 @@ module RuboCop
18
22
  # field :firstName, String, null: true
19
23
  # end
20
24
  #
21
- class FieldName < Cop
25
+ class FieldName < Base
26
+ extend AutoCorrector
22
27
  include RuboCop::GraphQL::NodePattern
23
28
 
24
29
  using RuboCop::GraphQL::Ext::SnakeCase
@@ -31,7 +36,17 @@ module RuboCop
31
36
  field = RuboCop::GraphQL::Field.new(node)
32
37
  return if field.name.snake_case?
33
38
 
34
- add_offense(node)
39
+ add_offense(node) do |corrector|
40
+ rename_field_name(corrector, field, node)
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def rename_field_name(corrector, field, node)
47
+ name_field = field.name.to_s
48
+ new_line = node.source.sub(name_field, name_field.underscore)
49
+ corrector.replace(node, new_line)
35
50
  end
36
51
  end
37
52
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # This cop checks whether type definitions are class-based or legacy.
6
+ #
7
+ # @example
8
+ # # good
9
+ #
10
+ # class Example < BaseType
11
+ # ....
12
+ # end
13
+ #
14
+ # # bad
15
+ #
16
+ # Example = GraphQL::ObjectType.define do
17
+ # ....
18
+ # ....
19
+ # end
20
+ #
21
+ module GraphQL
22
+ class LegacyDsl < Base
23
+ def_node_matcher :legacy_dsl?, <<~PATTERN
24
+ (block
25
+ (send
26
+ (const
27
+ (const nil? :GraphQL) _) :define)
28
+ ...
29
+ )
30
+ PATTERN
31
+
32
+ MSG = "Avoid using legacy based type-based definitions. Use class-based defintions instead."
33
+
34
+ def on_send(node)
35
+ return unless node.parent.type == :block
36
+
37
+ add_offense(node.parent) if legacy_dsl?(node.parent)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -20,7 +20,7 @@ module RuboCop
20
20
  # # ...
21
21
  # end
22
22
  #
23
- class ObjectDescription < Cop
23
+ class ObjectDescription < Base
24
24
  include RuboCop::GraphQL::NodePattern
25
25
  include RuboCop::GraphQL::DescriptionMethod
26
26
 
@@ -48,15 +48,16 @@ module RuboCop
48
48
  # end
49
49
  # end
50
50
  #
51
- class OrderedArguments < Cop
51
+ class OrderedArguments < Base
52
+ extend AutoCorrector
53
+
54
+ include RuboCop::GraphQL::SwapRange
55
+
52
56
  MSG = "Arguments should be sorted in an alphabetical order within their section. " \
53
57
  "Field `%<current>s` should appear before `%<previous>s`."
54
58
 
55
- def investigate(processed_source)
56
- return if processed_source.blank?
57
-
58
- argument_declarations(processed_source.ast)
59
- .each_cons(2) do |previous, current|
59
+ def on_class(node)
60
+ argument_declarations(node).each_cons(2) do |previous, current|
60
61
  next unless consecutive_lines(previous, current)
61
62
  next if argument_name(current) > argument_name(previous)
62
63
 
@@ -64,43 +65,18 @@ module RuboCop
64
65
  end
65
66
  end
66
67
 
67
- def autocorrect(node)
68
- declarations = argument_declarations(processed_source.ast)
69
- node_index = declarations.map(&:location).find_index(node.location)
70
- previous_declaration = declarations.to_a[node_index - 1]
71
-
72
- current_range = declaration(node)
73
- previous_range = declaration(previous_declaration)
74
-
75
- lambda do |corrector|
76
- swap_range(corrector, current_range, previous_range)
77
- end
78
- end
79
-
80
68
  private
81
69
 
82
- def declaration(node)
83
- buffer = processed_source.buffer
84
- begin_pos = node.source_range.begin_pos
85
- end_line = buffer.line_for_position(node.loc.expression.end_pos)
86
- end_pos = buffer.line_range(end_line).end_pos
87
- Parser::Source::Range.new(buffer, begin_pos, end_pos)
88
- end
89
-
90
- def swap_range(corrector, range1, range2)
91
- src1 = range1.source
92
- src2 = range2.source
93
- corrector.replace(range1, src2)
94
- corrector.replace(range2, src1)
95
- end
96
-
97
70
  def register_offense(previous, current)
98
71
  message = format(
99
72
  self.class::MSG,
100
73
  previous: argument_name(previous),
101
74
  current: argument_name(current)
102
75
  )
103
- add_offense(current, message: message)
76
+
77
+ add_offense(current, message: message) do |corrector|
78
+ swap_range(corrector, current, previous)
79
+ end
104
80
  end
105
81
 
106
82
  def argument_name(node)
@@ -30,33 +30,21 @@ module RuboCop
30
30
  # field :name, String, null: true
31
31
  # end
32
32
  #
33
- class OrderedFields < Cop
33
+ class OrderedFields < Base
34
+ extend AutoCorrector
35
+
36
+ include RuboCop::GraphQL::SwapRange
37
+
34
38
  MSG = "Fields should be sorted in an alphabetical order within their "\
35
39
  "section. "\
36
40
  "Field `%<current>s` should appear before `%<previous>s`."
37
41
 
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
- def autocorrect(node)
51
- declarations = field_declarations(processed_source.ast)
52
- node_index = declarations.map(&:location).find_index(node.location)
53
- previous_declaration = declarations.to_a[node_index - 1]
54
-
55
- current_range = declaration(node)
56
- previous_range = declaration(previous_declaration)
42
+ def on_class(node)
43
+ 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)
57
46
 
58
- lambda do |corrector|
59
- swap_range(corrector, current_range, previous_range)
47
+ register_offense(previous, current)
60
48
  end
61
49
  end
62
50
 
@@ -68,7 +56,10 @@ module RuboCop
68
56
  previous: field_name(previous),
69
57
  current: field_name(current)
70
58
  )
71
- add_offense(current, message: message)
59
+
60
+ add_offense(current, message: message) do |corrector|
61
+ swap_range(corrector, current, previous)
62
+ end
72
63
  end
73
64
 
74
65
  def field_name(node)
@@ -83,21 +74,6 @@ module RuboCop
83
74
  previous.source_range.last_line == current.source_range.first_line - 1
84
75
  end
85
76
 
86
- def declaration(node)
87
- buffer = processed_source.buffer
88
- begin_pos = node.source_range.begin_pos
89
- end_line = buffer.line_for_position(node.loc.expression.end_pos)
90
- end_pos = buffer.line_range(end_line).end_pos
91
- Parser::Source::Range.new(buffer, begin_pos, end_pos)
92
- end
93
-
94
- def swap_range(corrector, range1, range2)
95
- src1 = range1.source
96
- src2 = range2.source
97
- corrector.replace(range1, src2)
98
- corrector.replace(range2, src1)
99
- end
100
-
101
77
  def_node_search :field_declarations, <<~PATTERN
102
78
  {
103
79
  (send nil? :field (:sym _) ...)
@@ -7,7 +7,7 @@ module RuboCop
7
7
  # Comment lines can optionally be ignored.
8
8
  #
9
9
  # The maximum allowed length is configurable using the Max option.
10
- class ResolverMethodLength < Cop
10
+ class ResolverMethodLength < Base
11
11
  include RuboCop::Cop::ConfigurableMax
12
12
  include RuboCop::Cop::CodeLength
13
13
 
@@ -9,6 +9,7 @@ require_relative "graphql/field_description"
9
9
  require_relative "graphql/field_hash_key"
10
10
  require_relative "graphql/field_method"
11
11
  require_relative "graphql/field_name"
12
+ require_relative "graphql/legacy_dsl"
12
13
  require_relative "graphql/resolver_method_length"
13
14
  require_relative "graphql/object_description"
14
15
  require_relative "graphql/ordered_arguments"
@@ -20,7 +20,7 @@ module RuboCop
20
20
  PATTERN
21
21
 
22
22
  def initialize(argument_node)
23
- @nodes = argument_kwargs(argument_node)
23
+ @nodes = argument_kwargs(argument_node) || []
24
24
  end
25
25
 
26
26
  def description
@@ -11,7 +11,7 @@ module RuboCop
11
11
  (block
12
12
  (send nil? :field ...)
13
13
  (args)
14
- $...
14
+ {(begin $...)|$...}
15
15
  )
16
16
  PATTERN
17
17
 
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module GraphQL
5
+ module SwapRange
6
+ def swap_range(corrector, current, previous)
7
+ current_range = declaration(current)
8
+ previous_range = declaration(previous)
9
+
10
+ src1 = current_range.source
11
+ src2 = previous_range.source
12
+
13
+ corrector.replace(current_range, src2)
14
+ corrector.replace(previous_range, src1)
15
+ end
16
+
17
+ def declaration(node)
18
+ buffer = processed_source.buffer
19
+ begin_pos = node.source_range.begin_pos
20
+ end_line = buffer.line_for_position(node.loc.expression.end_pos)
21
+ end_pos = buffer.line_range(end_line).end_pos
22
+ Parser::Source::Range.new(buffer, begin_pos, end_pos)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,5 +1,5 @@
1
1
  module RuboCop
2
2
  module GraphQL
3
- VERSION = "0.8.2".freeze
3
+ VERSION = "0.10.2".freeze
4
4
  end
5
5
  end
@@ -9,6 +9,7 @@ require_relative "rubocop/graphql/version"
9
9
  require_relative "rubocop/graphql/inject"
10
10
  require_relative "rubocop/graphql/description_method"
11
11
  require_relative "rubocop/graphql/node_pattern"
12
+ require_relative "rubocop/graphql/swap_range"
12
13
 
13
14
  require_relative "rubocop/graphql/argument"
14
15
  require_relative "rubocop/graphql/argument/block"
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.8.2
4
+ version: 0.10.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Tsepelev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-23 00:00:00.000000000 Z
11
+ date: 2021-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -82,6 +82,7 @@ files:
82
82
  - LICENSE.txt
83
83
  - README.md
84
84
  - config/default.yml
85
+ - lib/refinements/underscore_string.rb
85
86
  - lib/rubocop-graphql.rb
86
87
  - lib/rubocop/cop/graphql/argument_description.rb
87
88
  - lib/rubocop/cop/graphql/argument_name.rb
@@ -92,6 +93,7 @@ files:
92
93
  - lib/rubocop/cop/graphql/field_hash_key.rb
93
94
  - lib/rubocop/cop/graphql/field_method.rb
94
95
  - lib/rubocop/cop/graphql/field_name.rb
96
+ - lib/rubocop/cop/graphql/legacy_dsl.rb
95
97
  - lib/rubocop/cop/graphql/object_description.rb
96
98
  - lib/rubocop/cop/graphql/ordered_arguments.rb
97
99
  - lib/rubocop/cop/graphql/ordered_fields.rb
@@ -109,6 +111,7 @@ files:
109
111
  - lib/rubocop/graphql/inject.rb
110
112
  - lib/rubocop/graphql/node_pattern.rb
111
113
  - lib/rubocop/graphql/schema_member.rb
114
+ - lib/rubocop/graphql/swap_range.rb
112
115
  - lib/rubocop/graphql/version.rb
113
116
  homepage: https://github.com/DmitryTsepelev/rubocop-graphql
114
117
  licenses: