leftovers 0.5.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -1
  3. data/README.md +21 -1
  4. data/docs/Configuration.md +188 -20
  5. data/leftovers.gemspec +8 -5
  6. data/lib/config/actionmailer.yml +11 -11
  7. data/lib/config/actionpack.yml +11 -0
  8. data/lib/config/activesupport.yml +1 -1
  9. data/lib/config/rails.yml +1 -1
  10. data/lib/config/railties.yml +11 -0
  11. data/lib/config/ruby.yml +79 -0
  12. data/lib/leftovers/ast/node.rb +16 -11
  13. data/lib/leftovers/collector.rb +3 -1
  14. data/lib/leftovers/config.rb +9 -28
  15. data/lib/leftovers/config_loader/argument_position_schema.rb +11 -0
  16. data/lib/leftovers/config_loader/argumentless_transform_schema.rb +21 -0
  17. data/lib/leftovers/config_loader/attribute.rb +30 -0
  18. data/lib/leftovers/config_loader/document_schema.rb +21 -0
  19. data/lib/leftovers/config_loader/dynamic_schema.rb +17 -0
  20. data/lib/leftovers/config_loader/has_argument_schema.rb +13 -0
  21. data/lib/leftovers/config_loader/has_value_schema.rb +18 -0
  22. data/lib/leftovers/config_loader/keep_test_only_schema.rb +13 -0
  23. data/lib/leftovers/config_loader/node.rb +106 -0
  24. data/lib/leftovers/config_loader/object_schema.rb +189 -0
  25. data/lib/leftovers/config_loader/privacy_processor_schema.rb +12 -0
  26. data/lib/leftovers/config_loader/privacy_schema.rb +15 -0
  27. data/lib/leftovers/config_loader/require_schema.rb +11 -0
  28. data/lib/leftovers/config_loader/rule_pattern_schema.rb +18 -0
  29. data/lib/leftovers/config_loader/scalar_argument_schema.rb +14 -0
  30. data/lib/leftovers/config_loader/scalar_value_schema.rb +14 -0
  31. data/lib/leftovers/config_loader/schema.rb +21 -0
  32. data/lib/leftovers/config_loader/string_enum_schema.rb +62 -0
  33. data/lib/leftovers/config_loader/string_pattern_schema.rb +14 -0
  34. data/lib/leftovers/config_loader/string_schema.rb +14 -0
  35. data/lib/leftovers/config_loader/string_value_processor_schema.rb +11 -0
  36. data/lib/leftovers/config_loader/suggester.rb +22 -0
  37. data/lib/leftovers/config_loader/transform_schema.rb +28 -0
  38. data/lib/leftovers/config_loader/true_schema.rb +18 -0
  39. data/lib/leftovers/config_loader/value_matcher_schema.rb +18 -0
  40. data/lib/leftovers/config_loader/value_or_array_schema.rb +64 -0
  41. data/lib/leftovers/config_loader/value_processor_schema.rb +14 -0
  42. data/lib/leftovers/config_loader/value_type_schema.rb +17 -0
  43. data/lib/leftovers/config_loader.rb +82 -0
  44. data/lib/leftovers/definition.rb +1 -1
  45. data/lib/leftovers/definition_node.rb +6 -17
  46. data/lib/leftovers/definition_node_set.rb +11 -0
  47. data/lib/leftovers/definition_to_add.rb +31 -0
  48. data/lib/leftovers/dynamic_processors/call.rb +4 -7
  49. data/lib/leftovers/dynamic_processors/call_definition.rb +14 -11
  50. data/lib/leftovers/dynamic_processors/definition.rb +8 -7
  51. data/lib/leftovers/dynamic_processors/set_default_privacy.rb +18 -0
  52. data/lib/leftovers/dynamic_processors/set_privacy.rb +23 -0
  53. data/lib/leftovers/dynamic_processors.rb +2 -0
  54. data/lib/leftovers/file.rb +57 -11
  55. data/lib/leftovers/file_collector.rb +77 -19
  56. data/lib/leftovers/json.rb +28 -0
  57. data/lib/leftovers/matcher_builders/document.rb +13 -0
  58. data/lib/leftovers/matcher_builders/node.rb +7 -1
  59. data/lib/leftovers/matcher_builders/node_has_argument.rb +3 -4
  60. data/lib/leftovers/matcher_builders/node_has_keyword_argument.rb +1 -1
  61. data/lib/leftovers/matcher_builders/node_has_positional_argument.rb +1 -1
  62. data/lib/leftovers/matcher_builders/node_privacy.rb +13 -0
  63. data/lib/leftovers/matcher_builders/node_type.rb +4 -4
  64. data/lib/leftovers/matcher_builders/node_value.rb +1 -1
  65. data/lib/leftovers/matcher_builders/string_pattern.rb +14 -5
  66. data/lib/leftovers/matcher_builders.rb +2 -0
  67. data/lib/leftovers/matchers/all.rb +0 -4
  68. data/lib/leftovers/matchers/and.rb +0 -4
  69. data/lib/leftovers/matchers/any.rb +0 -4
  70. data/lib/leftovers/matchers/node_has_any_keyword_argument.rb +1 -7
  71. data/lib/leftovers/matchers/node_has_any_positional_argument_with_value.rb +1 -7
  72. data/lib/leftovers/matchers/node_has_positional_argument_with_value.rb +0 -4
  73. data/lib/leftovers/matchers/node_name.rb +0 -4
  74. data/lib/leftovers/matchers/node_pair_value.rb +0 -4
  75. data/lib/leftovers/matchers/node_path.rb +0 -4
  76. data/lib/leftovers/matchers/node_privacy.rb +19 -0
  77. data/lib/leftovers/matchers/node_scalar_value.rb +0 -4
  78. data/lib/leftovers/matchers/node_type.rb +0 -4
  79. data/lib/leftovers/matchers/not.rb +0 -4
  80. data/lib/leftovers/matchers/or.rb +0 -4
  81. data/lib/leftovers/matchers.rb +1 -0
  82. data/lib/leftovers/merged_config.rb +25 -23
  83. data/lib/leftovers/processor_builders/add_prefix.rb +1 -1
  84. data/lib/leftovers/processor_builders/add_suffix.rb +1 -1
  85. data/lib/leftovers/processor_builders/dynamic.rb +51 -16
  86. data/lib/leftovers/processor_builders/transform.rb +2 -2
  87. data/lib/leftovers/processor_builders/transform_set.rb +8 -8
  88. data/lib/leftovers/todo_reporter.rb +10 -35
  89. data/lib/leftovers/value_processors/delete_prefix.rb +0 -6
  90. data/lib/leftovers/value_processors/delete_suffix.rb +0 -6
  91. data/lib/leftovers/value_processors/each_for_definition_set.rb +2 -5
  92. data/lib/leftovers/value_processors/each_positional_argument.rb +1 -1
  93. data/lib/leftovers/value_processors/keyword.rb +0 -4
  94. data/lib/leftovers/value_processors/keyword_argument.rb +0 -4
  95. data/lib/leftovers/value_processors/return_definition_node.rb +14 -0
  96. data/lib/leftovers/value_processors/{return_string.rb → return_sym.rb} +1 -1
  97. data/lib/leftovers/value_processors.rb +2 -2
  98. data/lib/leftovers/version.rb +1 -1
  99. data/lib/leftovers/yaml.rb +73 -0
  100. data/lib/leftovers.rb +29 -21
  101. metadata +85 -36
  102. data/lib/config/actioncable.yml +0 -4
  103. data/lib/leftovers/backports.rb +0 -40
  104. data/lib/leftovers/config_validator/error_processor.rb +0 -196
  105. data/lib/leftovers/config_validator/schema_hash.rb +0 -530
  106. data/lib/leftovers/config_validator.rb +0 -60
  107. data/lib/leftovers/value_processors/return_definition.rb +0 -26
