theme-check 0.3.2 → 0.7.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 (110) 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 +42 -0
  5. data/CONTRIBUTING.md +5 -2
  6. data/Gemfile +5 -3
  7. data/LICENSE.md +2 -0
  8. data/README.md +12 -4
  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 +27 -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 +98 -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 +16 -7
  68. data/lib/theme_check/checks/unknown_filter.rb +1 -0
  69. data/lib/theme_check/checks/unused_assign.rb +5 -3
  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 +49 -13
  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 +12 -0
  79. data/lib/theme_check/language_server/completion_engine.rb +38 -0
  80. data/lib/theme_check/language_server/completion_helper.rb +25 -0
  81. data/lib/theme_check/language_server/completion_provider.rb +28 -0
  82. data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +51 -0
  83. data/lib/theme_check/language_server/completion_providers/object_completion_provider.rb +31 -0
  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 +31 -0
  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 +105 -10
  89. data/lib/theme_check/language_server/position_helper.rb +27 -0
  90. data/lib/theme_check/language_server/protocol.rb +41 -0
  91. data/lib/theme_check/language_server/server.rb +9 -4
  92. data/lib/theme_check/language_server/tokens.rb +55 -0
  93. data/lib/theme_check/liquid_check.rb +11 -0
  94. data/lib/theme_check/node.rb +1 -2
  95. data/lib/theme_check/offense.rb +52 -15
  96. data/lib/theme_check/regex_helpers.rb +15 -0
  97. data/lib/theme_check/releaser.rb +39 -0
  98. data/lib/theme_check/remote_asset_file.rb +44 -0
  99. data/lib/theme_check/shopify_liquid.rb +1 -0
  100. data/lib/theme_check/shopify_liquid/deprecated_filter.rb +10 -8
  101. data/lib/theme_check/shopify_liquid/filter.rb +3 -5
  102. data/lib/theme_check/shopify_liquid/object.rb +2 -6
  103. data/lib/theme_check/shopify_liquid/tag.rb +14 -0
  104. data/lib/theme_check/storage.rb +3 -3
  105. data/lib/theme_check/string_helpers.rb +47 -0
  106. data/lib/theme_check/tags.rb +1 -2
  107. data/lib/theme_check/theme.rb +7 -1
  108. data/lib/theme_check/version.rb +1 -1
  109. data/theme-check.gemspec +1 -2
  110. metadata +57 -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,98 @@
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 = /<(?<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
+
80
+ start = match.begin(0) + resource_match.begin(:resource_url)
81
+ add_offense(
82
+ OFFENSE_MESSAGE,
83
+ node: node,
84
+ markup: resource_url,
85
+ line_number: source[0...start].count("\n") + 1,
86
+ )
87
+ end
88
+ end
89
+
90
+ def non_stylesheet_link?(tag)
91
+ tag =~ REL && !(Regexp.last_match[:rel] =~ /\A['"]stylesheet['"]\Z/)
92
+ end
93
+
94
+ def url_hosted_by_shopify?(url)
95
+ url =~ /\A#{VARIABLE}\Z/oim && url =~ ASSET_URL_FILTER
96
+ end
97
+ end
98
+ 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