theme-check 1.5.2 → 1.6.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 +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
|