theme-check 0.10.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/config/default.yml +19 -3
- data/data/shopify_liquid/objects.yml +2 -0
- data/docs/checks/asset_size_css_stylesheet_tag.md +50 -0
- data/docs/checks/deprecate_bgsizes.md +66 -0
- data/docs/checks/deprecate_lazysizes.md +61 -0
- data/docs/checks/liquid_tag.md +2 -2
- data/docs/checks/template_length.md +12 -2
- data/lib/theme_check/checks/asset_size_css.rb +11 -74
- data/lib/theme_check/checks/asset_size_css_stylesheet_tag.rb +24 -0
- data/lib/theme_check/checks/asset_size_javascript.rb +10 -36
- data/lib/theme_check/checks/deprecate_bgsizes.rb +14 -0
- data/lib/theme_check/checks/deprecate_lazysizes.rb +16 -0
- data/lib/theme_check/checks/img_lazy_loading.rb +1 -6
- data/lib/theme_check/checks/liquid_tag.rb +2 -2
- data/lib/theme_check/checks/remote_asset.rb +2 -0
- data/lib/theme_check/checks/template_length.rb +18 -4
- data/lib/theme_check/html_check.rb +2 -0
- data/lib/theme_check/liquid_check.rb +0 -12
- data/lib/theme_check/parsing_helpers.rb +3 -1
- data/lib/theme_check/regex_helpers.rb +17 -0
- data/lib/theme_check/tags.rb +37 -0
- data/lib/theme_check/version.rb +1 -1
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 94b918e53777cfb70c65dacd9d9bfbdb6f4463597c8f9c81969e1baef89df7a1
|
4
|
+
data.tar.gz: 7e45af9a3dfeab1eafdff5533df68a9e0d67007d94661a2c974c0c2fece61095
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea1b75ca11a66aefe5eef421f577d53716cb180799863185d36fad21a9033b5cf52579df3e593e41867513da410adc7d16de723f35b3a5435e50098b7af2a348
|
7
|
+
data.tar.gz: ceab091f6bf7317d899cb5459601b7dfb5294e6e4be962a421b33bad33c74fd730001dea90687f81f754d57e5cdedc5b39d0efb5d95d6c016ed965f7e4e86a9f
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,18 @@
|
|
1
1
|
|
2
|
+
v1.0.0 / 2021-06-28
|
3
|
+
==================
|
4
|
+
|
5
|
+
* Convert `AssetSizeCSS` to `HtmlCheck`
|
6
|
+
* Add `DeprecateLazysizes` & `DeprecateBgsizes` checks
|
7
|
+
* Allow hardcoded CDN urls in `RemoteAsset`
|
8
|
+
* Bump `LiquidTag` `min_consecutive_statements` default to 5
|
9
|
+
* Exclude {% javascript %} and {% stylesheet %} from line counts in `TemplateLength`
|
10
|
+
* Bump `TemplateLength` `max_length` default to 500
|
11
|
+
* Fix `StringScanner#skip(String)` not being supported on some Rubies
|
12
|
+
* Fix `ParsingHelpers#outside_of_strings` handling of empty strings
|
13
|
+
* Update to support new `{% render %}` syntax
|
14
|
+
* Converted `AssetSizeJavaScript` to `HtmlCheck`
|
15
|
+
|
2
16
|
v0.10.2 / 2021-06-18
|
3
17
|
==================
|
4
18
|
|
data/config/default.yml
CHANGED
@@ -12,7 +12,7 @@ ConvertIncludeToRender:
|
|
12
12
|
LiquidTag:
|
13
13
|
enabled: true
|
14
14
|
ignore: []
|
15
|
-
min_consecutive_statements:
|
15
|
+
min_consecutive_statements: 5
|
16
16
|
|
17
17
|
MissingTemplate:
|
18
18
|
enabled: true
|
@@ -38,9 +38,13 @@ SyntaxError:
|
|
38
38
|
TemplateLength:
|
39
39
|
enabled: true
|
40
40
|
ignore: []
|
41
|
-
max_length:
|
41
|
+
max_length: 500
|
42
42
|
# Exclude content of {% schema %} in line count
|
43
43
|
exclude_schema: true
|
44
|
+
# Exclude content of {% stylesheet %} in line count
|
45
|
+
exclude_stylesheet: true
|
46
|
+
# Exclude content of {% javascript %} in line count
|
47
|
+
exclude_javascript: true
|
44
48
|
|
45
49
|
UnknownFilter:
|
46
50
|
enabled: true
|
@@ -99,6 +103,14 @@ DeprecatedFilter:
|
|
99
103
|
enabled: true
|
100
104
|
ignore: []
|
101
105
|
|
106
|
+
DeprecateLazysizes:
|
107
|
+
enabled: true
|
108
|
+
ignore: []
|
109
|
+
|
110
|
+
DeprecateBgsizes:
|
111
|
+
enabled: true
|
112
|
+
ignore: []
|
113
|
+
|
102
114
|
MissingEnableComment:
|
103
115
|
enabled: true
|
104
116
|
ignore: []
|
@@ -109,16 +121,20 @@ ParserBlockingJavaScript:
|
|
109
121
|
|
110
122
|
ParserBlockingScriptTag:
|
111
123
|
enabled: true
|
124
|
+
ignore: []
|
112
125
|
|
113
126
|
AssetSizeJavaScript:
|
114
127
|
enabled: false
|
115
|
-
ignore: []
|
116
128
|
threshold_in_bytes: 10_000
|
117
129
|
ignore: []
|
118
130
|
|
119
131
|
AssetSizeCSS:
|
120
132
|
enabled: false
|
133
|
+
threshold_in_bytes: 100_000
|
121
134
|
ignore: []
|
135
|
+
|
136
|
+
AssetSizeCSSStylesheetTag:
|
137
|
+
enabled: false
|
122
138
|
threshold_in_bytes: 100_000
|
123
139
|
ignore: []
|
124
140
|
|
@@ -40,6 +40,7 @@
|
|
40
40
|
- linklist
|
41
41
|
- linklists
|
42
42
|
- location
|
43
|
+
- localization
|
43
44
|
- metafield
|
44
45
|
- model
|
45
46
|
- model_source
|
@@ -55,6 +56,7 @@
|
|
55
56
|
- powered_by_link
|
56
57
|
- product
|
57
58
|
- product_option
|
59
|
+
- product_variant
|
58
60
|
- recommendations
|
59
61
|
- request
|
60
62
|
- routes
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Check Title (`AssetSizeCSSStylesheetTag`)
|
2
|
+
|
3
|
+
The `stylesheet_tag` filter generates a link tag that points to a given stylesheet. This rule exists to prevent large CSS bundles (for speed).
|
4
|
+
|
5
|
+
## Check Details
|
6
|
+
|
7
|
+
This rule disallows the use of too much CSS in themes, as configured by `threshold_in_bytes`.
|
8
|
+
|
9
|
+
:-1: Examples of **incorrect** code for this check:
|
10
|
+
```liquid
|
11
|
+
<!-- Here, assets/theme.css is **greater** than `threshold_in_bytes` compressed. -->
|
12
|
+
{{ 'theme.css' | asset_url | stylesheet_tag }}
|
13
|
+
```
|
14
|
+
|
15
|
+
:+1: Example of **correct** code for this check:
|
16
|
+
```liquid
|
17
|
+
<!-- Here, assets/theme.css is **less** than `threshold_in_bytes` compressed. -->
|
18
|
+
{{ 'theme.css' | asset_url | stylesheet_tag }}
|
19
|
+
```
|
20
|
+
|
21
|
+
## Check Options
|
22
|
+
|
23
|
+
The default configuration for this check is the following:
|
24
|
+
|
25
|
+
```yaml
|
26
|
+
AssetSizeCSSStylesheetTag:
|
27
|
+
enabled: false
|
28
|
+
threshold_in_bytes: 100_000
|
29
|
+
```
|
30
|
+
|
31
|
+
### `threshold_in_bytes`
|
32
|
+
|
33
|
+
The `threshold_in_bytes` option (default: `100_000`) determines the maximum allowed compressed size in bytes that a single CSS file can take.
|
34
|
+
|
35
|
+
## When Not To Use It
|
36
|
+
|
37
|
+
This rule is safe to disable.
|
38
|
+
|
39
|
+
## Version
|
40
|
+
|
41
|
+
This check has been introduced in Theme Check 1.0.0
|
42
|
+
|
43
|
+
## Resources
|
44
|
+
|
45
|
+
- [The Performance Inequality Gap](https://infrequently.org/2021/03/the-performance-inequality-gap/)
|
46
|
+
- [Rule Source][codesource]
|
47
|
+
- [Documentation Source][docsource]
|
48
|
+
|
49
|
+
[codesource]: /lib/theme_check/checks/asset_size_css_stylesheet_tag.rb
|
50
|
+
[docsource]: /docs/checks/asset_size_css_stylesheet_tag.md
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Deprecate Bgsizes (`DeprecateBgsizes`)
|
2
|
+
|
3
|
+
The lazySizes bgset extension allows you to define multiple background images with a width descriptor. The extension will then load the best image size for the current viewport and device (https://github.com/aFarkas/lazysizes/tree/gh-pages/plugins/bgset)
|
4
|
+
|
5
|
+
|
6
|
+
## Check Details
|
7
|
+
|
8
|
+
This check is aimed at discouraging the use of the lazySizes bgset plugin
|
9
|
+
|
10
|
+
:-1: Examples of **incorrect** code for this check:
|
11
|
+
|
12
|
+
```liquid
|
13
|
+
|
14
|
+
<!-- Reports use of "lazyload" class and "data-bgset" attribute -->
|
15
|
+
|
16
|
+
<script src="ls.bgset.min.js"></script>
|
17
|
+
<script src="lazysizes.min.js"></script>
|
18
|
+
<div class="lazyload" data-bgset="image-200.jpg 200w, image-300.jpg 300w, image-400.jpg 400w" data-sizes="auto">
|
19
|
+
</div>
|
20
|
+
|
21
|
+
```
|
22
|
+
|
23
|
+
:+1: Examples of **correct** code for this check:
|
24
|
+
|
25
|
+
```liquid
|
26
|
+
|
27
|
+
<!-- Uses the CSS image-set() attribute instead of "data-bgset" -->
|
28
|
+
<!-- CSS Stylesheet -->
|
29
|
+
.box {
|
30
|
+
background-image: -webkit-image-set(
|
31
|
+
url("small-balloons.jpg") 1x,
|
32
|
+
url("large-balloons.jpg") 2x);
|
33
|
+
background-image: image-set(
|
34
|
+
url("small-balloons.jpg") 1x,
|
35
|
+
url("large-balloons.jpg") 2x);
|
36
|
+
}
|
37
|
+
|
38
|
+
<!-- HTML -->
|
39
|
+
<div class="box"></div>
|
40
|
+
|
41
|
+
```
|
42
|
+
|
43
|
+
## Check Options
|
44
|
+
|
45
|
+
The default configuration for this check is the following:
|
46
|
+
|
47
|
+
```yaml
|
48
|
+
DeprecateBgsizes:
|
49
|
+
enabled: true
|
50
|
+
```
|
51
|
+
|
52
|
+
## When Not To Use It
|
53
|
+
|
54
|
+
You should disable this rule in older browsers that don't support the CSS image-set attribute.
|
55
|
+
|
56
|
+
## Version
|
57
|
+
|
58
|
+
This check has been introduced in Theme Check 1.0.0.
|
59
|
+
|
60
|
+
## Resources
|
61
|
+
|
62
|
+
- [Rule Source][codesource]
|
63
|
+
- [Documentation Source][docsource]
|
64
|
+
|
65
|
+
[codesource]: /lib/theme_check/checks/deprecate_bgsizes.rb
|
66
|
+
[docsource]: /docs/checks/deprecate_bgsizes.md
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# Deprecate lazySizes (`DeprecateLazysizes`)
|
2
|
+
|
3
|
+
[lazysizes](https://github.com/aFarkas/lazysizes) is a common JavaScript library used to lazy load images, iframes and scripts.
|
4
|
+
|
5
|
+
## Check Details
|
6
|
+
|
7
|
+
This check is aimed at discouraging the use of the lazysizes JavaScript library
|
8
|
+
|
9
|
+
:-1: Examples of **incorrect** code for this check:
|
10
|
+
|
11
|
+
```liquid
|
12
|
+
|
13
|
+
<!-- Reports use of "lazyload" class -->
|
14
|
+
<img src="a.jpg" class="lazyload">
|
15
|
+
|
16
|
+
<!-- Reports use of "data-srcset" and "data-sizes" attribute. Reports data-sizes="auto" -->
|
17
|
+
<img
|
18
|
+
alt="House by the lake"
|
19
|
+
data-sizes="auto"
|
20
|
+
data-srcset="small.jpg 500w,
|
21
|
+
medium.jpg 640w,
|
22
|
+
big.jpg 1024w"
|
23
|
+
data-src="medium.jpg"
|
24
|
+
class="lazyload"
|
25
|
+
/>
|
26
|
+
|
27
|
+
```
|
28
|
+
|
29
|
+
:+1: Examples of **correct** code for this check:
|
30
|
+
|
31
|
+
```liquid
|
32
|
+
|
33
|
+
<!-- Does not use lazySizes library. Instead uses native "loading" attribute -->
|
34
|
+
<img src="a.jpg" loading="lazy">
|
35
|
+
|
36
|
+
```
|
37
|
+
|
38
|
+
## Check Options
|
39
|
+
|
40
|
+
The default configuration for this check is the following:
|
41
|
+
|
42
|
+
```yaml
|
43
|
+
DeprecateLazysizes:
|
44
|
+
enabled: true
|
45
|
+
```
|
46
|
+
|
47
|
+
## When Not To Use It
|
48
|
+
|
49
|
+
You should disable this rule if you want to support lazy loading of images in older browser that don't support the loading="lazy" attribute yet.
|
50
|
+
|
51
|
+
## Version
|
52
|
+
|
53
|
+
This check has been introduced in Theme Check 1.0.0.
|
54
|
+
|
55
|
+
## Resources
|
56
|
+
|
57
|
+
- [Rule Source][codesource]
|
58
|
+
- [Documentation Source][docsource]
|
59
|
+
|
60
|
+
[codesource]: /lib/theme_check/checks/deprecate_lazysizes.rb
|
61
|
+
[docsource]: /docs/checks/deprecate_lazysizes.md
|
data/docs/checks/liquid_tag.md
CHANGED
@@ -39,12 +39,12 @@ The default configuration for this check is the following:
|
|
39
39
|
```yaml
|
40
40
|
LiquidTag:
|
41
41
|
enabled: true
|
42
|
-
min_consecutive_statements:
|
42
|
+
min_consecutive_statements: 5
|
43
43
|
```
|
44
44
|
|
45
45
|
### `min_consecutive_statements`
|
46
46
|
|
47
|
-
The `min_consecutive_statements` option (Default: `
|
47
|
+
The `min_consecutive_statements` option (Default: `5`) determines the maximum (inclusive) number of consecutive statements before the check recommends a refactor.
|
48
48
|
|
49
49
|
## When Not To Use It
|
50
50
|
|
@@ -21,8 +21,10 @@ The default configuration for this check is the following:
|
|
21
21
|
```yaml
|
22
22
|
TemplateLength:
|
23
23
|
enabled: true
|
24
|
-
max_length:
|
24
|
+
max_length: 500
|
25
25
|
exclude_schema: true
|
26
|
+
exclude_stylesheet: true
|
27
|
+
exclude_javascript: true
|
26
28
|
```
|
27
29
|
|
28
30
|
### `max_length`
|
@@ -31,7 +33,15 @@ The `max_length` (Default: `200`) option determines the maximum number of lines
|
|
31
33
|
|
32
34
|
### `exclude_schema`
|
33
35
|
|
34
|
-
The `exclude_schema` (Default: `true`) option determines if the schema
|
36
|
+
The `exclude_schema` (Default: `true`) option determines if the lines inside `{% schema %}` blocks from a template should be excluded from the line count.
|
37
|
+
|
38
|
+
### `exclude_stylesheet`
|
39
|
+
|
40
|
+
The `exclude_stylesheet` (Default: `true`) option determines if the lines inside `{% stylesheet %}` blocks from a template should be excluded from the line count.
|
41
|
+
|
42
|
+
### `exclude_javascript`
|
43
|
+
|
44
|
+
The `exclude_javascript` (Default: `true`) option determines if the lines inside `{% javascript %}` blocks from a template should be excluded from the line count.
|
35
45
|
|
36
46
|
## When Not To Use It
|
37
47
|
|
@@ -1,89 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module ThemeCheck
|
3
|
-
class AssetSizeCSS <
|
3
|
+
class AssetSizeCSS < HtmlCheck
|
4
4
|
include RegexHelpers
|
5
5
|
severity :error
|
6
|
-
category :performance
|
6
|
+
category :html, :performance
|
7
7
|
doc docs_url(__FILE__)
|
8
8
|
|
9
|
-
Link = Struct.new(:href, :index)
|
10
|
-
|
11
|
-
LINK_TAG_HREF = %r{
|
12
|
-
<link
|
13
|
-
(?=[^>]+?rel=['"]?stylesheet['"]?) # Make sure rel=stylesheet is in the link with lookahead
|
14
|
-
[^>]+ # any non closing tag character
|
15
|
-
href= # href attribute start
|
16
|
-
(?<href>#{QUOTED_LIQUID_ATTRIBUTE}) # href attribute value (may contain liquid)
|
17
|
-
[^>]* # any non closing character till the end
|
18
|
-
>
|
19
|
-
}omix
|
20
|
-
STYLESHEET_TAG = %r{
|
21
|
-
#{Liquid::VariableStart} # VariableStart
|
22
|
-
(?:(?!#{Liquid::VariableEnd}).)*? # anything that isn't followed by a VariableEnd
|
23
|
-
\|\s*asset_url\s* # | asset_url
|
24
|
-
\|\s*stylesheet_tag\s* # | stylesheet_tag
|
25
|
-
#{Liquid::VariableEnd} # VariableEnd
|
26
|
-
}omix
|
27
|
-
|
28
9
|
attr_reader :threshold_in_bytes
|
29
10
|
|
30
11
|
def initialize(threshold_in_bytes: 100_000)
|
31
12
|
@threshold_in_bytes = threshold_in_bytes
|
32
13
|
end
|
33
14
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
next if file_size.nil?
|
44
|
-
next if file_size <= threshold_in_bytes
|
45
|
-
add_offense(
|
46
|
-
"CSS on every page load exceding compressed size threshold (#{threshold_in_bytes} Bytes).",
|
47
|
-
node: @node,
|
48
|
-
markup: stylesheet.href,
|
49
|
-
line_number: @source[0...stylesheet.index].count("\n") + 1
|
50
|
-
)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def stylesheets(source)
|
55
|
-
stylesheet_links = matches(source, LINK_TAG_HREF)
|
56
|
-
.map do |m|
|
57
|
-
Link.new(
|
58
|
-
m[:href].gsub(START_OR_END_QUOTE, ""),
|
59
|
-
m.begin(:href),
|
60
|
-
)
|
61
|
-
end
|
62
|
-
|
63
|
-
stylesheet_tags = matches(source, STYLESHEET_TAG)
|
64
|
-
.map do |m|
|
65
|
-
Link.new(
|
66
|
-
m[0],
|
67
|
-
m.begin(0),
|
68
|
-
)
|
69
|
-
end
|
70
|
-
|
71
|
-
stylesheet_links + stylesheet_tags
|
72
|
-
end
|
73
|
-
|
74
|
-
def href_to_file_size(href)
|
75
|
-
# asset_url (+ optional stylesheet_tag) variables
|
76
|
-
if href =~ /^#{VARIABLE}$/o && href =~ /asset_url/ && href =~ Liquid::QuotedString
|
77
|
-
asset_id = Regexp.last_match(0).gsub(START_OR_END_QUOTE, "")
|
78
|
-
asset = @theme.assets.find { |a| a.name.end_with?("/" + asset_id) }
|
79
|
-
return if asset.nil?
|
80
|
-
asset.gzipped_size
|
81
|
-
|
82
|
-
# remote URLs
|
83
|
-
elsif href =~ %r{^(https?:)?//}
|
84
|
-
asset = RemoteAssetFile.from_src(href)
|
85
|
-
asset.gzipped_size
|
86
|
-
end
|
15
|
+
def on_link(node)
|
16
|
+
return if node.attributes['rel']&.value != "stylesheet"
|
17
|
+
file_size = href_to_file_size(node.attributes['href']&.value)
|
18
|
+
return if file_size.nil?
|
19
|
+
return if file_size <= threshold_in_bytes
|
20
|
+
add_offense(
|
21
|
+
"CSS on every page load exceeding compressed size threshold (#{threshold_in_bytes} Bytes).",
|
22
|
+
node: node
|
23
|
+
)
|
87
24
|
end
|
88
25
|
end
|
89
26
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class AssetSizeCSSStylesheetTag < LiquidCheck
|
4
|
+
include RegexHelpers
|
5
|
+
severity :error
|
6
|
+
category :liquid, :performance
|
7
|
+
doc docs_url(__FILE__)
|
8
|
+
|
9
|
+
def initialize(threshold_in_bytes: 100_000)
|
10
|
+
@threshold_in_bytes = threshold_in_bytes
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_variable(node)
|
14
|
+
used_filters = node.value.filters.map { |name, *_rest| name }
|
15
|
+
return unless used_filters.include?("stylesheet_tag")
|
16
|
+
file_size = href_to_file_size('{{' + node.markup + '}}')
|
17
|
+
return if file_size <= @threshold_in_bytes
|
18
|
+
add_offense(
|
19
|
+
"CSS on every page load exceeding compressed size threshold (#{@threshold_in_bytes} Bytes).",
|
20
|
+
node: node
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -3,52 +3,26 @@ module ThemeCheck
|
|
3
3
|
# Reports errors when trying to use too much JavaScript on page load
|
4
4
|
# Encourages the use of the Import on Interaction pattern [1].
|
5
5
|
# [1]: https://addyosmani.com/blog/import-on-interaction/
|
6
|
-
class AssetSizeJavaScript <
|
6
|
+
class AssetSizeJavaScript < HtmlCheck
|
7
7
|
include RegexHelpers
|
8
8
|
severity :error
|
9
|
-
category :performance
|
9
|
+
category :html, :performance
|
10
10
|
doc docs_url(__FILE__)
|
11
11
|
|
12
|
-
Script = Struct.new(:src, :match)
|
13
|
-
|
14
|
-
SCRIPT_TAG_SRC = %r{
|
15
|
-
<script
|
16
|
-
[^>]+ # any non closing tag character
|
17
|
-
src= # src attribute start
|
18
|
-
(?<src>#{QUOTED_LIQUID_ATTRIBUTE}) # src attribute value (may contain liquid)
|
19
|
-
[^>]* # any non closing character till the end
|
20
|
-
>
|
21
|
-
}omix
|
22
|
-
|
23
12
|
attr_reader :threshold_in_bytes
|
24
13
|
|
25
14
|
def initialize(threshold_in_bytes: 10000)
|
26
15
|
@threshold_in_bytes = threshold_in_bytes
|
27
16
|
end
|
28
17
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
file_size = src_to_file_size(script.src)
|
38
|
-
next if file_size.nil?
|
39
|
-
next if file_size <= threshold_in_bytes
|
40
|
-
add_offense(
|
41
|
-
"JavaScript on every page load exceding compressed size threshold (#{threshold_in_bytes} Bytes), consider using the import on interaction pattern.",
|
42
|
-
node: @node,
|
43
|
-
markup: script.src,
|
44
|
-
line_number: @source[0...script.match.begin(:src)].count("\n") + 1
|
45
|
-
)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def scripts(source)
|
50
|
-
matches(source, SCRIPT_TAG_SRC)
|
51
|
-
.map { |m| Script.new(m[:src].gsub(START_OR_END_QUOTE, ""), m) }
|
18
|
+
def on_script(node)
|
19
|
+
file_size = src_to_file_size(node.attributes['src']&.value)
|
20
|
+
return if file_size.nil?
|
21
|
+
return if file_size <= threshold_in_bytes
|
22
|
+
add_offense(
|
23
|
+
"JavaScript on every page load exceeds compressed size threshold (#{threshold_in_bytes} Bytes), consider using the import on interaction pattern.",
|
24
|
+
node: node
|
25
|
+
)
|
52
26
|
end
|
53
27
|
|
54
28
|
def src_to_file_size(src)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class DeprecateBgsizes < HtmlCheck
|
4
|
+
severity :suggestion
|
5
|
+
category :html, :performance
|
6
|
+
doc docs_url(__FILE__)
|
7
|
+
|
8
|
+
def on_div(node)
|
9
|
+
class_list = node.attributes["class"]&.value&.split(" ")
|
10
|
+
add_offense("Use the native loading=\"lazy\" attribute instead of lazysizes", node: node) if class_list&.include?("lazyload")
|
11
|
+
add_offense("Use the CSS imageset attribute instead of data-bgset", node: node) if node.attributes["data-bgset"]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class DeprecateLazysizes < HtmlCheck
|
4
|
+
severity :suggestion
|
5
|
+
category :html, :performance
|
6
|
+
doc docs_url(__FILE__)
|
7
|
+
|
8
|
+
def on_img(node)
|
9
|
+
class_list = node.attributes["class"]&.value&.split(" ")
|
10
|
+
add_offense("Use the native loading=\"lazy\" attribute instead of lazysizes", node: node) if class_list&.include?("lazyload")
|
11
|
+
add_offense("Use the native srcset attribute instead of data-srcset", node: node) if node.attributes["data-srcset"]
|
12
|
+
add_offense("Use the native sizes attribute instead of data-sizes", node: node) if node.attributes["data-sizes"]
|
13
|
+
add_offense("Do not set the data-sizes attribute to auto", node: node) if node.attributes["data-sizes"]&.value == "auto"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -10,12 +10,7 @@ module ThemeCheck
|
|
10
10
|
def on_img(node)
|
11
11
|
loading = node.attributes["loading"]&.value&.downcase
|
12
12
|
return if ACCEPTED_LOADING_VALUES.include?(loading)
|
13
|
-
|
14
|
-
class_list = node.attributes["class"]&.value&.split(" ")
|
15
|
-
|
16
|
-
if class_list&.include?("lazyload")
|
17
|
-
add_offense("Use the native loading=\"lazy\" attribute instead of lazysizes", node: node)
|
18
|
-
elsif loading == "auto"
|
13
|
+
if loading == "auto"
|
19
14
|
add_offense("Prefer loading=\"lazy\" to defer loading of images", node: node)
|
20
15
|
else
|
21
16
|
add_offense("Add a loading=\"lazy\" attribute to defer loading of images", node: node)
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module ThemeCheck
|
3
|
-
# Recommends using {% liquid ... %} if
|
3
|
+
# Recommends using {% liquid ... %} if 5 or more consecutive {% ... %} are found.
|
4
4
|
class LiquidTag < LiquidCheck
|
5
5
|
severity :suggestion
|
6
6
|
category :liquid
|
7
7
|
doc docs_url(__FILE__)
|
8
8
|
|
9
|
-
def initialize(min_consecutive_statements:
|
9
|
+
def initialize(min_consecutive_statements: 5)
|
10
10
|
@first_statement = nil
|
11
11
|
@consecutive_statements = 0
|
12
12
|
@min_consecutive_statements = min_consecutive_statements
|
@@ -9,6 +9,7 @@ module ThemeCheck
|
|
9
9
|
PROTOCOL = %r{(https?:)?//}
|
10
10
|
ABSOLUTE_PATH = %r{\A/[^/]}im
|
11
11
|
RELATIVE_PATH = %r{\A(?!#{PROTOCOL})[^/\{]}oim
|
12
|
+
CDN_ROOT = "https://cdn.shopify.com/"
|
12
13
|
|
13
14
|
def on_element(node)
|
14
15
|
return unless TAGS.include?(node.name)
|
@@ -17,6 +18,7 @@ module ThemeCheck
|
|
17
18
|
return if resource_url.nil? || resource_url.empty?
|
18
19
|
|
19
20
|
# Ignore if URL is Liquid, taken care of by AssetUrlFilters check
|
21
|
+
return if resource_url.start_with?(CDN_ROOT)
|
20
22
|
return if resource_url =~ ABSOLUTE_PATH
|
21
23
|
return if resource_url =~ RELATIVE_PATH
|
22
24
|
return if url_hosted_by_shopify?(resource_url)
|
@@ -5,9 +5,11 @@ module ThemeCheck
|
|
5
5
|
category :liquid
|
6
6
|
doc docs_url(__FILE__)
|
7
7
|
|
8
|
-
def initialize(max_length:
|
8
|
+
def initialize(max_length: 500, exclude_schema: true, exclude_stylesheet: true, exclude_javascript: true)
|
9
9
|
@max_length = max_length
|
10
10
|
@exclude_schema = exclude_schema
|
11
|
+
@exclude_stylesheet = exclude_stylesheet
|
12
|
+
@exclude_javascript = exclude_javascript
|
11
13
|
end
|
12
14
|
|
13
15
|
def on_document(_node)
|
@@ -15,9 +17,15 @@ module ThemeCheck
|
|
15
17
|
end
|
16
18
|
|
17
19
|
def on_schema(node)
|
18
|
-
if @exclude_schema
|
19
|
-
|
20
|
-
|
20
|
+
exclude_node_lines(node) if @exclude_schema
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_stylesheet(node)
|
24
|
+
exclude_node_lines(node) if @exclude_stylesheet
|
25
|
+
end
|
26
|
+
|
27
|
+
def on_javascript(node)
|
28
|
+
exclude_node_lines(node) if @exclude_javascript
|
21
29
|
end
|
22
30
|
|
23
31
|
def after_document(node)
|
@@ -26,5 +34,11 @@ module ThemeCheck
|
|
26
34
|
add_offense("Template has too many lines [#{lines}/#{@max_length}]", template: node.template)
|
27
35
|
end
|
28
36
|
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def exclude_node_lines(node)
|
41
|
+
@excluded_lines += node.value.nodelist.join.count("\n")
|
42
|
+
end
|
29
43
|
end
|
30
44
|
end
|
@@ -5,17 +5,5 @@ module ThemeCheck
|
|
5
5
|
class LiquidCheck < Check
|
6
6
|
extend ChecksTracking
|
7
7
|
include ParsingHelpers
|
8
|
-
|
9
|
-
# TODO: remove this once all regex checks are migrate to HtmlCheck# TODO: remove this once all regex checks are migrate to HtmlCheck
|
10
|
-
TAG = /#{Liquid::TagStart}.*?#{Liquid::TagEnd}/om
|
11
|
-
VARIABLE = /#{Liquid::VariableStart}.*?#{Liquid::VariableEnd}/om
|
12
|
-
START_OR_END_QUOTE = /(^['"])|(['"]$)/
|
13
|
-
QUOTED_LIQUID_ATTRIBUTE = %r{
|
14
|
-
'(?:#{TAG}|#{VARIABLE}|[^'])*'| # any combination of tag/variable or non straight quote inside straight quotes
|
15
|
-
"(?:#{TAG}|#{VARIABLE}|[^"])*" # any combination of tag/variable or non double quotes inside double quotes
|
16
|
-
}omix
|
17
|
-
ATTR = /[a-z0-9-]+/i
|
18
|
-
HTML_ATTRIBUTE = /#{ATTR}(?:=#{QUOTED_LIQUID_ATTRIBUTE})?/omix
|
19
|
-
HTML_ATTRIBUTES = /(?:#{HTML_ATTRIBUTE}|\s)*/omix
|
20
8
|
end
|
21
9
|
end
|
@@ -7,8 +7,10 @@ module ThemeCheck
|
|
7
7
|
|
8
8
|
while scanner.scan(/.*?("|')/)
|
9
9
|
yield scanner.matched[0..-2]
|
10
|
+
quote = scanner.matched[-1] == "'" ? "'" : "\""
|
10
11
|
# Skip to the end of the string
|
11
|
-
|
12
|
+
# Check for empty string first, since follow regexp uses lookahead
|
13
|
+
scanner.skip(/#{quote}/) || scanner.skip_until(/[^\\]#{quote}/)
|
12
14
|
end
|
13
15
|
|
14
16
|
yield scanner.rest if scanner.rest?
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module ThemeCheck
|
4
4
|
module RegexHelpers
|
5
|
+
VARIABLE = /#{Liquid::VariableStart}.*?#{Liquid::VariableEnd}/om
|
6
|
+
START_OR_END_QUOTE = /(^['"])|(['"]$)/
|
5
7
|
def matches(s, re)
|
6
8
|
start_at = 0
|
7
9
|
matches = []
|
@@ -11,5 +13,20 @@ module ThemeCheck
|
|
11
13
|
end
|
12
14
|
matches
|
13
15
|
end
|
16
|
+
|
17
|
+
def href_to_file_size(href)
|
18
|
+
# asset_url (+ optional stylesheet_tag) variables
|
19
|
+
if href =~ /^#{VARIABLE}$/o && href =~ /asset_url/ && href =~ Liquid::QuotedString
|
20
|
+
asset_id = Regexp.last_match(0).gsub(START_OR_END_QUOTE, "")
|
21
|
+
asset = @theme.assets.find { |a| a.name.end_with?("/" + asset_id) }
|
22
|
+
return if asset.nil?
|
23
|
+
asset.gzipped_size
|
24
|
+
|
25
|
+
# remote URLs
|
26
|
+
elsif href =~ %r{^(https?:)?//}
|
27
|
+
asset = RemoteAssetFile.from_src(href)
|
28
|
+
asset.gzipped_size
|
29
|
+
end
|
30
|
+
end
|
14
31
|
end
|
15
32
|
end
|
data/lib/theme_check/tags.rb
CHANGED
@@ -125,6 +125,42 @@ module ThemeCheck
|
|
125
125
|
end
|
126
126
|
end
|
127
127
|
|
128
|
+
class Render < Liquid::Tag
|
129
|
+
SYNTAX = /((?:#{Liquid::QuotedString}|#{Liquid::VariableSegment})+)(\s+(with|#{Liquid::Render::FOR})\s+(#{Liquid::QuotedFragment}+))?(\s+(?:as)\s+(#{Liquid::VariableSegment}+))?/o
|
130
|
+
|
131
|
+
disable_tags "include"
|
132
|
+
|
133
|
+
attr_reader :template_name_expr, :attributes
|
134
|
+
|
135
|
+
def initialize(tag_name, markup, options)
|
136
|
+
super
|
137
|
+
|
138
|
+
raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX
|
139
|
+
|
140
|
+
template_name = Regexp.last_match(1)
|
141
|
+
with_or_for = Regexp.last_match(3)
|
142
|
+
variable_name = Regexp.last_match(4)
|
143
|
+
|
144
|
+
@alias_name = Regexp.last_match(6)
|
145
|
+
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
146
|
+
@template_name_expr = parse_expression(template_name)
|
147
|
+
@for = (with_or_for == Liquid::Render::FOR)
|
148
|
+
|
149
|
+
@attributes = {}
|
150
|
+
markup.scan(Liquid::TagAttributes) do |key, value|
|
151
|
+
@attributes[key] = parse_expression(value)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
156
|
+
def children
|
157
|
+
[
|
158
|
+
@node.template_name_expr,
|
159
|
+
] + @node.attributes.values
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
128
164
|
class Style < Liquid::Block; end
|
129
165
|
|
130
166
|
class Schema < Liquid::Raw; end
|
@@ -135,6 +171,7 @@ module ThemeCheck
|
|
135
171
|
|
136
172
|
Liquid::Template.register_tag('form', Form)
|
137
173
|
Liquid::Template.register_tag('layout', Layout)
|
174
|
+
Liquid::Template.register_tag('render', Render)
|
138
175
|
Liquid::Template.register_tag('paginate', Paginate)
|
139
176
|
Liquid::Template.register_tag('section', Section)
|
140
177
|
Liquid::Template.register_tag('style', Style)
|
data/lib/theme_check/version.rb
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: 0.
|
4
|
+
version: 1.0.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-06-
|
11
|
+
date: 2021-06-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: liquid
|
@@ -75,11 +75,14 @@ files:
|
|
75
75
|
- docs/api/liquid_check.md
|
76
76
|
- docs/checks/TEMPLATE.md.erb
|
77
77
|
- docs/checks/asset_size_css.md
|
78
|
+
- docs/checks/asset_size_css_stylesheet_tag.md
|
78
79
|
- docs/checks/asset_size_javascript.md
|
79
80
|
- docs/checks/asset_url_filters.md
|
80
81
|
- docs/checks/content_for_header_modification.md
|
81
82
|
- docs/checks/convert_include_to_render.md
|
82
83
|
- docs/checks/default_locale.md
|
84
|
+
- docs/checks/deprecate_bgsizes.md
|
85
|
+
- docs/checks/deprecate_lazysizes.md
|
83
86
|
- docs/checks/deprecated_filter.md
|
84
87
|
- docs/checks/html_parsing_error.md
|
85
88
|
- docs/checks/img_lazy_loading.md
|
@@ -118,11 +121,14 @@ files:
|
|
118
121
|
- lib/theme_check/checks.rb
|
119
122
|
- lib/theme_check/checks/TEMPLATE.rb.erb
|
120
123
|
- lib/theme_check/checks/asset_size_css.rb
|
124
|
+
- lib/theme_check/checks/asset_size_css_stylesheet_tag.rb
|
121
125
|
- lib/theme_check/checks/asset_size_javascript.rb
|
122
126
|
- lib/theme_check/checks/asset_url_filters.rb
|
123
127
|
- lib/theme_check/checks/content_for_header_modification.rb
|
124
128
|
- lib/theme_check/checks/convert_include_to_render.rb
|
125
129
|
- lib/theme_check/checks/default_locale.rb
|
130
|
+
- lib/theme_check/checks/deprecate_bgsizes.rb
|
131
|
+
- lib/theme_check/checks/deprecate_lazysizes.rb
|
126
132
|
- lib/theme_check/checks/deprecated_filter.rb
|
127
133
|
- lib/theme_check/checks/html_parsing_error.rb
|
128
134
|
- lib/theme_check/checks/img_lazy_loading.rb
|
@@ -227,7 +233,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
227
233
|
- !ruby/object:Gem::Version
|
228
234
|
version: '0'
|
229
235
|
requirements: []
|
230
|
-
rubygems_version: 3.2.
|
236
|
+
rubygems_version: 3.2.20
|
231
237
|
signing_key:
|
232
238
|
specification_version: 4
|
233
239
|
summary: A Shopify Theme Linter
|