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