theme-check 1.10.3 → 1.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/cla.yml +22 -0
- data/.github/workflows/theme-check.yml +1 -1
- data/.gitignore +3 -0
- data/CHANGELOG.md +48 -0
- data/CONTRIBUTING.md +82 -0
- data/README.md +11 -8
- data/Rakefile +7 -0
- data/TROUBLESHOOTING.md +65 -0
- data/config/default.yml +4 -0
- data/data/shopify_liquid/built_in_liquid_objects.json +60 -0
- data/data/shopify_liquid/documentation/filters.json +5528 -0
- data/data/shopify_liquid/documentation/latest.json +1 -0
- data/data/shopify_liquid/documentation/objects.json +19272 -0
- data/data/shopify_liquid/documentation/tags.json +1252 -0
- data/data/shopify_liquid/filters.yml +18 -0
- data/dev.yml +1 -1
- data/docs/checks/asset_preload.md +60 -0
- data/docs/checks/asset_size_javascript.md +2 -2
- data/docs/checks/missing_enable_comment.md +3 -3
- data/docs/checks/nested_snippet.md +8 -8
- data/docs/checks/translation_key_exists.md +4 -4
- data/docs/checks/valid_html_translation.md +1 -1
- data/lib/theme_check/analyzer.rb +18 -3
- data/lib/theme_check/check.rb +6 -1
- data/lib/theme_check/checks/asset_preload.rb +20 -0
- data/lib/theme_check/checks/deprecated_filter.rb +29 -5
- data/lib/theme_check/checks/missing_enable_comment.rb +4 -0
- data/lib/theme_check/checks/missing_required_template_files.rb +5 -1
- data/lib/theme_check/checks/missing_template.rb +5 -1
- data/lib/theme_check/checks/undefined_object.rb +4 -0
- data/lib/theme_check/checks/unused_assign.rb +6 -1
- data/lib/theme_check/checks/unused_snippet.rb +50 -2
- data/lib/theme_check/config.rb +2 -2
- data/lib/theme_check/disabled_checks.rb +11 -4
- data/lib/theme_check/file_system_storage.rb +2 -0
- data/lib/theme_check/in_memory_storage.rb +1 -1
- data/lib/theme_check/language_server/bridge.rb +31 -6
- data/lib/theme_check/language_server/completion_context.rb +52 -0
- data/lib/theme_check/language_server/completion_engine.rb +15 -21
- data/lib/theme_check/language_server/completion_provider.rb +16 -1
- data/lib/theme_check/language_server/completion_providers/assignments_completion_provider.rb +36 -0
- data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +49 -6
- data/lib/theme_check/language_server/completion_providers/object_attribute_completion_provider.rb +47 -0
- data/lib/theme_check/language_server/completion_providers/object_completion_provider.rb +10 -7
- data/lib/theme_check/language_server/completion_providers/render_snippet_completion_provider.rb +5 -1
- data/lib/theme_check/language_server/completion_providers/tag_completion_provider.rb +8 -1
- data/lib/theme_check/language_server/diagnostics_engine.rb +80 -34
- data/lib/theme_check/language_server/diagnostics_manager.rb +27 -6
- data/lib/theme_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +7 -6
- data/lib/theme_check/language_server/handler.rb +93 -9
- data/lib/theme_check/language_server/protocol.rb +9 -0
- data/lib/theme_check/language_server/server.rb +42 -14
- data/lib/theme_check/language_server/type_helper.rb +22 -0
- data/lib/theme_check/language_server/variable_lookup_finder/assignments_finder/node_handler.rb +63 -0
- data/lib/theme_check/language_server/variable_lookup_finder/assignments_finder/scope.rb +57 -0
- data/lib/theme_check/language_server/variable_lookup_finder/assignments_finder/scope_visitor.rb +42 -0
- data/lib/theme_check/language_server/variable_lookup_finder/assignments_finder.rb +76 -0
- data/lib/theme_check/language_server/variable_lookup_finder/constants.rb +43 -0
- data/lib/theme_check/language_server/variable_lookup_finder/liquid_fixer.rb +103 -0
- data/lib/theme_check/language_server/variable_lookup_finder/potential_lookup.rb +10 -0
- data/lib/theme_check/language_server/variable_lookup_finder/tolerant_parser.rb +94 -0
- data/lib/theme_check/language_server/variable_lookup_finder.rb +60 -100
- data/lib/theme_check/language_server/variable_lookup_traverser.rb +70 -0
- data/lib/theme_check/language_server/versioned_in_memory_storage.rb +17 -2
- data/lib/theme_check/language_server.rb +12 -0
- data/lib/theme_check/liquid_file.rb +22 -1
- data/lib/theme_check/liquid_node.rb +33 -1
- data/lib/theme_check/liquid_visitor.rb +1 -1
- data/lib/theme_check/remote_asset_file.rb +13 -7
- data/lib/theme_check/schema_helper.rb +1 -1
- data/lib/theme_check/shopify_liquid/documentation/markdown_template.rb +51 -0
- data/lib/theme_check/shopify_liquid/documentation.rb +44 -0
- data/lib/theme_check/shopify_liquid/filter.rb +4 -0
- data/lib/theme_check/shopify_liquid/object.rb +4 -0
- data/lib/theme_check/shopify_liquid/source_index/base_entry.rb +60 -0
- data/lib/theme_check/shopify_liquid/source_index/base_state.rb +23 -0
- data/lib/theme_check/shopify_liquid/source_index/filter_entry.rb +18 -0
- data/lib/theme_check/shopify_liquid/source_index/filter_state.rb +11 -0
- data/lib/theme_check/shopify_liquid/source_index/object_entry.rb +14 -0
- data/lib/theme_check/shopify_liquid/source_index/object_state.rb +11 -0
- data/lib/theme_check/shopify_liquid/source_index/parameter_entry.rb +21 -0
- data/lib/theme_check/shopify_liquid/source_index/property_entry.rb +9 -0
- data/lib/theme_check/shopify_liquid/source_index/return_type_entry.rb +37 -0
- data/lib/theme_check/shopify_liquid/source_index/tag_entry.rb +20 -0
- data/lib/theme_check/shopify_liquid/source_index/tag_state.rb +11 -0
- data/lib/theme_check/shopify_liquid/source_index.rb +56 -0
- data/lib/theme_check/shopify_liquid/source_manager.rb +111 -0
- data/lib/theme_check/shopify_liquid/tag.rb +4 -0
- data/lib/theme_check/shopify_liquid.rb +17 -1
- data/lib/theme_check/tags.rb +2 -1
- data/lib/theme_check/version.rb +1 -1
- data/shipit.rubygems.yml +3 -0
- data/theme-check.gemspec +5 -3
- metadata +45 -6
- data/.github/probots.yml +0 -3
@@ -1,4 +1,20 @@
|
|
1
1
|
---
|
2
|
+
# Here's an example workflow that's going to get ya the list of filters available
|
3
|
+
# ```bash
|
4
|
+
# spin up storefront-renderer
|
5
|
+
# spin ssh
|
6
|
+
# cd storefront-renderer
|
7
|
+
# bundle exec rake console
|
8
|
+
# ```
|
9
|
+
# ```ruby
|
10
|
+
# Pathname(ENV["HOME"]).join('filters.yml').write(YAML.dump(Liquid::StrainerFactory.global_filter_names))
|
11
|
+
# ```
|
12
|
+
# ```bash
|
13
|
+
# exit
|
14
|
+
# scp $(spin show -o fqdn):/filters.yml .
|
15
|
+
# ```
|
16
|
+
# The list of filters is now in a file named filters.yml in your $(pwd).
|
17
|
+
# Note that it'll probably need a bit of massaging before including here...
|
2
18
|
Liquid::StandardFilters:
|
3
19
|
- times
|
4
20
|
- h
|
@@ -19,7 +35,9 @@ Liquid::StandardFilters:
|
|
19
35
|
- remove
|
20
36
|
- sort_natural
|
21
37
|
- replace_first
|
38
|
+
- replace_last
|
22
39
|
- remove_first
|
40
|
+
- remove_last
|
23
41
|
- newline_to_br
|
24
42
|
- upcase
|
25
43
|
- downcase
|
data/dev.yml
CHANGED
@@ -0,0 +1,60 @@
|
|
1
|
+
# Prevent Manual Preloading of Assets (`AssetPreload`)
|
2
|
+
|
3
|
+
_Version 1.11.0+_
|
4
|
+
|
5
|
+
Preloading can be a useful way of making sure that critical assets are downloaded by the browser as soon as possible for better rendering performance.
|
6
|
+
|
7
|
+
Liquid provides multiple filters to [preload key resources][preload_key_resources] so they can be converted into `Link` headers automatically. This enables them to be discovered even faster, especially when combined with Early Hints that Shopify supports.
|
8
|
+
|
9
|
+
## Examples
|
10
|
+
|
11
|
+
The following examples contain code snippets that either fail or pass this check.
|
12
|
+
|
13
|
+
### ✗ Fail
|
14
|
+
|
15
|
+
```liquid
|
16
|
+
<link href="{{ 'script.js' | asset_url }}" rel="preload" as="script">
|
17
|
+
<link href="{{ 'style.css' | asset_url }}" rel="preload" as="style">
|
18
|
+
<link href="{{ 'image.png' | asset_url }}" rel="preload" as="image">
|
19
|
+
```
|
20
|
+
|
21
|
+
### ✓ Pass
|
22
|
+
|
23
|
+
```liquid
|
24
|
+
{{ 'script.js' | asset_url | preload_tag: as: 'script' }}
|
25
|
+
{{ 'style.css' | asset_url | stylesheet_tag: preload: true }}
|
26
|
+
{{
|
27
|
+
product.featured_image
|
28
|
+
| image_url: width: 600
|
29
|
+
| image_tag: preload: true
|
30
|
+
}}
|
31
|
+
```
|
32
|
+
|
33
|
+
## Options
|
34
|
+
|
35
|
+
The following example contains the default configuration for this check:
|
36
|
+
|
37
|
+
```yaml
|
38
|
+
AssetPreload:
|
39
|
+
enabled: true
|
40
|
+
severity: suggestion
|
41
|
+
```
|
42
|
+
|
43
|
+
| Parameter | Description |
|
44
|
+
| --- | --- |
|
45
|
+
| enabled | Whether the check is enabled. |
|
46
|
+
| severity | The [severity](https://shopify.dev/themes/tools/theme-check/configuration#check-severity) of the check. |
|
47
|
+
|
48
|
+
## Disabling this check
|
49
|
+
|
50
|
+
It's safe to disable this rule. You may want to do it when trying to preload assets from external domain and it is not possible
|
51
|
+
to move them to Shopify because they change frequently or are dynamically generated.
|
52
|
+
|
53
|
+
## Resources
|
54
|
+
|
55
|
+
- [Rule source][codesource]
|
56
|
+
- [Documentation source][docsource]
|
57
|
+
|
58
|
+
[codesource]: /lib/theme_check/checks/asset_preload.rb
|
59
|
+
[docsource]: /docs/checks/asset_preload.md
|
60
|
+
[preload_key_resources]: https://shopify.dev/themes/best-practices/performance#use-resource-hints-to-preload-key-resources
|
@@ -57,9 +57,9 @@ This includes theme and remote scripts.
|
|
57
57
|
When you can't do anything about it, it is preferable to disable this rule using the comment syntax:
|
58
58
|
|
59
59
|
```
|
60
|
-
{%
|
60
|
+
{% # theme-check-disable AssetSizeJavaScript %}
|
61
61
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js" defer></script>
|
62
|
-
{%
|
62
|
+
{% # theme-check-enable AssetSizeJavaScript %}
|
63
63
|
```
|
64
64
|
|
65
65
|
This makes disabling the rule an explicit affair and shows that the code is smelly.
|
@@ -12,7 +12,7 @@ This check aims at eliminating missing `theme-check-enable` comments.
|
|
12
12
|
<!doctype html>
|
13
13
|
<html>
|
14
14
|
<head>
|
15
|
-
{%
|
15
|
+
{% # theme-check-disable ParserBlockingJavaScript %}
|
16
16
|
<script src="https://cdnjs.com/jquery.min.js"></script>
|
17
17
|
</head>
|
18
18
|
<body>
|
@@ -27,9 +27,9 @@ This check aims at eliminating missing `theme-check-enable` comments.
|
|
27
27
|
<!doctype html>
|
28
28
|
<html>
|
29
29
|
<head>
|
30
|
-
{%
|
30
|
+
{% # theme-check-disable ParserBlockingJavaScript %}
|
31
31
|
<script src="https://cdnjs.com/jquery.min.js"></script>
|
32
|
-
{%
|
32
|
+
{% # theme-check-enable ParserBlockingJavaScript %}
|
33
33
|
</head>
|
34
34
|
<body>
|
35
35
|
<!-- ... -->
|
@@ -9,32 +9,32 @@ This check is aimed at eliminating excessive nesting of snippets.
|
|
9
9
|
:-1: Examples of **incorrect** code for this check:
|
10
10
|
|
11
11
|
```liquid
|
12
|
-
{%
|
12
|
+
{% # templates/index.liquid %}
|
13
13
|
{% render 'one' %}
|
14
14
|
|
15
|
-
{%
|
15
|
+
{% # snippets/one.liquid %}
|
16
16
|
{% render 'two' %}
|
17
17
|
|
18
|
-
{%
|
18
|
+
{% # snippets/two.liquid %}
|
19
19
|
{% render 'three' %}
|
20
20
|
|
21
|
-
{%
|
21
|
+
{% # snippets/three.liquid %}
|
22
22
|
{% render 'four' %}
|
23
23
|
|
24
|
-
{%
|
24
|
+
{% # snippets/four.liquid %}
|
25
25
|
ok
|
26
26
|
```
|
27
27
|
|
28
28
|
:+1: Examples of **correct** code for this check:
|
29
29
|
|
30
30
|
```liquid
|
31
|
-
{%
|
31
|
+
{% # templates/index.liquid %}
|
32
32
|
{% render 'one' %}
|
33
33
|
|
34
|
-
{%
|
34
|
+
{% # snippets/one.liquid %}
|
35
35
|
{% render 'two' %}
|
36
36
|
|
37
|
-
{%
|
37
|
+
{% # snippets/two.liquid %}
|
38
38
|
ok
|
39
39
|
```
|
40
40
|
|
@@ -9,7 +9,7 @@ This check is aimed at eliminating the use of translations that do not exist.
|
|
9
9
|
:-1: Examples of **incorrect** code for this check:
|
10
10
|
|
11
11
|
```liquid
|
12
|
-
{%
|
12
|
+
{% # locales/en.default.json %}
|
13
13
|
{
|
14
14
|
"greetings": "Hello, world!",
|
15
15
|
"general": {
|
@@ -17,14 +17,14 @@ This check is aimed at eliminating the use of translations that do not exist.
|
|
17
17
|
}
|
18
18
|
}
|
19
19
|
|
20
|
-
{%
|
20
|
+
{% # templates/index.liquid %}
|
21
21
|
{{ "notfound" | t }}
|
22
22
|
```
|
23
23
|
|
24
24
|
:+1: Examples of **correct** code for this check:
|
25
25
|
|
26
26
|
```liquid
|
27
|
-
{%
|
27
|
+
{% # locales/en.default.json %}
|
28
28
|
{
|
29
29
|
"greetings": "Hello, world!",
|
30
30
|
"general": {
|
@@ -32,7 +32,7 @@ This check is aimed at eliminating the use of translations that do not exist.
|
|
32
32
|
}
|
33
33
|
}
|
34
34
|
|
35
|
-
{%
|
35
|
+
{% # templates/index.liquid %}
|
36
36
|
{{ "greetings" | t }}
|
37
37
|
{{ "general.close" | t }}
|
38
38
|
```
|
@@ -18,7 +18,7 @@ This check is aimed at eliminating invalid HTML in translations.
|
|
18
18
|
:+1: Examples of **correct** code for this check:
|
19
19
|
|
20
20
|
```liquid
|
21
|
-
{%
|
21
|
+
{% # locales/en.default.json %}
|
22
22
|
{
|
23
23
|
"hello_html": "<h1>Hello, world</h1>",
|
24
24
|
"image_html": "<img src='spongebob.png'>",
|
data/lib/theme_check/analyzer.rb
CHANGED
@@ -41,6 +41,7 @@ module ThemeCheck
|
|
41
41
|
json_file_count + liquid_file_count
|
42
42
|
end
|
43
43
|
|
44
|
+
# Returns all offenses for all files in theme
|
44
45
|
def analyze_theme
|
45
46
|
reset
|
46
47
|
|
@@ -60,9 +61,23 @@ module ThemeCheck
|
|
60
61
|
@json_checks.call(:on_file, json_file)
|
61
62
|
end
|
62
63
|
|
63
|
-
finish
|
64
|
+
finish(false)
|
65
|
+
|
66
|
+
offenses
|
64
67
|
end
|
65
68
|
|
69
|
+
# When only_single_file is false:
|
70
|
+
# Runs single file checks for each file in `files`
|
71
|
+
# Runs whole theme checks
|
72
|
+
# Returns single file checks offenses for file in `files` + whole theme checks
|
73
|
+
# When only_single_file is true:
|
74
|
+
# Runs single file checks for each file in `files`
|
75
|
+
# Does not run whole theme checks
|
76
|
+
# Returns single file checks offenses for file in `files`
|
77
|
+
# When files is empty and only_single_file is false:
|
78
|
+
# Only returns whole theme checks
|
79
|
+
# When files is empty and only_single_file is true:
|
80
|
+
# Returns empty array
|
66
81
|
def analyze_files(files, only_single_file: false)
|
67
82
|
reset
|
68
83
|
|
@@ -103,6 +118,8 @@ module ThemeCheck
|
|
103
118
|
end
|
104
119
|
|
105
120
|
finish(only_single_file)
|
121
|
+
|
122
|
+
offenses
|
106
123
|
end
|
107
124
|
|
108
125
|
def uncorrectable_offenses
|
@@ -158,8 +175,6 @@ module ThemeCheck
|
|
158
175
|
@disabled_checks.remove_disabled_offenses(@liquid_checks)
|
159
176
|
@disabled_checks.remove_disabled_offenses(@json_checks)
|
160
177
|
@disabled_checks.remove_disabled_offenses(@html_checks)
|
161
|
-
|
162
|
-
offenses
|
163
178
|
end
|
164
179
|
end
|
165
180
|
end
|
data/lib/theme_check/check.rb
CHANGED
@@ -6,7 +6,8 @@ module ThemeCheck
|
|
6
6
|
include JsonHelpers
|
7
7
|
|
8
8
|
attr_accessor :theme
|
9
|
-
attr_accessor :options
|
9
|
+
attr_accessor :options
|
10
|
+
attr_writer :ignored_patterns
|
10
11
|
attr_writer :offenses
|
11
12
|
|
12
13
|
# The order matters.
|
@@ -130,6 +131,10 @@ module ThemeCheck
|
|
130
131
|
defined?(@ignored) && @ignored
|
131
132
|
end
|
132
133
|
|
134
|
+
def ignored_patterns
|
135
|
+
@ignored_patterns ||= []
|
136
|
+
end
|
137
|
+
|
133
138
|
def can_disable?
|
134
139
|
self.class.can_disable
|
135
140
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class AssetPreload < HtmlCheck
|
4
|
+
severity :suggestion
|
5
|
+
categories :html, :performance
|
6
|
+
doc docs_url(__FILE__)
|
7
|
+
|
8
|
+
def on_link(node)
|
9
|
+
return if node.attributes["rel"]&.downcase != "preload"
|
10
|
+
case node.attributes["as"]&.downcase
|
11
|
+
when "style"
|
12
|
+
add_offense("For better performance, prefer using the preload argument of the stylesheet_tag filter", node: node)
|
13
|
+
when "image"
|
14
|
+
add_offense("For better performance, prefer using the preload argument of the image_tag filter", node: node)
|
15
|
+
else
|
16
|
+
add_offense("For better performance, prefer using the preload_tag filter", node: node)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module ThemeCheck
|
3
4
|
class DeprecatedFilter < LiquidCheck
|
4
5
|
doc docs_url(__FILE__)
|
@@ -8,6 +9,19 @@ module ThemeCheck
|
|
8
9
|
# The image_url filter does not accept width or height values
|
9
10
|
# greater than this numbr.
|
10
11
|
MAX_SIZE = 5760
|
12
|
+
SIZE_REGEX = /^\d*x\d*$/
|
13
|
+
NAMED_SIZES = {
|
14
|
+
"pico" => 16,
|
15
|
+
"icon" => 32,
|
16
|
+
"thumb" => 50,
|
17
|
+
"small" => 100,
|
18
|
+
"compact" => 160,
|
19
|
+
"medium" => 240,
|
20
|
+
"large" => 480,
|
21
|
+
"grande" => 600,
|
22
|
+
"original" => 1024,
|
23
|
+
"master" => nil,
|
24
|
+
}
|
11
25
|
|
12
26
|
def on_variable(node)
|
13
27
|
used_filters = node.filters.map { |name, *_rest| name }
|
@@ -40,19 +54,29 @@ module ThemeCheck
|
|
40
54
|
|
41
55
|
# Can't correct those.
|
42
56
|
return add_default_offense(node, 'img_url', ['image_url']) unless
|
43
|
-
|
44
|
-
|
45
|
-
|
57
|
+
(size_spec.nil? || size_spec.is_a?(String)) &&
|
58
|
+
(scale.nil? || scale.is_a?(Numeric))
|
59
|
+
|
60
|
+
return add_default_offense(node, 'img_url', ['image_url']) if
|
61
|
+
size_spec.is_a?(String) &&
|
62
|
+
size_spec !~ SIZE_REGEX &&
|
63
|
+
!NAMED_SIZES.key?(size_spec)
|
46
64
|
|
47
65
|
node_source = node.markup
|
66
|
+
|
48
67
|
node_start_index = node.start_index
|
49
68
|
match = node_source.match(/img_url[^|]*/)
|
50
69
|
img_url_character_range =
|
51
70
|
(node_start_index + match.begin(0))...(node_start_index + match.end(0))
|
52
71
|
|
53
72
|
scale = (scale || 1).to_i
|
54
|
-
width, height =
|
55
|
-
|
73
|
+
width, height = if size_spec.nil?
|
74
|
+
[100, 100]
|
75
|
+
elsif NAMED_SIZES.key?(size_spec)
|
76
|
+
[NAMED_SIZES[size_spec], NAMED_SIZES[size_spec]]
|
77
|
+
else
|
78
|
+
size_spec.split('x')
|
79
|
+
end.map { |v| v.to_i * scale }
|
56
80
|
|
57
81
|
image_url_filter_params = [
|
58
82
|
width && width > 0 ? "width: #{[width, MAX_SIZE].min}" : nil,
|
@@ -16,6 +16,10 @@ module ThemeCheck
|
|
16
16
|
@disabled_checks.update(node)
|
17
17
|
end
|
18
18
|
|
19
|
+
def on_inline_comment(node)
|
20
|
+
@disabled_checks.update(node)
|
21
|
+
end
|
22
|
+
|
19
23
|
def after_document(node)
|
20
24
|
checks_missing_end_index = @disabled_checks.checks_missing_end_index
|
21
25
|
return if checks_missing_end_index.empty?
|
@@ -33,7 +33,11 @@ module ThemeCheck
|
|
33
33
|
if REQUIRED_LIQUID_TEMPLATE_FILES.include?(file)
|
34
34
|
corrector.create_file(@theme.storage, "#{file}.liquid", "")
|
35
35
|
else
|
36
|
-
corrector.create_file(@theme.storage, "#{file}.json",
|
36
|
+
corrector.create_file(@theme.storage, "#{file}.json", JSON.pretty_generate({
|
37
|
+
name: "TODO",
|
38
|
+
sections: {},
|
39
|
+
order: [],
|
40
|
+
}))
|
37
41
|
end
|
38
42
|
end
|
39
43
|
end
|
@@ -28,7 +28,11 @@ module ThemeCheck
|
|
28
28
|
private
|
29
29
|
|
30
30
|
def ignore?(path)
|
31
|
-
|
31
|
+
all_ignored_patterns.any? { |pattern| File.fnmatch?(pattern, path) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def all_ignored_patterns
|
35
|
+
@all_ignored_patterns ||= @ignore_missing + ignored_patterns
|
32
36
|
end
|
33
37
|
|
34
38
|
def add_missing_offense(name, node:)
|
@@ -120,6 +120,10 @@ module ThemeCheck
|
|
120
120
|
# NOTE: `email` is exceptionally exposed as a theme object in
|
121
121
|
# the customers' reset password template
|
122
122
|
check_object(info, all_global_objects + ['email'])
|
123
|
+
elsif 'templates/robots.txt' == name
|
124
|
+
# NOTE: `robots` is the only object exposed object in
|
125
|
+
# the robots.txt template
|
126
|
+
check_object(info, ['robots'])
|
123
127
|
elsif 'layout/checkout' == name
|
124
128
|
# NOTE: Shopify Plus has exceptionally exposed objects in
|
125
129
|
# the checkout template
|
@@ -39,7 +39,12 @@ module ThemeCheck
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def on_variable_lookup(node)
|
42
|
-
@templates[node.theme_file.name].used_assigns << node.value.name
|
42
|
+
@templates[node.theme_file.name].used_assigns << case node.value.name
|
43
|
+
when Liquid::VariableLookup
|
44
|
+
node.value.name.name
|
45
|
+
else
|
46
|
+
node.value.name
|
47
|
+
end
|
43
48
|
end
|
44
49
|
|
45
50
|
def on_end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "set"
|
3
4
|
|
4
5
|
module ThemeCheck
|
@@ -11,16 +12,22 @@ module ThemeCheck
|
|
11
12
|
@used_snippets = Set.new
|
12
13
|
end
|
13
14
|
|
14
|
-
def
|
15
|
+
def on_render(node)
|
15
16
|
if node.value.template_name_expr.is_a?(String)
|
16
17
|
@used_snippets << "snippets/#{node.value.template_name_expr}"
|
18
|
+
|
19
|
+
elsif might_have_a_block_as_variable_lookup?(node)
|
20
|
+
# We ignore this case, because that's a "proper" use case for
|
21
|
+
# the render tag with OS 2.0
|
22
|
+
# {% render block %} shouldn't turn off the UnusedSnippet check
|
23
|
+
|
17
24
|
else
|
18
25
|
# Can't reliably track unused snippets if an expression is used, ignore this check
|
19
26
|
@used_snippets.clear
|
20
27
|
ignore!
|
21
28
|
end
|
22
29
|
end
|
23
|
-
alias_method :
|
30
|
+
alias_method :on_include, :on_render
|
24
31
|
|
25
32
|
def on_end
|
26
33
|
missing_snippets.each do |theme_file|
|
@@ -33,5 +40,46 @@ module ThemeCheck
|
|
33
40
|
def missing_snippets
|
34
41
|
theme.snippets.reject { |t| @used_snippets.include?(t.name) }
|
35
42
|
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# This function returns true when the render node passed might have a
|
47
|
+
# variable lookup that refers to a block as template_name_expr.
|
48
|
+
#
|
49
|
+
# e.g.
|
50
|
+
#
|
51
|
+
# {% for block in col %}
|
52
|
+
# {% render block %}
|
53
|
+
# {% endfor %}
|
54
|
+
#
|
55
|
+
# In this case, the `block` variable_lookup in the render tag might be
|
56
|
+
# a Block because col might be an array of blocks.
|
57
|
+
#
|
58
|
+
# @param node [Node]
|
59
|
+
def might_have_a_block_as_variable_lookup?(node)
|
60
|
+
return false unless node.type_name == :render
|
61
|
+
|
62
|
+
return false unless node.value.template_name_expr.is_a?(Liquid::VariableLookup)
|
63
|
+
|
64
|
+
name = node.value.template_name_expr.name
|
65
|
+
return false unless name.is_a?(String)
|
66
|
+
|
67
|
+
# We're going through all the parents of the nodes until we find
|
68
|
+
# a For node with variable_name === to the template_name_expr's name
|
69
|
+
find_parent(node.parent) do |parent_node|
|
70
|
+
next false unless parent_node.type_name == :for
|
71
|
+
|
72
|
+
parent_node.value.variable_name == name
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param node [Node]
|
77
|
+
def find_parent(node, &pred)
|
78
|
+
return nil unless node
|
79
|
+
|
80
|
+
return node if yield node
|
81
|
+
|
82
|
+
find_parent(node.parent, &pred)
|
83
|
+
end
|
36
84
|
end
|
37
85
|
end
|
data/lib/theme_check/config.rb
CHANGED
@@ -126,14 +126,14 @@ module ThemeCheck
|
|
126
126
|
options_for_check = options.transform_keys(&:to_sym)
|
127
127
|
options_for_check.delete(:enabled)
|
128
128
|
severity = options_for_check.delete(:severity)
|
129
|
-
|
129
|
+
check_ignored_patterns = options_for_check.delete(:ignore) || []
|
130
130
|
check = if options_for_check.empty?
|
131
131
|
check_class.new
|
132
132
|
else
|
133
133
|
check_class.new(**options_for_check)
|
134
134
|
end
|
135
135
|
check.severity = severity.to_sym if severity
|
136
|
-
check.ignored_patterns = ignored_patterns
|
136
|
+
check.ignored_patterns = check_ignored_patterns + ignored_patterns
|
137
137
|
check.options = options_for_check
|
138
138
|
check
|
139
139
|
end.compact
|
@@ -34,14 +34,16 @@ module ThemeCheck
|
|
34
34
|
# We want to disable checks inside comments
|
35
35
|
# (e.g. html checks inside {% comment %})
|
36
36
|
disabled = @disabled_checks[[node.theme_file, :all]]
|
37
|
-
disabled.
|
38
|
-
|
37
|
+
unless disabled.first_line
|
38
|
+
disabled.start_index = node.inner_markup_start_index
|
39
|
+
disabled.end_index = node.inner_markup_end_index
|
40
|
+
end
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
42
44
|
def disabled?(check, theme_file, check_name, index)
|
43
45
|
return true if check.ignored_patterns&.any? do |pattern|
|
44
|
-
theme_file
|
46
|
+
theme_file&.relative_path&.fnmatch?(pattern)
|
45
47
|
end
|
46
48
|
|
47
49
|
@disabled_checks[[theme_file, :all]]&.disabled?(index) ||
|
@@ -65,7 +67,12 @@ module ThemeCheck
|
|
65
67
|
private
|
66
68
|
|
67
69
|
def comment_text(node)
|
68
|
-
node.
|
70
|
+
case node.type_name
|
71
|
+
when :comment
|
72
|
+
node.value.nodelist.join
|
73
|
+
when :inline_comment
|
74
|
+
node.markup.sub(/\s*#+\s*/, '')
|
75
|
+
end
|
69
76
|
end
|
70
77
|
|
71
78
|
def start_disabling?(text)
|