leftovers 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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