theme-check 0.4.0 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/theme-check.yml +10 -3
  3. data/.rubocop.yml +12 -3
  4. data/CHANGELOG.md +40 -0
  5. data/CONTRIBUTING.md +5 -2
  6. data/Gemfile +5 -3
  7. data/LICENSE.md +2 -0
  8. data/README.md +12 -2
  9. data/RELEASING.md +10 -3
  10. data/Rakefile +6 -0
  11. data/config/default.yml +16 -0
  12. data/data/shopify_liquid/tags.yml +1 -0
  13. data/data/shopify_translation_keys.yml +850 -0
  14. data/docs/checks/CHECK_DOCS_TEMPLATE.md +47 -0
  15. data/docs/checks/asset_size_css.md +52 -0
  16. data/docs/checks/asset_size_javascript.md +79 -0
  17. data/docs/checks/convert_include_to_render.md +48 -0
  18. data/docs/checks/default_locale.md +46 -0
  19. data/docs/checks/deprecated_filter.md +46 -0
  20. data/docs/checks/img_width_and_height.md +79 -0
  21. data/docs/checks/liquid_tag.md +65 -0
  22. data/docs/checks/matching_schema_translations.md +93 -0
  23. data/docs/checks/matching_translations.md +72 -0
  24. data/docs/checks/missing_enable_comment.md +50 -0
  25. data/docs/checks/missing_required_template_files.md +26 -0
  26. data/docs/checks/missing_template.md +40 -0
  27. data/docs/checks/nested_snippet.md +69 -0
  28. data/docs/checks/parser_blocking_javascript.md +97 -0
  29. data/docs/checks/remote_asset.md +82 -0
  30. data/docs/checks/required_directories.md +25 -0
  31. data/docs/checks/required_layout_theme_object.md +28 -0
  32. data/docs/checks/space_inside_braces.md +63 -0
  33. data/docs/checks/syntax_error.md +49 -0
  34. data/docs/checks/template_length.md +50 -0
  35. data/docs/checks/translation_key_exists.md +63 -0
  36. data/docs/checks/undefined_object.md +53 -0
  37. data/docs/checks/unknown_filter.md +45 -0
  38. data/docs/checks/unused_assign.md +47 -0
  39. data/docs/checks/unused_snippet.md +32 -0
  40. data/docs/checks/valid_html_translation.md +53 -0
  41. data/docs/checks/valid_json.md +60 -0
  42. data/docs/checks/valid_schema.md +50 -0
  43. data/lib/theme_check.rb +4 -0
  44. data/lib/theme_check/asset_file.rb +34 -0
  45. data/lib/theme_check/check.rb +20 -10
  46. data/lib/theme_check/checks/asset_size_css.rb +89 -0
  47. data/lib/theme_check/checks/asset_size_javascript.rb +68 -0
  48. data/lib/theme_check/checks/convert_include_to_render.rb +1 -1
  49. data/lib/theme_check/checks/default_locale.rb +1 -0
  50. data/lib/theme_check/checks/deprecated_filter.rb +1 -1
  51. data/lib/theme_check/checks/img_width_and_height.rb +74 -0
  52. data/lib/theme_check/checks/liquid_tag.rb +3 -3
  53. data/lib/theme_check/checks/matching_schema_translations.rb +1 -0
  54. data/lib/theme_check/checks/matching_translations.rb +2 -1
  55. data/lib/theme_check/checks/missing_enable_comment.rb +1 -0
  56. data/lib/theme_check/checks/missing_required_template_files.rb +1 -2
  57. data/lib/theme_check/checks/missing_template.rb +1 -0
  58. data/lib/theme_check/checks/nested_snippet.rb +1 -0
  59. data/lib/theme_check/checks/parser_blocking_javascript.rb +8 -15
  60. data/lib/theme_check/checks/remote_asset.rb +99 -0
  61. data/lib/theme_check/checks/required_directories.rb +1 -1
  62. data/lib/theme_check/checks/required_layout_theme_object.rb +1 -1
  63. data/lib/theme_check/checks/space_inside_braces.rb +1 -0
  64. data/lib/theme_check/checks/syntax_error.rb +1 -0
  65. data/lib/theme_check/checks/template_length.rb +1 -0
  66. data/lib/theme_check/checks/translation_key_exists.rb +14 -1
  67. data/lib/theme_check/checks/undefined_object.rb +11 -5
  68. data/lib/theme_check/checks/unknown_filter.rb +1 -0
  69. data/lib/theme_check/checks/unused_assign.rb +1 -0
  70. data/lib/theme_check/checks/unused_snippet.rb +1 -0
  71. data/lib/theme_check/checks/valid_html_translation.rb +2 -1
  72. data/lib/theme_check/checks/valid_json.rb +1 -0
  73. data/lib/theme_check/checks/valid_schema.rb +1 -0
  74. data/lib/theme_check/cli.rb +29 -9
  75. data/lib/theme_check/config.rb +5 -2
  76. data/lib/theme_check/disabled_checks.rb +2 -2
  77. data/lib/theme_check/in_memory_storage.rb +13 -8
  78. data/lib/theme_check/language_server.rb +2 -0
  79. data/lib/theme_check/language_server/completion_engine.rb +3 -3
  80. data/lib/theme_check/language_server/completion_helper.rb +0 -10
  81. data/lib/theme_check/language_server/completion_provider.rb +5 -0
  82. data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +6 -2
  83. data/lib/theme_check/language_server/completion_providers/object_completion_provider.rb +1 -1
  84. data/lib/theme_check/language_server/completion_providers/render_snippet_completion_provider.rb +43 -0
  85. data/lib/theme_check/language_server/completion_providers/tag_completion_provider.rb +2 -2
  86. data/lib/theme_check/language_server/constants.rb +10 -0
  87. data/lib/theme_check/language_server/document_link_engine.rb +48 -0
  88. data/lib/theme_check/language_server/handler.rb +67 -20
  89. data/lib/theme_check/language_server/server.rb +9 -4
  90. data/lib/theme_check/liquid_check.rb +11 -0
  91. data/lib/theme_check/node.rb +1 -2
  92. data/lib/theme_check/offense.rb +3 -1
  93. data/lib/theme_check/packager.rb +1 -1
  94. data/lib/theme_check/regex_helpers.rb +15 -0
  95. data/lib/theme_check/releaser.rb +39 -0
  96. data/lib/theme_check/remote_asset_file.rb +44 -0
  97. data/lib/theme_check/shopify_liquid/deprecated_filter.rb +10 -8
  98. data/lib/theme_check/shopify_liquid/filter.rb +3 -5
  99. data/lib/theme_check/shopify_liquid/object.rb +2 -6
  100. data/lib/theme_check/shopify_liquid/tag.rb +1 -3
  101. data/lib/theme_check/storage.rb +3 -3
  102. data/lib/theme_check/string_helpers.rb +47 -0
  103. data/lib/theme_check/tags.rb +1 -2
  104. data/lib/theme_check/theme.rb +7 -1
  105. data/lib/theme_check/version.rb +1 -1
  106. data/packaging/homebrew/theme_check.base.rb +1 -1
  107. data/theme-check.gemspec +1 -2
  108. metadata +46 -18
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+ module ThemeCheck
3
+ # Reports errors when trying to use too much JavaScript on page load
4
+ # Encourages the use of the Import on Interaction pattern [1].
5
+ # [1]: https://addyosmani.com/blog/import-on-interaction/
6
+ class AssetSizeJavaScript < LiquidCheck
7
+ include RegexHelpers
8
+ severity :error
9
+ category :performance
10
+ doc docs_url(__FILE__)
11
+
12
+ Script = Struct.new(:src, :match)
13
+
14
+ SCRIPT_TAG_SRC = %r{
15
+ <script
16
+ [^>]+ # any non closing tag character
17
+ src= # src attribute start
18
+ (?<src>#{QUOTED_LIQUID_ATTRIBUTE}) # src attribute value (may contain liquid)
19
+ [^>]* # any non closing character till the end
20
+ >
21
+ }omix
22
+
23
+ attr_reader :threshold_in_bytes
24
+
25
+ def initialize(threshold_in_bytes: 10000)
26
+ @threshold_in_bytes = threshold_in_bytes
27
+ end
28
+
29
+ def on_document(node)
30
+ @node = node
31
+ @source = node.template.source
32
+ record_offenses
33
+ end
34
+
35
+ def record_offenses
36
+ scripts(@source).each do |script|
37
+ file_size = src_to_file_size(script.src)
38
+ next if file_size.nil?
39
+ next if file_size <= threshold_in_bytes
40
+ add_offense(
41
+ "JavaScript on every page load exceding compressed size threshold (#{threshold_in_bytes} Bytes), consider using the import on interaction pattern.",
42
+ node: @node,
43
+ markup: script.src,
44
+ line_number: @source[0...script.match.begin(:src)].count("\n") + 1
45
+ )
46
+ end
47
+ end
48
+
49
+ def scripts(source)
50
+ matches(source, SCRIPT_TAG_SRC)
51
+ .map { |m| Script.new(m[:src].gsub(START_OR_END_QUOTE, ""), m) }
52
+ end
53
+
54
+ def src_to_file_size(src)
55
+ # We're kind of intentionally only looking at {{ 'asset' | asset_url }} or full urls in here.
56
+ # More complicated liquid statements are not in scope.
57
+ if src =~ /^#{VARIABLE}$/o && src =~ /asset_url/ && src =~ Liquid::QuotedString
58
+ asset_id = Regexp.last_match(0).gsub(START_OR_END_QUOTE, "")
59
+ asset = @theme.assets.find { |a| a.name.end_with?("/" + asset_id) }
60
+ return if asset.nil?
61
+ asset.gzipped_size
62
+ elsif src =~ %r{^(https?:)?//}
63
+ asset = RemoteAssetFile.from_src(src)
64
+ asset.gzipped_size
65
+ end
66
+ end
67
+ end
68
+ end
@@ -4,7 +4,7 @@ module ThemeCheck
4
4
  class ConvertIncludeToRender < LiquidCheck
5
5
  severity :suggestion
6
6
  category :liquid
7
- doc "https://shopify.dev/docs/themes/liquid/reference/tags/deprecated-tags#include"
7
+ doc docs_url(__FILE__)
8
8
 
9
9
  def on_include(node)
10
10
  add_offense("`include` is deprecated - convert it to `render`", node: node)
@@ -3,6 +3,7 @@ module ThemeCheck
3
3
  class DefaultLocale < JsonCheck
4
4
  severity :suggestion
5
5
  category :translation
6
+ doc docs_url(__FILE__)
6
7
 
7
8
  def on_end
8
9
  return if @theme.default_locale_json
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
3
  class DeprecatedFilter < LiquidCheck
4
- doc "https://shopify.dev/docs/themes/liquid/reference/filters/deprecated-filters"
4
+ doc docs_url(__FILE__)
5
5
  category :liquid
6
6
  severity :suggestion
7
7
 
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+ module ThemeCheck
3
+ # Reports errors when trying to use parser-blocking script tags
4
+ class ImgWidthAndHeight < LiquidCheck
5
+ include RegexHelpers
6
+ severity :error
7
+ categories :liquid, :performance
8
+ doc docs_url(__FILE__)
9
+
10
+ # Not implemented with lookbehinds and lookaheads because performance was shit!
11
+ IMG_TAG = %r{<img#{HTML_ATTRIBUTES}/?>}oxim
12
+ SRC_ATTRIBUTE = /\s(src)=(#{QUOTED_LIQUID_ATTRIBUTE})/oxim
13
+ WIDTH_ATTRIBUTE = /\s(width)=(#{QUOTED_LIQUID_ATTRIBUTE})/oxim
14
+ HEIGHT_ATTRIBUTE = /\s(height)=(#{QUOTED_LIQUID_ATTRIBUTE})/oxim
15
+
16
+ FIELDS = [WIDTH_ATTRIBUTE, HEIGHT_ATTRIBUTE]
17
+ ENDS_IN_CSS_UNIT = /(cm|mm|in|px|pt|pc|em|ex|ch|rem|vw|vh|vmin|vmax|%)$/i
18
+
19
+ def on_document(node)
20
+ @source = node.template.source
21
+ @node = node
22
+ record_offenses
23
+ end
24
+
25
+ private
26
+
27
+ def record_offenses
28
+ matches(@source, IMG_TAG).each do |img_match|
29
+ next unless img_match[0] =~ SRC_ATTRIBUTE
30
+ record_missing_field_offenses(img_match)
31
+ record_units_in_field_offenses(img_match)
32
+ end
33
+ end
34
+
35
+ def record_missing_field_offenses(img_match)
36
+ width = WIDTH_ATTRIBUTE.match(img_match[0])
37
+ height = HEIGHT_ATTRIBUTE.match(img_match[0])
38
+ return if width && height
39
+ missing_width = width.nil?
40
+ missing_height = height.nil?
41
+ error_message = if missing_width && missing_height
42
+ "Missing width and height attributes"
43
+ elsif missing_width
44
+ "Missing width attribute"
45
+ elsif missing_height
46
+ "Missing height attribute"
47
+ end
48
+
49
+ add_offense(
50
+ error_message,
51
+ node: @node,
52
+ markup: img_match[0],
53
+ line_number: @source[0...img_match.begin(0)].count("\n") + 1
54
+ )
55
+ end
56
+
57
+ def record_units_in_field_offenses(img_match)
58
+ FIELDS.each do |field|
59
+ field_match = field.match(img_match[0])
60
+ next if field_match.nil?
61
+ value = field_match[2].gsub(START_OR_END_QUOTE, '')
62
+ next unless value =~ ENDS_IN_CSS_UNIT
63
+ value_without_units = value.gsub(ENDS_IN_CSS_UNIT, '')
64
+ start = img_match.begin(0) + field_match.begin(2)
65
+ add_offense(
66
+ "The #{field_match[1]} attribute does not take units. Replace with \"#{value_without_units}\".",
67
+ node: @node,
68
+ markup: value,
69
+ line_number: @source[0...start].count("\n") + 1
70
+ )
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
- # Recommends using {% liquid ... %} if 3 or more consecutive {% ... %} are found.
3
+ # Recommends using {% liquid ... %} if 4 or more consecutive {% ... %} are found.
4
4
  class LiquidTag < LiquidCheck
5
5
  severity :suggestion
6
6
  category :liquid
7
- doc "https://shopify.dev/docs/themes/liquid/reference/tags/theme-tags#liquid"
7
+ doc docs_url(__FILE__)
8
8
 
9
- def initialize(min_consecutive_statements: 10)
9
+ def initialize(min_consecutive_statements: 4)
10
10
  @first_statement = nil
11
11
  @consecutive_statements = 0
12
12
  @min_consecutive_statements = min_consecutive_statements
@@ -3,6 +3,7 @@ module ThemeCheck
3
3
  class MatchingSchemaTranslations < LiquidCheck
4
4
  severity :suggestion
5
5
  category :translation
6
+ doc docs_url(__FILE__)
6
7
 
7
8
  def on_schema(node)
8
9
  schema = JSON.parse(node.value.nodelist.join)
@@ -4,13 +4,14 @@ module ThemeCheck
4
4
  class MatchingTranslations < JsonCheck
5
5
  severity :suggestion
6
6
  category :translation
7
+ doc docs_url(__FILE__)
7
8
 
8
9
  def initialize
9
10
  @files = []
10
11
  end
11
12
 
12
13
  def on_file(file)
13
- return unless file.name.starts_with?("locales/")
14
+ return unless file.name.start_with?("locales/")
14
15
  return unless file.content.is_a?(Hash)
15
16
  return if file.name == @theme.default_locale_json&.name
16
17
 
@@ -2,6 +2,7 @@
2
2
  module ThemeCheck
3
3
  class MissingEnableComment < LiquidCheck
4
4
  severity :error
5
+ doc docs_url(__FILE__)
5
6
 
6
7
  # Don't allow this check to be disabled with a comment,
7
8
  # as we need to be able to check for disabled checks.
@@ -3,11 +3,10 @@
3
3
  module ThemeCheck
4
4
  # Reports missing shopify required theme files
5
5
  # required templates: https://shopify.dev/tutorials/review-theme-store-requirements-files
6
-
7
6
  class MissingRequiredTemplateFiles < LiquidCheck
8
7
  severity :error
9
8
  category :liquid
10
- doc "https://shopify.dev/docs/themes/theme-templates"
9
+ doc docs_url(__FILE__)
11
10
 
12
11
  REQUIRED_LIQUID_FILES = %w(layout/theme)
13
12
  REQUIRED_TEMPLATE_FILES = %w(
@@ -4,6 +4,7 @@ module ThemeCheck
4
4
  class MissingTemplate < LiquidCheck
5
5
  severity :suggestion
6
6
  category :liquid
7
+ doc docs_url(__FILE__)
7
8
 
8
9
  def on_include(node)
9
10
  template = node.value.template_name_expr
@@ -4,6 +4,7 @@ module ThemeCheck
4
4
  class NestedSnippet < LiquidCheck
5
5
  severity :suggestion
6
6
  category :liquid
7
+ doc docs_url(__FILE__)
7
8
 
8
9
  class TemplateInfo < Struct.new(:includes)
9
10
  def with_deep_nested(templates, max, current_level = 0)
@@ -2,14 +2,16 @@
2
2
  module ThemeCheck
3
3
  # Reports errors when trying to use parser-blocking script tags
4
4
  class ParserBlockingJavaScript < LiquidCheck
5
+ include RegexHelpers
5
6
  severity :error
6
- category :liquid
7
+ categories :liquid, :performance
8
+ doc docs_url(__FILE__)
7
9
 
8
10
  PARSER_BLOCKING_SCRIPT_TAG = %r{
9
11
  <script # Find the start of a script tag
10
- (?=(?:[^>]|\n|\r)+?src=)+? # Make sure src= is in the script with a lookahead
12
+ (?=[^>]+?src=) # Make sure src= is in the script with a lookahead
11
13
  (?:(?!defer|async|type=["']module['"]).)*? # Find tags that don't have defer|async|type="module"
12
- >
14
+ /?>
13
15
  }xim
14
16
  SCRIPT_TAG_FILTER = /\{\{[^}]+script_tag\s+\}\}/
15
17
 
@@ -32,23 +34,14 @@ module ThemeCheck
32
34
  )
33
35
  end
34
36
 
35
- # The trickiness here is matching on scripts that are defined on
36
- # multiple lines (or repeat matches). This makes the line_number
37
- # calculation a bit weird. So instead, we traverse the string in
38
- # a very imperative way.
39
37
  def record_offenses_from_regex(regex: nil, message: nil)
40
- i = 0
41
- while (i = @source.index(regex, i))
42
- script = @source.match(regex, i)[0]
43
-
38
+ matches(@source, regex).each do |match|
44
39
  add_offense(
45
40
  message,
46
41
  node: @node,
47
- markup: script,
48
- line_number: @source[0...i].count("\n") + 1
42
+ markup: match[0],
43
+ line_number: @source[0...match.begin(0)].count("\n") + 1
49
44
  )
50
-
51
- i += script.size
52
45
  end
53
46
  end
54
47
  end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+ module ThemeCheck
3
+ class RemoteAsset < LiquidCheck
4
+ include RegexHelpers
5
+ severity :suggestion
6
+ categories :liquid, :performance
7
+ doc docs_url(__FILE__)
8
+
9
+ OFFENSE_MESSAGE = "Asset should be served by the Shopify CDN for better performance."
10
+
11
+ HTML_FILTERS = [
12
+ 'stylesheet_tag',
13
+ 'script_tag',
14
+ 'img_tag',
15
+ ]
16
+ ASSET_URL_FILTERS = [
17
+ 'asset_url',
18
+ 'asset_img_url',
19
+ 'file_img_url',
20
+ 'file_url',
21
+ 'global_asset_url',
22
+ 'img_url',
23
+ 'payment_type_img_url',
24
+ 'shopify_asset_url',
25
+ ]
26
+
27
+ RESOURCE_TAG = %r{<(?<tag_name>img|script|link|source)#{HTML_ATTRIBUTES}/?>}oim
28
+ RESOURCE_URL = /\s(?:src|href)=(?<resource_url>#{QUOTED_LIQUID_ATTRIBUTE})/oim
29
+ ASSET_URL_FILTER = /[\|\s]*(#{ASSET_URL_FILTERS.join('|')})/omi
30
+ PROTOCOL = %r{(https?:)?//}
31
+ ABSOLUTE_PATH = %r{\A/[^/]}im
32
+ RELATIVE_PATH = %r{\A(?!#{PROTOCOL})[^/\{]}oim
33
+ REL = /\srel=(?<rel>#{QUOTED_LIQUID_ATTRIBUTE})/oim
34
+
35
+ def on_variable(node)
36
+ record_variable_offense(node)
37
+ end
38
+
39
+ def on_document(node)
40
+ source = node.template.source
41
+ record_html_offenses(node, source)
42
+ end
43
+
44
+ private
45
+
46
+ def record_variable_offense(variable_node)
47
+ # We flag HTML tags with URLs not hosted by Shopify
48
+ return if !html_resource_drop?(variable_node) || variable_hosted_by_shopify?(variable_node)
49
+ add_offense(OFFENSE_MESSAGE, node: variable_node)
50
+ end
51
+
52
+ def html_resource_drop?(variable_node)
53
+ variable_node.value.filters
54
+ .any? { |(filter_name, *_filter_args)| HTML_FILTERS.include?(filter_name) }
55
+ end
56
+
57
+ def variable_hosted_by_shopify?(variable_node)
58
+ variable_node.value.filters
59
+ .any? { |(filter_name, *_filter_args)| ASSET_URL_FILTERS.include?(filter_name) }
60
+ end
61
+
62
+ # This part is slightly more complicated because we don't have an
63
+ # HTML AST. We have to resort to looking at the HTML with regexes
64
+ # to figure out if we have a resource (stylesheet, script, or media)
65
+ # that points to a remote domain.
66
+ def record_html_offenses(node, source)
67
+ matches(source, RESOURCE_TAG).each do |match|
68
+ tag = match[0]
69
+
70
+ # We don't flag stuff without URLs
71
+ next unless tag =~ RESOURCE_URL
72
+ resource_match = Regexp.last_match
73
+ resource_url = resource_match[:resource_url].gsub(START_OR_END_QUOTE, '')
74
+
75
+ next if non_stylesheet_link?(tag)
76
+ next if url_hosted_by_shopify?(resource_url)
77
+ next if resource_url =~ ABSOLUTE_PATH
78
+ next if resource_url =~ RELATIVE_PATH
79
+ next if resource_url.empty?
80
+
81
+ start = match.begin(0) + resource_match.begin(:resource_url)
82
+ add_offense(
83
+ OFFENSE_MESSAGE,
84
+ node: node,
85
+ markup: resource_url,
86
+ line_number: source[0...start].count("\n") + 1,
87
+ )
88
+ end
89
+ end
90
+
91
+ def non_stylesheet_link?(tag)
92
+ tag =~ REL && !(Regexp.last_match[:rel] =~ /\A['"]stylesheet['"]\Z/)
93
+ end
94
+
95
+ def url_hosted_by_shopify?(url)
96
+ url =~ /\A#{VARIABLE}\Z/oim && url =~ ASSET_URL_FILTER
97
+ end
98
+ end
99
+ end
@@ -5,7 +5,7 @@ module ThemeCheck
5
5
  class RequiredDirectories < LiquidCheck
6
6
  severity :error
7
7
  category :liquid
8
- doc "https://shopify.dev/tutorials/develop-theme-files"
8
+ doc docs_url(__FILE__)
9
9
 
10
10
  REQUIRED_DIRECTORIES = %w(assets config layout locales sections snippets templates)
11
11
 
@@ -4,7 +4,7 @@ module ThemeCheck
4
4
  class RequiredLayoutThemeObject < LiquidCheck
5
5
  severity :error
6
6
  category :liquid
7
- doc "https://shopify.dev/docs/themes/theme-templates/theme-liquid"
7
+ doc docs_url(__FILE__)
8
8
 
9
9
  LAYOUT_FILENAME = "layout/theme"
10
10
 
@@ -4,6 +4,7 @@ module ThemeCheck
4
4
  class SpaceInsideBraces < LiquidCheck
5
5
  severity :style
6
6
  category :liquid
7
+ doc docs_url(__FILE__)
7
8
 
8
9
  def initialize
9
10
  @ignore = false
@@ -4,6 +4,7 @@ module ThemeCheck
4
4
  class SyntaxError < LiquidCheck
5
5
  severity :error
6
6
  category :liquid
7
+ doc docs_url(__FILE__)
7
8
 
8
9
  def on_document(node)
9
10
  node.template.warnings.each do |warning|
@@ -3,6 +3,7 @@ module ThemeCheck
3
3
  class TemplateLength < LiquidCheck
4
4
  severity :suggestion
5
5
  category :liquid
6
+ doc docs_url(__FILE__)
6
7
 
7
8
  def initialize(max_length: 200, exclude_schema: true)
8
9
  @max_length = max_length