theme-check 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +2 -0
- data/README.md +45 -2
- data/RELEASING.md +41 -0
- data/Rakefile +24 -4
- data/config/default.yml +15 -0
- data/data/shopify_liquid/plus_objects.yml +15 -0
- data/dev.yml +2 -0
- data/lib/theme_check.rb +4 -0
- data/lib/theme_check/analyzer.rb +0 -6
- data/lib/theme_check/check.rb +11 -0
- data/lib/theme_check/checks.rb +10 -0
- data/lib/theme_check/checks/missing_enable_comment.rb +31 -0
- data/lib/theme_check/checks/parser_blocking_javascript.rb +55 -0
- data/lib/theme_check/checks/space_inside_braces.rb +1 -0
- data/lib/theme_check/checks/template_length.rb +11 -3
- data/lib/theme_check/checks/undefined_object.rb +8 -0
- data/lib/theme_check/checks/valid_html_translation.rb +2 -2
- data/lib/theme_check/cli.rb +9 -1
- data/lib/theme_check/config.rb +95 -43
- data/lib/theme_check/corrector.rb +0 -4
- data/lib/theme_check/disabled_checks.rb +77 -0
- data/lib/theme_check/file_system_storage.rb +51 -0
- data/lib/theme_check/in_memory_storage.rb +37 -0
- data/lib/theme_check/json_file.rb +12 -10
- data/lib/theme_check/language_server/handler.rb +23 -10
- data/lib/theme_check/language_server/server.rb +2 -2
- data/lib/theme_check/shopify_liquid/object.rb +6 -0
- data/lib/theme_check/storage.rb +25 -0
- data/lib/theme_check/template.rb +26 -21
- data/lib/theme_check/theme.rb +14 -9
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check/visitor.rb +14 -3
- data/packaging/homebrew/theme_check.base.rb +10 -6
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a07597b217b1f596cd5e777726950e396934726f3572bc7660129bec2b3b30c8
|
4
|
+
data.tar.gz: 80e54966286e332f3308021b9347feb4cdb683497991b9bb451dcf58f138a2df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4c7a1c159dd1b688618c95e1e5fb943a133b606878f57c91b12e5793b07022325c5cfb2e81c49cb45ea484cb3688c6f7c2b75af1b2a50f13da6db2ba0e8b0bb3
|
7
|
+
data.tar.gz: 83fe6a74375ecf2b58f14681c99bc672a710cd6bbc70cb5f58ff06332d4123171544f6cf73c6e7f5f750c1a870d17de0ce3bd3e1f9cbfca6e1bdc7fceb767b80
|
data/CONTRIBUTING.md
CHANGED
data/README.md
CHANGED
@@ -4,7 +4,7 @@ Think RuboCop, or eslint, but for Shopify themes.
|
|
4
4
|
|
5
5
|
Theme Check is a command line tool that helps you follow Shopify Themes & Liquid best practices by analyzing the Liquid & JSON inside your theme.
|
6
6
|
|
7
|
-
|
7
|
+
Theme Check is also available [inside some code editors](https://github.com/Shopify/theme-check/wiki).
|
8
8
|
|
9
9
|
![](docs/preview.png)
|
10
10
|
|
@@ -30,8 +30,9 @@ Theme Check currently checks for the following:
|
|
30
30
|
✅ Unmatching translation keys in locale files
|
31
31
|
✅ Using unknown translation keys in `{{ 'missing_key' | t }}`
|
32
32
|
✅ Using several `{% ... %}` instead of `{% liquid ... %}`
|
33
|
-
✅ Undefined [objects](https://shopify.dev/docs/themes/liquid/reference/objects)
|
33
|
+
✅ Undefined [objects](https://shopify.dev/docs/themes/liquid/reference/objects)
|
34
34
|
✅ Deprecated filters
|
35
|
+
✅ Missing `theme-check-enable` comment
|
35
36
|
|
36
37
|
And many more to come! Suggestions welcome ([create an issue](https://github.com/Shopify/theme-check/issues)).
|
37
38
|
|
@@ -79,11 +80,53 @@ Add a `.theme-check.yml` file at the root of your theme to configure:
|
|
79
80
|
# be uploaded to Shopify.
|
80
81
|
root: dist
|
81
82
|
|
83
|
+
# It is possible to extend theme-check with custom checks
|
84
|
+
require:
|
85
|
+
- ./path/to/my_custom_check.rb
|
86
|
+
|
82
87
|
# Disable some checks
|
83
88
|
TemplateLength:
|
84
89
|
enabled: false
|
85
90
|
# Or configure options
|
86
91
|
max_length: 300
|
92
|
+
|
93
|
+
# Enable a custom check
|
94
|
+
MyCustomCheck
|
95
|
+
enabled: true
|
87
96
|
```
|
88
97
|
|
89
98
|
See [config/default.yml](config/default.yml) for available options & defaults.
|
99
|
+
|
100
|
+
## Disable checks with comments
|
101
|
+
|
102
|
+
Use Liquid comments to disable and re-enable all checks for a section of your template:
|
103
|
+
|
104
|
+
```liquid
|
105
|
+
{% comment %}theme-check-disable{% endcomment %}
|
106
|
+
{% assign x = 1 %}
|
107
|
+
{% comment %}theme-check-enable{% endcomment %}
|
108
|
+
```
|
109
|
+
|
110
|
+
Disable a specific check by including it in the comment:
|
111
|
+
|
112
|
+
```liquid
|
113
|
+
{% comment %}theme-check-disable UnusedAssign{% endcomment %}
|
114
|
+
{% assign x = 1 %}
|
115
|
+
{% comment %}theme-check-enable UnusedAssign{% endcomment %}
|
116
|
+
```
|
117
|
+
|
118
|
+
Disable multiple checks by including them as a comma-separated list:
|
119
|
+
|
120
|
+
```liquid
|
121
|
+
{% comment %}theme-check-disable UnusedAssign,SpaceInsideBraces{% endcomment %}
|
122
|
+
{%assign x = 1%}
|
123
|
+
{% comment %}theme-check-enable UnusedAssign,SpaceInsideBraces{% endcomment %}
|
124
|
+
```
|
125
|
+
|
126
|
+
Disable checks for the _entire document_ by placing the comment on the first line:
|
127
|
+
|
128
|
+
```liquid
|
129
|
+
{% comment %}theme-check-disable SpaceInsideBraces{% endcomment %}
|
130
|
+
|
131
|
+
{%assign x = 1%}
|
132
|
+
```
|
data/RELEASING.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
## Releasing Theme Check
|
2
|
+
|
3
|
+
1. Check the Semantic Versioning page for info on how to version the new release: http://semver.org
|
4
|
+
|
5
|
+
2. Create a PR to update the version in `lib/theme_check/version.rb`
|
6
|
+
|
7
|
+
3. Merge your PR to master
|
8
|
+
|
9
|
+
4. On [Shipit](https://shipit.shopify.io/shopify/theme-check/rubygems), deploy your commit.
|
10
|
+
|
11
|
+
## Homebrew Release Process
|
12
|
+
|
13
|
+
1. Release `theme-check` on RubyGems by following the steps in the previous section.
|
14
|
+
|
15
|
+
2. Generate the homebrew formula.
|
16
|
+
|
17
|
+
```bash
|
18
|
+
rake package
|
19
|
+
```
|
20
|
+
|
21
|
+
3. Copy the formula over in the [`homebrew-shopify`](https://github.com/Shopify/homebrew-shopify) repository.
|
22
|
+
|
23
|
+
```bash
|
24
|
+
VERSION=X.X.X
|
25
|
+
cp packaging/builds/$VERSION/theme-check ../homebrew-shopify
|
26
|
+
```
|
27
|
+
|
28
|
+
4. Create a branch + a commit on the [`homebrew-shopify`](https://github.com/Shopify/homebrew-shopify) repository.
|
29
|
+
|
30
|
+
```bash
|
31
|
+
git checkout -b "bump/theme-check-$VERSION"
|
32
|
+
git add theme-check.rb
|
33
|
+
git commit -m "Bump theme-check version to $VERSION"
|
34
|
+
```
|
35
|
+
|
36
|
+
5. Create a pull-request for those changes on the [`homebrew-shopify`](https://github.com/Shopify/homebrew-shopify) repository.
|
37
|
+
|
38
|
+
```bash
|
39
|
+
# shortcut if you have `hub` installed
|
40
|
+
hub compare "master:bump/theme-check-$VERSION"
|
41
|
+
```
|
data/Rakefile
CHANGED
@@ -3,12 +3,32 @@ require "rake/testtask"
|
|
3
3
|
require "rubocop/rake_task"
|
4
4
|
require "bundler/gem_tasks"
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
namespace :tests do
|
7
|
+
task all: [:in_memory, :file_system]
|
8
|
+
|
9
|
+
Rake::TestTask.new(:suite) do |t|
|
10
|
+
t.libs << "test"
|
11
|
+
t.libs << "lib"
|
12
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
13
|
+
end
|
14
|
+
|
15
|
+
desc("Runs the tests with InMemoryStorage")
|
16
|
+
task :in_memory do
|
17
|
+
ENV["THEME_STORAGE"] = 'InMemoryStorage'
|
18
|
+
puts "Running tests with #{ENV['THEME_STORAGE']}"
|
19
|
+
Rake::Task['tests:suite'].execute
|
20
|
+
end
|
21
|
+
|
22
|
+
desc("Runs the tests with FileSystemStorage")
|
23
|
+
task :file_system do
|
24
|
+
ENV["THEME_STORAGE"] = 'FileSystemStorage'
|
25
|
+
puts "Running tests with #{ENV['THEME_STORAGE']}"
|
26
|
+
Rake::Task['tests:suite'].execute
|
27
|
+
end
|
10
28
|
end
|
11
29
|
|
30
|
+
task(test: 'tests:all')
|
31
|
+
|
12
32
|
RuboCop::RakeTask.new
|
13
33
|
|
14
34
|
task default: [:test, :rubocop]
|
data/config/default.yml
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
root: .
|
2
|
+
|
3
|
+
require: []
|
4
|
+
|
5
|
+
ignore:
|
6
|
+
- node_modules/*
|
7
|
+
|
1
8
|
ConvertIncludeToRender:
|
2
9
|
enabled: true
|
3
10
|
|
@@ -22,6 +29,8 @@ SyntaxError:
|
|
22
29
|
TemplateLength:
|
23
30
|
enabled: true
|
24
31
|
max_length: 200
|
32
|
+
# Exclude content of {% schema %} in line count
|
33
|
+
exclude_schema: true
|
25
34
|
|
26
35
|
UnknownFilter:
|
27
36
|
enabled: true
|
@@ -64,3 +73,9 @@ RequiredDirectories:
|
|
64
73
|
|
65
74
|
DeprecatedFilter:
|
66
75
|
enabled: true
|
76
|
+
|
77
|
+
MissingEnableComment:
|
78
|
+
enabled: true
|
79
|
+
|
80
|
+
ParserBlockingJavaScript:
|
81
|
+
enabled: true
|
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
- alternative_payment_methods
|
3
|
+
- breadcrumb
|
4
|
+
- checkout_html_classes
|
5
|
+
- checkout_scripts
|
6
|
+
- checkout_stylesheets
|
7
|
+
- content_for_footer
|
8
|
+
- content_for_logo
|
9
|
+
- content_for_order_summary
|
10
|
+
- direction
|
11
|
+
- locale
|
12
|
+
- order_summary_toggle
|
13
|
+
- page_title
|
14
|
+
- skip_to_content_link
|
15
|
+
- tracking_code
|
data/dev.yml
CHANGED
data/lib/theme_check.rb
CHANGED
@@ -5,6 +5,7 @@ require_relative "theme_check/analyzer"
|
|
5
5
|
require_relative "theme_check/check"
|
6
6
|
require_relative "theme_check/checks_tracking"
|
7
7
|
require_relative "theme_check/cli"
|
8
|
+
require_relative "theme_check/disabled_checks"
|
8
9
|
require_relative "theme_check/liquid_check"
|
9
10
|
require_relative "theme_check/locale_diff"
|
10
11
|
require_relative "theme_check/json_check"
|
@@ -17,6 +18,9 @@ require_relative "theme_check/node"
|
|
17
18
|
require_relative "theme_check/offense"
|
18
19
|
require_relative "theme_check/printer"
|
19
20
|
require_relative "theme_check/shopify_liquid"
|
21
|
+
require_relative "theme_check/storage"
|
22
|
+
require_relative "theme_check/file_system_storage"
|
23
|
+
require_relative "theme_check/in_memory_storage"
|
20
24
|
require_relative "theme_check/tags"
|
21
25
|
require_relative "theme_check/template"
|
22
26
|
require_relative "theme_check/theme"
|
data/lib/theme_check/analyzer.rb
CHANGED
@@ -35,12 +35,6 @@ module ThemeCheck
|
|
35
35
|
@offenses
|
36
36
|
end
|
37
37
|
|
38
|
-
def analyze_file(path)
|
39
|
-
path = Pathname.new(path)
|
40
|
-
analyze_theme
|
41
|
-
@offenses.reject! { |offense| offense.template.path != path }
|
42
|
-
end
|
43
|
-
|
44
38
|
def uncorrectable_offenses
|
45
39
|
unless @auto_correct
|
46
40
|
return @offenses
|
data/lib/theme_check/check.rb
CHANGED
@@ -50,6 +50,13 @@ module ThemeCheck
|
|
50
50
|
@doc = doc if doc
|
51
51
|
@doc if defined?(@doc)
|
52
52
|
end
|
53
|
+
|
54
|
+
def can_disable(disableable = nil)
|
55
|
+
unless disableable.nil?
|
56
|
+
@can_disable = disableable
|
57
|
+
end
|
58
|
+
defined?(@can_disable) ? @can_disable : true
|
59
|
+
end
|
53
60
|
end
|
54
61
|
|
55
62
|
def severity
|
@@ -80,6 +87,10 @@ module ThemeCheck
|
|
80
87
|
defined?(@ignored) && @ignored
|
81
88
|
end
|
82
89
|
|
90
|
+
def can_disable?
|
91
|
+
self.class.can_disable
|
92
|
+
end
|
93
|
+
|
83
94
|
def to_s
|
84
95
|
s = +"#{code_name}:\n"
|
85
96
|
properties = { severity: severity, category: category, doc: doc }.merge(options)
|
data/lib/theme_check/checks.rb
CHANGED
@@ -8,5 +8,15 @@ module ThemeCheck
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
11
|
+
|
12
|
+
def always_enabled
|
13
|
+
self.class.new(reject(&:can_disable?))
|
14
|
+
end
|
15
|
+
|
16
|
+
def except_for(disabled_checks)
|
17
|
+
still_enabled = reject { |check| disabled_checks.all.include?(check.code_name) }
|
18
|
+
|
19
|
+
self.class.new((always_enabled + still_enabled).uniq)
|
20
|
+
end
|
11
21
|
end
|
12
22
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class MissingEnableComment < LiquidCheck
|
4
|
+
severity :error
|
5
|
+
|
6
|
+
# Don't allow this check to be disabled with a comment,
|
7
|
+
# as we need to be able to check for disabled checks.
|
8
|
+
can_disable false
|
9
|
+
|
10
|
+
def on_document(_node)
|
11
|
+
@disabled_checks = DisabledChecks.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def on_comment(node)
|
15
|
+
@disabled_checks.update(node)
|
16
|
+
end
|
17
|
+
|
18
|
+
def after_document(node)
|
19
|
+
return if @disabled_checks.full_document_disabled?
|
20
|
+
return unless @disabled_checks.any?
|
21
|
+
|
22
|
+
message = if @disabled_checks.all_disabled?
|
23
|
+
"All checks were"
|
24
|
+
else
|
25
|
+
@disabled_checks.all.join(', ') + " " + (@disabled_checks.all.size == 1 ? "was" : "were")
|
26
|
+
end
|
27
|
+
|
28
|
+
add_offense("#{message} disabled but not re-enabled with theme-check-enable", node: node)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
# Reports errors when trying to use parser-blocking script tags
|
4
|
+
class ParserBlockingJavaScript < LiquidCheck
|
5
|
+
severity :error
|
6
|
+
category :liquid
|
7
|
+
|
8
|
+
PARSER_BLOCKING_SCRIPT_TAG = %r{
|
9
|
+
<script # Find the start of a script tag
|
10
|
+
(?=(?:[^>]|\n|\r)+?src=)+? # Make sure src= is in the script with a lookahead
|
11
|
+
(?:(?!defer|async|type=["']module['"]).)*? # Find tags that don't have defer|async|type="module"
|
12
|
+
>
|
13
|
+
}xim
|
14
|
+
SCRIPT_TAG_FILTER = /\{\{[^}]+script_tag\s+\}\}/
|
15
|
+
|
16
|
+
def on_document(node)
|
17
|
+
@source = node.template.source
|
18
|
+
@node = node
|
19
|
+
record_offenses
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def record_offenses
|
25
|
+
record_offenses_from_regex(
|
26
|
+
message: "Missing async or defer attribute on script tag",
|
27
|
+
regex: PARSER_BLOCKING_SCRIPT_TAG,
|
28
|
+
)
|
29
|
+
record_offenses_from_regex(
|
30
|
+
message: "The script_tag filter is parser-blocking. Use a script tag with the async or defer attribute for better performance",
|
31
|
+
regex: SCRIPT_TAG_FILTER,
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
# The trickiness here is matching on scripts that are defined on
|
36
|
+
# multiple lines (or repeat matches). This makes the line_number
|
37
|
+
# calculation a bit weird. So instead, we traverse the string in
|
38
|
+
# a very imperative way.
|
39
|
+
def record_offenses_from_regex(regex: nil, message: nil)
|
40
|
+
i = 0
|
41
|
+
while (i = @source.index(regex, i))
|
42
|
+
script = @source.match(regex, i)[0]
|
43
|
+
|
44
|
+
add_offense(
|
45
|
+
message,
|
46
|
+
node: @node,
|
47
|
+
markup: script,
|
48
|
+
line_number: @source[0...i].count("\n") + 1
|
49
|
+
)
|
50
|
+
|
51
|
+
i += script.size
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -4,12 +4,20 @@ module ThemeCheck
|
|
4
4
|
severity :suggestion
|
5
5
|
category :liquid
|
6
6
|
|
7
|
-
def initialize(max_length: 200)
|
7
|
+
def initialize(max_length: 200, exclude_schema: true)
|
8
8
|
@max_length = max_length
|
9
|
+
@exclude_schema = exclude_schema
|
10
|
+
@excluded_lines = 0
|
9
11
|
end
|
10
12
|
|
11
|
-
def
|
12
|
-
|
13
|
+
def on_schema(node)
|
14
|
+
if @exclude_schema
|
15
|
+
@excluded_lines += node.value.nodelist.join.count("\n")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def after_document(node)
|
20
|
+
lines = node.template.source.count("\n") - @excluded_lines
|
13
21
|
if lines > @max_length
|
14
22
|
add_offense("Template has too many lines [#{lines}/#{@max_length}]", template: node.template)
|
15
23
|
end
|
@@ -95,11 +95,19 @@ module ThemeCheck
|
|
95
95
|
all_global_objects = ThemeCheck::ShopifyLiquid::Object.labels
|
96
96
|
all_global_objects.freeze
|
97
97
|
|
98
|
+
shopify_plus_objects = ThemeCheck::ShopifyLiquid::Object.plus_labels
|
99
|
+
shopify_plus_objects.freeze
|
100
|
+
|
98
101
|
each_template do |(name, info)|
|
99
102
|
if 'templates/customers/reset_password' == name
|
100
103
|
# NOTE: `email` is exceptionally exposed as a theme object in
|
101
104
|
# the customers' reset password template
|
102
105
|
check_object(info, all_global_objects + ['email'])
|
106
|
+
elsif 'layout/checkout' == name
|
107
|
+
# NOTE: Shopify Plus has exceptionally exposed objects in
|
108
|
+
# the checkout template
|
109
|
+
# https://shopify.dev/docs/themes/theme-templates/checkout-liquid#optional-objects
|
110
|
+
check_object(info, all_global_objects + shopify_plus_objects)
|
103
111
|
else
|
104
112
|
check_object(info, all_global_objects)
|
105
113
|
end
|