leftovers 0.3.0 → 0.4.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.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/README.md +28 -45
  4. data/docs/Configuration.md +598 -0
  5. data/exe/leftovers +1 -1
  6. data/leftovers.gemspec +9 -6
  7. data/lib/config/attr_encrypted.yml +3 -4
  8. data/lib/config/audited.yml +9 -4
  9. data/lib/config/datagrid.yml +1 -1
  10. data/lib/config/flipper.yml +1 -3
  11. data/lib/config/graphql.yml +15 -13
  12. data/lib/config/okcomputer.yml +1 -3
  13. data/lib/config/parser.yml +89 -91
  14. data/lib/config/rails.yml +125 -107
  15. data/lib/config/redcarpet.yml +35 -38
  16. data/lib/config/rollbar.yml +1 -3
  17. data/lib/config/rspec.yml +18 -10
  18. data/lib/config/ruby.yml +40 -50
  19. data/lib/config/selenium-webdriver.yml +19 -21
  20. data/lib/config/sidekiq.yml +4 -8
  21. data/lib/config/will_paginate.yml +12 -14
  22. data/lib/leftovers.rb +38 -26
  23. data/lib/leftovers/ast.rb +8 -0
  24. data/lib/leftovers/ast/builder.rb +4 -4
  25. data/lib/leftovers/ast/node.rb +50 -77
  26. data/lib/leftovers/backports.rb +27 -23
  27. data/lib/leftovers/cli.rb +0 -3
  28. data/lib/leftovers/collector.rb +4 -8
  29. data/lib/leftovers/config.rb +19 -25
  30. data/lib/leftovers/config_validator.rb +60 -0
  31. data/lib/leftovers/config_validator/error_processor.rb +196 -0
  32. data/lib/leftovers/config_validator/schema_hash.rb +495 -0
  33. data/lib/leftovers/definition.rb +11 -36
  34. data/lib/leftovers/definition_set.rb +7 -17
  35. data/lib/leftovers/dynamic_processors.rb +11 -0
  36. data/lib/leftovers/dynamic_processors/call.rb +25 -0
  37. data/lib/leftovers/dynamic_processors/call_definition.rb +31 -0
  38. data/lib/leftovers/dynamic_processors/definition.rb +26 -0
  39. data/lib/leftovers/dynamic_processors/each.rb +19 -0
  40. data/lib/leftovers/dynamic_processors/null.rb +9 -0
  41. data/lib/leftovers/erb.rb +2 -2
  42. data/lib/leftovers/file.rb +3 -5
  43. data/lib/leftovers/file_collector.rb +65 -49
  44. data/lib/leftovers/file_list.rb +0 -1
  45. data/lib/leftovers/haml.rb +1 -1
  46. data/lib/leftovers/matcher_builders.rb +24 -0
  47. data/lib/leftovers/matcher_builders/and.rb +19 -0
  48. data/lib/leftovers/matcher_builders/and_not.rb +14 -0
  49. data/lib/leftovers/matcher_builders/argument_node_value.rb +21 -0
  50. data/lib/leftovers/matcher_builders/name.rb +29 -0
  51. data/lib/leftovers/matcher_builders/node.rb +40 -0
  52. data/lib/leftovers/matcher_builders/node_has_argument.rb +71 -0
  53. data/lib/leftovers/matcher_builders/node_has_keyword_argument.rb +22 -0
  54. data/lib/leftovers/matcher_builders/node_has_positional_argument.rb +24 -0
  55. data/lib/leftovers/matcher_builders/node_name.rb +15 -0
  56. data/lib/leftovers/matcher_builders/node_pair_name.rb +18 -0
  57. data/lib/leftovers/matcher_builders/node_pair_value.rb +16 -0
  58. data/lib/leftovers/matcher_builders/node_path.rb +14 -0
  59. data/lib/leftovers/matcher_builders/node_type.rb +28 -0
  60. data/lib/leftovers/matcher_builders/or.rb +73 -0
  61. data/lib/leftovers/matcher_builders/path.rb +15 -0
  62. data/lib/leftovers/matcher_builders/string.rb +11 -0
  63. data/lib/leftovers/matcher_builders/string_pattern.rb +19 -0
  64. data/lib/leftovers/matcher_builders/unless.rb +13 -0
  65. data/lib/leftovers/matchers.rb +26 -0
  66. data/lib/leftovers/matchers/all.rb +25 -0
  67. data/lib/leftovers/matchers/and.rb +24 -0
  68. data/lib/leftovers/matchers/any.rb +27 -0
  69. data/lib/leftovers/matchers/node_has_any_keyword_argument.rb +28 -0
  70. data/lib/leftovers/matchers/node_has_any_positional_argument_with_value.rb +25 -0
  71. data/lib/leftovers/matchers/node_has_positional_argument.rb +23 -0
  72. data/lib/leftovers/matchers/node_has_positional_argument_with_value.rb +25 -0
  73. data/lib/leftovers/matchers/node_name.rb +23 -0
  74. data/lib/leftovers/matchers/node_pair_value.rb +23 -0
  75. data/lib/leftovers/matchers/node_path.rb +23 -0
  76. data/lib/leftovers/matchers/node_scalar_value.rb +25 -0
  77. data/lib/leftovers/matchers/node_type.rb +23 -0
  78. data/lib/leftovers/matchers/not.rb +23 -0
  79. data/lib/leftovers/matchers/or.rb +26 -0
  80. data/lib/leftovers/merged_config.rb +13 -8
  81. data/lib/leftovers/parser.rb +1 -4
  82. data/lib/leftovers/processor_builders.rb +22 -0
  83. data/lib/leftovers/processor_builders/action.rb +63 -0
  84. data/lib/leftovers/processor_builders/add_prefix.rb +20 -0
  85. data/lib/leftovers/processor_builders/add_suffix.rb +20 -0
  86. data/lib/leftovers/processor_builders/argument.rb +25 -0
  87. data/lib/leftovers/processor_builders/dynamic.rb +27 -0
  88. data/lib/leftovers/processor_builders/each.rb +36 -0
  89. data/lib/leftovers/processor_builders/each_action.rb +51 -0
  90. data/lib/leftovers/processor_builders/each_dynamic.rb +54 -0
  91. data/lib/leftovers/processor_builders/each_for_definition_set.rb +36 -0
  92. data/lib/leftovers/processor_builders/itself.rb +13 -0
  93. data/lib/leftovers/processor_builders/keyword.rb +24 -0
  94. data/lib/leftovers/processor_builders/keyword_argument.rb +14 -0
  95. data/lib/leftovers/processor_builders/transform.rb +55 -0
  96. data/lib/leftovers/processor_builders/transform_chain.rb +24 -0
  97. data/lib/leftovers/processor_builders/transform_set.rb +47 -0
  98. data/lib/leftovers/processor_builders/value.rb +13 -0
  99. data/lib/leftovers/rake_task.rb +1 -1
  100. data/lib/leftovers/reporter.rb +1 -1
  101. data/lib/leftovers/value_processors.rb +40 -0
  102. data/lib/leftovers/value_processors/add_dynamic_prefix.rb +31 -0
  103. data/lib/leftovers/value_processors/add_dynamic_suffix.rb +31 -0
  104. data/lib/leftovers/value_processors/add_prefix.rb +20 -0
  105. data/lib/leftovers/value_processors/add_suffix.rb +20 -0
  106. data/lib/leftovers/value_processors/camelize.rb +24 -0
  107. data/lib/leftovers/value_processors/capitalize.rb +19 -0
  108. data/lib/leftovers/value_processors/deconstantize.rb +24 -0
  109. data/lib/leftovers/value_processors/delete_after.rb +22 -0
  110. data/lib/leftovers/value_processors/delete_before.rb +22 -0
  111. data/lib/leftovers/value_processors/delete_prefix.rb +26 -0
  112. data/lib/leftovers/value_processors/delete_suffix.rb +26 -0
  113. data/lib/leftovers/value_processors/demodulize.rb +24 -0
  114. data/lib/leftovers/value_processors/downcase.rb +19 -0
  115. data/lib/leftovers/value_processors/each.rb +21 -0
  116. data/lib/leftovers/value_processors/each_for_definition_set.rb +33 -0
  117. data/lib/leftovers/value_processors/each_keyword.rb +27 -0
  118. data/lib/leftovers/value_processors/each_keyword_argument.rb +27 -0
  119. data/lib/leftovers/value_processors/each_positional_argument.rb +24 -0
  120. data/lib/leftovers/value_processors/itself.rb +17 -0
  121. data/lib/leftovers/value_processors/keyword.rb +36 -0
  122. data/lib/leftovers/value_processors/keyword_argument.rb +36 -0
  123. data/lib/leftovers/value_processors/parameterize.rb +24 -0
  124. data/lib/leftovers/value_processors/placeholder.rb +18 -0
  125. data/lib/leftovers/value_processors/pluralize.rb +24 -0
  126. data/lib/leftovers/value_processors/positional_argument.rb +26 -0
  127. data/lib/leftovers/value_processors/replace_value.rb +18 -0
  128. data/lib/leftovers/value_processors/return_definition.rb +24 -0
  129. data/lib/leftovers/value_processors/return_string.rb +14 -0
  130. data/lib/leftovers/value_processors/singularize.rb +24 -0
  131. data/lib/leftovers/value_processors/split.rb +22 -0
  132. data/lib/leftovers/value_processors/swapcase.rb +19 -0
  133. data/lib/leftovers/value_processors/titleize.rb +24 -0
  134. data/lib/leftovers/value_processors/underscore.rb +24 -0
  135. data/lib/leftovers/value_processors/upcase.rb +19 -0
  136. data/lib/leftovers/version.rb +1 -1
  137. metadata +143 -24
  138. data/lib/leftovers/argument_rule.rb +0 -229
  139. data/lib/leftovers/core_ext.rb +0 -15
  140. data/lib/leftovers/hash_rule.rb +0 -42
  141. data/lib/leftovers/name_rule.rb +0 -98
  142. data/lib/leftovers/rule.rb +0 -82
  143. data/lib/leftovers/transform_rule.rb +0 -169
  144. data/lib/leftovers/value_rule.rb +0 -57