@@ -25,14 +25,6 @@ module Leftovers
25
25
  @memo[:path] ||= loc.expression.source_buffer.name.to_s
26
26
  end
27
27
 
28
- def test_line?
29
- @memo[:test_line]
30
- end
31
-
32
- def test_line=(value)
33
- @memo[:test_line] = value
34
- end
35
-
36
28
  def keep_line=(value)
37
29
  @memo[:keep_line] = value
38
30
  end
@@ -41,6 +33,14 @@ module Leftovers
41
33
  @memo[:keep_line]
42
34
  end
43
35
 
36
+ def privacy=(value)
37
+ @memo[:privacy] = value
38
+ end
39
+
40
+ def privacy
41
+ @memo[:privacy] || :public
42
+ end
43
+
44
44
  def to_scalar_value
45
45
  case type
46
46
  when :sym, :int, :float, :str
@@ -58,7 +58,8 @@ module Leftovers
58
58
  case type
59
59
  when :sym, :int, :float, :str, :true, :false, :nil
60
60
  true
61
- else false
61
+ else
62
+ false
62
63
  end
63
64
  end
64
65
 
@@ -78,6 +79,10 @@ module Leftovers
78
79
  type == :str || type == :sym
79
80
  end
80
81
 
82
+ def string_or_symbol_or_def?
83
+ type == :str || type == :sym || type == :def || type == :defs
84
+ end
85
+
81
86
  def proc?
