theme-check 1.2.0 → 1.5.1
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 +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
|