@@ -0,0 +1,196 @@
1
+ # frozen-string-literal: true
2
+
3
+ ::Leftovers.try_require 'did_you_mean'
4
+
5
+ module Leftovers
6
+ module ConfigValidator
7
+ module ErrorProcessor # rubocop:disable Metrics/ModuleLength
8
+ LENGTH_TYPE = %w{minItems minLength minProperties}.freeze
9
+ TYPE_TYPE = %w{null string boolean integer number array object}.freeze
10
+ VALUE_TYPE = %w{enum const}.freeze
11
+ REQUIRED_TYPE = %w{required}.freeze
12
+
13
+ class << self
14
+ def process(errors) # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize
15
+ error_data_pointers = []
16
+ errors = group_errors(errors)
17
+ errors.flat_map do |data_pointer, error_group| # rubocop:disable Metrics/BlockLength
18
+ next if error_data_pointers.find { |x| x.start_with?(data_pointer) }
19
+
20
+ # original_error_group = error_group.dup
21
+ length_errors = error_group.select { |x| LENGTH_TYPE.include?(x['type']) }
22
+ value_errors = error_group.select { |x| VALUE_TYPE.include?(x['type']) }
23
+ type_errors = error_group.select { |x| TYPE_TYPE.include?(x['type']) }
24
+ required_errors = error_group.select { |x| REQUIRED_TYPE.include?(x['type']) }
25
+ messages = []
26
+
27
+ if !type_errors.empty? && (error_group - type_errors).empty?
28
+ error_group -= type_errors
29
+ error_data_pointers << data_pointer
30
+ any_of = group_by_same_any_of(type_errors)
31
+ if any_of.length == 1
32
+ was_class = to_json_type(type_errors.first['data'])
33
+ error_types = type_errors.map { |x| x['type'] }
34
+
35
+ messages << <<~MESSAGE.chomp
36
+ #{data_pointer}: must be #{an to_sentence(error_types, 'or')} (was #{an was_class})
37
+ MESSAGE
38
+ # :nocov:
39
+ else
40
+ nil
41
+ # :nocov:
42
+ end
43
+ elsif !required_errors.empty? && error_group.first['data'].is_a?(Hash)
44
+ error_data_pointers << data_pointer
45
+ group_by_same_any_of(required_errors).each do |_any_of_key, any_of_value|
46
+ required_keywords = any_of_value.flat_map { |x| x['details']['missing_keys'] }
47
+ error_group -= any_of_value
48
+ if any_of_value.length > 1
49
+ messages << <<~MESSAGE
50
+ #{data_pointer}: requires at least one of these keywords: #{to_sentence(required_keywords, 'or')}
51
+ MESSAGE
52
+
53
+ # :nocov:
54
+ else
55
+ nil
56
+ # :nocov:
57
+ # messages << "#{data_pointer}: requires keyword: #{required_keywords.first}"
58
+ end
59
+ end
60
+ elsif !length_errors.empty?
61
+ error_data_pointers << data_pointer
62
+ messages << "#{data_pointer}: can't be empty"
63
+ end
64
+
65
+ error_group.each do |error| # rubocop:disable Metrics/BlockLength
66
+ type = error['type']
67
+ case type
68
+ when 'schema'
69
+ error_data_pointers << data_pointer
70
+ parent_pointer = parent(data_pointer)
71
+ if ::File.basename(error['schema_pointer']) == 'additionalProperties'
72
+ keyword = tail(data_pointer)
73
+ parent_schema_pointer = parent(error['schema_pointer'])
74
+ actual_keywords = schema_hash_dig(parent_schema_pointer)['properties'].keys
75
+ corrections = did_you_mean(keyword, actual_keywords)
76
+
77
+ messages << <<~MESSAGE.chomp
78
+ #{parent_pointer}: invalid property keyword: #{keyword}
79
+ Valid keywords: #{to_sentence actual_keywords}
80
+ #{"Did you mean? #{to_sentence corrections, 'or'}" unless corrections.empty?}
81
+ MESSAGE
82
+ # :nocov:
83
+ else
84
+ nil
85
+ # :nocov:
86
+ end
87
+ when 'enum'
88
+ next if error['data'].is_a?(Hash)
89
+
90
+ error_data_pointers << data_pointer
91
+ given_value = error['data']
92
+ valid_values = value_errors.first['schema']['enum']
93
+ corrections = did_you_mean(given_value, valid_values)
94
+ messages << <<~MESSAGE
95
+ #{data_pointer}: can't be: #{given_value}
96
+ Valid values: #{to_sentence valid_values, 'or'}
97
+ #{"Did you mean? #{to_sentence corrections, 'or'}" unless corrections.empty?}
98
+ MESSAGE
99
+ when 'not'
100
+ next unless error['data'].is_a?(Hash)
101
+
102
+ error_data_pointers << data_pointer
103
+ if error['schema']['required']
104
+ invalid_combinations = error['schema']['required'] & error['data'].keys
105
+ messages << <<~MESSAGE
106
+ #{data_pointer}: use only one of: #{to_sentence invalid_combinations, 'or'}
107
+ MESSAGE
108
+ # :nocov:
109
+ else
110
+ end
111
+ else
112
+ end
113
+ # :nocov:
114
+ end
115
+
116
+ if messages.empty?
117
+ error_data_pointers << data_pointer
118
+ "#{data_pointer} is invalid"
119
+ else
120
+ messages
121
+ end
122
+ end.compact.map(&:strip)
123
+ end
124
+
125
+ private
126
+
127
+ def did_you_mean(word, dictionary)
128
+ # :nocov:
129
+ if defined?(::DidYouMean::SpellChecker)
130
+ ::DidYouMean::SpellChecker.new(dictionary: dictionary).correct(word)
131
+ else
132
+ []
133
+ end
134
+ # :nocov:
135
+ end
136
+
137
+ def schema_hash_dig(schema_pointer)
138
+ ::Leftovers::ConfigValidator::SCHEMA_HASH.dig(
139
+ *schema_pointer.split('/').drop(1).map { |x| x.match?(/\A\d+\z/) ? x.to_i : x }
140
+ )
141
+ end
142
+
143
+ def parent(pointer)
144
+ ::File.dirname(pointer)
145
+ end
146
+
147
+ def tail(pointer)
148
+ ::File.basename(pointer)
149
+ end
150
+
151
+ def to_json_type(value)
152
+ case value
153
+ when Hash then 'object'
154
+ when Float then 'number'
155
+ when true, false then 'boolean'
156
+ when nil then 'null'
157
+ else value.class.name.downcase
158
+ end
159
+ end
160
+
161
+ def an(str)
162
+ case str[0]
163
+ # when nil then ""
164
+ when 'a', 'e', 'i', 'o', 'u' then "an #{str}"
165
+ else "a #{str}"
166
+ end
167
+ end
168
+
169
+ def to_sentence(ary, join_word = 'and')
170
+ case ary.length
171
+ when 1 then ary.first
172
+ when 2 then ary.join(" #{join_word} ")
173
+ else
174
+ ary = ary.dup
175
+ last = ary.pop(2)
176
+ [*ary, last.join(", #{join_word} ")].join(', ')
177
+ end
178
+ end
179
+
180
+ def group_errors(errors)
181
+ errors = errors.map do |x|
182
+ x.delete('root_schema')
183
+ x
184
+ end
185
+ errors.group_by { |x| x['data_pointer'] }.sort.reverse.to_h
186
+ end
187
+
188
+ def group_by_same_any_of(errors)
189
+ errors.group_by do |x|
190
+ x['schema_pointer'].match?(%r{/anyOf/\d+$}) && parent(parent(x['schema_pointer']))
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,495 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Leftovers
4
+ module ConfigValidator # rubocop:disable Metrics/ModuleLength
5
+ SCHEMA_HASH = {
6
+ '$schema' => 'http://json-schema.org/draft-06/schema#',
7
+ 'type' => 'object',
8
+ 'definitions' => {
9
+ 'true' => { 'enum' => [true, 'true'] },
10
+ 'string' => {
11
+ 'type' => 'string',
12
+ 'minLength' => 1
13
+ },
14
+ 'stringPattern' => {
15
+ 'type' => 'object',
16
+ 'properties' => {
17
+ 'match' => { '$ref' => '#/definitions/string' },
18
+ 'matches' => { '$ref' => '#/definitions/string' },
19
+ 'has_prefix' => { '$ref' => '#/definitions/string' },
20
+ 'has_suffix' => { '$ref' => '#/definitions/string' }
21
+ },
22
+ 'minProperties' => 0,
23
+ 'additionalProperties' => true,
24
+ 'allOf' => [
25
+ # synonyms
26
+ { 'not' => { 'required' => %w{match matches} } },
27
+ # incompatible groups
28
+ { 'not' => { 'required' => %w{match has_prefix} } },
29
+ { 'not' => { 'required' => %w{match has_suffix} } },
30
+ { 'not' => { 'required' => %w{matches has_prefix} } },
31
+ { 'not' => { 'required' => %w{matches has_suffix} } }
32
+ ]
33
+ },
34
+ 'stringList' => {
35
+ 'anyOf' => [
36
+ {
37
+ 'type' => 'array',
38
+ 'items' => { '$ref' => '#/definitions/string' },
39
+ 'minItems' => 1,
40
+ 'uniqueItems' => true
41
+ },
42
+ { '$ref' => '#/definitions/string' }
43
+ ]
44
+ },
45
+ 'name' => {
46
+ 'anyOf' => [
47
+ { '$ref' => '#/definitions/string' },
48
+ { 'allOf' => [
49
+ { '$ref' => '#/definitions/stringPattern' },
50
+ {
51
+ 'type' => 'object',
52
+ 'properties' => {
53
+ 'match' => true, 'matches' => true,
54
+ 'has_prefix' => true, 'has_suffix' => true,
55
+ 'unless' => { '$ref' => '#/definitions/nameList' }
56
+ },
57
+ 'minProperties' => 1,
58
+ 'additionalProperties' => false
59
+ }
60
+ ] }
61
+ ]
62
+ },
63
+ 'nameList' => {
64
+ 'anyOf' => [
65
+ {
66
+ 'type' => 'array',
67
+ 'items' => { '$ref' => '#/definitions/name' },
68
+ 'minItems' => 1,
69
+ 'uniqueItems' => true
70
+ },
71
+ { '$ref' => '#/definitions/name' }
72
+ ]
73
+ },
74
+ 'argumentPosition' => {
75
+ 'anyOf' => [
76
+ { '$ref' => '#/definitions/string' },
77
+ { 'type' => 'integer', 'minimum' => 0 },
78
+ { '$ref' => '#/definitions/name' }
79
+ ]
80
+ },
81
+ 'argumentPositionList' => {
82
+ 'anyOf' => [
83
+ { '$ref' => '#/definitions/argumentPosition' },
84
+ {
85
+ 'type' => 'array',
86
+ 'items' => { '$ref' => '#/definitions/argumentPosition' },
87
+ 'minItems' => 1,
88
+ 'uniqueItems' => true
89
+ }
90
+ ]
91
+ },
92
+ 'valueType' => {
93
+ 'type' => 'string',
94
+ 'enum' => %w{String Symbol Integer Float}
95
+ },
96
+ 'valueTypeList' => {
97
+ 'anyOf' => [
98
+ { '$ref' => '#/definitions/valueType' },
99
+ {
100
+ 'type' => 'array',
101
+ 'items' => { '$ref' => '#/definitions/valueType' },
102
+ 'minItems' => 1,
103
+ 'uniqueItems' => true
104
+ }
105
+ ]
106
+ },
107
+ 'hasValue' => {
108
+ 'anyOf' => [
109
+ { 'type' => 'string' },
110
+ { 'type' => 'integer' },
111
+ { 'type' => 'number' },
112
+ { 'type' => 'boolean' },
113
+ { 'type' => 'null' }
114
+ ]
115
+ },
116
+ 'hasValueList' => {
117
+ 'anyOf' => [
118
+ { '$ref' => '#/definitions/hasValue' },
119
+ {
120
+ 'type' => 'array',
121
+ 'items' => { '$ref' => '#/definitions/hasValue' },
122
+ 'minItems' => 1,
123
+ 'uniqueItems' => true
124
+ }
125
+ ]
126
+ },
127
+ 'hasArgument' => {
128
+ 'anyOf' => [
129
+ { '$ref' => '#/definitions/string' },
130
+ { 'type' => 'integer', 'minimum' => 0 },
131
+ {
132
+ 'type' => 'object',
133
+ 'properties' => {
134
+ 'at' => { '$ref' => '#/definitions/argumentPositionList' },
135
+ 'has_value' => { '$ref' => '#/definitions/hasValueList' },
136
+ 'has_value_type' => { '$ref' => '#/definitions/valueTypeList' },
137
+ 'unless' => { '$ref' => '#/definitions/hasArgumentList' }
138
+ },
139
+ 'minProperties' => 1,
140
+ 'additionalProperties' => false
141
+ }
142
+ ]
143
+ },
144
+ 'hasArgumentList' => {
145
+ 'anyOf' => [
146
+ { '$ref' => '#/definitions/hasArgument' },
147
+ {
148
+ 'type' => 'array',
149
+ 'items' => { '$ref' => '#/definitions/hasArgument' },
150
+ 'minItems' => 1,
151
+ 'uniqueItems' => true
152
+ }
153
+ ]
154
+ },
155
+ 'rulePattern' => {
156
+ 'type' => 'object',
157
+ 'properties' => {
158
+ 'name' => { '$ref' => '#/definitions/nameList' },
159
+ 'names' => { '$ref' => '#/definitions/nameList' },
160
+ 'path' => { '$ref' => '#/definitions/stringList' },
161
+ 'paths' => { '$ref' => '#/definitions/stringList' },
162
+ 'has_argument' => { '$ref' => '#/definitions/hasArgumentList' },
163
+ 'has_arguments' => { '$ref' => '#/definitions/hasArgumentList' }
164
+ },
165
+ 'minProperties' => 1,
166
+ 'allOf' => [
167
+ # synonyms
168
+ { 'not' => { 'required' => %w{name names} } },
169
+ { 'not' => { 'required' => %w{path paths} } },
170
+ { 'not' => { 'required' => %w{has_argument has_arguments} } }
171
+ ]
172
+ },
173
+ 'transformProperties' => {
174
+ 'type' => 'object',
175
+ 'properties' => {
176
+ 'original' => { '$ref' => '#/definitions/true' },
177
+ 'pluralize' => { '$ref' => '#/definitions/true' },
178
+ 'singularize' => { '$ref' => '#/definitions/true' },
179
+ 'camelize' => { '$ref' => '#/definitions/true' },
180
+ 'camelcase' => { '$ref' => '#/definitions/true' },
181
+ 'underscore' => { '$ref' => '#/definitions/true' },
182
+ 'titleize' => { '$ref' => '#/definitions/true' },
183
+ 'titlecase' => { '$ref' => '#/definitions/true' },
184
+ 'demodulize' => { '$ref' => '#/definitions/true' },
185
+ 'deconstantize' => { '$ref' => '#/definitions/true' },
186
+ 'parameterize' => { '$ref' => '#/definitions/true' },
187
+ 'downcase' => { '$ref' => '#/definitions/true' },
188
+ 'upcase' => { '$ref' => '#/definitions/true' },
189
+ 'capitalize' => { '$ref' => '#/definitions/true' },
190
+ 'swapcase' => { '$ref' => '#/definitions/true' },
191
+
192
+ 'add_prefix' => { '$ref' => '#/definitions/actionList' },
193
+ 'add_suffix' => { '$ref' => '#/definitions/actionList' },
194
+
195
+ 'split' => { '$ref' => '#/definitions/string' },
196
+ 'delete_prefix' => { '$ref' => '#/definitions/string' },
197
+ 'delete_suffix' => { '$ref' => '#/definitions/string' },
198
+ 'delete_before' => { '$ref' => '#/definitions/string' },
199
+ 'delete_after' => { '$ref' => '#/definitions/string' }
200
+ }
201
+ },
202
+ 'transform' => {
203
+ 'anyOf' => [
204
+ {
205
+ 'type' => 'string',
206
+ 'enum' => %w{
207
+ original
208
+ pluralize
209
+ singularize
210
+ camelize
211
+ camelcase
212
+ underscore
213
+ titleize
214
+ titlecase
215
+ demodulize
216
+ deconstantize
217
+ parameterize
218
+ downcase
219
+ upcase
220
+ capitalize
221
+ swapcase
222
+ }
223
+ },
224
+ { 'allOf' => [
225
+ { '$ref' => '#/definitions/transformProperties' },
226
+ {
227
+ 'type' => 'object',
228
+ 'properties' => {
229
+ 'original' => true,
230
+ 'pluralize' => true,
231
+ 'singularize' => true,
232
+ 'camelize' => true,
233
+ 'camelcase' => true,
234
+ 'underscore' => true,
235
+ 'titleize' => true,
236
+ 'titlecase' => true,
237
+ 'demodulize' => true,
238
+ 'deconstantize' => true,
239
+ 'parameterize' => true,
240
+ 'downcase' => true,
241
+ 'upcase' => true,
242
+ 'capitalize' => true,
243
+ 'swapcase' => true,
244
+ 'add_prefix' => true,
245
+ 'add_suffix' => true,
246
+ 'split' => true,
247
+ 'delete_prefix' => true,
248
+ 'delete_suffix' => true,
249
+ 'delete_before' => true,
250
+ 'delete_after' => true
251
+ },
252
+ 'additionalProperties' => false
253
+ }
254
+ ] }
255
+ ]
256
+ },
257
+ 'transformList' => {
258
+ 'anyOf' => [
259
+ { '$ref' => '#/definitions/transform' },
260
+ {
261
+ 'type' => 'array',
262
+ 'items' => { '$ref' => '#/definitions/transform' },
263
+ 'minItems' => 1,
264
+ 'uniqueItems' => true
265
+ }
266
+ ]
267
+ },
268
+ 'keyword' => {
269
+ 'anyOf' => [
270
+ { '$ref' => '#/definitions/name' }
271
+ ]
272
+ },
273
+ 'keywordList' => {
274
+ 'anyOf' => [
275
+ { '$ref' => '#/definitions/keyword' },
276
+ {
277
+ 'type' => 'array',
278
+ 'items' => { '$ref' => '#/definitions/keyword' },
279
+ 'minItems' => 1,
280
+ 'uniqueItems' => true
281
+ }
282
+ ]
283
+ },
284
+ 'action' => {
285
+ 'anyOf' => [
286
+ { '$ref' => '#/definitions/string' },
287
+ { 'type' => 'integer', 'minimum' => 0 },
288
+ { 'allOf' => [
289
+ { '$ref' => '#/definitions/transformProperties' },
290
+ {
291
+ 'type' => 'object',
292
+ 'properties' => {
293
+ 'argument' => { '$ref' => '#/definitions/argumentPositionList' },
294
+ 'arguments' => { '$ref' => '#/definitions/argumentPositionList' },
295
+ 'keyword' => { '$ref' => '#/definitions/keywordList' },
296
+ 'keywords' => { '$ref' => '#/definitions/keywordList' },
297
+ 'itself' => { '$ref' => '#/definitions/true' },
298
+ 'value' => { '$ref' => '#/definitions/string' },
299
+ 'nested' => { '$ref' => '#/definitions/actionList' },
300
+ 'recursive' => { '$ref' => '#/definitions/true' },
301
+ 'transforms' => { '$ref' => '#/definitions/transformList' },
302
+ 'pluralize' => true,
303
+ 'singularize' => true,
304
+ 'camelize' => true,
305
+ 'camelcase' => true,
306
+ 'underscore' => true,
307
+ 'titleize' => true,
308
+ 'titlecase' => true,
309
+ 'demodulize' => true,
310
+ 'deconstantize' => true,
311
+ 'parameterize' => true,
312
+ 'downcase' => true,
313
+ 'upcase' => true,
314
+ 'capitalize' => true,
315
+ 'swapcase' => true,
316
+ 'add_prefix' => true,
317
+ 'add_suffix' => true,
318
+ 'split' => true,
319
+ 'delete_prefix' => true,
320
+ 'delete_suffix' => true,
321
+ 'delete_before' => true,
322
+ 'delete_after' => true
323
+ },
324
+ 'additionalProperties' => false,
325
+ 'allOf' => [
326
+ # synonyms
327
+ { 'not' => { 'required' => %w{keyword keywords} } },
328
+ { 'not' => { 'required' => %w{argument arguments} } },
329
+ # any of
330
+ { 'anyOf' => [
331
+ { 'required' => ['argument'] },
332
+ { 'required' => ['arguments'] },
333
+ { 'required' => ['keyword'] },
334
+ { 'required' => ['keywords'] },
335
+ { 'required' => ['itself'] },
336
+ { 'required' => ['value'] }
337
+ ] }
338
+ ]
339
+ }
340
+ ] }
341
+ ]
342
+ },
343
+ 'actionList' => {
344
+ 'anyOf' => [
345
+ { '$ref' => '#/definitions/action' },
346
+ {
347
+ 'type' => 'array',
348
+ 'items' => { '$ref' => '#/definitions/action' },
349
+ 'minItems' => 1,
350
+ 'uniqueItems' => true
351
+ }
352
+ ]
353
+ },
354
+ 'dynamicAction' => {
355
+ 'type' => 'object',
356
+ 'properties' => {
357
+ 'call' => { '$ref' => '#/definitions/actionList' },
358
+ 'calls' => { '$ref' => '#/definitions/actionList' },
359
+ 'define' => { '$ref' => '#/definitions/actionList' },
360
+ 'defines' => { '$ref' => '#/definitions/actionList' }
361
+ },
362
+ 'additionalProperties' => true,
363
+ 'minProperties' => 1,
364
+ 'allOf' => [
365
+ # synonyms
366
+ { 'not' => { 'required' => %w{call calls} } },
367
+ { 'not' => { 'required' => %w{define defines} } },
368
+ # At least one of
369
+ { 'anyOf' => [
370
+ { 'required' => ['call'] }, { 'required' => ['calls'] },
371
+ { 'required' => ['define'] }, { 'required' => ['defines'] }
372
+ ] }
373
+ ]
374
+ },
375
+ 'ruleMatcherList' => {
376
+ 'anyOf' => [
377
+ {
378
+ 'type' => 'array',
379
+ 'items' => { '$ref' => '#/definitions/ruleMatcher' },
380
+ 'minItems' => 1,
381
+ 'uniqueItems' => true
382
+ },
383
+ { '$ref' => '#/definitions/ruleMatcher' }
384
+ ]
385
+ },
386
+ 'ruleMatcher' => {
387
+ 'allOf' => [
388
+ { '$ref' => '#/definitions/rulePattern' },
389
+ { 'anyOf' => [
390
+ { 'required' => ['name'] }, { 'required' => ['names'] },
391
+ { 'required' => ['path'] }, { 'required' => ['paths'] },
392
+ { 'required' => ['has_argument'] }, { 'required' => ['has_arguments'] },
393
+ { 'required' => ['unless'] }
394
+ ] },
395
+ {
396
+ 'type' => 'object',
397
+ 'properties' => {
398
+ # unfortunately this repetition is necessary to use additionalProperties: false
399
+ 'name' => true, 'names' => true,
400
+ 'path' => true, 'paths' => true,
401
+ 'has_argument' => true, 'has_arguments' => true,
402
+ 'unless' => { '$ref' => '#/definitions/ruleMatcherList' }
403
+
404
+ },
405
+ 'additionalProperties' => false,
406
+ 'minProperties' => 1
407
+ }
408
+ ]
409
+ },
410
+ 'dynamic' => {
411
+ 'allOf' => [
412
+ { '$ref' => '#/definitions/rulePattern' },
413
+ { '$ref' => '#/definitions/dynamicAction' },
414
+ { 'anyOf' => [
415
+ { 'required' => ['name'] }, { 'required' => ['names'] },
416
+ { 'required' => ['path'] }, { 'required' => ['paths'] },
417
+ { 'required' => ['has_argument'] }, { 'required' => ['has_arguments'] },
418
+ { 'required' => ['unless'] }
419
+ ] },
420
+ {
421
+ 'type' => 'object',
422
+ 'properties' => {
423
+ # unfortunately this repetition is necessary to use additionalProperties: false
424
+ 'name' => true, 'names' => true,
425
+ 'path' => true, 'paths' => true,
426
+ 'has_argument' => true, 'has_arguments' => true,
427
+ 'unless' => { '$ref' => '#/definitions/ruleMatcherList' },
428
+
429
+ 'call' => true, 'calls' => true,
430
+ 'define' => true, 'defines' => true
431
+ },
432
+ 'additionalProperties' => false,
433
+ 'minProperties' => 1
434
+ }
435
+ ]
436
+ },
437
+ 'dynamicList' => {
438
+ 'anyOf' => [
439
+ {
440
+ 'type' => 'array',
441
+ 'items' => { '$ref' => '#/definitions/dynamic' },
442
+ 'minItems' => 1,
443
+ 'uniqueItems' => true
444
+ },
445
+ { '$ref' => '#/definitions/dynamic' }
446
+ ]
447
+ },
448
+ 'keep' => {
449
+ 'anyOf' => [
450
+ { '$ref' => '#/definitions/string' },
451
+ {
452
+ 'allOf' => [
453
+ { '$ref' => '#/definitions/stringPattern' },
454
+ { '$ref' => '#/definitions/rulePattern' },
455
+ {
456
+ 'type' => 'object',
457
+ 'properties' => {
458
+ # unfortunately this repetition is necessary to use additionalProperties: false
459
+ 'name' => true, 'names' => true,
460
+ 'has_prefix' => true, 'has_suffix' => true, 'matches' => true,
461
+ 'path' => true, 'paths' => true,
462
+ 'has_argument' => true, 'has_arguments' => true,
463
+ 'unless' => { '$ref' => '#/definitions/keepList' }
464
+ },
465
+ 'additionalProperties' => false,
466
+ 'minProperties' => 1
467
+ }
468
+ ]
469
+ }
470
+ ]
471
+ },
472
+ 'keepList' => {
473
+ 'anyOf' => [
474
+ {
475
+ 'type' => 'array',
476
+ 'items' => { '$ref' => '#/definitions/keep' },
477
+ 'minItems' => 1,
478
+ 'uniqueItems' => true
479
+ },
480
+ { '$ref' => '#/definitions/keep' }
481
+ ]
482
+ }
483
+ },
484
+ 'properties' => {
485
+ 'include_paths' => { '$ref' => '#/definitions/stringList' },
486
+ 'exclude_paths' => { '$ref' => '#/definitions/stringList' },
487
+ 'test_paths' => { '$ref' => '#/definitions/stringList' },
488
+ 'requires' => { '$ref' => '#/definitions/stringList' },
489
+ 'gems' => { '$ref' => '#/definitions/stringList' },
490
+ 'keep' => { '$ref' => '#/definitions/keepList' },
491
+ 'dynamic' => { '$ref' => '#/definitions/dynamicList' }
492
+ }
493
+ }.freeze
494
+ end
495
+ end