82
87
  return unless type == :block
83
88
 
@@ -140,9 +145,9 @@ module Leftovers
140
145
 
141
146
  def name
142
147
  @memo[:name] ||= case type
143
- when :send, :csend, :casgn, :const
148
+ when :send, :csend, :casgn, :const, :defs
144
149
  second
145
- when :def, :ivasgn, :ivar, :gvar, :cvar, :gvasgn, :cvasgn, :sym
150
+ when :def, :ivasgn, :ivar, :gvar, :cvar, :gvasgn, :cvasgn, :sym, :lvar
146
151
  first
147
152
  when :str
148
153
  first.to_sym
@@ -28,7 +28,9 @@ module Leftovers
28
28
 
29
29
  def collect_file_list(list)
30
30
  if Leftovers.parallel?
31
- Parallel.each(list, finish: method(:finish_file), &method(:collect_file))
31
+ Parallel.each(list, finish: method(:finish_file)) do |file|
32
+ collect_file(file)
33
+ end
32
34
  else
33
35
  list.each { |file| finish_file(nil, nil, collect_file(file)) }
34
36
  end
@@ -1,13 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'yaml'
4
-
5
3
  module Leftovers
6
4
  class Config
7
- # :nocov:
8
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
9
- # :nocov:
10
-
11
5
  attr_reader :name
12
6
 
13
7
  def initialize(name, path: nil, content: nil)
@@ -40,6 +34,14 @@ module Leftovers
40
34
  @slim_paths ||= Array(yaml[:slim_paths])
41
35
  end
42
36
 
37
+ def yaml_paths
38
+ @yaml_paths ||= Array(yaml[:yaml_paths])
39
+ end
40
+
41
+ def json_paths
42
+ @json_paths ||= Array(yaml[:json_paths])
43
+ end
44
+
43
45
  def erb_paths
44
46
  @erb_paths ||= Array(yaml[:erb_paths])
45
47
  end
@@ -62,29 +64,8 @@ module Leftovers
62
64
 
63
65
  private
64
66
 
65
- def content
66
- @content ||= ::File.exist?(path) ? ::File.read(path) : ''
67
- end
68
-
69
- def path
70
- @path ||= ::File.expand_path("../config/#{name}.yml", __dir__)
71
- end
72
-
73
67
  def yaml
