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.
- checksums.yaml +4 -4
- data/.github/workflows/theme-check.yml +10 -3
- data/.rubocop.yml +12 -3
- data/CHANGELOG.md +42 -0
- data/CONTRIBUTING.md +5 -2
- data/Gemfile +5 -3
- data/LICENSE.md +2 -0
- data/README.md +12 -4
- data/RELEASING.md +10 -3
- data/Rakefile +6 -0
- data/config/default.yml +16 -0
- data/data/shopify_liquid/tags.yml +27 -0
- data/data/shopify_translation_keys.yml +850 -0
- data/docs/checks/CHECK_DOCS_TEMPLATE.md +47 -0
- data/docs/checks/asset_size_css.md +52 -0
- data/docs/checks/asset_size_javascript.md +79 -0
- data/docs/checks/convert_include_to_render.md +48 -0
- data/docs/checks/default_locale.md +46 -0
- data/docs/checks/deprecated_filter.md +46 -0
- data/docs/checks/img_width_and_height.md +79 -0
- data/docs/checks/liquid_tag.md +65 -0
- data/docs/checks/matching_schema_translations.md +93 -0
- data/docs/checks/matching_translations.md +72 -0
- data/docs/checks/missing_enable_comment.md +50 -0
- data/docs/checks/missing_required_template_files.md +26 -0
- data/docs/checks/missing_template.md +40 -0
- data/docs/checks/nested_snippet.md +69 -0
- data/docs/checks/parser_blocking_javascript.md +97 -0
- data/docs/checks/remote_asset.md +82 -0
- data/docs/checks/required_directories.md +25 -0
- data/docs/checks/required_layout_theme_object.md +28 -0
- data/docs/checks/space_inside_braces.md +63 -0
- data/docs/checks/syntax_error.md +49 -0
- data/docs/checks/template_length.md +50 -0
- data/docs/checks/translation_key_exists.md +63 -0
- data/docs/checks/undefined_object.md +53 -0
- data/docs/checks/unknown_filter.md +45 -0
- data/docs/checks/unused_assign.md +47 -0
- data/docs/checks/unused_snippet.md +32 -0
- data/docs/checks/valid_html_translation.md +53 -0
- data/docs/checks/valid_json.md +60 -0
- data/docs/checks/valid_schema.md +50 -0
- data/lib/theme_check.rb +4 -0
- data/lib/theme_check/asset_file.rb +34 -0
- data/lib/theme_check/check.rb +20 -10
- data/lib/theme_check/checks/asset_size_css.rb +89 -0
- data/lib/theme_check/checks/asset_size_javascript.rb +68 -0
- data/lib/theme_check/checks/convert_include_to_render.rb +1 -1
- data/lib/theme_check/checks/default_locale.rb +1 -0
- data/lib/theme_check/checks/deprecated_filter.rb +1 -1
- data/lib/theme_check/checks/img_width_and_height.rb +74 -0
- data/lib/theme_check/checks/liquid_tag.rb +3 -3
- data/lib/theme_check/checks/matching_schema_translations.rb +1 -0
- data/lib/theme_check/checks/matching_translations.rb +2 -1
- data/lib/theme_check/checks/missing_enable_comment.rb +1 -0
- data/lib/theme_check/checks/missing_required_template_files.rb +1 -2
- data/lib/theme_check/checks/missing_template.rb +1 -0
- data/lib/theme_check/checks/nested_snippet.rb +1 -0
- data/lib/theme_check/checks/parser_blocking_javascript.rb +8 -15
- data/lib/theme_check/checks/remote_asset.rb +98 -0
- data/lib/theme_check/checks/required_directories.rb +1 -1
- data/lib/theme_check/checks/required_layout_theme_object.rb +1 -1
- data/lib/theme_check/checks/space_inside_braces.rb +1 -0
- data/lib/theme_check/checks/syntax_error.rb +1 -0
- data/lib/theme_check/checks/template_length.rb +1 -0
- data/lib/theme_check/checks/translation_key_exists.rb +14 -1
- data/lib/theme_check/checks/undefined_object.rb +16 -7
- data/lib/theme_check/checks/unknown_filter.rb +1 -0
- data/lib/theme_check/checks/unused_assign.rb +5 -3
- data/lib/theme_check/checks/unused_snippet.rb +1 -0
- data/lib/theme_check/checks/valid_html_translation.rb +2 -1
- data/lib/theme_check/checks/valid_json.rb +1 -0
- data/lib/theme_check/checks/valid_schema.rb +1 -0
- data/lib/theme_check/cli.rb +49 -13
- data/lib/theme_check/config.rb +5 -2
- data/lib/theme_check/disabled_checks.rb +2 -2
- data/lib/theme_check/in_memory_storage.rb +13 -8
- data/lib/theme_check/language_server.rb +12 -0
- data/lib/theme_check/language_server/completion_engine.rb +38 -0
- data/lib/theme_check/language_server/completion_helper.rb +25 -0
- data/lib/theme_check/language_server/completion_provider.rb +28 -0
- data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +51 -0
- data/lib/theme_check/language_server/completion_providers/object_completion_provider.rb +31 -0
- data/lib/theme_check/language_server/completion_providers/render_snippet_completion_provider.rb +43 -0
- data/lib/theme_check/language_server/completion_providers/tag_completion_provider.rb +31 -0
- data/lib/theme_check/language_server/constants.rb +10 -0
- data/lib/theme_check/language_server/document_link_engine.rb +48 -0
- data/lib/theme_check/language_server/handler.rb +105 -10
- data/lib/theme_check/language_server/position_helper.rb +27 -0
- data/lib/theme_check/language_server/protocol.rb +41 -0
- data/lib/theme_check/language_server/server.rb +9 -4
- data/lib/theme_check/language_server/tokens.rb +55 -0
- data/lib/theme_check/liquid_check.rb +11 -0
- data/lib/theme_check/node.rb +1 -2
- data/lib/theme_check/offense.rb +52 -15
- data/lib/theme_check/regex_helpers.rb +15 -0
- data/lib/theme_check/releaser.rb +39 -0
- data/lib/theme_check/remote_asset_file.rb +44 -0
- data/lib/theme_check/shopify_liquid.rb +1 -0
- data/lib/theme_check/shopify_liquid/deprecated_filter.rb +10 -8
- data/lib/theme_check/shopify_liquid/filter.rb +3 -5
- data/lib/theme_check/shopify_liquid/object.rb +2 -6
- data/lib/theme_check/shopify_liquid/tag.rb +14 -0
- data/lib/theme_check/storage.rb +3 -3
- data/lib/theme_check/string_helpers.rb +47 -0
- data/lib/theme_check/tags.rb +1 -2
- data/lib/theme_check/theme.rb +7 -1
- data/lib/theme_check/version.rb +1 -1
- data/theme-check.gemspec +1 -2
- 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
|
|
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)
|
|
@@ -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
|
+
# 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
|
|
7
|
+
doc docs_url(__FILE__)
|
|
8
8
|
|
|
9
|
-
def initialize(min_consecutive_statements:
|
|
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
|
|
@@ -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.
|
|
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
|
|
|
@@ -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
|
|
9
|
+
doc docs_url(__FILE__)
|
|
11
10
|
|
|
12
11
|
REQUIRED_LIQUID_FILES = %w(layout/theme)
|
|
13
12
|
REQUIRED_TEMPLATE_FILES = %w(
|
|
@@ -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
|
-
|
|
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
|
-
(?=
|
|
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
|
-
|
|
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:
|
|
48
|
-
line_number: @source[0...
|
|
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
|
|
8
|
+
doc docs_url(__FILE__)
|
|
9
9
|
|
|
10
10
|
REQUIRED_DIRECTORIES = %w(assets config layout locales sections snippets templates)
|
|
11
11
|
|