theme-check 0.1.2 → 0.2.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/.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
|

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