theme-check 1.0.0 → 1.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.
- checksums.yaml +4 -4
- data/.github/workflows/theme-check.yml +2 -6
- data/CHANGELOG.md +50 -0
- data/CONTRIBUTING.md +1 -1
- data/README.md +39 -0
- data/RELEASING.md +34 -2
- data/bin/theme-check +29 -0
- data/bin/theme-check-language-server +29 -0
- data/config/default.yml +28 -1
- data/config/nothing.yml +11 -0
- data/config/theme_app_extension.yml +168 -0
- data/data/shopify_liquid/objects.yml +1 -0
- data/docs/checks/app_block_valid_tags.md +40 -0
- data/docs/checks/asset_size_app_block_css.md +52 -0
- data/docs/checks/asset_size_app_block_javascript.md +57 -0
- data/docs/checks/deprecate_lazysizes.md +0 -3
- data/docs/checks/missing_template.md +25 -0
- data/docs/checks/pagination_size.md +44 -0
- data/docs/checks/template_length.md +1 -1
- data/docs/checks/undefined_object.md +5 -0
- data/lib/theme_check/analyzer.rb +26 -21
- data/lib/theme_check/asset_file.rb +3 -15
- data/lib/theme_check/bug.rb +3 -1
- data/lib/theme_check/check.rb +26 -4
- data/lib/theme_check/checks/app_block_valid_tags.rb +36 -0
- data/lib/theme_check/checks/asset_size_app_block_css.rb +44 -0
- data/lib/theme_check/checks/asset_size_app_block_javascript.rb +44 -0
- data/lib/theme_check/checks/asset_size_css.rb +3 -3
- data/lib/theme_check/checks/asset_size_javascript.rb +2 -2
- data/lib/theme_check/checks/convert_include_to_render.rb +3 -1
- data/lib/theme_check/checks/default_locale.rb +3 -1
- data/lib/theme_check/checks/deprecate_bgsizes.rb +1 -1
- data/lib/theme_check/checks/deprecate_lazysizes.rb +7 -4
- data/lib/theme_check/checks/img_lazy_loading.rb +1 -1
- data/lib/theme_check/checks/img_width_and_height.rb +3 -3
- data/lib/theme_check/checks/missing_template.rb +21 -5
- data/lib/theme_check/checks/pagination_size.rb +65 -0
- data/lib/theme_check/checks/parser_blocking_javascript.rb +1 -1
- data/lib/theme_check/checks/remote_asset.rb +3 -3
- data/lib/theme_check/checks/space_inside_braces.rb +27 -7
- data/lib/theme_check/checks/template_length.rb +1 -1
- data/lib/theme_check/checks/undefined_object.rb +1 -1
- data/lib/theme_check/checks/valid_html_translation.rb +1 -1
- data/lib/theme_check/checks.rb +11 -1
- data/lib/theme_check/cli.rb +52 -15
- data/lib/theme_check/config.rb +56 -10
- data/lib/theme_check/corrector.rb +9 -0
- data/lib/theme_check/exceptions.rb +29 -27
- data/lib/theme_check/file_system_storage.rb +12 -0
- data/lib/theme_check/html_check.rb +0 -1
- data/lib/theme_check/html_node.rb +37 -16
- data/lib/theme_check/html_visitor.rb +17 -3
- data/lib/theme_check/json_check.rb +2 -2
- data/lib/theme_check/json_file.rb +11 -27
- data/lib/theme_check/json_printer.rb +26 -0
- data/lib/theme_check/language_server/constants.rb +21 -6
- data/lib/theme_check/language_server/document_link_engine.rb +3 -31
- data/lib/theme_check/language_server/document_link_provider.rb +70 -0
- data/lib/theme_check/language_server/document_link_providers/asset_document_link_provider.rb +11 -0
- data/lib/theme_check/language_server/document_link_providers/include_document_link_provider.rb +11 -0
- data/lib/theme_check/language_server/document_link_providers/render_document_link_provider.rb +11 -0
- data/lib/theme_check/language_server/document_link_providers/section_document_link_provider.rb +11 -0
- data/lib/theme_check/language_server/handler.rb +7 -4
- data/lib/theme_check/language_server/server.rb +13 -2
- data/lib/theme_check/language_server.rb +5 -0
- data/lib/theme_check/node.rb +6 -4
- data/lib/theme_check/offense.rb +56 -3
- data/lib/theme_check/parsing_helpers.rb +4 -3
- data/lib/theme_check/position.rb +98 -14
- data/lib/theme_check/regex_helpers.rb +5 -2
- data/lib/theme_check/tags.rb +26 -9
- data/lib/theme_check/template.rb +3 -32
- data/lib/theme_check/theme.rb +3 -0
- data/lib/theme_check/theme_file.rb +40 -0
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check.rb +16 -0
- data/theme-check.gemspec +1 -1
- metadata +24 -6
- data/bin/liquid-server +0 -4
@@ -5,7 +5,7 @@ module ThemeCheck
|
|
5
5
|
category :liquid
|
6
6
|
doc docs_url(__FILE__)
|
7
7
|
|
8
|
-
def initialize(max_length:
|
8
|
+
def initialize(max_length: 600, exclude_schema: true, exclude_stylesheet: true, exclude_javascript: true)
|
9
9
|
@max_length = max_length
|
10
10
|
@exclude_schema = exclude_schema
|
11
11
|
@exclude_stylesheet = exclude_stylesheet
|
data/lib/theme_check/checks.rb
CHANGED
@@ -29,8 +29,18 @@ module ThemeCheck
|
|
29
29
|
def call_check_method(check, method, *args)
|
30
30
|
return unless check.respond_to?(method) && !check.ignored?
|
31
31
|
|
32
|
-
|
32
|
+
# If you want to use binding.pry in unit tests, define the
|
33
|
+
# THEME_CHECK_DEBUG environment variable. e.g.
|
34
|
+
#
|
35
|
+
# $ export THEME_CHECK_DEBUG=true
|
36
|
+
# $ bundle exec rake tests:in_memory
|
37
|
+
#
|
38
|
+
if ENV['THEME_CHECK_DEBUG']
|
33
39
|
check.send(method, *args)
|
40
|
+
else
|
41
|
+
Timeout.timeout(CHECK_METHOD_TIMEOUT) do
|
42
|
+
check.send(method, *args)
|
43
|
+
end
|
34
44
|
end
|
35
45
|
rescue Liquid::Error
|
36
46
|
# Pass-through Liquid errors
|
data/lib/theme_check/cli.rb
CHANGED
@@ -5,15 +5,19 @@ module ThemeCheck
|
|
5
5
|
class Cli
|
6
6
|
class Abort < StandardError; end
|
7
7
|
|
8
|
+
FORMATS = [:text, :json]
|
9
|
+
|
8
10
|
attr_accessor :path
|
9
11
|
|
10
12
|
def initialize
|
11
13
|
@path = "."
|
12
14
|
@command = :check
|
13
|
-
@
|
15
|
+
@include_categories = []
|
14
16
|
@exclude_categories = []
|
15
17
|
@auto_correct = false
|
16
18
|
@config_path = nil
|
19
|
+
@fail_level = :error
|
20
|
+
@format = :text
|
17
21
|
end
|
18
22
|
|
19
23
|
def option_parser(parser = OptionParser.new, help: true)
|
@@ -25,20 +29,31 @@ module ThemeCheck
|
|
25
29
|
@option_parser.separator("Basic Options:")
|
26
30
|
@option_parser.on(
|
27
31
|
"-C", "--config PATH",
|
28
|
-
"Use the config provided, overriding .theme-check.yml if present"
|
32
|
+
"Use the config provided, overriding .theme-check.yml if present",
|
33
|
+
"Use :theme_app_extension to use default checks for theme app extensions"
|
29
34
|
) { |path| @config_path = path }
|
30
35
|
@option_parser.on(
|
31
|
-
"-
|
32
|
-
"
|
33
|
-
) { |
|
36
|
+
"-o", "--output FORMAT", FORMATS,
|
37
|
+
"The output format to use. (text|json, default: text)"
|
38
|
+
) { |format| @format = format.to_sym }
|
39
|
+
@option_parser.on(
|
40
|
+
"-c", "--category CATEGORY", Check::CATEGORIES, "Only run this category of checks",
|
41
|
+
"Runs checks matching all categories when specified more than once"
|
42
|
+
) { |category| @include_categories << category.to_sym }
|
34
43
|
@option_parser.on(
|
35
|
-
"-x", "--exclude-category CATEGORY",
|
36
|
-
"
|
44
|
+
"-x", "--exclude-category CATEGORY", Check::CATEGORIES, "Exclude this category of checks",
|
45
|
+
"Excludes checks matching any category when specified more than once"
|
37
46
|
) { |category| @exclude_categories << category.to_sym }
|
38
47
|
@option_parser.on(
|
39
48
|
"-a", "--auto-correct",
|
40
49
|
"Automatically fix offenses"
|
41
50
|
) { @auto_correct = true }
|
51
|
+
@option_parser.on(
|
52
|
+
"--fail-level SEVERITY", Check::SEVERITIES,
|
53
|
+
"Minimum severity (error|suggestion|style) for exit with error code"
|
54
|
+
) do |severity|
|
55
|
+
@fail_level = severity.to_sym
|
56
|
+
end
|
42
57
|
|
43
58
|
@option_parser.separator("")
|
44
59
|
@option_parser.separator("Miscellaneous:")
|
@@ -77,6 +92,8 @@ module ThemeCheck
|
|
77
92
|
|
78
93
|
def parse(argv)
|
79
94
|
@path = option_parser.parse(argv).first || "."
|
95
|
+
rescue OptionParser::InvalidArgument => e
|
96
|
+
abort(e.message)
|
80
97
|
end
|
81
98
|
|
82
99
|
def run!
|
@@ -84,13 +101,13 @@ module ThemeCheck
|
|
84
101
|
@config = if @config_path
|
85
102
|
ThemeCheck::Config.new(
|
86
103
|
root: @path,
|
87
|
-
configuration: ThemeCheck::Config.
|
104
|
+
configuration: ThemeCheck::Config.load_config(@config_path)
|
88
105
|
)
|
89
106
|
else
|
90
107
|
ThemeCheck::Config.from_path(@path)
|
91
108
|
end
|
92
|
-
@config.
|
93
|
-
@config.exclude_categories = @exclude_categories
|
109
|
+
@config.include_categories = @include_categories unless @include_categories.empty?
|
110
|
+
@config.exclude_categories = @exclude_categories unless @exclude_categories.empty?
|
94
111
|
@config.auto_correct = @auto_correct
|
95
112
|
end
|
96
113
|
|
@@ -99,12 +116,16 @@ module ThemeCheck
|
|
99
116
|
|
100
117
|
def run
|
101
118
|
run!
|
119
|
+
exit(0)
|
102
120
|
rescue Abort => e
|
103
121
|
if e.message.empty?
|
104
122
|
exit(1)
|
105
123
|
else
|
106
124
|
abort(e.message)
|
107
125
|
end
|
126
|
+
rescue ThemeCheckError => e
|
127
|
+
STDERR.puts(e.message)
|
128
|
+
exit(2)
|
108
129
|
end
|
109
130
|
|
110
131
|
def self.parse_and_run!(argv)
|
@@ -130,11 +151,16 @@ module ThemeCheck
|
|
130
151
|
def init
|
131
152
|
dotfile_path = ThemeCheck::Config.find(@path)
|
132
153
|
if dotfile_path.nil?
|
133
|
-
|
154
|
+
config_name = if @config_path && @config_path[0] == ":"
|
155
|
+
"#{@config_path[1..]}.yml"
|
156
|
+
else
|
157
|
+
"default.yml"
|
158
|
+
end
|
159
|
+
File.write(File.join(@path, ThemeCheck::Config::DOTFILE), File.read(ThemeCheck::Config.bundled_config_path(config_name)))
|
134
160
|
|
135
161
|
puts "Writing new #{ThemeCheck::Config::DOTFILE} to #{@path}"
|
136
162
|
else
|
137
|
-
raise Abort, "#{ThemeCheck::Config::DOTFILE} already exists at #{@path}
|
163
|
+
raise Abort, "#{ThemeCheck::Config::DOTFILE} already exists at #{@path}"
|
138
164
|
end
|
139
165
|
end
|
140
166
|
|
@@ -147,7 +173,7 @@ module ThemeCheck
|
|
147
173
|
end
|
148
174
|
|
149
175
|
def check
|
150
|
-
puts "Checking #{@config.root} ..."
|
176
|
+
STDERR.puts "Checking #{@config.root} ..."
|
151
177
|
storage = ThemeCheck::FileSystemStorage.new(@config.root, ignored_patterns: @config.ignored_patterns)
|
152
178
|
theme = ThemeCheck::Theme.new(storage)
|
153
179
|
if theme.all.empty?
|
@@ -156,8 +182,19 @@ module ThemeCheck
|
|
156
182
|
analyzer = ThemeCheck::Analyzer.new(theme, @config.enabled_checks, @config.auto_correct)
|
157
183
|
analyzer.analyze_theme
|
158
184
|
analyzer.correct_offenses
|
159
|
-
|
160
|
-
raise Abort, "" if analyzer.uncorrectable_offenses.any?
|
185
|
+
output_with_format(theme, analyzer)
|
186
|
+
raise Abort, "" if analyzer.uncorrectable_offenses.any? do |offense|
|
187
|
+
offense.check.severity_value <= Check.severity_value(@fail_level)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def output_with_format(theme, analyzer)
|
192
|
+
case @format
|
193
|
+
when :text
|
194
|
+
ThemeCheck::Printer.new.print(theme, analyzer.offenses, @config.auto_correct)
|
195
|
+
when :json
|
196
|
+
ThemeCheck::JsonPrinter.new.print(analyzer.offenses)
|
197
|
+
end
|
161
198
|
end
|
162
199
|
end
|
163
200
|
end
|
data/lib/theme_check/config.rb
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
module ThemeCheck
|
4
4
|
class Config
|
5
5
|
DOTFILE = '.theme-check.yml'
|
6
|
-
|
6
|
+
BUNDLED_CONFIGS_DIR = "#{__dir__}/../../config"
|
7
7
|
BOOLEAN = [true, false]
|
8
8
|
|
9
9
|
attr_reader :root
|
10
|
-
attr_accessor :
|
10
|
+
attr_accessor :auto_correct
|
11
11
|
|
12
12
|
class << self
|
13
13
|
attr_reader :last_loaded_config
|
@@ -42,18 +42,46 @@ module ThemeCheck
|
|
42
42
|
YAML.load_file(absolute_path)
|
43
43
|
end
|
44
44
|
|
45
|
+
def bundled_config_path(name)
|
46
|
+
"#{BUNDLED_CONFIGS_DIR}/#{name}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_bundled_config(name)
|
50
|
+
load_file(bundled_config_path(name))
|
51
|
+
end
|
52
|
+
|
53
|
+
def load_config(path)
|
54
|
+
if path[0] == ":"
|
55
|
+
load_bundled_config("#{path[1..]}.yml")
|
56
|
+
elsif path.is_a?(Symbol)
|
57
|
+
load_bundled_config("#{path}.yml")
|
58
|
+
else
|
59
|
+
load_file(path)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
45
63
|
def default
|
46
|
-
@default ||=
|
64
|
+
@default ||= load_config(":default")
|
47
65
|
end
|
48
66
|
end
|
49
67
|
|
50
68
|
def initialize(root: nil, configuration: nil, should_resolve_requires: true)
|
51
69
|
@configuration = if configuration
|
70
|
+
# TODO: Do we need to handle extends here? What base configuration
|
71
|
+
# should we validate against once Theme App Extensions has its own
|
72
|
+
# checks? :all?
|
52
73
|
validate_configuration(configuration)
|
53
74
|
else
|
54
75
|
{}
|
55
76
|
end
|
56
|
-
|
77
|
+
|
78
|
+
# Follow extends
|
79
|
+
extends = @configuration["extends"] || ":default"
|
80
|
+
while extends
|
81
|
+
extended_configuration = self.class.load_config(extends)
|
82
|
+
extends = extended_configuration["extends"]
|
83
|
+
@configuration = merge_configurations!(@configuration, extended_configuration)
|
84
|
+
end
|
57
85
|
|
58
86
|
@root = if root && @configuration.key?("root")
|
59
87
|
Pathname.new(root).join(@configuration["root"])
|
@@ -61,8 +89,6 @@ module ThemeCheck
|
|
61
89
|
Pathname.new(root)
|
62
90
|
end
|
63
91
|
|
64
|
-
@only_categories = []
|
65
|
-
@exclude_categories = []
|
66
92
|
@auto_correct = false
|
67
93
|
|
68
94
|
resolve_requires if @root && should_resolve_requires
|
@@ -87,16 +113,18 @@ module ThemeCheck
|
|
87
113
|
check_class = ThemeCheck.const_get(check_name)
|
88
114
|
|
89
115
|
next if check_class.categories.any? { |category| exclude_categories.include?(category) }
|
90
|
-
next if
|
116
|
+
next if include_categories.any? && !include_categories.all? { |category| check_class.categories.include?(category) }
|
91
117
|
|
92
118
|
options_for_check = options.transform_keys(&:to_sym)
|
93
119
|
options_for_check.delete(:enabled)
|
120
|
+
severity = options_for_check.delete(:severity)
|
94
121
|
ignored_patterns = options_for_check.delete(:ignore) || []
|
95
122
|
check = if options_for_check.empty?
|
96
123
|
check_class.new
|
97
124
|
else
|
98
125
|
check_class.new(**options_for_check)
|
99
126
|
end
|
127
|
+
check.severity = severity.to_sym if severity
|
100
128
|
check.ignored_patterns = ignored_patterns
|
101
129
|
check.options = options_for_check
|
102
130
|
check
|
@@ -107,6 +135,22 @@ module ThemeCheck
|
|
107
135
|
self["ignore"] || []
|
108
136
|
end
|
109
137
|
|
138
|
+
def include_categories
|
139
|
+
self["include_categories"] || []
|
140
|
+
end
|
141
|
+
|
142
|
+
def include_categories=(categories)
|
143
|
+
@configuration["include_categories"] = categories
|
144
|
+
end
|
145
|
+
|
146
|
+
def exclude_categories
|
147
|
+
self["exclude_categories"] || []
|
148
|
+
end
|
149
|
+
|
150
|
+
def exclude_categories=(categories)
|
151
|
+
@configuration["exclude_categories"] = categories
|
152
|
+
end
|
153
|
+
|
110
154
|
private
|
111
155
|
|
112
156
|
def check_name?(name)
|
@@ -133,6 +177,8 @@ module ThemeCheck
|
|
133
177
|
else
|
134
178
|
warn("bad configuration type for #{name}: expected a Hash, got #{value.inspect}")
|
135
179
|
end
|
180
|
+
elsif key == "severity"
|
181
|
+
valid_configuration[key] = value
|
136
182
|
elsif default.nil?
|
137
183
|
warn("unknown configuration: #{name}")
|
138
184
|
elsif BOOLEAN.include?(default) && !BOOLEAN.include?(value)
|
@@ -147,13 +193,13 @@ module ThemeCheck
|
|
147
193
|
valid_configuration
|
148
194
|
end
|
149
195
|
|
150
|
-
def
|
151
|
-
|
196
|
+
def merge_configurations!(configuration, extended_configuration)
|
197
|
+
extended_configuration.each do |key, default|
|
152
198
|
value = configuration[key]
|
153
199
|
|
154
200
|
case value
|
155
201
|
when Hash
|
156
|
-
|
202
|
+
merge_configurations!(value, default)
|
157
203
|
when nil
|
158
204
|
configuration[key] = default
|
159
205
|
end
|
@@ -27,5 +27,14 @@ module ThemeCheck
|
|
27
27
|
line.insert(node.range[0], insert_before)
|
28
28
|
line.insert(node.range[1] + 1 + insert_before.length, insert_after)
|
29
29
|
end
|
30
|
+
|
31
|
+
def create(theme, relative_path, content)
|
32
|
+
theme.storage.write(relative_path, content)
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_default_locale_json(theme)
|
36
|
+
theme.default_locale_json = JsonFile.new("locales/#{theme.default_locale}.default.json", theme.storage)
|
37
|
+
theme.default_locale_json.update_contents('{}')
|
38
|
+
end
|
30
39
|
end
|
31
40
|
end
|
@@ -1,32 +1,34 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "net/http"
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
module ThemeCheck
|
5
|
+
TIMEOUT_EXCEPTIONS = [
|
6
|
+
Net::ReadTimeout,
|
7
|
+
Net::OpenTimeout,
|
8
|
+
Net::WriteTimeout,
|
9
|
+
Errno::ETIMEDOUT,
|
10
|
+
Timeout::Error,
|
11
|
+
]
|
11
12
|
|
12
|
-
CONNECTION_EXCEPTIONS = [
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
]
|
13
|
+
CONNECTION_EXCEPTIONS = [
|
14
|
+
IOError,
|
15
|
+
EOFError,
|
16
|
+
SocketError,
|
17
|
+
Errno::EINVAL,
|
18
|
+
Errno::ECONNRESET,
|
19
|
+
Errno::ECONNABORTED,
|
20
|
+
Errno::EPIPE,
|
21
|
+
Errno::ECONNREFUSED,
|
22
|
+
Errno::EAGAIN,
|
23
|
+
Errno::EHOSTUNREACH,
|
24
|
+
Errno::ENETUNREACH,
|
25
|
+
]
|
25
26
|
|
26
|
-
NET_HTTP_EXCEPTIONS = [
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
]
|
27
|
+
NET_HTTP_EXCEPTIONS = [
|
28
|
+
Net::HTTPBadResponse,
|
29
|
+
Net::HTTPHeaderSyntaxError,
|
30
|
+
Net::ProtocolError,
|
31
|
+
*TIMEOUT_EXCEPTIONS,
|
32
|
+
*CONNECTION_EXCEPTIONS,
|
33
|
+
]
|
34
|
+
end
|
@@ -20,6 +20,9 @@ module ThemeCheck
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def write(relative_path, content)
|
23
|
+
reset_memoizers unless file_exists?(relative_path)
|
24
|
+
|
25
|
+
file(relative_path).dirname.mkpath unless file(relative_path).dirname.directory?
|
23
26
|
file(relative_path).write(content)
|
24
27
|
end
|
25
28
|
|
@@ -36,6 +39,15 @@ module ThemeCheck
|
|
36
39
|
|
37
40
|
private
|
38
41
|
|
42
|
+
def file_exists?(relative_path)
|
43
|
+
!!@files[relative_path]
|
44
|
+
end
|
45
|
+
|
46
|
+
def reset_memoizers
|
47
|
+
@file_array = nil
|
48
|
+
@directories = nil
|
49
|
+
end
|
50
|
+
|
39
51
|
def glob(pattern)
|
40
52
|
@root.glob(pattern).reject do |path|
|
41
53
|
relative_path = path.relative_path_from(@root)
|