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