theme-check 0.1.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.
- checksums.yaml +7 -0
- data/.github/probots.yml +3 -0
- data/.github/workflows/theme-check.yml +28 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +18 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +132 -0
- data/Gemfile +26 -0
- data/Guardfile +7 -0
- data/LICENSE.md +8 -0
- data/README.md +71 -0
- data/Rakefile +14 -0
- data/bin/liquid-server +4 -0
- data/config/default.yml +63 -0
- data/data/shopify_liquid/filters.yml +174 -0
- data/data/shopify_liquid/objects.yml +81 -0
- data/dev.yml +23 -0
- data/docs/preview.png +0 -0
- data/exe/theme-check +6 -0
- data/exe/theme-check-language-server +12 -0
- data/lib/theme_check.rb +25 -0
- data/lib/theme_check/analyzer.rb +43 -0
- data/lib/theme_check/check.rb +92 -0
- data/lib/theme_check/checks.rb +12 -0
- data/lib/theme_check/checks/convert_include_to_render.rb +13 -0
- data/lib/theme_check/checks/default_locale.rb +12 -0
- data/lib/theme_check/checks/liquid_tag.rb +48 -0
- data/lib/theme_check/checks/matching_schema_translations.rb +73 -0
- data/lib/theme_check/checks/matching_translations.rb +29 -0
- data/lib/theme_check/checks/missing_required_template_files.rb +29 -0
- data/lib/theme_check/checks/missing_template.rb +25 -0
- data/lib/theme_check/checks/nested_snippet.rb +46 -0
- data/lib/theme_check/checks/required_directories.rb +24 -0
- data/lib/theme_check/checks/required_layout_theme_object.rb +40 -0
- data/lib/theme_check/checks/space_inside_braces.rb +58 -0
- data/lib/theme_check/checks/syntax_error.rb +29 -0
- data/lib/theme_check/checks/template_length.rb +18 -0
- data/lib/theme_check/checks/translation_key_exists.rb +35 -0
- data/lib/theme_check/checks/undefined_object.rb +86 -0
- data/lib/theme_check/checks/unknown_filter.rb +25 -0
- data/lib/theme_check/checks/unused_assign.rb +54 -0
- data/lib/theme_check/checks/unused_snippet.rb +34 -0
- data/lib/theme_check/checks/valid_html_translation.rb +43 -0
- data/lib/theme_check/checks/valid_json.rb +14 -0
- data/lib/theme_check/checks/valid_schema.rb +13 -0
- data/lib/theme_check/checks_tracking.rb +8 -0
- data/lib/theme_check/cli.rb +78 -0
- data/lib/theme_check/config.rb +108 -0
- data/lib/theme_check/json_check.rb +11 -0
- data/lib/theme_check/json_file.rb +47 -0
- data/lib/theme_check/json_helpers.rb +9 -0
- data/lib/theme_check/language_server.rb +11 -0
- data/lib/theme_check/language_server/handler.rb +117 -0
- data/lib/theme_check/language_server/server.rb +140 -0
- data/lib/theme_check/liquid_check.rb +13 -0
- data/lib/theme_check/locale_diff.rb +69 -0
- data/lib/theme_check/node.rb +117 -0
- data/lib/theme_check/offense.rb +104 -0
- data/lib/theme_check/parsing_helpers.rb +17 -0
- data/lib/theme_check/printer.rb +74 -0
- data/lib/theme_check/shopify_liquid.rb +3 -0
- data/lib/theme_check/shopify_liquid/filter.rb +18 -0
- data/lib/theme_check/shopify_liquid/object.rb +16 -0
- data/lib/theme_check/tags.rb +146 -0
- data/lib/theme_check/template.rb +73 -0
- data/lib/theme_check/theme.rb +60 -0
- data/lib/theme_check/version.rb +4 -0
- data/lib/theme_check/visitor.rb +37 -0
- data/theme-check.gemspec +28 -0
- metadata +156 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class TemplateLength < LiquidCheck
|
4
|
+
severity :suggestion
|
5
|
+
category :liquid
|
6
|
+
|
7
|
+
def initialize(max_length: 200)
|
8
|
+
@max_length = max_length
|
9
|
+
end
|
10
|
+
|
11
|
+
def on_document(node)
|
12
|
+
lines = node.template.source.count("\n")
|
13
|
+
if lines > @max_length
|
14
|
+
add_offense("Template has too many lines [#{lines}/#{@max_length}]", template: node.template)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class TranslationKeyExists < LiquidCheck
|
4
|
+
severity :error
|
5
|
+
category :translation
|
6
|
+
|
7
|
+
def on_variable(node)
|
8
|
+
return unless @theme.default_locale_json&.content&.is_a?(Hash)
|
9
|
+
|
10
|
+
return unless node.value.filters.any? { |name, _| name == "t" || name == "translate" }
|
11
|
+
return unless (key_node = node.children.first)
|
12
|
+
return unless key_node.value.is_a?(String)
|
13
|
+
|
14
|
+
unless key_exists?(key_node.value)
|
15
|
+
add_offense(
|
16
|
+
"'#{key_node.value}' does not have a matching entry in '#{@theme.default_locale_json.relative_path}'",
|
17
|
+
node: node,
|
18
|
+
markup: key_node.value,
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def key_exists?(key)
|
26
|
+
pointer = @theme.default_locale_json.content
|
27
|
+
key.split(".").each do |token|
|
28
|
+
return false unless pointer.key?(token)
|
29
|
+
pointer = pointer[token]
|
30
|
+
end
|
31
|
+
|
32
|
+
true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class UndefinedObject < LiquidCheck
|
4
|
+
category :liquid
|
5
|
+
doc "https://shopify.dev/docs/themes/liquid/reference/objects"
|
6
|
+
severity :error
|
7
|
+
|
8
|
+
class TemplateInfo
|
9
|
+
def initialize
|
10
|
+
@all_variable_lookups = {}
|
11
|
+
@all_assigns = {}
|
12
|
+
@all_captures = {}
|
13
|
+
@all_forloops = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :all_variable_lookups, :all_assigns, :all_captures, :all_forloops
|
17
|
+
def all
|
18
|
+
all_assigns.keys + all_captures.keys + all_forloops.keys
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@templates = {}
|
24
|
+
@used_snippets = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def on_document(node)
|
28
|
+
@templates[node.template.name] = TemplateInfo.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def on_assign(node)
|
32
|
+
@templates[node.template.name].all_assigns[node.value.to] = node
|
33
|
+
end
|
34
|
+
|
35
|
+
def on_capture(node)
|
36
|
+
@templates[node.template.name].all_captures[node.value.instance_variable_get('@to')] = node
|
37
|
+
end
|
38
|
+
|
39
|
+
def on_for(node)
|
40
|
+
@templates[node.template.name].all_forloops[node.value.variable_name] = node
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_include(node)
|
44
|
+
return unless node.value.template_name_expr.is_a?(String)
|
45
|
+
name = "snippets/#{node.value.template_name_expr}"
|
46
|
+
@used_snippets[name] ||= Set.new
|
47
|
+
@used_snippets[name] << node.template.name
|
48
|
+
end
|
49
|
+
|
50
|
+
def on_variable_lookup(node)
|
51
|
+
@templates[node.template.name].all_variable_lookups[node.value.name] = node
|
52
|
+
end
|
53
|
+
|
54
|
+
def on_end
|
55
|
+
foster_snippets = theme.snippets
|
56
|
+
.reject { |t| @used_snippets.include?(t.name) }
|
57
|
+
.map(&:name)
|
58
|
+
|
59
|
+
@templates.each do |(template_name, info)|
|
60
|
+
next if foster_snippets.include?(template_name)
|
61
|
+
if (all_including_templates = @used_snippets[template_name])
|
62
|
+
all_including_templates.each do |including_template|
|
63
|
+
including_template_info = @templates[including_template]
|
64
|
+
check_object(info.all_variable_lookups, including_template_info.all)
|
65
|
+
end
|
66
|
+
else
|
67
|
+
all = info.all
|
68
|
+
all += ['email'] if 'templates/customers/reset_password' == template_name
|
69
|
+
check_object(info.all_variable_lookups, all)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def check_object(variable_lookups, all)
|
75
|
+
variable_lookups.each do |(name, node)|
|
76
|
+
next if all.include?(name)
|
77
|
+
next if ThemeCheck::ShopifyLiquid::Object.labels.include?(name)
|
78
|
+
|
79
|
+
parent = node.parent
|
80
|
+
parent = parent.parent if :variable_lookup == parent.type_name
|
81
|
+
add_offense("Undefined object `#{name}`", node: parent)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
private :check_object
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
#
|
4
|
+
# Unwanted:
|
5
|
+
#
|
6
|
+
# {{ x | some_unknown_filter }}
|
7
|
+
#
|
8
|
+
# Wanted:
|
9
|
+
#
|
10
|
+
# {{ x | upcase }}
|
11
|
+
#
|
12
|
+
class UnknownFilter < LiquidCheck
|
13
|
+
severity :error
|
14
|
+
category :liquid
|
15
|
+
|
16
|
+
def on_variable(node)
|
17
|
+
used_filters = node.value.filters.map { |name, *_rest| name }
|
18
|
+
undefined_filters = used_filters - ShopifyLiquid::Filter.labels
|
19
|
+
|
20
|
+
undefined_filters.each do |undefined_filter|
|
21
|
+
add_offense("Undefined filter `#{undefined_filter}`", node: node)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
# Checks unused {% assign x = ... %}
|
4
|
+
class UnusedAssign < LiquidCheck
|
5
|
+
severity :suggestion
|
6
|
+
category :liquid
|
7
|
+
|
8
|
+
class TemplateInfo < Struct.new(:used_assigns, :assign_nodes, :includes)
|
9
|
+
def collect_used_assigns(templates)
|
10
|
+
collected = used_assigns
|
11
|
+
# Check recursively inside included snippets for use
|
12
|
+
includes.each do |name|
|
13
|
+
if templates[name]
|
14
|
+
collected += templates[name].collect_used_assigns(templates)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
collected
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@templates = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_document(node)
|
26
|
+
@templates[node.template.name] = TemplateInfo.new(Set.new, {}, Set.new)
|
27
|
+
end
|
28
|
+
|
29
|
+
def on_assign(node)
|
30
|
+
@templates[node.template.name].assign_nodes[node.value.to] = node
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_include(node)
|
34
|
+
if node.value.template_name_expr.is_a?(String)
|
35
|
+
@templates[node.template.name].includes << "snippets/#{node.value.template_name_expr}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def on_variable_lookup(node)
|
40
|
+
@templates[node.template.name].used_assigns << node.value.name
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_end
|
44
|
+
@templates.each_pair do |_, info|
|
45
|
+
used = info.collect_used_assigns(@templates)
|
46
|
+
info.assign_nodes.each_pair do |name, node|
|
47
|
+
unless used.include?(name)
|
48
|
+
add_offense("`#{name}` is never used", node: node)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "set"
|
3
|
+
|
4
|
+
module ThemeCheck
|
5
|
+
class UnusedSnippet < LiquidCheck
|
6
|
+
severity :suggestion
|
7
|
+
category :liquid
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@used_templates = Set.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_include(node)
|
14
|
+
if node.value.template_name_expr.is_a?(String)
|
15
|
+
@used_templates << "snippets/#{node.value.template_name_expr}"
|
16
|
+
else
|
17
|
+
# Can't reliably track unused snippets if an expression is used, ignore this check
|
18
|
+
@used_templates.clear
|
19
|
+
ignore!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
alias_method :on_render, :on_include
|
23
|
+
|
24
|
+
def on_end
|
25
|
+
missing_snippets.each do |template|
|
26
|
+
add_offense("This template is not used", template: template)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def missing_snippets
|
31
|
+
theme.snippets.reject { |t| @used_templates.include?(t.name) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'nokogumbo'
|
4
|
+
|
5
|
+
module ThemeCheck
|
6
|
+
class ValidHTMLTranslation < JsonCheck
|
7
|
+
severity :suggestion
|
8
|
+
|
9
|
+
def on_file(file)
|
10
|
+
return unless file.name.starts_with?("locales/")
|
11
|
+
return unless file.content.is_a?(Hash)
|
12
|
+
|
13
|
+
visit_nested(file.content)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def html_key?(keys)
|
19
|
+
pluralized_key = keys[-2] if keys.length > 1
|
20
|
+
keys[-1].end_with?('_html') || pluralized_key.end_with?('_html')
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_and_add_offence(key, value)
|
24
|
+
return unless value.is_a?(String)
|
25
|
+
|
26
|
+
html = Nokogiri::HTML5.fragment(value, max_errors: -1)
|
27
|
+
unless html.errors.empty?
|
28
|
+
err_msg = html.errors.join("\n")
|
29
|
+
add_offense("'#{key}' contains invalid HTML:\n#{err_msg}")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def visit_nested(value, keys = [])
|
34
|
+
if value.is_a?(Hash)
|
35
|
+
value.each do |k, v|
|
36
|
+
visit_nested(v, keys + [k])
|
37
|
+
end
|
38
|
+
elsif html_key?(keys)
|
39
|
+
parse_and_add_offence(keys.join('.'), value)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class ValidJson < JsonCheck
|
4
|
+
severity :error
|
5
|
+
category :json
|
6
|
+
|
7
|
+
def on_file(file)
|
8
|
+
if file.parse_error
|
9
|
+
message = format_json_parse_error(file.parse_error)
|
10
|
+
add_offense(message, template: file)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class ValidSchema < LiquidCheck
|
4
|
+
severity :suggestion
|
5
|
+
category :json
|
6
|
+
|
7
|
+
def on_schema(node)
|
8
|
+
JSON.parse(node.value.nodelist.join)
|
9
|
+
rescue JSON::ParserError => e
|
10
|
+
add_offense(format_json_parse_error(e), node: node)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class Cli
|
4
|
+
class Abort < StandardError; end
|
5
|
+
|
6
|
+
USAGE = <<~END
|
7
|
+
Usage: theme-check [options] /path/to/your/theme
|
8
|
+
|
9
|
+
Options:
|
10
|
+
-c, [--category] # Only run this category of checks
|
11
|
+
-x, [--exclude-category] # Exclude this category of checks
|
12
|
+
-l, [--list] # List enabled checks
|
13
|
+
-h, [--help] # Show this. Hi!
|
14
|
+
|
15
|
+
Description:
|
16
|
+
Theme Check helps you follow Shopify Themes & Liquid best practices by analyzing the
|
17
|
+
Liquid & JSON inside your theme.
|
18
|
+
|
19
|
+
You can configure checks in the .theme-check.yml file of your theme root directory.
|
20
|
+
END
|
21
|
+
|
22
|
+
def run(argv)
|
23
|
+
path = "."
|
24
|
+
|
25
|
+
command = :check
|
26
|
+
only_categories = []
|
27
|
+
exclude_categories = []
|
28
|
+
|
29
|
+
args = argv.dup
|
30
|
+
while (arg = args.shift)
|
31
|
+
case arg
|
32
|
+
when "--help", "-h"
|
33
|
+
raise Abort, USAGE
|
34
|
+
when "--category", "-c"
|
35
|
+
only_categories << args.shift.to_sym
|
36
|
+
when "--exclude-category", "-x"
|
37
|
+
exclude_categories << args.shift.to_sym
|
38
|
+
when "--list", "-l"
|
39
|
+
command = :list
|
40
|
+
else
|
41
|
+
path = arg
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
@config = ThemeCheck::Config.from_path(path)
|
46
|
+
@config.only_categories = only_categories
|
47
|
+
@config.exclude_categories = exclude_categories
|
48
|
+
|
49
|
+
send(command)
|
50
|
+
end
|
51
|
+
|
52
|
+
def run!(argv)
|
53
|
+
run(argv)
|
54
|
+
rescue Abort => e
|
55
|
+
if e.message.empty?
|
56
|
+
exit(1)
|
57
|
+
else
|
58
|
+
abort(e.message)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def list
|
63
|
+
puts @config.enabled_checks
|
64
|
+
end
|
65
|
+
|
66
|
+
def check
|
67
|
+
puts "Checking #{@config.root} ..."
|
68
|
+
theme = ThemeCheck::Theme.new(@config.root)
|
69
|
+
if theme.all.empty?
|
70
|
+
raise Abort, "No templates found.\n#{USAGE}"
|
71
|
+
end
|
72
|
+
analyzer = ThemeCheck::Analyzer.new(theme, @config.enabled_checks)
|
73
|
+
analyzer.analyze_theme
|
74
|
+
ThemeCheck::Printer.new.print(theme, analyzer.offenses)
|
75
|
+
raise Abort, "" if analyzer.offenses.any?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
class Config
|
5
|
+
DOTFILE = '.theme-check.yml'
|
6
|
+
DEFAULT_CONFIG = "#{__dir__}/../../config/default.yml"
|
7
|
+
|
8
|
+
attr_reader :root
|
9
|
+
attr_accessor :only_categories, :exclude_categories
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def from_path(path)
|
13
|
+
if (filename = find(path))
|
14
|
+
new(filename.dirname, load_file(filename))
|
15
|
+
else
|
16
|
+
# No configuration file
|
17
|
+
new(path)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def find(root, needle = DOTFILE)
|
22
|
+
Pathname.new(root).descend.reverse_each do |path|
|
23
|
+
pathname = path.join(needle)
|
24
|
+
return pathname if pathname.exist?
|
25
|
+
end
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def load_file(absolute_path)
|
30
|
+
YAML.load_file(absolute_path)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(root, configuration = nil)
|
35
|
+
@configuration = configuration || {}
|
36
|
+
@checks = @configuration.dup
|
37
|
+
@root = Pathname.new(root)
|
38
|
+
if @checks.key?("root")
|
39
|
+
@root = @root.join(@checks.delete("root"))
|
40
|
+
end
|
41
|
+
@only_categories = []
|
42
|
+
@exclude_categories = []
|
43
|
+
resolve_requires
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_h
|
47
|
+
@configuration
|
48
|
+
end
|
49
|
+
|
50
|
+
def enabled_checks
|
51
|
+
checks = []
|
52
|
+
|
53
|
+
default_configuration.merge(@checks).each do |check_name, properties|
|
54
|
+
if @checks[check_name] && !default_configuration[check_name].nil?
|
55
|
+
valid_properties = valid_check_configuration(check_name)
|
56
|
+
properties = properties.merge(valid_properties)
|
57
|
+
end
|
58
|
+
|
59
|
+
next if properties.delete('enabled') == false
|
60
|
+
|
61
|
+
options = properties.transform_keys(&:to_sym)
|
62
|
+
check_class = ThemeCheck.const_get(check_name)
|
63
|
+
next if exclude_categories.include?(check_class.category)
|
64
|
+
next if only_categories.any? && !only_categories.include?(check_class.category)
|
65
|
+
|
66
|
+
check = check_class.new(**options)
|
67
|
+
check.options = options
|
68
|
+
checks << check
|
69
|
+
end
|
70
|
+
|
71
|
+
checks
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def default_configuration
|
77
|
+
@default_configuration ||= Config.load_file(DEFAULT_CONFIG)
|
78
|
+
end
|
79
|
+
|
80
|
+
def resolve_requires
|
81
|
+
if @checks.key?("require")
|
82
|
+
@checks.delete("require").tap do |paths|
|
83
|
+
paths.each do |path|
|
84
|
+
if path.start_with?('.')
|
85
|
+
require(File.join(@root, path))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def valid_check_configuration(check_name)
|
93
|
+
default_properties = default_configuration[check_name]
|
94
|
+
|
95
|
+
valid = {}
|
96
|
+
|
97
|
+
@checks[check_name].each do |property, value|
|
98
|
+
if !default_properties.key?(property)
|
99
|
+
warn("#{check_name} does not support #{property} parameter.")
|
100
|
+
else
|
101
|
+
valid[property] = value
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
valid
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|