74
- @yaml ||= ::Leftovers::ConfigValidator.validate_and_process!(parse_yaml, path)
75
- end
76
-
77
- def parse_yaml
78
- # :nocov:
79
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6')
80
- Psych.safe_load(content, filename: path) || {}
81
- else
82
- Psych.safe_load(content, [], [], false, path) || {}
83
- end
84
- # :nocov:
85
- rescue ::Psych::SyntaxError => e
86
- warn "\e[31mConfig SyntaxError: #{e.message}\e[0m"
87
- Leftovers.exit 1
68
+ @yaml ||= ::Leftovers::ConfigLoader.load(name, path: @path, content: @content)
88
69
  end
89
70
  end
90
71
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class ArgumentPositionSchema < ObjectSchema
6
+ inherit_attributes_from StringPatternSchema
7
+
8
+ self.or_schema = ScalarArgumentSchema
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class ArgumentlessTransformSchema < StringEnumSchema
6
+ value :original
7
+ value :pluralize
8
+ value :singularize
9
+ value :camelize, aliases: :camelcase
10
+ value :underscore
11
+ value :titleize, aliases: :titlecase
12
+ value :demodulize
13
+ value :deconstantize
14
+ value :parameterize
15
+ value :downcase
16
+ value :upcase
17
+ value :capitalize
18
+ value :swapcase
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class Attribute
6
+ def initialize(name, value_schema, aliases: nil, require_group: nil)
7
+ @name = name
8
+ @value_schema = value_schema
9
+ @aliases = aliases
10
+ @require_group = require_group
11
+ end
12
+
13
+ def attributes
14
+ { @name => @value_schema }
15
+ end
16
+
17
+ def aliases
18
+ ::Leftovers.each_or_self(@aliases).map do |aka|
19
+ [aka, @name]
20
+ end.to_h
21
+ end
22
+
23
+ def require_groups
24
+ return {} unless @require_group
25
+
26
+ { @require_group => [@name, *::Leftovers.each_or_self(@aliases)] }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class DocumentSchema < ObjectSchema
6
+ attribute :include_paths, ValueOrArraySchema[StringSchema], aliases: :include_path
7
+ attribute :exclude_paths, ValueOrArraySchema[StringSchema], aliases: :exclude_path
8
+ attribute :test_paths, ValueOrArraySchema[StringSchema], aliases: :test_path
9
+ attribute :haml_paths, ValueOrArraySchema[StringSchema], aliases: :haml_path
10
+ attribute :slim_paths, ValueOrArraySchema[StringSchema], aliases: :slim_path
11
+ attribute :yaml_paths, ValueOrArraySchema[StringSchema], aliases: :yaml_path
12
+ attribute :json_paths, ValueOrArraySchema[StringSchema], aliases: :json_path
13
+ attribute :erb_paths, ValueOrArraySchema[StringSchema], aliases: :erb_path
14
+ attribute :requires, ValueOrArraySchema[RequireSchema], aliases: :require
15
+ attribute :gems, ValueOrArraySchema[StringSchema], aliases: :gem
16
+ attribute :keep, ValueOrArraySchema[KeepTestOnlySchema]
17
+ attribute :test_only, ValueOrArraySchema[KeepTestOnlySchema]
18
+ attribute :dynamic, ValueOrArraySchema[DynamicSchema]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class DynamicSchema < ObjectSchema
6
+ inherit_attributes_from RulePatternSchema
7
+
8
+ attribute :call, ValueOrArraySchema[ValueProcessorSchema], aliases: :calls,
9
+ require_group: :processor
10
+ attribute :define, ValueOrArraySchema[ValueProcessorSchema], aliases: :defines,
11
+ require_group: :processor
12
+ attribute :set_privacy, ValueOrArraySchema[PrivacyProcessorSchema],
13
+ require_group: :processor
14
+ attribute :set_default_privacy, PrivacySchema, require_group: :processor
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class HasArgumentSchema < ObjectSchema
6
+ attribute :at, ValueOrArraySchema[ArgumentPositionSchema], require_group: :matcher
7
+ attribute :has_value, ValueOrArraySchema[HasValueSchema], require_group: :matcher
8
+ attribute :unless, ValueOrArraySchema[HasArgumentSchema], require_group: :matcher
9
+
10
+ self.or_schema = ScalarArgumentSchema
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class HasValueSchema < ObjectSchema
6
+ inherit_attributes_from StringPatternSchema, except: :unless
7
+
8
+ attribute :at, ValueOrArraySchema[ArgumentPositionSchema], require_group: :matcher
9
+ attribute :has_value, ValueOrArraySchema[HasValueSchema], require_group: :matcher
10
+
11
+ attribute :has_receiver, ValueOrArraySchema[HasValueSchema], require_group: :matcher
12
+ attribute :type, ValueOrArraySchema[ValueTypeSchema], require_group: :matcher
13
+ attribute :unless, ValueOrArraySchema[HasValueSchema], require_group: :matcher
14
+
15
+ self.or_schema = ScalarValueSchema
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class KeepTestOnlySchema < ObjectSchema
6
+ inherit_attributes_from StringPatternSchema, except: :unless
7
+ inherit_attributes_from RulePatternSchema, except: :unless
8
+ attribute :unless, ValueOrArraySchema[KeepTestOnlySchema], require_group: :matcher
9
+
10
+ self.or_schema = StringSchema
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class Node
6
+ class_loader = Psych::ClassLoader::Restricted.new([], [])
7
+ ToRuby = Psych::Visitors::ToRuby.new(
8
+ Psych::ScalarScanner.new(class_loader),
9
+ class_loader
10
+ )
11
+
12
+ attr_reader :node, :file, :name
13
+ attr_accessor :error
14
+
15
+ def initialize(node, file, name = nil)
16
+ @node = node
17
+ @file = file
18
+ @name = name
19
+ end
20
+
21
+ def name_
22
+ "#{name} " if name
23
+ end
24
+
25
+ def valid?
26
+ !error
27
+ end
28
+
29
+ def error_message
30
+ "Config SchemaError: #{location} #{@error}" if @error
31
+ end
32
+
33
+ def all_errors
34
+ Array(error_message) + children.flat_map(&:all_errors)
35
+ end
36
+
37
+ def to_s
38
+ to_ruby.to_s
39
+ end
40
+
41
+ def hash?
42
+ node.is_a?(Psych::Nodes::Mapping)
43
+ end
44
+
45
+ def location
46
+ "#{file.relative_path}:#{node.start_line + 1}:#{node.start_column}"
47
+ end
48
+
49
+ def children
50
+ @children ||= if hash?
51
+ prepare_hash_children
52
+ elsif array?
53
+ node.children.map { |value| self.class.new(value, file, "#{name} value") }
54
+ else
55
+ []
56
+ end
57
+ end
58
+
59
+ def keys
60
+ @keys ||= pairs.map(&:first)
61
+ end
62
+
63
+ def pairs
64
+ @pairs ||= children.each_slice(2).to_a
65
+ end
66
+
67
+ def each_key(&block)
68
+ keys.each(&block)
69
+ end
70
+
71
+ def to_ruby
72
+ @to_ruby ||= ToRuby.accept(node)
73
+ end
74
+
75
+ def to_sym
76
+ to_ruby.to_sym if string?
77
+ end
78
+
79
+ def string?
80
+ to_ruby.is_a?(String)
81
+ end
82
+
83
+ def scalar?
84
+ !array? and !hash?
85
+ end
86
+
87
+ def array?
88
+ node.is_a?(Psych::Nodes::Sequence)
89
+ end
90
+
91
+ def integer?
92
+ to_ruby.is_a?(Integer)
93
+ end
94
+
95
+ private
96
+
97
+ def prepare_hash_children
98
+ node.children.each_slice(2).flat_map do |key, value|
99
+ key = self.class.new(key, file, name)
100
+ value = self.class.new(value, file, key.to_ruby)
101
+ [key, value]
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class ObjectSchema < Schema # rubocop:disable Metrics/ClassLength
6
+ class << self
7
+ def inherit_attributes_from(schema, require_group: true, except: nil)
8
+ attributes_and_schemas_to_inherit_from << schema
9
+
10
+ inherit_except[schema] = Leftovers.each_or_self(except)
11
+
12
+ return if require_group
13
+
14
+ skip_require_group << schema
15
+ end
16
+
17
+ def attributes
18
+ nonexcluded_attributes = attributes_and_schemas_to_inherit_from.map do |attr_or_schema|
19
+ attr_or_schema.attributes.dup.tap do |attributes_copy|
20
+ inherit_except[attr_or_schema]&.each { |e| attributes_copy.delete(e) }
21
+ end
22
+ end
23
+
24
+ nonexcluded_attributes.reduce { |a, e| a.merge(e) { raise 'Duplicate attributes' } }
25
+ end
26
+
27
+ def aliases
28
+ attributes_and_schemas_to_inherit_from.map(&:aliases)
29
+ .reduce { |a, e| a.merge(e) { raise 'Duplicate aliases' } }
30
+ end
31
+
32
+ def require_groups
33
+ (attributes_and_schemas_to_inherit_from - skip_require_group)
34
+ .map(&:require_groups).each_with_object({}) do |require_groups, hash|
35
+ require_groups.each do |group, keys|
36
+ hash[group] ||= []
37
+ hash[group] += keys
38
+ end
39
+ end
40
+ end
41
+
42
+ attr_accessor :or_schema
43
+
44
+ def attribute(name, value_schema, aliases: nil, require_group: nil)
45
+ attributes_and_schemas_to_inherit_from << Attribute.new(
46
+ name,
47
+ value_schema,
48
+ aliases: aliases,
49
+ require_group: require_group
50
+ )
51
+ end
52
+
53
+ def validate(node)
54
+ if node.hash?
55
+ validate_attributes(node)
56
+ elsif or_schema
57
+ validate_or_schema(node)
58
+ else
59
+ validate_is_hash(node)
60
+ end
61
+ end
62
+
63
+ def to_ruby(node)
64
+ if node.hash?
65
+ node.pairs.map { |(key, value)| pair_to_ruby(key, value) }.to_h
66
+ else
67
+ or_schema.to_ruby(node)
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def inherit_except
74
+ @inherit_except ||= {}
75
+ end
76
+
77
+ def skip_require_group
78
+ @skip_require_group ||= []
79
+ end
80
+
81
+ def attributes_and_schemas_to_inherit_from
82
+ @attributes_and_schemas_to_inherit_from ||= []
83
+ end
84
+
85
+ def pair_to_ruby(key, value)
86
+ key_sym = key.to_sym
87
+ key_sym = aliases[key_sym] || key_sym
88
+ key_sym = :unless_arg if key_sym == :unless
89
+ [key_sym, schema_for_attribute(key).to_ruby(value)]
90
+ end
91
+
92
+ def validate_attributes(node)
93
+ validate_attribute_keys(node) && validate_required_keys(node)
94
+ validate_alias_uniqueness_of_keys(node)
95
+ validate_valid_attribute_values(node)
96
+
97
+ node.children.all?(&:valid?)
98
+ end
99
+
100
+ def validate_or_schema(node)
101
+ or_schema.validate(node)
102
+ return true if node.valid?
103
+
104
+ if node.string? && valid_keys.include?(node.to_sym)
105
+ node.error = "#{node.name_}#{node.to_sym} must be a hash key"
106
+ else
107
+ node.error += " or a hash with any of #{attributes.keys.join(', ')}"
108
+ end
109
+ end
110
+
111
+ def validate_is_hash(node)
112
+ error(node, 'be a hash')
113
+ node.valid?
114
+ end
115
+
116
+ def validate_attribute_keys(node)
117
+ node.each_key { |key| validate_recognized_key(key, node) }
118
+ node.keys.all?(&:valid?)
119
+ end
120
+
121
+ def valid_keys
122
+ attributes.keys + aliases.keys
123
+ end
124
+
125
+ def validate_alias_uniqueness_of_keys(node)
126
+ node.keys.select(&:valid?)
127
+ .group_by { |key| aliases[key.to_sym] || key.to_sym }
128
+ .each_value do |keys|
129
+ next unless keys.length > 1
130
+
131
+ error_message_for_non_unique_keys(node, keys)
132
+ end
133
+ end
134
+
135
+ def error_message_for_non_unique_keys(node, keys)
136
+ keys.each do |key|
137
+ key.error = "#{node.name_}must only use one of #{keys.uniq.join(' or ')}"
138
+ end
139
+ end
140
+
141
+ def validate_recognized_key(key, node)
142
+ return true if valid_keys.include?(key.to_sym)
143
+
144
+ suggestions = suggestions_for_unrecognized_key(key, node)
145
+ did_you_mean = "\nDid you mean: #{suggestions.join(', ')}" unless suggestions.empty?
146
+ for_name = " for #{node.name}" if node.name
147
+
148
+ key.error = "unrecognized key #{key}#{for_name}#{did_you_mean}"
149
+
150
+ false
151
+ end
152
+
153
+ def suggester
154
+ @suggester ||= Suggester.new(attributes.keys)
155
+ end
156
+
157
+ def suggestions_for_unrecognized_key(key, node)
158
+ existing_keys = node.keys.flat_map { |k| [k.to_sym, aliases[k.to_sym]] }.compact
159
+ suggester.suggest(key.to_ruby) - existing_keys
160
+ end
161
+
162
+ def validate_required_keys(node)
163
+ missing_groups = require_groups.map do |_name, group|
164
+ next if node.keys.any? { |key| group.include?(key.to_sym) }
165
+
166
+ "include at least one of #{(attributes.keys & group).join(', ')}"
167
+ end.compact
168
+
169
+ error(node, missing_groups.join(' and ')) unless missing_groups.empty?
170
+
171
+ node.valid?
172
+ end
173
+
174
+ def schema_for_attribute(key)
175
+ key_sym = key.to_sym
176
+ attributes[key_sym] || attributes.fetch(aliases[key_sym])
177
+ end
178
+
179
+ def validate_valid_attribute_values(node)
180
+ node.pairs.each do |(key, value)|
181
+ next unless key.valid?
182
+
183
+ schema_for_attribute(key).validate(value)
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class PrivacyProcessorSchema < ObjectSchema
6
+ inherit_attributes_from ValueProcessorSchema
7
+ attribute :to, PrivacySchema, require_group: :privacy_setting
8
+
9
+ self.or_schema = nil
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class PrivacySchema < StringEnumSchema
6
+ value :public
7
+ value :protected
8
+ value :private
9
+
10
+ def self.to_ruby(node)
11
+ super.to_sym
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class RequireSchema < ObjectSchema
6
+ attribute :quiet, StringSchema, aliases: :name, require_group: :quiet
7
+
8
+ self.or_schema = StringSchema
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class RulePatternSchema < ObjectSchema
6
+ attribute :names, ValueOrArraySchema[StringPatternSchema], aliases: :name,
7
+ require_group: :matcher
8
+ attribute :paths, ValueOrArraySchema[StringSchema], aliases: :path, require_group: :matcher
9
+ attribute :document, TrueSchema, require_group: :matcher
10
+ attribute :has_arguments, ValueOrArraySchema[HasArgumentSchema], aliases: :has_argument,
11
+ require_group: :matcher
12
+ attribute :has_receiver, ValueOrArraySchema[HasValueSchema], require_group: :matcher
13
+ attribute :type, ValueOrArraySchema[ValueTypeSchema], require_group: :matcher
14
+ attribute :privacy, ValueOrArraySchema[PrivacySchema], require_group: :matcher
15
+ attribute :unless, ValueOrArraySchema[RulePatternSchema], require_group: :matcher
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class ScalarArgumentSchema < Schema
6
+ class << self
7
+ def validate(node)
8
+ error(node, 'be a string or an integer') unless node.string? || node.integer?
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class ScalarValueSchema < Schema
6
+ class << self
7
+ def validate(node)
8
+ error(node, 'be any scalar value') unless node.scalar?
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end