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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e51f58be8347c6d602ef2f5f6385117f1c8ad5d07a66c111c8c8bf623008f3b1
4
- data.tar.gz: 88eb1fbc392ff70251255f5aa681e6e7bf37fbc1c0a65bc1651e291c13baf5d9
3
+ metadata.gz: 4001e286b9a0c851493de76250770565088d1d8f646a6e592ee3f2c142b47c3c
4
+ data.tar.gz: 3c3cd833684e0020211365eec091b70be3a064ae72f4c1d7337908fbfdbdacd8
5
5
  SHA512:
6
- metadata.gz: 0c7865cb5cb44da5811b78429d34c3309264ba21f55954a83b97784e15375798794461c0d19edfe620e8cbed45b6b8a80d63755b258505acb4c2b61bbae77e4a
7
- data.tar.gz: 625963de20e85bc5b27989fba14a9a8243aafffc21aba5f4ea54b8e937fba05f73df99549a6b6b575dd29b7df48319c054aa2089e98c31e1e8212b1521c73c04
6
+ metadata.gz: 35855ff5a3e10f8a8dc60cc40d619fe713f013eb8ee64f92c565c34e3a3b58b9646ca1b707535f777e53cabae1dd58facc2adc4722e5c704b11ff70d9407e959
7
+ data.tar.gz: fb9f94add954ec84a0fb8ca6616e8d91981701ff0d62f5dc89692dcd4ddf7715073db531dc81ec7dd1c8a4fc8df93360adbc32ee02254d8538a027236995cbdf
data/.gitignore CHANGED
@@ -11,3 +11,4 @@
11
11
  Gemfile.lock
12
12
 
13
13
  .rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml
14
+ packaging/builds
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
- Theme Check is also available [inside some code editors](https://github.com/Shopify/theme-check/wiki).
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')
@@ -57,7 +57,10 @@ MissingRequiredTemplateFiles:
57
57
  enabled: true
58
58
 
59
59
  UndefinedObject:
60
- enabled: false
60
+ enabled: true
61
61
 
62
62
  RequiredDirectories:
63
63
  enabled: true
64
+
65
+ DeprecatedFilter:
66
+ enabled: true
@@ -0,0 +1,10 @@
1
+ ---
2
+ Liquid::ColorFilter:
3
+ hex_to_rgba:
4
+ - color_to_rgb
5
+ - color_modify
6
+ Liquid::UrlFilter:
7
+ collection_img_url:
8
+ - img_url
9
+ product_img_url:
10
+ - img_url
@@ -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 }
@@ -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
- elsif node.markup[-1] != " "
50
- add_offense("Space missing before '}}'", node: node)
51
- elsif node.markup[1] == " "
52
- add_offense("Too many spaces after '{{'", node: node)
53
- elsif node.markup[-2] == " "
54
- add_offense("Too many spaces before '}}'", node: node)
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 :all_variable_lookups, :all_assigns, :all_captures, :all_forloops
17
- def all
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
- @templates = {}
24
- @used_snippets = {}
53
+ @files = {}
25
54
  end
26
55
 
27
56
  def on_document(node)
28
- @templates[node.template.name] = TemplateInfo.new
57
+ @files[node.template.name] = TemplateInfo.new
29
58
  end
30
59
 
31
60
  def on_assign(node)
32
- @templates[node.template.name].all_assigns[node.value.to] = node
61
+ @files[node.template.name].all_assigns[node.value.to] = node
33
62
  end
34
63
 
35
64
  def on_capture(node)
36
- @templates[node.template.name].all_captures[node.value.instance_variable_get('@to')] = node
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
- @templates[node.template.name].all_forloops[node.value.variable_name] = node
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 on_include(node)
77
+ def on_render(node)
44
78
  return unless node.value.template_name_expr.is_a?(String)
45
- name = "snippets/#{node.value.template_name_expr}"
46
- @used_snippets[name] ||= Set.new
47
- @used_snippets[name] << node.template.name
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
- @templates[node.template.name].all_variable_lookups[node.value.name] = node
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
- foster_snippets = theme.snippets
56
- .reject { |t| @used_snippets.include?(t.name) }
57
- .map(&:name)
58
-
59
- @templates.each do |(template_name, info)|
60
- next if foster_snippets.include?(template_name)
61
- if (all_including_templates = @used_snippets[template_name])
62
- all_including_templates.each do |including_template|
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
- all = info.all
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 check_object(variable_lookups, all)
75
- variable_lookups.each do |(name, node)|
76
- next if all.include?(name)
77
- next if ThemeCheck::ShopifyLiquid::Object.labels.include?(name)
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
- parent = node.parent
80
- parent = parent.parent if :variable_lookup == parent.type_name
81
- add_offense("Undefined object `#{name}`", node: parent)
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
@@ -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
- ThemeCheck::Printer.new.print(theme, analyzer.offenses)
75
- raise Abort, "" if analyzer.offenses.any?
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
@@ -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
@@ -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
@@ -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.excerpt(line_number)
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
- return 0 unless line_number
58
- line_number - 1
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(line_number).index(markup)
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
- template.full_line(line_number).index(markup) + markup.size
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
@@ -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
- puts "#{theme.all.size} files inspected, #{red(offenses.size.to_s + ' offenses')} detected"
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}"
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
+ require_relative 'shopify_liquid/deprecated_filter'
2
3
  require_relative 'shopify_liquid/filter'
3
4
  require_relative 'shopify_liquid/object'
@@ -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
@@ -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
- @lines ||= source.split("\n")
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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
- VERSION = "0.1.2"
3
+ VERSION = "0.2.0"
4
4
  end
@@ -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.1.2
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-06 00:00:00.000000000 Z
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: