theme-check 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CONTRIBUTING.md +2 -0
  4. data/README.md +62 -1
  5. data/RELEASING.md +41 -0
  6. data/Rakefile +38 -4
  7. data/config/default.yml +18 -0
  8. data/data/shopify_liquid/deprecated_filters.yml +10 -0
  9. data/data/shopify_liquid/plus_objects.yml +15 -0
  10. data/dev.yml +2 -0
  11. data/lib/theme_check.rb +5 -0
  12. data/lib/theme_check/analyzer.rb +15 -5
  13. data/lib/theme_check/check.rb +11 -0
  14. data/lib/theme_check/checks.rb +10 -0
  15. data/lib/theme_check/checks/deprecated_filter.rb +22 -0
  16. data/lib/theme_check/checks/missing_enable_comment.rb +31 -0
  17. data/lib/theme_check/checks/missing_required_template_files.rb +12 -12
  18. data/lib/theme_check/checks/parser_blocking_javascript.rb +55 -0
  19. data/lib/theme_check/checks/space_inside_braces.rb +19 -7
  20. data/lib/theme_check/checks/template_length.rb +11 -3
  21. data/lib/theme_check/checks/undefined_object.rb +107 -34
  22. data/lib/theme_check/checks/valid_html_translation.rb +2 -2
  23. data/lib/theme_check/cli.rb +18 -4
  24. data/lib/theme_check/config.rb +97 -44
  25. data/lib/theme_check/corrector.rb +31 -0
  26. data/lib/theme_check/disabled_checks.rb +77 -0
  27. data/lib/theme_check/file_system_storage.rb +51 -0
  28. data/lib/theme_check/in_memory_storage.rb +37 -0
  29. data/lib/theme_check/json_file.rb +12 -10
  30. data/lib/theme_check/language_server/handler.rb +38 -13
  31. data/lib/theme_check/language_server/server.rb +2 -2
  32. data/lib/theme_check/liquid_check.rb +2 -2
  33. data/lib/theme_check/node.rb +13 -0
  34. data/lib/theme_check/offense.rb +25 -9
  35. data/lib/theme_check/packager.rb +51 -0
  36. data/lib/theme_check/printer.rb +13 -4
  37. data/lib/theme_check/shopify_liquid.rb +1 -0
  38. data/lib/theme_check/shopify_liquid/deprecated_filter.rb +28 -0
  39. data/lib/theme_check/shopify_liquid/object.rb +6 -0
  40. data/lib/theme_check/storage.rb +25 -0
  41. data/lib/theme_check/template.rb +32 -10
  42. data/lib/theme_check/theme.rb +14 -9
  43. data/lib/theme_check/version.rb +1 -1
  44. data/lib/theme_check/visitor.rb +14 -3
  45. data/packaging/homebrew/theme_check.base.rb +98 -0
  46. metadata +21 -7
@@ -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
@@ -11,6 +11,12 @@ module ThemeCheck
11
11
  YAML.load(File.read("#{__dir__}/../../../data/shopify_liquid/objects.yml"))
12
12
  end
13
13
  end
14
+
15
+ def plus_labels
16
+ @plus_labels ||= begin
17
+ YAML.load(File.read("#{__dir__}/../../../data/shopify_liquid/plus_objects.yml"))
18
+ end
19
+ end
14
20
  end
15
21
  end
16
22
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThemeCheck
4
+ class Storage
5
+ def read(relative_path)
6
+ raise NotImplementedError
7
+ end
8
+
9
+ def write(relative_path, content)
10
+ raise NotImplementedError
11
+ end
12
+
13
+ def path(relative_path)
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def files
18
+ raise NotImplementedError
19
+ end
20
+
21
+ def directories
22
+ raise NotImplementedError
23
+ end
24
+ end
25
+ end
@@ -3,15 +3,29 @@ require "pathname"
3
3
 
4
4
  module ThemeCheck
5
5
  class Template
6
- attr_reader :path
6
+ def initialize(relative_path, storage)
7
+ @storage = storage
8
+ @relative_path = relative_path
9
+ end
7
10
 
8
- def initialize(path, root)
9
- @path = Pathname(path)
10
- @root = Pathname(root)
11
+ def path
12
+ @storage.path(@relative_path)
11
13
  end
12
14
 
13
15
  def relative_path
14
- @path.relative_path_from(@root)
16
+ @relative_pathname ||= Pathname.new(@relative_path)
17
+ end
18
+
19
+ def source
20
+ @source ||= @storage.read(@relative_path)
21
+ end
22
+
23
+ def write
24
+ content = updated_content
25
+ if source != content
26
+ @storage.write(@relative_path, content)
27
+ @source = content
28
+ end
15
29
  end
16
30
 
17
31
  def name
@@ -30,18 +44,26 @@ module ThemeCheck
30
44
  name.start_with?('snippets')
31
45
  end
32
46
 
33
- def source
34
- @source ||= @path.read
47
+ def lines
48
+ # Retain trailing newline character
49
+ @lines ||= source.split("\n", -1)
35
50
  end
36
51
 
37
- def lines
38
- @lines ||= source.split("\n")
52
+ # Not entirely obvious but lines is mutable, corrections are to be
53
+ # applied on @lines.
54
+ def updated_content
55
+ lines.join("\n")
39
56
  end
40
57
 
41
58
  def excerpt(line)
42
59
  lines[line - 1].strip
43
60
  end
44
61
 
62
+ def source_excerpt(line)
63
+ original_lines = source.split("\n")
64
+ original_lines[line - 1].strip
65
+ end
66
+
45
67
  def full_line(line)
46
68
  lines[line - 1]
47
69
  end
@@ -59,7 +81,7 @@ module ThemeCheck
59
81
  end
60
82
 
61
83
  def ==(other)
62
- other.is_a?(Template) && @path == other.path
84
+ other.is_a?(Template) && relative_path == other.relative_path
63
85
  end
64
86
 
65
87
  def self.parse(source)
@@ -4,18 +4,27 @@ require "pathname"
4
4
  module ThemeCheck
5
5
  class Theme
6
6
  DEFAULT_LOCALE_REGEXP = %r{^locales/(.*)\.default$}
7
- attr_reader :root
7
+ LIQUID_REGEX = /\.liquid$/i
8
+ JSON_REGEX = /\.json$/i
8
9
 
9
- def initialize(root)
10
- @root = Pathname.new(root)
10
+ def initialize(storage)
11
+ @storage = storage
11
12
  end
12
13
 
13
14
  def liquid
14
- @liquid ||= @root.glob("**/*.liquid").map { |path| Template.new(path, @root) }
15
+ @liquid ||= @storage.files
16
+ .select { |path| LIQUID_REGEX.match?(path) }
17
+ .map { |path| Template.new(path, @storage) }
15
18
  end
16
19
 
17
20
  def json
18
- @json ||= @root.glob("**/*.json").map { |path| JsonFile.new(path, @root) }
21
+ @json ||= @storage.files
22
+ .select { |path| JSON_REGEX.match?(path) }
23
+ .map { |path| JsonFile.new(path, @storage) }
24
+ end
25
+
26
+ def directories
27
+ @storage.directories
19
28
  end
20
29
 
21
30
  def default_locale_json
@@ -52,9 +61,5 @@ module ThemeCheck
52
61
  def snippets
53
62
  liquid.select(&:snippet?)
54
63
  end
55
-
56
- def directories
57
- @directories ||= @root.glob('*').select { |f| File.directory?(f) }.map { |f| f.relative_path_from(@root) }
58
- end
59
64
  end
60
65
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
- VERSION = "0.1.0"
3
+ VERSION = "0.3.0"
4
4
  end
@@ -6,12 +6,15 @@ module ThemeCheck
6
6
  end
7
7
 
8
8
  def visit_template(template)
9
+ @disabled_checks = DisabledChecks.new
9
10
  visit(Node.new(template.root, nil, template))
10
11
  rescue Liquid::Error => exception
11
12
  exception.template_name = template.name
12
13
  call_checks(:on_error, exception)
13
14
  end
14
15
 
16
+ private
17
+
15
18
  def visit(node)
16
19
  call_checks(:on_node, node)
17
20
  call_checks(:on_tag, node) if node.tag?
@@ -22,16 +25,24 @@ module ThemeCheck
22
25
  call_checks(:after_tag, node) if node.tag?
23
26
  call_checks(:after_node, node)
24
27
  end
25
- end
26
28
 
27
- private
29
+ @disabled_checks.update(node) if node.comment?
30
+ end
28
31
 
29
32
  def visit_children(node)
30
33
  node.children.each { |child| visit(child) }
31
34
  end
32
35
 
33
36
  def call_checks(method, *args)
34
- @checks.call(method, *args)
37
+ checks.call(method, *args)
38
+ end
39
+
40
+ def checks
41
+ return @checks unless @disabled_checks.any?
42
+
43
+ return @checks.always_enabled if @disabled_checks.all_disabled?
44
+
45
+ @checks.except_for(@disabled_checks)
35
46
  end
36
47
  end
37
48
  end
@@ -0,0 +1,98 @@
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(
65
+ "#{ruby_bin}/gem",
66
+ "install",
67
+ cached_download,
68
+ "--no-document",
69
+ "--no-wrapper",
70
+ "--no-user-install",
71
+ "--install-dir", prefix,
72
+ "--bindir", bin
73
+ )
74
+
75
+ raise "gem install 'theme-check' failed with status #{$CHILD_STATUS.exitstatus}" unless $CHILD_STATUS.success?
76
+
77
+ bin.rmtree if bin.exist?
78
+ bin.mkpath
79
+
80
+ brew_gem_prefix = "#{prefix}/gems/theme-check-#{version}"
81
+
82
+ gemspec = Gem::Specification.load("#{prefix}/specifications/theme-check-#{version}.gemspec")
83
+ ruby_libs = Dir.glob("#{prefix}/gems/*/lib")
84
+ gemspec.executables.each do |exe|
85
+ file = Pathname.new("#{brew_gem_prefix}/#{gemspec.bindir}/#{exe}")
86
+ (bin + file.basename).open('w') do |f|
87
+ f << <<~RUBY
88
+ #!#{ruby_bin}/ruby --disable-gems
89
+ ENV['GEM_HOME']="#{prefix}"
90
+ ENV['GEM_PATH']="#{prefix}"
91
+ require 'rubygems'
92
+ $:.unshift(#{ruby_libs.map(&:inspect).join(',')})
93
+ load "#{file}"
94
+ RUBY
95
+ end
96
+ end
97
+ end
98
+ 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.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc-André Cournoyer
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-06 00:00:00.000000000 Z
11
+ date: 2021-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: liquid
@@ -52,7 +52,7 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- description:
55
+ description:
56
56
  email:
57
57
  - marcandre.cournoyer@shopify.com
58
58
  executables:
@@ -71,11 +71,14 @@ files:
71
71
  - Guardfile
72
72
  - LICENSE.md
73
73
  - README.md
74
+ - RELEASING.md
74
75
  - Rakefile
75
76
  - bin/liquid-server
76
77
  - config/default.yml
78
+ - data/shopify_liquid/deprecated_filters.yml
77
79
  - data/shopify_liquid/filters.yml
78
80
  - data/shopify_liquid/objects.yml
81
+ - data/shopify_liquid/plus_objects.yml
79
82
  - dev.yml
80
83
  - docs/preview.png
81
84
  - exe/theme-check
@@ -86,12 +89,15 @@ files:
86
89
  - lib/theme_check/checks.rb
87
90
  - lib/theme_check/checks/convert_include_to_render.rb
88
91
  - lib/theme_check/checks/default_locale.rb
92
+ - lib/theme_check/checks/deprecated_filter.rb
89
93
  - lib/theme_check/checks/liquid_tag.rb
90
94
  - lib/theme_check/checks/matching_schema_translations.rb
91
95
  - lib/theme_check/checks/matching_translations.rb
96
+ - lib/theme_check/checks/missing_enable_comment.rb
92
97
  - lib/theme_check/checks/missing_required_template_files.rb
93
98
  - lib/theme_check/checks/missing_template.rb
94
99
  - lib/theme_check/checks/nested_snippet.rb
100
+ - lib/theme_check/checks/parser_blocking_javascript.rb
95
101
  - lib/theme_check/checks/required_directories.rb
96
102
  - lib/theme_check/checks/required_layout_theme_object.rb
97
103
  - lib/theme_check/checks/space_inside_braces.rb
@@ -108,6 +114,10 @@ files:
108
114
  - lib/theme_check/checks_tracking.rb
109
115
  - lib/theme_check/cli.rb
110
116
  - lib/theme_check/config.rb
117
+ - lib/theme_check/corrector.rb
118
+ - lib/theme_check/disabled_checks.rb
119
+ - lib/theme_check/file_system_storage.rb
120
+ - lib/theme_check/in_memory_storage.rb
111
121
  - lib/theme_check/json_check.rb
112
122
  - lib/theme_check/json_file.rb
113
123
  - lib/theme_check/json_helpers.rb
@@ -118,23 +128,27 @@ files:
118
128
  - lib/theme_check/locale_diff.rb
119
129
  - lib/theme_check/node.rb
120
130
  - lib/theme_check/offense.rb
131
+ - lib/theme_check/packager.rb
121
132
  - lib/theme_check/parsing_helpers.rb
122
133
  - lib/theme_check/printer.rb
123
134
  - lib/theme_check/shopify_liquid.rb
135
+ - lib/theme_check/shopify_liquid/deprecated_filter.rb
124
136
  - lib/theme_check/shopify_liquid/filter.rb
125
137
  - lib/theme_check/shopify_liquid/object.rb
138
+ - lib/theme_check/storage.rb
126
139
  - lib/theme_check/tags.rb
127
140
  - lib/theme_check/template.rb
128
141
  - lib/theme_check/theme.rb
129
142
  - lib/theme_check/version.rb
130
143
  - lib/theme_check/visitor.rb
144
+ - packaging/homebrew/theme_check.base.rb
131
145
  - theme-check.gemspec
132
146
  homepage: https://github.com/Shopify/theme-check
133
147
  licenses:
134
148
  - MIT
135
149
  metadata:
136
150
  allowed_push_host: https://rubygems.org
137
- post_install_message:
151
+ post_install_message:
138
152
  rdoc_options: []
139
153
  require_paths:
140
154
  - lib
@@ -149,8 +163,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
149
163
  - !ruby/object:Gem::Version
150
164
  version: '0'
151
165
  requirements: []
152
- rubygems_version: 3.1.2
153
- signing_key:
166
+ rubygems_version: 3.0.3
167
+ signing_key:
154
168
  specification_version: 4
155
169
  summary: A Shopify Theme Linter
156
170
  test_files: []