theme-check 1.5.2 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/theme-check.yml +12 -4
- data/CHANGELOG.md +17 -0
- data/lib/theme_check/analyzer.rb +5 -0
- data/lib/theme_check/asset_file.rb +13 -2
- data/lib/theme_check/check.rb +1 -1
- data/lib/theme_check/checks/asset_size_css.rb +15 -0
- data/lib/theme_check/checks/asset_size_css_stylesheet_tag.rb +18 -1
- data/lib/theme_check/checks/convert_include_to_render.rb +2 -1
- data/lib/theme_check/checks/missing_required_template_files.rb +21 -7
- data/lib/theme_check/checks/translation_key_exists.rb +3 -1
- data/lib/theme_check/checks/unused_snippet.rb +3 -1
- data/lib/theme_check/cli.rb +6 -3
- data/lib/theme_check/corrector.rb +19 -10
- data/lib/theme_check/file_system_storage.rb +7 -2
- data/lib/theme_check/html_node.rb +4 -4
- data/lib/theme_check/html_visitor.rb +20 -8
- data/lib/theme_check/in_memory_storage.rb +4 -0
- data/lib/theme_check/json_file.rb +9 -4
- data/lib/theme_check/node.rb +8 -13
- data/lib/theme_check/offense.rb +26 -0
- data/lib/theme_check/regex_helpers.rb +1 -15
- data/lib/theme_check/template.rb +5 -19
- data/lib/theme_check/template_rewriter.rb +57 -0
- data/lib/theme_check/theme_file.rb +18 -1
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check.rb +1 -0
- data/theme-check.gemspec +1 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 713459ed0c35e11e175e51d42237fceb583184af08a00f8f56cdecbbb1c494b8
|
4
|
+
data.tar.gz: aa2059da0b820755eee1a8778748d7c7758fef705e29f5def5c74905743553d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa6216908fb067365d11ef7c0ea1c2e0a847de124f81ad43824eb49f9373093b6fc54dabbc1062dd9068af11724abc9dad020c6bebc2c71702f4750a8829aa78
|
7
|
+
data.tar.gz: f0412fbb0407d75e33ac96f66c760dd39ddb5cd18c751b8f6cb8a6edb4da1d0867147d929b7bae1401eb5837ee1c4aa0f40f60340d70c4a75c5f47a0753ebf58
|
@@ -8,15 +8,23 @@ jobs:
|
|
8
8
|
|
9
9
|
strategy:
|
10
10
|
matrix:
|
11
|
-
platform:
|
11
|
+
platform:
|
12
|
+
- ubuntu-latest
|
13
|
+
- windows-latest
|
12
14
|
version:
|
13
15
|
- 3.0.0
|
14
16
|
- 2.6.6
|
17
|
+
theme:
|
18
|
+
- Shopify/dawn
|
15
19
|
|
16
20
|
name: Ruby ${{ matrix.platform }} ${{ matrix.version }}
|
17
21
|
|
18
22
|
steps:
|
19
23
|
- uses: actions/checkout@v2
|
24
|
+
- uses: actions/checkout@v2
|
25
|
+
with:
|
26
|
+
repository: ${{ matrix.theme }}
|
27
|
+
path: ./crash-test-theme
|
20
28
|
- name: Set up Ruby ${{ matrix.version }}
|
21
29
|
uses: ruby/setup-ruby@v1
|
22
30
|
with:
|
@@ -30,6 +38,6 @@ jobs:
|
|
30
38
|
run: bundle install --jobs=3 --retry=3 --path=vendor/bundle
|
31
39
|
- name: Run tests
|
32
40
|
run: bundle exec rake
|
33
|
-
- name:
|
34
|
-
|
35
|
-
|
41
|
+
- name: Crash test
|
42
|
+
run: |
|
43
|
+
bundle exec theme-check --fail-level crash ./crash-test-theme
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,21 @@
|
|
1
1
|
|
2
|
+
v1.6.0 / 2021-09-14
|
3
|
+
===================
|
4
|
+
|
5
|
+
### Features
|
6
|
+
|
7
|
+
* Add `--auto-correct` support to `TranslationKeyExists` (add missing translation as TODO to default locale) ([#422](https://github.com/shopify/theme-check/issues/422))
|
8
|
+
* Add `--auto-correct` support to `UnusedSnippet` (delete unused file) ([#416](https://github.com/shopify/theme-check/issues/416))
|
9
|
+
* Add `--auto-correct` support to `MissingRequiredTemplateFiles` (create missing files) ([#385](https://github.com/shopify/theme-check/issues/385))
|
10
|
+
|
11
|
+
### Fixes
|
12
|
+
|
13
|
+
* Fix `undefined method [] of nil` in `replace_placeholders` ([#441](https://github.com/shopify/theme-check/issues/441), [#444](https://github.com/shopify/theme-check/issues/444))
|
14
|
+
* Disable ConvertIncludeToRender corrector until we fix [#445](https://github.com/shopify/theme-check/issues/445) ([#446](https://github.com/shopify/theme-check/issues/446))
|
15
|
+
* Fix a couple of correction bugs ([#442](https://github.com/shopify/theme-check/issues/442), [#439](https://github.com/shopify/theme-check/issues/439))
|
16
|
+
* Fix `AssetSizeCSS` error when size is nil ([#419](https://github.com/shopify/theme-check/issues/419))
|
17
|
+
* Write JSON to file, not a Ruby Hash. ([#434](https://github.com/shopify/theme-check/issues/434), [#432](https://github.com/shopify/theme-check/issues/432))
|
18
|
+
|
2
19
|
v1.5.2 / 2021-09-09
|
3
20
|
===================
|
4
21
|
|
data/lib/theme_check/analyzer.rb
CHANGED
@@ -9,10 +9,21 @@ module ThemeCheck
|
|
9
9
|
@content = nil
|
10
10
|
end
|
11
11
|
|
12
|
-
|
12
|
+
def rewriter
|
13
|
+
@rewriter ||= TemplateRewriter.new(@relative_path, source)
|
14
|
+
end
|
15
|
+
|
16
|
+
def write
|
17
|
+
content = rewriter.to_s
|
18
|
+
if source != content
|
19
|
+
@storage.write(@relative_path, content.gsub("\n", @eol))
|
20
|
+
@source = content
|
21
|
+
@rewriter = nil
|
22
|
+
end
|
23
|
+
end
|
13
24
|
|
14
25
|
def gzipped_size
|
15
|
-
@gzipped_size ||= Zlib.gzip(
|
26
|
+
@gzipped_size ||= Zlib.gzip(source).bytesize
|
16
27
|
end
|
17
28
|
|
18
29
|
def name
|
data/lib/theme_check/check.rb
CHANGED
@@ -22,5 +22,20 @@ module ThemeCheck
|
|
22
22
|
node: node
|
23
23
|
)
|
24
24
|
end
|
25
|
+
|
26
|
+
def href_to_file_size(href)
|
27
|
+
# asset_url (+ optional stylesheet_tag) variables
|
28
|
+
if href =~ /^#{LIQUID_VARIABLE}$/o && href =~ /asset_url/ && href =~ Liquid::QuotedString
|
29
|
+
asset_id = Regexp.last_match(0).gsub(START_OR_END_QUOTE, "")
|
30
|
+
asset = @theme.assets.find { |a| a.name.end_with?("/" + asset_id) }
|
31
|
+
return if asset.nil?
|
32
|
+
asset.gzipped_size
|
33
|
+
|
34
|
+
# remote URLs
|
35
|
+
elsif href =~ %r{^(https?:)?//}
|
36
|
+
asset = RemoteAssetFile.from_src(href)
|
37
|
+
asset.gzipped_size
|
38
|
+
end
|
39
|
+
end
|
25
40
|
end
|
26
41
|
end
|
@@ -13,12 +13,29 @@ module ThemeCheck
|
|
13
13
|
def on_variable(node)
|
14
14
|
used_filters = node.value.filters.map { |name, *_rest| name }
|
15
15
|
return unless used_filters.include?("stylesheet_tag")
|
16
|
-
file_size =
|
16
|
+
file_size = stylesheet_tag_pipeline_to_file_size(node.markup)
|
17
|
+
return if file_size.nil?
|
17
18
|
return if file_size <= @threshold_in_bytes
|
18
19
|
add_offense(
|
19
20
|
"CSS on every page load exceeding compressed size threshold (#{@threshold_in_bytes} Bytes).",
|
20
21
|
node: node
|
21
22
|
)
|
22
23
|
end
|
24
|
+
|
25
|
+
def stylesheet_tag_pipeline_to_file_size(href)
|
26
|
+
# asset_url
|
27
|
+
if href =~ /asset_url/ && href =~ Liquid::QuotedString
|
28
|
+
asset_id = Regexp.last_match(0).gsub(START_OR_END_QUOTE, "")
|
29
|
+
asset = @theme.assets.find { |a| a.name.end_with?("/" + asset_id) }
|
30
|
+
return if asset.nil?
|
31
|
+
asset.gzipped_size
|
32
|
+
|
33
|
+
# remote URLs
|
34
|
+
elsif href =~ %r{(https?:)?//[^'"]+}
|
35
|
+
url = Regexp.last_match(0)
|
36
|
+
asset = RemoteAssetFile.from_src(url)
|
37
|
+
asset.gzipped_size
|
38
|
+
end
|
39
|
+
end
|
23
40
|
end
|
24
41
|
end
|
@@ -8,7 +8,8 @@ module ThemeCheck
|
|
8
8
|
|
9
9
|
def on_include(node)
|
10
10
|
add_offense("`include` is deprecated - convert it to `render`", node: node) do |corrector|
|
11
|
-
|
11
|
+
# We need to fix #445 and pass the variables from the context or don't replace at all.
|
12
|
+
# corrector.replace(node, "render \'#{node.value.template_name_expr}\' ")
|
12
13
|
end
|
13
14
|
end
|
14
15
|
end
|
@@ -9,19 +9,33 @@ module ThemeCheck
|
|
9
9
|
doc docs_url(__FILE__)
|
10
10
|
|
11
11
|
REQUIRED_LIQUID_FILES = %w(layout/theme)
|
12
|
-
|
13
|
-
|
12
|
+
|
13
|
+
REQUIRED_LIQUID_TEMPLATE_FILES = %w(
|
14
14
|
gift_card customers/account customers/activate_account customers/addresses
|
15
|
-
customers/login customers/order customers/register customers/reset_password
|
16
|
-
)
|
17
|
-
|
15
|
+
customers/login customers/order customers/register customers/reset_password
|
16
|
+
).map { |file| "templates/#{file}" }
|
17
|
+
|
18
|
+
REQUIRED_JSON_TEMPLATE_FILES = %w(
|
19
|
+
index product collection cart blog article page list-collections search 404
|
20
|
+
password
|
21
|
+
).map { |file| "templates/#{file}" }
|
22
|
+
|
23
|
+
REQUIRED_TEMPLATE_FILES = (REQUIRED_LIQUID_TEMPLATE_FILES + REQUIRED_JSON_TEMPLATE_FILES)
|
18
24
|
|
19
25
|
def on_end
|
20
26
|
(REQUIRED_LIQUID_FILES - theme.liquid.map(&:name)).each do |file|
|
21
|
-
add_offense("'#{file}.liquid' is missing")
|
27
|
+
add_offense("'#{file}.liquid' is missing") do |corrector|
|
28
|
+
corrector.create(@theme, "#{file}.liquid", "")
|
29
|
+
end
|
22
30
|
end
|
23
31
|
(REQUIRED_TEMPLATE_FILES - (theme.liquid + theme.json).map(&:name)).each do |file|
|
24
|
-
add_offense("'#{file}.liquid' or '#{file}.json' is missing")
|
32
|
+
add_offense("'#{file}.liquid' or '#{file}.json' is missing") do |corrector|
|
33
|
+
if REQUIRED_LIQUID_TEMPLATE_FILES.include?(file)
|
34
|
+
corrector.create(@theme, "#{file}.liquid", "")
|
35
|
+
else
|
36
|
+
corrector.create(@theme, "#{file}.json", "")
|
37
|
+
end
|
38
|
+
end
|
25
39
|
end
|
26
40
|
end
|
27
41
|
end
|
@@ -29,7 +29,9 @@ module ThemeCheck
|
|
29
29
|
"'#{key_node.value}' does not have a matching entry in '#{@theme.default_locale_json.relative_path}'",
|
30
30
|
node: node,
|
31
31
|
markup: key_node.value,
|
32
|
-
)
|
32
|
+
) do |corrector|
|
33
|
+
corrector.add_default_translation_key(@theme.default_locale_json, key_node.value.split("."), "TODO")
|
34
|
+
end
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
@@ -24,7 +24,9 @@ module ThemeCheck
|
|
24
24
|
|
25
25
|
def on_end
|
26
26
|
missing_snippets.each do |template|
|
27
|
-
add_offense("This template is not used", template: template)
|
27
|
+
add_offense("This template is not used", template: template) do |corrector|
|
28
|
+
corrector.remove(@theme, template.relative_path.to_s)
|
29
|
+
end
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
data/lib/theme_check/cli.rb
CHANGED
@@ -49,7 +49,7 @@ module ThemeCheck
|
|
49
49
|
"Automatically fix offenses"
|
50
50
|
) { @auto_correct = true }
|
51
51
|
@option_parser.on(
|
52
|
-
"--fail-level SEVERITY", Check::SEVERITIES,
|
52
|
+
"--fail-level SEVERITY", [:crash] + Check::SEVERITIES,
|
53
53
|
"Minimum severity (error|suggestion|style) for exit with error code"
|
54
54
|
) do |severity|
|
55
55
|
@fail_level = severity.to_sym
|
@@ -191,7 +191,10 @@ module ThemeCheck
|
|
191
191
|
analyzer = ThemeCheck::Analyzer.new(theme, @config.enabled_checks, @config.auto_correct)
|
192
192
|
analyzer.analyze_theme
|
193
193
|
analyzer.correct_offenses
|
194
|
-
|
194
|
+
print_with_format(theme, analyzer, out_stream)
|
195
|
+
# corrections are committed after printing so that the
|
196
|
+
# source_excerpts are still pointing to the uncorrected source.
|
197
|
+
analyzer.write_corrections
|
195
198
|
raise Abort, "" if analyzer.uncorrectable_offenses.any? do |offense|
|
196
199
|
offense.check.severity_value <= Check.severity_value(@fail_level)
|
197
200
|
end
|
@@ -211,7 +214,7 @@ module ThemeCheck
|
|
211
214
|
STDERR.puts "Profiling is only available in development"
|
212
215
|
end
|
213
216
|
|
214
|
-
def
|
217
|
+
def print_with_format(theme, analyzer, out_stream)
|
215
218
|
case @format
|
216
219
|
when :text
|
217
220
|
ThemeCheck::Printer.new(out_stream).print(theme, analyzer.offenses, @config.auto_correct)
|
@@ -7,25 +7,20 @@ module ThemeCheck
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def insert_after(node, content)
|
10
|
-
|
11
|
-
line.insert(node.range[1] + 1, content)
|
10
|
+
@template.rewriter.insert_after(node, content)
|
12
11
|
end
|
13
12
|
|
14
13
|
def insert_before(node, content)
|
15
|
-
|
16
|
-
line.insert(node.range[0], content)
|
14
|
+
@template.rewriter.insert_before(node, content)
|
17
15
|
end
|
18
16
|
|
19
17
|
def replace(node, content)
|
20
|
-
|
21
|
-
line[node.range[0]..node.range[1]] = content
|
18
|
+
@template.rewriter.replace(node, content)
|
22
19
|
node.markup = content
|
23
20
|
end
|
24
21
|
|
25
22
|
def wrap(node, insert_before, insert_after)
|
26
|
-
|
27
|
-
line.insert(node.range[0], insert_before)
|
28
|
-
line.insert(node.range[1] + 1 + insert_before.length, insert_after)
|
23
|
+
@template.rewriter.wrap(node, insert_before, insert_after)
|
29
24
|
end
|
30
25
|
|
31
26
|
def create(theme, relative_path, content)
|
@@ -34,11 +29,25 @@ module ThemeCheck
|
|
34
29
|
|
35
30
|
def create_default_locale_json(theme)
|
36
31
|
theme.default_locale_json = JsonFile.new("locales/#{theme.default_locale}.default.json", theme.storage)
|
37
|
-
theme.default_locale_json.update_contents(
|
32
|
+
theme.default_locale_json.update_contents({})
|
33
|
+
end
|
34
|
+
|
35
|
+
def remove(theme, relative_path)
|
36
|
+
theme.storage.remove(relative_path)
|
38
37
|
end
|
39
38
|
|
40
39
|
def mkdir(theme, relative_path)
|
41
40
|
theme.storage.mkdir(relative_path)
|
42
41
|
end
|
42
|
+
|
43
|
+
def add_default_translation_key(file, key, value)
|
44
|
+
hash = file.content
|
45
|
+
key.reduce(hash) do |pointer, token|
|
46
|
+
return pointer[token] = value if token == key.last
|
47
|
+
pointer[token] = {} unless pointer.key?(token)
|
48
|
+
pointer[token]
|
49
|
+
end
|
50
|
+
file.update_contents(hash)
|
51
|
+
end
|
43
52
|
end
|
44
53
|
end
|
@@ -16,14 +16,19 @@ module ThemeCheck
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def read(relative_path)
|
19
|
-
file(relative_path).read
|
19
|
+
file(relative_path).read(mode: 'rb', encoding: 'UTF-8')
|
20
20
|
end
|
21
21
|
|
22
22
|
def write(relative_path, content)
|
23
23
|
reset_memoizers unless file_exists?(relative_path)
|
24
24
|
|
25
25
|
file(relative_path).dirname.mkpath unless file(relative_path).dirname.directory?
|
26
|
-
file(relative_path).write(content)
|
26
|
+
file(relative_path).write(content, mode: 'w+b', encoding: 'UTF-8')
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove(relative_path)
|
30
|
+
file(relative_path).delete
|
31
|
+
reset_memoizers
|
27
32
|
end
|
28
33
|
|
29
34
|
def mkdir(relative_path)
|
@@ -67,10 +67,10 @@ module ThemeCheck
|
|
67
67
|
private
|
68
68
|
|
69
69
|
def replace_placeholders(string)
|
70
|
-
# Replace all {
|
71
|
-
string.gsub(
|
72
|
-
key =
|
73
|
-
@placeholder_values[key.to_i]
|
70
|
+
# Replace all ≬{i}####≬ with the actual content.
|
71
|
+
string.gsub(HTML_LIQUID_PLACEHOLDER) do |match|
|
72
|
+
key = /[0-9a-z]+/.match(match)[0]
|
73
|
+
@placeholder_values[key.to_i(36)]
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
@@ -9,12 +9,11 @@ module ThemeCheck
|
|
9
9
|
|
10
10
|
def initialize(checks)
|
11
11
|
@checks = checks
|
12
|
-
@placeholder_values = []
|
13
12
|
end
|
14
13
|
|
15
14
|
def visit_template(template)
|
16
|
-
doc = parse(template)
|
17
|
-
visit(HtmlNode.new(doc, template,
|
15
|
+
doc, placeholder_values = parse(template)
|
16
|
+
visit(HtmlNode.new(doc, template, placeholder_values))
|
18
17
|
rescue ArgumentError => e
|
19
18
|
call_checks(:on_parse_error, e, template)
|
20
19
|
end
|
@@ -22,19 +21,32 @@ module ThemeCheck
|
|
22
21
|
private
|
23
22
|
|
24
23
|
def parse(template)
|
24
|
+
placeholder_values = []
|
25
25
|
parseable_source = +template.source.clone
|
26
26
|
|
27
|
-
# Replace all liquid tags with {
|
27
|
+
# Replace all non-empty liquid tags with ≬{i}######≬ to prevent the HTML
|
28
28
|
# parser from freaking out. We transparently replace those placeholders in
|
29
29
|
# HtmlNode.
|
30
|
+
#
|
31
|
+
# We're using base36 to prevent index bleeding on 36^3 tags.
|
32
|
+
# `{{x}}` -> `≬#{i}≬` would properly be transformed for 46656 tags in a single file.
|
33
|
+
# Should be enough.
|
34
|
+
#
|
35
|
+
# The base10 alternative would have overflowed at 1000 (`{{x}}` -> `≬1000≬`) which seemed more likely.
|
36
|
+
#
|
37
|
+
# Didn't go with base64 because of the `=` character that would have messed with HTML parsing.
|
30
38
|
matches(parseable_source, LIQUID_TAG_OR_VARIABLE).each do |m|
|
31
39
|
value = m[0]
|
32
|
-
|
33
|
-
|
34
|
-
|
40
|
+
next unless value.size > 4 # skip empty tags/variables {%%} and {{}}
|
41
|
+
placeholder_values.push(value)
|
42
|
+
key = (placeholder_values.size - 1).to_s(36)
|
43
|
+
parseable_source[m.begin(0)...m.end(0)] = "≬#{key.ljust(m.end(0) - m.begin(0) - 2, '#')}≬"
|
35
44
|
end
|
36
45
|
|
37
|
-
|
46
|
+
[
|
47
|
+
Nokogiri::HTML5.fragment(parseable_source, max_tree_depth: 400, max_attributes: 400),
|
48
|
+
placeholder_values,
|
49
|
+
]
|
38
50
|
end
|
39
51
|
|
40
52
|
def visit(node)
|
@@ -20,14 +20,19 @@ module ThemeCheck
|
|
20
20
|
@parser_error
|
21
21
|
end
|
22
22
|
|
23
|
-
def update_contents(new_content =
|
23
|
+
def update_contents(new_content = {})
|
24
|
+
raise ArgumentError if new_content.is_a?(String)
|
24
25
|
@content = new_content
|
25
26
|
end
|
26
27
|
|
27
28
|
def write
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
pretty = JSON.pretty_generate(@content)
|
30
|
+
if source.rstrip != pretty.rstrip
|
31
|
+
# Most editors add a trailing \n at the end of files. Here we
|
32
|
+
# try to maintain the convention.
|
33
|
+
eof = source.end_with?("\n") ? "\n" : ""
|
34
|
+
@storage.write(@relative_path, pretty.gsub("\n", @eol) + eof)
|
35
|
+
@source = pretty
|
31
36
|
end
|
32
37
|
end
|
33
38
|
|
data/lib/theme_check/node.rb
CHANGED
@@ -156,11 +156,6 @@ module ThemeCheck
|
|
156
156
|
end
|
157
157
|
end
|
158
158
|
|
159
|
-
def range
|
160
|
-
start = template.full_line(line_number).index(markup)
|
161
|
-
[start, start + markup.length - 1]
|
162
|
-
end
|
163
|
-
|
164
159
|
def position
|
165
160
|
@position ||= Position.new(
|
166
161
|
markup,
|
@@ -169,6 +164,14 @@ module ThemeCheck
|
|
169
164
|
)
|
170
165
|
end
|
171
166
|
|
167
|
+
def start_index
|
168
|
+
position.start_index
|
169
|
+
end
|
170
|
+
|
171
|
+
def end_index
|
172
|
+
position.end_index
|
173
|
+
end
|
174
|
+
|
172
175
|
def start_token
|
173
176
|
return "" if inside_liquid_tag?
|
174
177
|
output = ""
|
@@ -187,14 +190,6 @@ module ThemeCheck
|
|
187
190
|
output
|
188
191
|
end
|
189
192
|
|
190
|
-
def start_index
|
191
|
-
position.start_index
|
192
|
-
end
|
193
|
-
|
194
|
-
def end_index
|
195
|
-
position.end_index
|
196
|
-
end
|
197
|
-
|
198
193
|
private
|
199
194
|
|
200
195
|
# Here we're hacking around a glorious bug in Liquid that makes it so the
|
data/lib/theme_check/offense.rb
CHANGED
@@ -144,6 +144,32 @@ module ThemeCheck
|
|
144
144
|
corrector = Corrector.new(template: template)
|
145
145
|
correction.call(corrector)
|
146
146
|
end
|
147
|
+
rescue => e
|
148
|
+
ThemeCheck.bug(<<~EOS)
|
149
|
+
Exception while running `Offense#correct`:
|
150
|
+
```
|
151
|
+
#{e.class}: #{e.message}
|
152
|
+
#{e.backtrace.join("\n ")}
|
153
|
+
```
|
154
|
+
|
155
|
+
Offense:
|
156
|
+
```
|
157
|
+
#{JSON.pretty_generate(to_h)}
|
158
|
+
```
|
159
|
+
Check options:
|
160
|
+
```
|
161
|
+
#{check.options.pretty_inspect}
|
162
|
+
```
|
163
|
+
Markup:
|
164
|
+
```
|
165
|
+
#{markup}
|
166
|
+
```
|
167
|
+
Node.Markup:
|
168
|
+
```
|
169
|
+
#{node&.markup}
|
170
|
+
```
|
171
|
+
EOS
|
172
|
+
exit(2)
|
147
173
|
end
|
148
174
|
|
149
175
|
def whole_theme?
|
@@ -5,6 +5,7 @@ module ThemeCheck
|
|
5
5
|
LIQUID_TAG = /#{Liquid::TagStart}.*?#{Liquid::TagEnd}/om
|
6
6
|
LIQUID_VARIABLE = /#{Liquid::VariableStart}.*?#{Liquid::VariableEnd}/om
|
7
7
|
LIQUID_TAG_OR_VARIABLE = /#{LIQUID_TAG}|#{LIQUID_VARIABLE}/om
|
8
|
+
HTML_LIQUID_PLACEHOLDER = /≬[0-9a-z]+#*≬/m
|
8
9
|
START_OR_END_QUOTE = /(^['"])|(['"]$)/
|
9
10
|
|
10
11
|
def matches(s, re)
|
@@ -16,20 +17,5 @@ module ThemeCheck
|
|
16
17
|
end
|
17
18
|
matches
|
18
19
|
end
|
19
|
-
|
20
|
-
def href_to_file_size(href)
|
21
|
-
# asset_url (+ optional stylesheet_tag) variables
|
22
|
-
if href =~ /^#{LIQUID_VARIABLE}$/o && href =~ /asset_url/ && href =~ Liquid::QuotedString
|
23
|
-
asset_id = Regexp.last_match(0).gsub(START_OR_END_QUOTE, "")
|
24
|
-
asset = @theme.assets.find { |a| a.name.end_with?("/" + asset_id) }
|
25
|
-
return if asset.nil?
|
26
|
-
asset.gzipped_size
|
27
|
-
|
28
|
-
# remote URLs
|
29
|
-
elsif href =~ %r{^(https?:)?//}
|
30
|
-
asset = RemoteAssetFile.from_src(href)
|
31
|
-
asset.gzipped_size
|
32
|
-
end
|
33
|
-
end
|
34
20
|
end
|
35
21
|
end
|
data/lib/theme_check/template.rb
CHANGED
@@ -3,10 +3,11 @@
|
|
3
3
|
module ThemeCheck
|
4
4
|
class Template < ThemeFile
|
5
5
|
def write
|
6
|
-
content =
|
6
|
+
content = rewriter.to_s
|
7
7
|
if source != content
|
8
|
-
@storage.write(@relative_path, content)
|
8
|
+
@storage.write(@relative_path, content.gsub("\n", @eol))
|
9
9
|
@source = content
|
10
|
+
@rewriter = nil
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
@@ -26,19 +27,8 @@ module ThemeCheck
|
|
26
27
|
name.start_with?('snippets')
|
27
28
|
end
|
28
29
|
|
29
|
-
def
|
30
|
-
|
31
|
-
@lines ||= source.split("\n", -1)
|
32
|
-
end
|
33
|
-
|
34
|
-
# Not entirely obvious but lines is mutable, corrections are to be
|
35
|
-
# applied on @lines.
|
36
|
-
def updated_content
|
37
|
-
lines.join("\n")
|
38
|
-
end
|
39
|
-
|
40
|
-
def excerpt(line)
|
41
|
-
lines[line - 1].strip
|
30
|
+
def rewriter
|
31
|
+
@rewriter ||= TemplateRewriter.new(@relative_path, source)
|
42
32
|
end
|
43
33
|
|
44
34
|
def source_excerpt(line)
|
@@ -46,10 +36,6 @@ module ThemeCheck
|
|
46
36
|
original_lines[line - 1].strip
|
47
37
|
end
|
48
38
|
|
49
|
-
def full_line(line)
|
50
|
-
lines[line - 1]
|
51
|
-
end
|
52
|
-
|
53
39
|
def parse
|
54
40
|
@ast ||= self.class.parse(source)
|
55
41
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'parser'
|
4
|
+
|
5
|
+
module ThemeCheck
|
6
|
+
class TemplateRewriter
|
7
|
+
def initialize(name, source)
|
8
|
+
@buffer = Parser::Source::Buffer.new(name, source: source)
|
9
|
+
@rewriter = Parser::Source::TreeRewriter.new(
|
10
|
+
@buffer
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def insert_before(node, content)
|
15
|
+
@rewriter.insert_before(
|
16
|
+
range(node.start_index, node.end_index),
|
17
|
+
content
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def insert_after(node, content)
|
22
|
+
@rewriter.insert_after(
|
23
|
+
range(node.start_index, node.end_index),
|
24
|
+
content
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def replace(node, content)
|
29
|
+
@rewriter.replace(
|
30
|
+
range(node.start_index, node.end_index),
|
31
|
+
content
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def wrap(node, insert_before, insert_after)
|
36
|
+
@rewriter.wrap(
|
37
|
+
range(node.start_index, node.end_index),
|
38
|
+
insert_before,
|
39
|
+
insert_after,
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
@rewriter.process
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def range(start_index, end_index)
|
50
|
+
Parser::Source::Range.new(
|
51
|
+
@buffer,
|
52
|
+
start_index,
|
53
|
+
end_index,
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -6,6 +6,8 @@ module ThemeCheck
|
|
6
6
|
def initialize(relative_path, storage)
|
7
7
|
@relative_path = relative_path
|
8
8
|
@storage = storage
|
9
|
+
@source = nil
|
10
|
+
@eol = "\n"
|
9
11
|
end
|
10
12
|
|
11
13
|
def path
|
@@ -20,8 +22,23 @@ module ThemeCheck
|
|
20
22
|
relative_path.sub_ext('').to_s
|
21
23
|
end
|
22
24
|
|
25
|
+
# For the corrector to work properly, we should have a
|
26
|
+
# simple mental model of the internal representation of eol
|
27
|
+
# characters (Windows uses \r\n, Linux uses \n).
|
28
|
+
#
|
29
|
+
# Parser::Source::Buffer strips the \r from the source file, so if
|
30
|
+
# you are autocorrecting the file you might lose that info and
|
31
|
+
# cause a git diff. It also makes the node.start_index/end_index
|
32
|
+
# calculation break. That's not cool.
|
33
|
+
#
|
34
|
+
# So in here we track whether the source file has \r\n in it and
|
35
|
+
# we'll make sure that the file we write has the same eol as the
|
36
|
+
# source file.
|
23
37
|
def source
|
24
|
-
@source
|
38
|
+
return @source if @source
|
39
|
+
@source = @storage.read(@relative_path)
|
40
|
+
@eol = @source.include?("\r\n") ? "\r\n" : "\n"
|
41
|
+
@source = @source.gsub("\r\n", "\n")
|
25
42
|
end
|
26
43
|
|
27
44
|
def json?
|
data/lib/theme_check/version.rb
CHANGED
data/lib/theme_check.rb
CHANGED
@@ -34,6 +34,7 @@ require_relative "theme_check/string_helpers"
|
|
34
34
|
require_relative "theme_check/file_system_storage"
|
35
35
|
require_relative "theme_check/in_memory_storage"
|
36
36
|
require_relative "theme_check/tags"
|
37
|
+
require_relative "theme_check/template_rewriter"
|
37
38
|
require_relative "theme_check/template"
|
38
39
|
require_relative "theme_check/theme"
|
39
40
|
require_relative "theme_check/visitor"
|
data/theme-check.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: theme-check
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marc-André Cournoyer
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-09-
|
11
|
+
date: 2021-09-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: liquid
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.12'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: parser
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3'
|
41
55
|
description:
|
42
56
|
email:
|
43
57
|
- marcandre.cournoyer@shopify.com
|
@@ -229,6 +243,7 @@ files:
|
|
229
243
|
- lib/theme_check/string_helpers.rb
|
230
244
|
- lib/theme_check/tags.rb
|
231
245
|
- lib/theme_check/template.rb
|
246
|
+
- lib/theme_check/template_rewriter.rb
|
232
247
|
- lib/theme_check/theme.rb
|
233
248
|
- lib/theme_check/theme_file.rb
|
234
249
|
- lib/theme_check/version.rb
|