theme-check 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +19 -1
- data/Rakefile +14 -0
- data/config/default.yml +4 -1
- data/data/shopify_liquid/deprecated_filters.yml +10 -0
- data/lib/theme_check.rb +1 -0
- data/lib/theme_check/analyzer.rb +17 -1
- data/lib/theme_check/checks/deprecated_filter.rb +22 -0
- data/lib/theme_check/checks/space_inside_braces.rb +18 -7
- data/lib/theme_check/checks/undefined_object.rb +99 -34
- data/lib/theme_check/cli.rb +9 -3
- data/lib/theme_check/config.rb +2 -1
- data/lib/theme_check/corrector.rb +35 -0
- data/lib/theme_check/liquid_check.rb +2 -2
- data/lib/theme_check/node.rb +13 -0
- data/lib/theme_check/offense.rb +25 -9
- data/lib/theme_check/packager.rb +51 -0
- data/lib/theme_check/printer.rb +13 -4
- data/lib/theme_check/shopify_liquid.rb +1 -0
- data/lib/theme_check/shopify_liquid/deprecated_filter.rb +28 -0
- data/lib/theme_check/template.rb +18 -1
- data/lib/theme_check/version.rb +1 -1
- data/packaging/homebrew/theme_check.base.rb +94 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4001e286b9a0c851493de76250770565088d1d8f646a6e592ee3f2c142b47c3c
|
4
|
+
data.tar.gz: 3c3cd833684e0020211365eec091b70be3a064ae72f4c1d7337908fbfdbdacd8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 35855ff5a3e10f8a8dc60cc40d619fe713f013eb8ee64f92c565c34e3a3b58b9646ca1b707535f777e53cabae1dd58facc2adc4722e5c704b11ff70d9407e959
|
7
|
+
data.tar.gz: fb9f94add954ec84a0fb8ca6616e8d91981701ff0d62f5dc89692dcd4ddf7715073db531dc81ec7dd1c8a4fc8df93360adbc32ee02254d8538a027236995cbdf
|
data/.gitignore
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
|
+
Code editor support coming soon!
|
8
8
|
|
9
9
|
![](docs/preview.png)
|
10
10
|
|
@@ -31,12 +31,30 @@ Theme Check currently checks for the following:
|
|
31
31
|
✅ Using unknown translation keys in `{{ 'missing_key' | t }}`
|
32
32
|
✅ Using several `{% ... %}` instead of `{% liquid ... %}`
|
33
33
|
✅ Undefined [objects](https://shopify.dev/docs/themes/liquid/reference/objects)
|
34
|
+
✅ Deprecated filters
|
34
35
|
|
35
36
|
And many more to come! Suggestions welcome ([create an issue](https://github.com/Shopify/theme-check/issues)).
|
36
37
|
|
38
|
+
## Requirements
|
39
|
+
|
40
|
+
- Ruby 2.7+
|
41
|
+
|
37
42
|
## Installation
|
38
43
|
|
44
|
+
Theme Check is available through Homebrew _or_ RubyGems.
|
45
|
+
|
46
|
+
**Homebrew**
|
47
|
+
|
48
|
+
You’ll need to run `brew tap` first to add Shopify’s third-party repositories to Homebrew.
|
49
|
+
|
50
|
+
```sh
|
51
|
+
brew tap shopify/shopify
|
52
|
+
brew install theme-check
|
39
53
|
```
|
54
|
+
|
55
|
+
**RubyGems**
|
56
|
+
|
57
|
+
```sh
|
40
58
|
gem install theme-check
|
41
59
|
```
|
42
60
|
|
data/Rakefile
CHANGED
@@ -12,3 +12,17 @@ end
|
|
12
12
|
RuboCop::RakeTask.new
|
13
13
|
|
14
14
|
task default: [:test, :rubocop]
|
15
|
+
|
16
|
+
namespace :package do
|
17
|
+
require 'theme_check/packager'
|
18
|
+
|
19
|
+
task all: [:homebrew]
|
20
|
+
|
21
|
+
desc("Builds a Homebrew package of the CLI")
|
22
|
+
task :homebrew do
|
23
|
+
ThemeCheck::Packager.new.build_homebrew
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
desc("Builds all distribution packages of the CLI")
|
28
|
+
task(package: 'package:all')
|
data/config/default.yml
CHANGED
data/lib/theme_check.rb
CHANGED
@@ -21,5 +21,6 @@ require_relative "theme_check/tags"
|
|
21
21
|
require_relative "theme_check/template"
|
22
22
|
require_relative "theme_check/theme"
|
23
23
|
require_relative "theme_check/visitor"
|
24
|
+
require_relative "theme_check/corrector"
|
24
25
|
|
25
26
|
Dir[__dir__ + "/theme_check/checks/*.rb"].each { |file| require file }
|
data/lib/theme_check/analyzer.rb
CHANGED
@@ -3,9 +3,10 @@ module ThemeCheck
|
|
3
3
|
class Analyzer
|
4
4
|
attr_reader :offenses
|
5
5
|
|
6
|
-
def initialize(theme, checks = Check.all.map(&:new))
|
6
|
+
def initialize(theme, checks = Check.all.map(&:new), auto_correct = false)
|
7
7
|
@theme = theme
|
8
8
|
@offenses = []
|
9
|
+
@auto_correct = auto_correct
|
9
10
|
|
10
11
|
@liquid_checks = Checks.new
|
11
12
|
@json_checks = Checks.new
|
@@ -39,5 +40,20 @@ module ThemeCheck
|
|
39
40
|
analyze_theme
|
40
41
|
@offenses.reject! { |offense| offense.template.path != path }
|
41
42
|
end
|
43
|
+
|
44
|
+
def uncorrectable_offenses
|
45
|
+
unless @auto_correct
|
46
|
+
return @offenses
|
47
|
+
end
|
48
|
+
|
49
|
+
@offenses.select { |offense| !offense.correctable? }
|
50
|
+
end
|
51
|
+
|
52
|
+
def correct_offenses
|
53
|
+
if @auto_correct
|
54
|
+
@offenses.each(&:correct)
|
55
|
+
@theme.liquid.each(&:write)
|
56
|
+
end
|
57
|
+
end
|
42
58
|
end
|
43
59
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class DeprecatedFilter < LiquidCheck
|
4
|
+
doc "https://shopify.dev/docs/themes/liquid/reference/filters/deprecated-filters"
|
5
|
+
category :liquid
|
6
|
+
severity :suggestion
|
7
|
+
|
8
|
+
def on_variable(node)
|
9
|
+
used_filters = node.value.filters.map { |name, *_rest| name }
|
10
|
+
used_filters.each do |filter|
|
11
|
+
alternatives = ShopifyLiquid::DeprecatedFilter.alternatives(filter)
|
12
|
+
next unless alternatives
|
13
|
+
|
14
|
+
alternatives = alternatives.map { |alt| "`#{alt}`" }
|
15
|
+
add_offense(
|
16
|
+
"Deprecated filter `#{filter}`, consider using an alternative: #{alternatives.join(', ')}",
|
17
|
+
node: node,
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -45,13 +45,24 @@ module ThemeCheck
|
|
45
45
|
def on_variable(node)
|
46
46
|
return if @ignore
|
47
47
|
if node.markup[0] != " "
|
48
|
-
add_offense("Space missing after '{{'", node: node)
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
48
|
+
add_offense("Space missing after '{{'", node: node) do |corrector|
|
49
|
+
corrector.insert_before(node, " ")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
if node.markup[-1] != " "
|
53
|
+
add_offense("Space missing before '}}'", node: node) do |corrector|
|
54
|
+
corrector.insert_after(node, " ")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
if node.markup[0] == " " && node.markup[1] == " "
|
58
|
+
add_offense("Too many spaces after '{{'", node: node) do |corrector|
|
59
|
+
corrector.replace(node, " #{node.markup.lstrip}")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
if node.markup[-1] == " " && node.markup[-2] == " "
|
63
|
+
add_offense("Too many spaces before '}}'", node: node) do |corrector|
|
64
|
+
corrector.replace(node, "#{node.markup.rstrip} ")
|
65
|
+
end
|
55
66
|
end
|
56
67
|
end
|
57
68
|
end
|
@@ -11,76 +11,141 @@ module ThemeCheck
|
|
11
11
|
@all_assigns = {}
|
12
12
|
@all_captures = {}
|
13
13
|
@all_forloops = {}
|
14
|
+
@all_renders = {}
|
14
15
|
end
|
15
16
|
|
16
|
-
attr_reader :
|
17
|
-
|
17
|
+
attr_reader :all_assigns, :all_captures, :all_forloops
|
18
|
+
|
19
|
+
def add_render(name:, node:)
|
20
|
+
@all_renders[name] = node
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_variable_lookup(name:, node:)
|
24
|
+
line_number = node.parent.line_number
|
25
|
+
key = [name, line_number]
|
26
|
+
@all_variable_lookups[key] = node
|
27
|
+
end
|
28
|
+
|
29
|
+
def all_variables
|
18
30
|
all_assigns.keys + all_captures.keys + all_forloops.keys
|
19
31
|
end
|
32
|
+
|
33
|
+
def each_snippet
|
34
|
+
@all_renders.each do |(name, info)|
|
35
|
+
yield [name, info]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def each_variable_lookup(unique_keys = false)
|
40
|
+
seen = Set.new
|
41
|
+
@all_variable_lookups.each do |(key, info)|
|
42
|
+
name, _line_number = key
|
43
|
+
|
44
|
+
next if unique_keys && seen.include?(name)
|
45
|
+
seen << name
|
46
|
+
|
47
|
+
yield [key, info]
|
48
|
+
end
|
49
|
+
end
|
20
50
|
end
|
21
51
|
|
22
52
|
def initialize
|
23
|
-
@
|
24
|
-
@used_snippets = {}
|
53
|
+
@files = {}
|
25
54
|
end
|
26
55
|
|
27
56
|
def on_document(node)
|
28
|
-
@
|
57
|
+
@files[node.template.name] = TemplateInfo.new
|
29
58
|
end
|
30
59
|
|
31
60
|
def on_assign(node)
|
32
|
-
@
|
61
|
+
@files[node.template.name].all_assigns[node.value.to] = node
|
33
62
|
end
|
34
63
|
|
35
64
|
def on_capture(node)
|
36
|
-
@
|
65
|
+
@files[node.template.name].all_captures[node.value.instance_variable_get('@to')] = node
|
37
66
|
end
|
38
67
|
|
39
68
|
def on_for(node)
|
40
|
-
@
|
69
|
+
@files[node.template.name].all_forloops[node.value.variable_name] = node
|
70
|
+
end
|
71
|
+
|
72
|
+
def on_include(_node)
|
73
|
+
# NOOP: we purposely do nothing on `include` since it is deprecated
|
74
|
+
# https://shopify.dev/docs/themes/liquid/reference/tags/deprecated-tags#include
|
41
75
|
end
|
42
76
|
|
43
|
-
def
|
77
|
+
def on_render(node)
|
44
78
|
return unless node.value.template_name_expr.is_a?(String)
|
45
|
-
|
46
|
-
|
47
|
-
@
|
79
|
+
|
80
|
+
snippet_name = "snippets/#{node.value.template_name_expr}"
|
81
|
+
@files[node.template.name].add_render(
|
82
|
+
name: snippet_name,
|
83
|
+
node: node,
|
84
|
+
)
|
48
85
|
end
|
49
86
|
|
50
87
|
def on_variable_lookup(node)
|
51
|
-
@
|
88
|
+
@files[node.template.name].add_variable_lookup(
|
89
|
+
name: node.value.name,
|
90
|
+
node: node,
|
91
|
+
)
|
52
92
|
end
|
53
93
|
|
54
94
|
def on_end
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
including_template_info = @templates[including_template]
|
64
|
-
check_object(info.all_variable_lookups, including_template_info.all)
|
65
|
-
end
|
95
|
+
all_global_objects = ThemeCheck::ShopifyLiquid::Object.labels
|
96
|
+
all_global_objects.freeze
|
97
|
+
|
98
|
+
each_template do |(name, info)|
|
99
|
+
if 'templates/customers/reset_password' == name
|
100
|
+
# NOTE: `email` is exceptionally exposed as a theme object in
|
101
|
+
# the customers' reset password template
|
102
|
+
check_object(info, all_global_objects + ['email'])
|
66
103
|
else
|
67
|
-
|
68
|
-
all += ['email'] if 'templates/customers/reset_password' == template_name
|
69
|
-
check_object(info.all_variable_lookups, all)
|
104
|
+
check_object(info, all_global_objects)
|
70
105
|
end
|
71
106
|
end
|
72
107
|
end
|
73
108
|
|
74
|
-
def
|
75
|
-
|
76
|
-
next if
|
77
|
-
|
109
|
+
def each_template
|
110
|
+
@files.each do |(name, info)|
|
111
|
+
next if name.starts_with?('snippets/')
|
112
|
+
yield [name, info]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
private :each_template
|
78
116
|
|
79
|
-
|
80
|
-
|
81
|
-
|
117
|
+
def check_object(info, all_global_objects, render_node = nil)
|
118
|
+
check_undefined(info, all_global_objects, render_node)
|
119
|
+
|
120
|
+
info.each_snippet do |(snippet_name, node)|
|
121
|
+
snippet_info = @files[snippet_name]
|
122
|
+
next unless snippet_info # NOTE: undefined snippet
|
123
|
+
|
124
|
+
snippet_variables = node.value.attributes.keys +
|
125
|
+
Array[node.value.instance_variable_get("@alias_name")]
|
126
|
+
check_object(snippet_info, all_global_objects + snippet_variables, node)
|
82
127
|
end
|
83
128
|
end
|
84
129
|
private :check_object
|
130
|
+
|
131
|
+
def check_undefined(info, all_global_objects, render_node)
|
132
|
+
all_variables = info.all_variables
|
133
|
+
|
134
|
+
info.each_variable_lookup(!!render_node) do |(key, node)|
|
135
|
+
name, _line_number = key
|
136
|
+
next if all_variables.include?(name)
|
137
|
+
next if all_global_objects.include?(name)
|
138
|
+
|
139
|
+
node = node.parent
|
140
|
+
node = node.parent if %i(condition variable_lookup).include?(node.type_name)
|
141
|
+
|
142
|
+
if render_node
|
143
|
+
add_offense("Missing argument `#{name}`", node: render_node)
|
144
|
+
else
|
145
|
+
add_offense("Undefined object `#{name}`", node: node)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
private :check_undefined
|
85
150
|
end
|
86
151
|
end
|
data/lib/theme_check/cli.rb
CHANGED
@@ -10,6 +10,7 @@ module ThemeCheck
|
|
10
10
|
-c, [--category] # Only run this category of checks
|
11
11
|
-x, [--exclude-category] # Exclude this category of checks
|
12
12
|
-l, [--list] # List enabled checks
|
13
|
+
-a, [--auto-correct] # Automatically fix offenses
|
13
14
|
-h, [--help] # Show this. Hi!
|
14
15
|
|
15
16
|
Description:
|
@@ -25,6 +26,7 @@ module ThemeCheck
|
|
25
26
|
command = :check
|
26
27
|
only_categories = []
|
27
28
|
exclude_categories = []
|
29
|
+
auto_correct = false
|
28
30
|
|
29
31
|
args = argv.dup
|
30
32
|
while (arg = args.shift)
|
@@ -37,6 +39,8 @@ module ThemeCheck
|
|
37
39
|
exclude_categories << args.shift.to_sym
|
38
40
|
when "--list", "-l"
|
39
41
|
command = :list
|
42
|
+
when "--auto-correct", "-a"
|
43
|
+
auto_correct = true
|
40
44
|
else
|
41
45
|
path = arg
|
42
46
|
end
|
@@ -45,6 +49,7 @@ module ThemeCheck
|
|
45
49
|
@config = ThemeCheck::Config.from_path(path)
|
46
50
|
@config.only_categories = only_categories
|
47
51
|
@config.exclude_categories = exclude_categories
|
52
|
+
@config.auto_correct = auto_correct
|
48
53
|
|
49
54
|
send(command)
|
50
55
|
end
|
@@ -69,10 +74,11 @@ module ThemeCheck
|
|
69
74
|
if theme.all.empty?
|
70
75
|
raise Abort, "No templates found.\n#{USAGE}"
|
71
76
|
end
|
72
|
-
analyzer = ThemeCheck::Analyzer.new(theme, @config.enabled_checks)
|
77
|
+
analyzer = ThemeCheck::Analyzer.new(theme, @config.enabled_checks, @config.auto_correct)
|
73
78
|
analyzer.analyze_theme
|
74
|
-
|
75
|
-
|
79
|
+
analyzer.correct_offenses
|
80
|
+
ThemeCheck::Printer.new.print(theme, analyzer.offenses, @config.auto_correct)
|
81
|
+
raise Abort, "" if analyzer.uncorrectable_offenses.any?
|
76
82
|
end
|
77
83
|
end
|
78
84
|
end
|
data/lib/theme_check/config.rb
CHANGED
@@ -6,7 +6,7 @@ module ThemeCheck
|
|
6
6
|
DEFAULT_CONFIG = "#{__dir__}/../../config/default.yml"
|
7
7
|
|
8
8
|
attr_reader :root
|
9
|
-
attr_accessor :only_categories, :exclude_categories
|
9
|
+
attr_accessor :only_categories, :exclude_categories, :auto_correct
|
10
10
|
|
11
11
|
class << self
|
12
12
|
def from_path(path)
|
@@ -40,6 +40,7 @@ module ThemeCheck
|
|
40
40
|
end
|
41
41
|
@only_categories = []
|
42
42
|
@exclude_categories = []
|
43
|
+
@auto_correct = false
|
43
44
|
resolve_requires
|
44
45
|
end
|
45
46
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
class Corrector
|
5
|
+
def initialize(template:)
|
6
|
+
@template = template
|
7
|
+
end
|
8
|
+
|
9
|
+
def insert_after(node, content)
|
10
|
+
line = @template.full_line(node.line_number)
|
11
|
+
line.insert(node.range[1] + 1, content)
|
12
|
+
@template.update!
|
13
|
+
end
|
14
|
+
|
15
|
+
def insert_before(node, content)
|
16
|
+
line = @template.full_line(node.line_number)
|
17
|
+
line.insert(node.range[0], content)
|
18
|
+
@template.update!
|
19
|
+
end
|
20
|
+
|
21
|
+
def replace(node, content)
|
22
|
+
line = @template.full_line(node.line_number)
|
23
|
+
line[node.range[0]..node.range[1]] = content
|
24
|
+
node.markup = content
|
25
|
+
@template.update!
|
26
|
+
end
|
27
|
+
|
28
|
+
def wrap(node, insert_before, insert_after)
|
29
|
+
line = @template.full_line(node.line_number)
|
30
|
+
line.insert(node.range[0], insert_before)
|
31
|
+
line.insert(node.range[1] + 1 + insert_before.length, insert_after)
|
32
|
+
@template.update!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -6,8 +6,8 @@ module ThemeCheck
|
|
6
6
|
extend ChecksTracking
|
7
7
|
include ParsingHelpers
|
8
8
|
|
9
|
-
def add_offense(message, node: nil, template: node&.template, markup: nil, line_number: nil)
|
10
|
-
offenses << Offense.new(check: self, message: message, template: template, node: node, markup: markup, line_number: line_number)
|
9
|
+
def add_offense(message, node: nil, template: node&.template, markup: nil, line_number: nil, &block)
|
10
|
+
offenses << Offense.new(check: self, message: message, template: template, node: node, markup: markup, line_number: line_number, correction: block)
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
data/lib/theme_check/node.rb
CHANGED
@@ -22,6 +22,14 @@ module ThemeCheck
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
def markup=(markup)
|
26
|
+
if tag?
|
27
|
+
@value.raw = markup
|
28
|
+
elsif @value.instance_variable_defined?(:@markup)
|
29
|
+
@value.instance_variable_set(:@markup, markup)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
25
33
|
# Array of children nodes.
|
26
34
|
def children
|
27
35
|
@children ||= begin
|
@@ -113,5 +121,10 @@ module ThemeCheck
|
|
113
121
|
false
|
114
122
|
end
|
115
123
|
end
|
124
|
+
|
125
|
+
def range
|
126
|
+
start = template.full_line(line_number).index(markup)
|
127
|
+
[start, start + markup.length - 1]
|
128
|
+
end
|
116
129
|
end
|
117
130
|
end
|
data/lib/theme_check/offense.rb
CHANGED
@@ -3,10 +3,11 @@ module ThemeCheck
|
|
3
3
|
class Offense
|
4
4
|
MAX_SOURCE_EXCERPT_SIZE = 120
|
5
5
|
|
6
|
-
attr_reader :check, :message, :template, :node, :markup, :line_number
|
6
|
+
attr_reader :check, :message, :template, :node, :markup, :line_number, :correction
|
7
7
|
|
8
|
-
def initialize(check:, message: nil, template: nil, node: nil, markup: nil, line_number: nil)
|
8
|
+
def initialize(check:, message: nil, template: nil, node: nil, markup: nil, line_number: nil, correction: nil)
|
9
9
|
@check = check
|
10
|
+
@correction = correction
|
10
11
|
|
11
12
|
if message
|
12
13
|
@message = message
|
@@ -39,7 +40,7 @@ module ThemeCheck
|
|
39
40
|
def source_excerpt
|
40
41
|
return unless line_number
|
41
42
|
@source_excerpt ||= begin
|
42
|
-
excerpt = template.
|
43
|
+
excerpt = template.source_excerpt(line_number)
|
43
44
|
if excerpt.size > MAX_SOURCE_EXCERPT_SIZE
|
44
45
|
excerpt[0, MAX_SOURCE_EXCERPT_SIZE - 3] + '...'
|
45
46
|
else
|
@@ -54,18 +55,22 @@ module ThemeCheck
|
|
54
55
|
end
|
55
56
|
|
56
57
|
def end_line
|
57
|
-
|
58
|
-
|
58
|
+
if markup
|
59
|
+
start_line + markup.count("\n")
|
60
|
+
else
|
61
|
+
start_line
|
62
|
+
end
|
59
63
|
end
|
60
64
|
|
61
65
|
def start_column
|
62
|
-
return 0 unless line_number
|
63
|
-
template.full_line(
|
66
|
+
return 0 unless line_number && markup
|
67
|
+
template.full_line(start_line + 1).index(markup.split("\n", 2).first)
|
64
68
|
end
|
65
69
|
|
66
70
|
def end_column
|
67
|
-
return 0 unless line_number
|
68
|
-
|
71
|
+
return 0 unless line_number && markup
|
72
|
+
markup_end = markup.split("\n").last
|
73
|
+
template.full_line(end_line + 1).index(markup_end) + markup_end.size
|
69
74
|
end
|
70
75
|
|
71
76
|
def code_name
|
@@ -93,6 +98,17 @@ module ThemeCheck
|
|
93
98
|
tokens.join(":") if tokens.any?
|
94
99
|
end
|
95
100
|
|
101
|
+
def correctable?
|
102
|
+
line_number && correction
|
103
|
+
end
|
104
|
+
|
105
|
+
def correct
|
106
|
+
if correctable?
|
107
|
+
corrector = Corrector.new(template: template)
|
108
|
+
correction.call(corrector)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
96
112
|
def to_s
|
97
113
|
if template
|
98
114
|
"#{message} at #{location}"
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class Packager
|
4
|
+
ROOT = File.expand_path('../../..', __FILE__)
|
5
|
+
PACKAGING_DIR = File.join(ROOT, 'packaging')
|
6
|
+
BUILDS_DIR = File.join(PACKAGING_DIR, 'builds', ThemeCheck::VERSION)
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
FileUtils.mkdir_p(BUILDS_DIR)
|
10
|
+
end
|
11
|
+
|
12
|
+
def build_homebrew
|
13
|
+
root_dir = File.join(PACKAGING_DIR, 'homebrew')
|
14
|
+
|
15
|
+
build_path = File.join(BUILDS_DIR, "theme-check.rb")
|
16
|
+
puts "\nBuilding Homebrew package"
|
17
|
+
|
18
|
+
puts "Generating formula..."
|
19
|
+
File.delete(build_path) if File.exist?(build_path)
|
20
|
+
|
21
|
+
spec_contents = File.read(File.join(root_dir, 'theme_check.base.rb'))
|
22
|
+
spec_contents = spec_contents.gsub('THEME_CHECK_VERSION', ThemeCheck::VERSION)
|
23
|
+
|
24
|
+
puts "Grabbing sha256 checksum from Rubygems.org"
|
25
|
+
require 'digest/sha2'
|
26
|
+
require 'open-uri'
|
27
|
+
gem_checksum = open("https://rubygems.org/downloads/theme-check-#{ThemeCheck::VERSION}.gem") do |io|
|
28
|
+
Digest::SHA256.new.hexdigest(io.read)
|
29
|
+
end
|
30
|
+
|
31
|
+
puts "Got sha256 checksum for gem: #{gem_checksum}"
|
32
|
+
spec_contents = spec_contents.gsub('THEME_CHECK_GEM_CHECKSUM', gem_checksum)
|
33
|
+
|
34
|
+
puts "Writing generated formula\n To: #{build_path}\n\n"
|
35
|
+
File.write(build_path, spec_contents)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def ensure_program_installed(program, installation_cmd)
|
41
|
+
unless system(program, '--version', out: File::NULL, err: File::NULL)
|
42
|
+
raise <<~MESSAGE
|
43
|
+
|
44
|
+
Could not find program #{program} which is required to build the package.
|
45
|
+
You can install it by running `#{installation_cmd}`.
|
46
|
+
|
47
|
+
MESSAGE
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/theme_check/printer.rb
CHANGED
@@ -2,25 +2,34 @@
|
|
2
2
|
|
3
3
|
module ThemeCheck
|
4
4
|
class Printer
|
5
|
-
def print(theme, offenses)
|
5
|
+
def print(theme, offenses, auto_correct)
|
6
6
|
offenses.each do |offense|
|
7
|
-
print_offense(offense)
|
7
|
+
print_offense(offense, auto_correct)
|
8
8
|
puts
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
correctable = offenses.select(&:correctable?)
|
12
|
+
puts "#{theme.all.size} files inspected, #{red(offenses.size.to_s + ' offenses')} detected, \
|
13
|
+
#{yellow(correctable.size.to_s + ' offenses')} #{auto_correct ? 'corrected' : 'auto-correctable'}"
|
12
14
|
end
|
13
15
|
|
14
|
-
def print_offense(offense)
|
16
|
+
def print_offense(offense, auto_correct)
|
15
17
|
location = if offense.location
|
16
18
|
blue(offense.location) + ": "
|
17
19
|
else
|
18
20
|
""
|
19
21
|
end
|
20
22
|
|
23
|
+
corrected = if auto_correct && offense.correctable?
|
24
|
+
green("[Corrected] ")
|
25
|
+
else
|
26
|
+
""
|
27
|
+
end
|
28
|
+
|
21
29
|
puts location +
|
22
30
|
colorized_severity(offense.severity) + ": " +
|
23
31
|
yellow(offense.check_name) + ": " +
|
32
|
+
corrected +
|
24
33
|
offense.message + "."
|
25
34
|
if offense.source_excerpt
|
26
35
|
puts "\t#{offense.source_excerpt}"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module ThemeCheck
|
5
|
+
module ShopifyLiquid
|
6
|
+
module DeprecatedFilter
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def alternatives(filter)
|
10
|
+
all.fetch(filter, nil)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def all
|
16
|
+
@all ||= begin
|
17
|
+
YAML.load(File.read("#{__dir__}/../../../data/shopify_liquid/deprecated_filters.yml"))
|
18
|
+
.values
|
19
|
+
.each_with_object({}) do |filters, acc|
|
20
|
+
filters.each do |(filter, alternatives)|
|
21
|
+
acc[filter] = alternatives
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/theme_check/template.rb
CHANGED
@@ -8,6 +8,7 @@ module ThemeCheck
|
|
8
8
|
def initialize(path, root)
|
9
9
|
@path = Pathname(path)
|
10
10
|
@root = Pathname(root)
|
11
|
+
@updated = false
|
11
12
|
end
|
12
13
|
|
13
14
|
def relative_path
|
@@ -35,13 +36,19 @@ module ThemeCheck
|
|
35
36
|
end
|
36
37
|
|
37
38
|
def lines
|
38
|
-
|
39
|
+
# Retain trailing newline character
|
40
|
+
@lines ||= source.split("\n", -1)
|
39
41
|
end
|
40
42
|
|
41
43
|
def excerpt(line)
|
42
44
|
lines[line - 1].strip
|
43
45
|
end
|
44
46
|
|
47
|
+
def source_excerpt(line)
|
48
|
+
original_lines = source.split("\n")
|
49
|
+
original_lines[line - 1].strip
|
50
|
+
end
|
51
|
+
|
45
52
|
def full_line(line)
|
46
53
|
lines[line - 1]
|
47
54
|
end
|
@@ -58,6 +65,16 @@ module ThemeCheck
|
|
58
65
|
parse.root
|
59
66
|
end
|
60
67
|
|
68
|
+
def update!
|
69
|
+
@updated = true
|
70
|
+
end
|
71
|
+
|
72
|
+
def write
|
73
|
+
if @updated
|
74
|
+
@path.write(lines.join("\n"))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
61
78
|
def ==(other)
|
62
79
|
other.is_a?(Template) && @path == other.path
|
63
80
|
end
|
data/lib/theme_check/version.rb
CHANGED
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'formula'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
class ThemeCheck < Formula
|
7
|
+
module RubyBin
|
8
|
+
def ruby_bin
|
9
|
+
Formula["ruby"].opt_bin
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class RubyGemsDownloadStrategy < AbstractDownloadStrategy
|
14
|
+
include RubyBin
|
15
|
+
|
16
|
+
def fetch
|
17
|
+
ohai("Fetching theme-check from gem source")
|
18
|
+
cache.cd do
|
19
|
+
ENV['GEM_SPEC_CACHE'] = "#{cache}/gem_spec_cache"
|
20
|
+
system("#{ruby_bin}/gem", "fetch", "theme-check", "--version", gem_version)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def cached_location
|
25
|
+
Pathname.new("#{cache}/theme-check-#{gem_version}.gem")
|
26
|
+
end
|
27
|
+
|
28
|
+
def cache
|
29
|
+
@cache ||= HOMEBREW_CACHE
|
30
|
+
end
|
31
|
+
|
32
|
+
def gem_version
|
33
|
+
return @version if defined?(@version) && @version
|
34
|
+
@version = @resource.version if defined?(@resource)
|
35
|
+
raise "Unable to determine version; did Homebrew change?" unless @version
|
36
|
+
@version
|
37
|
+
end
|
38
|
+
|
39
|
+
def clear_cache
|
40
|
+
cached_location.unlink if cached_location.exist?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
include RubyBin
|
45
|
+
|
46
|
+
url "theme-check", using: RubyGemsDownloadStrategy
|
47
|
+
version "THEME_CHECK_VERSION"
|
48
|
+
sha256 'THEME_CHECK_GEM_CHECKSUM'
|
49
|
+
depends_on "ruby"
|
50
|
+
|
51
|
+
def install
|
52
|
+
# set GEM_HOME and GEM_PATH to make sure we package all the dependent gems
|
53
|
+
# together without accidently picking up other gems on the gem path since
|
54
|
+
# they might not be there if, say, we change to a different rvm gemset
|
55
|
+
ENV['GEM_HOME'] = prefix.to_s
|
56
|
+
ENV['GEM_PATH'] = prefix.to_s
|
57
|
+
|
58
|
+
# Use /usr/local/bin at the front of the path instead of Homebrew shims,
|
59
|
+
# which mess with Ruby's own compiler config when building native extensions
|
60
|
+
if defined?(HOMEBREW_SHIMS_PATH)
|
61
|
+
ENV['PATH'] = ENV['PATH'].sub(HOMEBREW_SHIMS_PATH.to_s, '/usr/local/bin')
|
62
|
+
end
|
63
|
+
|
64
|
+
system("#{ruby_bin}/gem", "install", cached_download,
|
65
|
+
"--no-document",
|
66
|
+
"--no-wrapper",
|
67
|
+
"--no-user-install",
|
68
|
+
"--install-dir", prefix,
|
69
|
+
"--bindir", bin)
|
70
|
+
|
71
|
+
raise "gem install 'theme-check' failed with status #{$CHILD_STATUS.exitstatus}" unless $CHILD_STATUS.success?
|
72
|
+
|
73
|
+
bin.rmtree if bin.exist?
|
74
|
+
bin.mkpath
|
75
|
+
|
76
|
+
brew_gem_prefix = "#{prefix}/gems/theme-check-#{version}"
|
77
|
+
|
78
|
+
gemspec = Gem::Specification.load("#{prefix}/specifications/theme-check-#{version}.gemspec")
|
79
|
+
ruby_libs = Dir.glob("#{prefix}/gems/*/lib")
|
80
|
+
gemspec.executables.each do |exe|
|
81
|
+
file = Pathname.new("#{brew_gem_prefix}/#{gemspec.bindir}/#{exe}")
|
82
|
+
(bin + file.basename).open('w') do |f|
|
83
|
+
f << <<~RUBY
|
84
|
+
#!#{ruby_bin}/ruby --disable-gems
|
85
|
+
ENV['GEM_HOME']="#{prefix}"
|
86
|
+
ENV['GEM_PATH']="#{prefix}"
|
87
|
+
require 'rubygems'
|
88
|
+
$:.unshift(#{ruby_libs.map(&:inspect).join(',')})
|
89
|
+
load "#{file}"
|
90
|
+
RUBY
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
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: 0.2.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-01-
|
11
|
+
date: 2021-01-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: liquid
|
@@ -74,6 +74,7 @@ files:
|
|
74
74
|
- Rakefile
|
75
75
|
- bin/liquid-server
|
76
76
|
- config/default.yml
|
77
|
+
- data/shopify_liquid/deprecated_filters.yml
|
77
78
|
- data/shopify_liquid/filters.yml
|
78
79
|
- data/shopify_liquid/objects.yml
|
79
80
|
- dev.yml
|
@@ -86,6 +87,7 @@ files:
|
|
86
87
|
- lib/theme_check/checks.rb
|
87
88
|
- lib/theme_check/checks/convert_include_to_render.rb
|
88
89
|
- lib/theme_check/checks/default_locale.rb
|
90
|
+
- lib/theme_check/checks/deprecated_filter.rb
|
89
91
|
- lib/theme_check/checks/liquid_tag.rb
|
90
92
|
- lib/theme_check/checks/matching_schema_translations.rb
|
91
93
|
- lib/theme_check/checks/matching_translations.rb
|
@@ -108,6 +110,7 @@ files:
|
|
108
110
|
- lib/theme_check/checks_tracking.rb
|
109
111
|
- lib/theme_check/cli.rb
|
110
112
|
- lib/theme_check/config.rb
|
113
|
+
- lib/theme_check/corrector.rb
|
111
114
|
- lib/theme_check/json_check.rb
|
112
115
|
- lib/theme_check/json_file.rb
|
113
116
|
- lib/theme_check/json_helpers.rb
|
@@ -118,9 +121,11 @@ files:
|
|
118
121
|
- lib/theme_check/locale_diff.rb
|
119
122
|
- lib/theme_check/node.rb
|
120
123
|
- lib/theme_check/offense.rb
|
124
|
+
- lib/theme_check/packager.rb
|
121
125
|
- lib/theme_check/parsing_helpers.rb
|
122
126
|
- lib/theme_check/printer.rb
|
123
127
|
- lib/theme_check/shopify_liquid.rb
|
128
|
+
- lib/theme_check/shopify_liquid/deprecated_filter.rb
|
124
129
|
- lib/theme_check/shopify_liquid/filter.rb
|
125
130
|
- lib/theme_check/shopify_liquid/object.rb
|
126
131
|
- lib/theme_check/tags.rb
|
@@ -128,6 +133,7 @@ files:
|
|
128
133
|
- lib/theme_check/theme.rb
|
129
134
|
- lib/theme_check/version.rb
|
130
135
|
- lib/theme_check/visitor.rb
|
136
|
+
- packaging/homebrew/theme_check.base.rb
|
131
137
|
- theme-check.gemspec
|
132
138
|
homepage: https://github.com/Shopify/theme-check
|
133
139
|
licenses:
|