theme-check 0.2.2 → 0.3.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/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
|

|
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
|