theme-check 1.2.0 → 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/theme-check.yml +3 -3
- data/.gitignore +1 -0
- data/CHANGELOG.md +47 -0
- data/CONTRIBUTING.md +59 -1
- data/Gemfile +3 -0
- data/bin/theme-check +29 -0
- data/bin/theme-check-language-server +29 -0
- data/config/default.yml +15 -1
- data/config/theme_app_extension.yml +15 -0
- data/data/shopify_liquid/objects.yml +1 -0
- data/docs/checks/app_block_valid_tags.md +40 -0
- data/docs/checks/asset_size_app_block_css.md +1 -1
- data/docs/checks/deprecate_lazysizes.md +0 -3
- data/docs/checks/deprecated_global_app_block_type.md +65 -0
- data/docs/checks/missing_template.md +25 -0
- data/docs/checks/pagination_size.md +44 -0
- data/docs/checks/template_length.md +1 -1
- data/docs/checks/undefined_object.md +5 -0
- data/docs/flamegraph.svg +18488 -0
- data/lib/theme_check/analyzer.rb +1 -0
- data/lib/theme_check/check.rb +2 -2
- data/lib/theme_check/checks/app_block_valid_tags.rb +36 -0
- data/lib/theme_check/checks/asset_size_css.rb +3 -3
- data/lib/theme_check/checks/asset_size_javascript.rb +2 -2
- data/lib/theme_check/checks/convert_include_to_render.rb +3 -1
- data/lib/theme_check/checks/default_locale.rb +3 -1
- data/lib/theme_check/checks/deprecate_bgsizes.rb +1 -1
- data/lib/theme_check/checks/deprecate_lazysizes.rb +7 -4
- data/lib/theme_check/checks/deprecated_global_app_block_type.rb +57 -0
- data/lib/theme_check/checks/img_lazy_loading.rb +1 -1
- data/lib/theme_check/checks/img_width_and_height.rb +3 -3
- data/lib/theme_check/checks/missing_template.rb +21 -5
- data/lib/theme_check/checks/pagination_size.rb +84 -0
- data/lib/theme_check/checks/parser_blocking_javascript.rb +1 -1
- data/lib/theme_check/checks/remote_asset.rb +3 -3
- data/lib/theme_check/checks/space_inside_braces.rb +26 -6
- data/lib/theme_check/checks/template_length.rb +1 -1
- data/lib/theme_check/checks/undefined_object.rb +1 -1
- data/lib/theme_check/checks/valid_html_translation.rb +1 -1
- data/lib/theme_check/checks.rb +11 -1
- data/lib/theme_check/cli.rb +42 -3
- data/lib/theme_check/corrector.rb +9 -0
- data/lib/theme_check/file_system_storage.rb +12 -0
- data/lib/theme_check/html_check.rb +0 -1
- data/lib/theme_check/html_node.rb +37 -16
- data/lib/theme_check/html_visitor.rb +17 -3
- data/lib/theme_check/json_check.rb +2 -2
- data/lib/theme_check/json_file.rb +11 -0
- data/lib/theme_check/json_printer.rb +31 -0
- data/lib/theme_check/language_server/constants.rb +18 -11
- data/lib/theme_check/language_server/document_link_engine.rb +3 -67
- data/lib/theme_check/language_server/document_link_provider.rb +71 -0
- data/lib/theme_check/language_server/document_link_providers/asset_document_link_provider.rb +11 -0
- data/lib/theme_check/language_server/document_link_providers/include_document_link_provider.rb +11 -0
- data/lib/theme_check/language_server/document_link_providers/render_document_link_provider.rb +11 -0
- data/lib/theme_check/language_server/document_link_providers/section_document_link_provider.rb +11 -0
- data/lib/theme_check/language_server/handler.rb +17 -9
- data/lib/theme_check/language_server/server.rb +11 -13
- data/lib/theme_check/language_server/uri_helper.rb +37 -0
- data/lib/theme_check/language_server.rb +6 -0
- data/lib/theme_check/node.rb +6 -4
- data/lib/theme_check/offense.rb +56 -3
- data/lib/theme_check/parsing_helpers.rb +4 -3
- data/lib/theme_check/position.rb +98 -14
- data/lib/theme_check/printer.rb +9 -5
- data/lib/theme_check/regex_helpers.rb +5 -2
- data/lib/theme_check/theme.rb +3 -0
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check.rb +1 -0
- data/theme-check.gemspec +1 -1
- metadata +21 -10
- data/bin/liquid-server +0 -4
- data/exe/theme-check-language-server.bat +0 -3
- data/exe/theme-check.bat +0 -3
data/lib/theme_check/analyzer.rb
CHANGED
data/lib/theme_check/check.rb
CHANGED
@@ -91,8 +91,8 @@ module ThemeCheck
|
|
91
91
|
@offenses ||= []
|
92
92
|
end
|
93
93
|
|
94
|
-
def add_offense(message, node: nil, template: node&.template, markup: nil, line_number: nil, &block)
|
95
|
-
offenses << Offense.new(check: self, message: message, template: template, node: node, markup: markup, line_number: line_number, correction: block)
|
94
|
+
def add_offense(message, node: nil, template: node&.template, markup: nil, line_number: nil, node_markup_offset: 0, &block)
|
95
|
+
offenses << Offense.new(check: self, message: message, template: template, node: node, markup: markup, line_number: line_number, node_markup_offset: node_markup_offset, correction: block)
|
96
96
|
end
|
97
97
|
|
98
98
|
def severity
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
# Reports errors when invalid tags are used in a Theme App
|
4
|
+
# Extension block
|
5
|
+
class AppBlockValidTags < LiquidCheck
|
6
|
+
severity :error
|
7
|
+
category :liquid
|
8
|
+
doc docs_url(__FILE__)
|
9
|
+
|
10
|
+
# Don't allow this check to be disabled with a comment,
|
11
|
+
# since we need to be able to enforce this server-side
|
12
|
+
can_disable false
|
13
|
+
|
14
|
+
OFFENSE_MSG = "Theme app extension blocks cannot contain %s tags"
|
15
|
+
|
16
|
+
def on_javascript(node)
|
17
|
+
add_offense(OFFENSE_MSG % 'javascript', node: node)
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_stylesheet(node)
|
21
|
+
add_offense(OFFENSE_MSG % 'stylesheet', node: node)
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_include(node)
|
25
|
+
add_offense(OFFENSE_MSG % 'include', node: node)
|
26
|
+
end
|
27
|
+
|
28
|
+
def on_layout(node)
|
29
|
+
add_offense(OFFENSE_MSG % 'layout', node: node)
|
30
|
+
end
|
31
|
+
|
32
|
+
def on_section(node)
|
33
|
+
add_offense(OFFENSE_MSG % 'section', node: node)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -13,12 +13,12 @@ module ThemeCheck
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def on_link(node)
|
16
|
-
return if node.attributes['rel']
|
17
|
-
file_size = href_to_file_size(node.attributes['href']
|
16
|
+
return if node.attributes['rel'] != "stylesheet"
|
17
|
+
file_size = href_to_file_size(node.attributes['href'])
|
18
18
|
return if file_size.nil?
|
19
19
|
return if file_size <= threshold_in_bytes
|
20
20
|
add_offense(
|
21
|
-
"CSS on every page load exceeding compressed size threshold (#{threshold_in_bytes} Bytes)
|
21
|
+
"CSS on every page load exceeding compressed size threshold (#{threshold_in_bytes} Bytes)",
|
22
22
|
node: node
|
23
23
|
)
|
24
24
|
end
|
@@ -16,7 +16,7 @@ module ThemeCheck
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def on_script(node)
|
19
|
-
file_size = src_to_file_size(node.attributes['src']
|
19
|
+
file_size = src_to_file_size(node.attributes['src'])
|
20
20
|
return if file_size.nil?
|
21
21
|
return if file_size <= threshold_in_bytes
|
22
22
|
add_offense(
|
@@ -28,7 +28,7 @@ module ThemeCheck
|
|
28
28
|
def src_to_file_size(src)
|
29
29
|
# We're kind of intentionally only looking at {{ 'asset' | asset_url }} or full urls in here.
|
30
30
|
# More complicated liquid statements are not in scope.
|
31
|
-
if src =~ /^#{
|
31
|
+
if src =~ /^#{LIQUID_VARIABLE}$/o && src =~ /asset_url/ && src =~ Liquid::QuotedString
|
32
32
|
asset_id = Regexp.last_match(0).gsub(START_OR_END_QUOTE, "")
|
33
33
|
asset = @theme.assets.find { |a| a.name.end_with?("/" + asset_id) }
|
34
34
|
return if asset.nil?
|
@@ -7,7 +7,9 @@ module ThemeCheck
|
|
7
7
|
doc docs_url(__FILE__)
|
8
8
|
|
9
9
|
def on_include(node)
|
10
|
-
add_offense("`include` is deprecated - convert it to `render`", node: node)
|
10
|
+
add_offense("`include` is deprecated - convert it to `render`", node: node) do |corrector|
|
11
|
+
corrector.replace(node, "render \'#{node.value.template_name_expr}\' ")
|
12
|
+
end
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
@@ -7,7 +7,9 @@ module ThemeCheck
|
|
7
7
|
|
8
8
|
def on_end
|
9
9
|
return if @theme.default_locale_json
|
10
|
-
add_offense("Default translation file not found (for example locales/en.default.json)")
|
10
|
+
add_offense("Default translation file not found (for example locales/en.default.json)") do |corrector|
|
11
|
+
corrector.create_default_locale_json(@theme)
|
12
|
+
end
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
@@ -6,7 +6,7 @@ module ThemeCheck
|
|
6
6
|
doc docs_url(__FILE__)
|
7
7
|
|
8
8
|
def on_div(node)
|
9
|
-
class_list = node.attributes["class"]&.
|
9
|
+
class_list = node.attributes["class"]&.split(" ")
|
10
10
|
add_offense("Use the native loading=\"lazy\" attribute instead of lazysizes", node: node) if class_list&.include?("lazyload")
|
11
11
|
add_offense("Use the CSS imageset attribute instead of data-bgset", node: node) if node.attributes["data-bgset"]
|
12
12
|
end
|
@@ -6,11 +6,14 @@ module ThemeCheck
|
|
6
6
|
doc docs_url(__FILE__)
|
7
7
|
|
8
8
|
def on_img(node)
|
9
|
-
class_list = node.attributes["class"]&.
|
9
|
+
class_list = node.attributes["class"]&.split(" ")
|
10
|
+
has_loading_lazy = node.attributes["loading"] == "lazy"
|
11
|
+
has_native_source = node.attributes["src"] || node.attributes["srcset"]
|
12
|
+
return if has_native_source && has_loading_lazy
|
13
|
+
has_lazysize_source = node.attributes["data-srcset"] || node.attributes["data-src"]
|
14
|
+
has_lazysize_class = class_list&.include?("lazyload")
|
15
|
+
return unless has_lazysize_class && has_lazysize_source
|
10
16
|
add_offense("Use the native loading=\"lazy\" attribute instead of lazysizes", node: node) if class_list&.include?("lazyload")
|
11
|
-
add_offense("Use the native srcset attribute instead of data-srcset", node: node) if node.attributes["data-srcset"]
|
12
|
-
add_offense("Use the native sizes attribute instead of data-sizes", node: node) if node.attributes["data-sizes"]
|
13
|
-
add_offense("Do not set the data-sizes attribute to auto", node: node) if node.attributes["data-sizes"]&.value == "auto"
|
14
17
|
end
|
15
18
|
end
|
16
19
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class DeprecatedGlobalAppBlockType < LiquidCheck
|
4
|
+
severity :error
|
5
|
+
category :liquid
|
6
|
+
doc docs_url(__FILE__)
|
7
|
+
|
8
|
+
INVALID_GLOBAL_APP_BLOCK_TYPE = "@global"
|
9
|
+
VALID_GLOBAL_APP_BLOCK_TYPE = "@app"
|
10
|
+
|
11
|
+
def on_schema(node)
|
12
|
+
schema = JSON.parse(node.value.nodelist.join)
|
13
|
+
|
14
|
+
if block_types_from(schema).include?(INVALID_GLOBAL_APP_BLOCK_TYPE)
|
15
|
+
add_offense(
|
16
|
+
"Deprecated '#{INVALID_GLOBAL_APP_BLOCK_TYPE}' block type defined in the schema, use '#{VALID_GLOBAL_APP_BLOCK_TYPE}' block type instead.",
|
17
|
+
node: node
|
18
|
+
)
|
19
|
+
end
|
20
|
+
rescue JSON::ParserError
|
21
|
+
# Ignored, handled in ValidSchema.
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_case(node)
|
25
|
+
if node.value == INVALID_GLOBAL_APP_BLOCK_TYPE
|
26
|
+
report_offense(node)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_condition(node)
|
31
|
+
if node.value.right == INVALID_GLOBAL_APP_BLOCK_TYPE || node.value.left == INVALID_GLOBAL_APP_BLOCK_TYPE
|
32
|
+
report_offense(node)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def on_variable(node)
|
37
|
+
if node.value.name == INVALID_GLOBAL_APP_BLOCK_TYPE
|
38
|
+
report_offense(node)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def report_offense(node)
|
45
|
+
add_offense(
|
46
|
+
"Deprecated '#{INVALID_GLOBAL_APP_BLOCK_TYPE}' block type, use '#{VALID_GLOBAL_APP_BLOCK_TYPE}' block type instead.",
|
47
|
+
node: node
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def block_types_from(schema)
|
52
|
+
schema.fetch("blocks", []).map do |block|
|
53
|
+
block.fetch("type", "")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -8,7 +8,7 @@ module ThemeCheck
|
|
8
8
|
ACCEPTED_LOADING_VALUES = %w[lazy eager]
|
9
9
|
|
10
10
|
def on_img(node)
|
11
|
-
loading = node.attributes["loading"]&.
|
11
|
+
loading = node.attributes["loading"]&.downcase
|
12
12
|
return if ACCEPTED_LOADING_VALUES.include?(loading)
|
13
13
|
if loading == "auto"
|
14
14
|
add_offense("Prefer loading=\"lazy\" to defer loading of images", node: node)
|
@@ -9,8 +9,8 @@ module ThemeCheck
|
|
9
9
|
ENDS_IN_CSS_UNIT = /(cm|mm|in|px|pt|pc|em|ex|ch|rem|vw|vh|vmin|vmax|%)$/i
|
10
10
|
|
11
11
|
def on_img(node)
|
12
|
-
width = node.attributes["width"]
|
13
|
-
height = node.attributes["height"]
|
12
|
+
width = node.attributes["width"]
|
13
|
+
height = node.attributes["height"]
|
14
14
|
|
15
15
|
record_units_in_field_offenses("width", width, node: node)
|
16
16
|
record_units_in_field_offenses("height", height, node: node)
|
@@ -35,7 +35,7 @@ module ThemeCheck
|
|
35
35
|
return unless value =~ ENDS_IN_CSS_UNIT
|
36
36
|
value_without_units = value.gsub(ENDS_IN_CSS_UNIT, '')
|
37
37
|
add_offense(
|
38
|
-
"The #{attribute} attribute does not take units. Replace with \"#{value_without_units}\"
|
38
|
+
"The #{attribute} attribute does not take units. Replace with \"#{value_without_units}\"",
|
39
39
|
node: node,
|
40
40
|
)
|
41
41
|
end
|
@@ -7,20 +7,36 @@ module ThemeCheck
|
|
7
7
|
doc docs_url(__FILE__)
|
8
8
|
single_file false
|
9
9
|
|
10
|
+
def initialize(ignore_missing: [])
|
11
|
+
@ignore_missing = ignore_missing
|
12
|
+
end
|
13
|
+
|
10
14
|
def on_include(node)
|
11
15
|
template = node.value.template_name_expr
|
12
16
|
if template.is_a?(String)
|
13
|
-
|
14
|
-
add_offense("'snippets/#{template}.liquid' is not found", node: node)
|
15
|
-
end
|
17
|
+
add_missing_offense("snippets/#{template}", node: node)
|
16
18
|
end
|
17
19
|
end
|
20
|
+
|
18
21
|
alias_method :on_render, :on_include
|
19
22
|
|
20
23
|
def on_section(node)
|
21
24
|
template = node.value.section_name
|
22
|
-
|
23
|
-
|
25
|
+
add_missing_offense("sections/#{template}", node: node)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def ignore?(path)
|
31
|
+
@ignore_missing.any? { |pattern| File.fnmatch?(pattern, path) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_missing_offense(name, node:)
|
35
|
+
path = "#{name}.liquid"
|
36
|
+
unless ignore?(path) || theme[name]
|
37
|
+
add_offense("'#{path}' is not found", node: node) do |corrector|
|
38
|
+
corrector.create(@theme, "#{name}.liquid", "")
|
39
|
+
end
|
24
40
|
end
|
25
41
|
end
|
26
42
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class PaginationSize < LiquidCheck
|
4
|
+
severity :suggestion
|
5
|
+
categories :performance
|
6
|
+
doc docs_url(__FILE__)
|
7
|
+
|
8
|
+
attr_reader :min_size
|
9
|
+
attr_reader :max_size
|
10
|
+
|
11
|
+
def initialize(min_size: 1, max_size: 50)
|
12
|
+
@min_size = min_size
|
13
|
+
@max_size = max_size
|
14
|
+
end
|
15
|
+
|
16
|
+
def on_document(_node)
|
17
|
+
@paginations = {}
|
18
|
+
@schema_settings = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def on_paginate(node)
|
22
|
+
size = node.value.page_size
|
23
|
+
unless @paginations.key?(size)
|
24
|
+
@paginations[size] = []
|
25
|
+
end
|
26
|
+
@paginations[size].push(node)
|
27
|
+
end
|
28
|
+
|
29
|
+
def on_schema(node)
|
30
|
+
schema = JSON.parse(node.value.nodelist.join)
|
31
|
+
|
32
|
+
if (settings = schema["settings"])
|
33
|
+
@schema_settings = settings
|
34
|
+
end
|
35
|
+
rescue JSON::ParserError
|
36
|
+
# Ignored, handled in ValidSchema.
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Section settings look like:
|
41
|
+
# #<Liquid::VariableLookup:0x00007fd699c50c48 @name="section", @lookups=["settings", "products_per_page"], @command_flags=0>
|
42
|
+
def size_is_a_section_setting?(size)
|
43
|
+
size.is_a?(Liquid::VariableLookup) &&
|
44
|
+
size.name == 'section' &&
|
45
|
+
size.lookups.first == 'settings'
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# We'll work with either an explicit value, or the default value of the section setting.
|
50
|
+
def get_value(size)
|
51
|
+
return size if size.is_a?(Numeric)
|
52
|
+
return get_setting_default_value(size) if size_is_a_section_setting?(size)
|
53
|
+
end
|
54
|
+
|
55
|
+
def after_document(_node)
|
56
|
+
@paginations.each_pair do |size, nodes|
|
57
|
+
# Validate presence of default section setting.
|
58
|
+
if size_is_a_section_setting?(size) && !get_setting_default_value(size)
|
59
|
+
nodes.each { |node| add_offense("Default pagination size should be defined in the section settings", node: node) }
|
60
|
+
end
|
61
|
+
|
62
|
+
# Validate if size is within range.
|
63
|
+
next unless (numerical_size = get_value(size))
|
64
|
+
if numerical_size > @max_size || numerical_size < @min_size || !numerical_size.is_a?(Integer)
|
65
|
+
nodes.each { |node| add_offense("Pagination size must be a positive integer between #{@min_size} and #{@max_size}", node: node) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def get_setting_default_value(variable_lookup)
|
73
|
+
setting = @schema_settings.select { |s| s['id'] == variable_lookup.lookups.last }
|
74
|
+
|
75
|
+
# Setting does not exist.
|
76
|
+
return nil if setting.empty?
|
77
|
+
|
78
|
+
default_value = setting.last['default'].to_i
|
79
|
+
return nil if default_value == 0
|
80
|
+
|
81
|
+
default_value
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -8,7 +8,7 @@ module ThemeCheck
|
|
8
8
|
|
9
9
|
def on_script(node)
|
10
10
|
return unless node.attributes["src"]
|
11
|
-
return if node.attributes["defer"] || node.attributes["async"] || node.attributes["type"]
|
11
|
+
return if node.attributes["defer"] || node.attributes["async"] || node.attributes["type"] == "module"
|
12
12
|
|
13
13
|
add_offense("Missing async or defer attribute on script tag", node: node)
|
14
14
|
end
|
@@ -14,7 +14,7 @@ module ThemeCheck
|
|
14
14
|
def on_element(node)
|
15
15
|
return unless TAGS.include?(node.name)
|
16
16
|
|
17
|
-
resource_url = node.attributes["src"]
|
17
|
+
resource_url = node.attributes["src"] || node.attributes["href"]
|
18
18
|
return if resource_url.nil? || resource_url.empty?
|
19
19
|
|
20
20
|
# Ignore if URL is Liquid, taken care of by AssetUrlFilters check
|
@@ -23,9 +23,9 @@ module ThemeCheck
|
|
23
23
|
return if resource_url =~ RELATIVE_PATH
|
24
24
|
return if url_hosted_by_shopify?(resource_url)
|
25
25
|
|
26
|
-
# Ignore non-stylesheet
|
26
|
+
# Ignore non-stylesheet link tags
|
27
27
|
rel = node.attributes["rel"]
|
28
|
-
return if
|
28
|
+
return if node.name == "link" && rel != "stylesheet"
|
29
29
|
|
30
30
|
add_offense(
|
31
31
|
"Asset should be served by the Shopify CDN for better performance.",
|
@@ -14,18 +14,38 @@ module ThemeCheck
|
|
14
14
|
return unless node.markup
|
15
15
|
return if :assign == node.type_name
|
16
16
|
|
17
|
-
outside_of_strings(node.markup) do |chunk|
|
17
|
+
outside_of_strings(node.markup) do |chunk, chunk_start|
|
18
18
|
chunk.scan(/([,:|]|==|<>|<=|>=|<|>|!=) +/) do |_match|
|
19
|
-
add_offense(
|
19
|
+
add_offense(
|
20
|
+
"Too many spaces after '#{Regexp.last_match(1)}'",
|
21
|
+
node: node,
|
22
|
+
markup: Regexp.last_match(0),
|
23
|
+
node_markup_offset: chunk_start + Regexp.last_match.begin(0)
|
24
|
+
)
|
20
25
|
end
|
21
26
|
chunk.scan(/([,:|]|==|<>|<=|>=|<\b|>\b|!=)(\S|\z)/) do |_match|
|
22
|
-
add_offense(
|
27
|
+
add_offense(
|
28
|
+
"Space missing after '#{Regexp.last_match(1)}'",
|
29
|
+
node: node,
|
30
|
+
markup: Regexp.last_match(0),
|
31
|
+
node_markup_offset: chunk_start + Regexp.last_match.begin(0),
|
32
|
+
)
|
23
33
|
end
|
24
34
|
chunk.scan(/ (\||==|<>|<=|>=|<|>|!=)+/) do |_match|
|
25
|
-
add_offense(
|
35
|
+
add_offense(
|
36
|
+
"Too many spaces before '#{Regexp.last_match(1)}'",
|
37
|
+
node: node,
|
38
|
+
markup: Regexp.last_match(0),
|
39
|
+
node_markup_offset: chunk_start + Regexp.last_match.begin(0)
|
40
|
+
)
|
26
41
|
end
|
27
42
|
chunk.scan(/(\A|\S)(?<match>\||==|<>|<=|>=|<|\b>|!=)/) do |_match|
|
28
|
-
add_offense(
|
43
|
+
add_offense(
|
44
|
+
"Space missing before '#{Regexp.last_match(1)}'",
|
45
|
+
node: node,
|
46
|
+
markup: Regexp.last_match(0),
|
47
|
+
node_markup_offset: chunk_start + Regexp.last_match.begin(0)
|
48
|
+
)
|
29
49
|
end
|
30
50
|
end
|
31
51
|
end
|
@@ -57,7 +77,7 @@ module ThemeCheck
|
|
57
77
|
corrector.insert_before(node, " ")
|
58
78
|
end
|
59
79
|
end
|
60
|
-
if node.markup[-1] != " "
|
80
|
+
if node.markup[-1] != " " && node.markup[-1] != "\n"
|
61
81
|
add_offense("Space missing before '}}'", node: node) do |corrector|
|
62
82
|
corrector.insert_after(node, " ")
|
63
83
|
end
|