theme-check 0.8.0 → 0.9.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 -0
- data/CHANGELOG.md +44 -0
- data/CONTRIBUTING.md +2 -1
- data/README.md +4 -1
- data/RELEASING.md +5 -3
- data/config/default.yml +42 -1
- data/data/shopify_liquid/tags.yml +3 -0
- data/data/shopify_translation_keys.yml +1 -0
- data/docs/checks/asset_url_filters.md +56 -0
- data/docs/checks/content_for_header_modification.md +42 -0
- data/docs/checks/nested_snippet.md +1 -1
- data/docs/checks/parser_blocking_script_tag.md +53 -0
- data/docs/checks/space_inside_braces.md +28 -0
- data/exe/theme-check-language-server +1 -2
- data/lib/theme_check.rb +13 -1
- data/lib/theme_check/analyzer.rb +79 -13
- data/lib/theme_check/bug.rb +20 -0
- data/lib/theme_check/check.rb +36 -7
- data/lib/theme_check/checks.rb +47 -8
- data/lib/theme_check/checks/asset_url_filters.rb +46 -0
- data/lib/theme_check/checks/content_for_header_modification.rb +41 -0
- data/lib/theme_check/checks/img_width_and_height.rb +18 -49
- data/lib/theme_check/checks/missing_enable_comment.rb +4 -4
- data/lib/theme_check/checks/missing_template.rb +1 -0
- data/lib/theme_check/checks/nested_snippet.rb +1 -1
- data/lib/theme_check/checks/parser_blocking_javascript.rb +6 -38
- data/lib/theme_check/checks/parser_blocking_script_tag.rb +20 -0
- data/lib/theme_check/checks/remote_asset.rb +21 -79
- data/lib/theme_check/checks/space_inside_braces.rb +8 -2
- data/lib/theme_check/checks/template_length.rb +3 -0
- data/lib/theme_check/checks/valid_html_translation.rb +1 -0
- data/lib/theme_check/config.rb +2 -0
- data/lib/theme_check/disabled_check.rb +41 -0
- data/lib/theme_check/disabled_checks.rb +33 -29
- data/lib/theme_check/exceptions.rb +32 -0
- data/lib/theme_check/html_check.rb +7 -0
- data/lib/theme_check/html_node.rb +56 -0
- data/lib/theme_check/html_visitor.rb +38 -0
- data/lib/theme_check/json_file.rb +13 -1
- data/lib/theme_check/language_server.rb +2 -1
- data/lib/theme_check/language_server/completion_engine.rb +1 -1
- data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +1 -0
- data/lib/theme_check/language_server/completion_providers/object_completion_provider.rb +10 -8
- data/lib/theme_check/language_server/constants.rb +5 -1
- data/lib/theme_check/language_server/diagnostics_tracker.rb +64 -0
- data/lib/theme_check/language_server/document_link_engine.rb +2 -2
- data/lib/theme_check/language_server/handler.rb +63 -50
- data/lib/theme_check/language_server/server.rb +1 -1
- data/lib/theme_check/language_server/variable_lookup_finder.rb +295 -0
- data/lib/theme_check/liquid_check.rb +1 -4
- data/lib/theme_check/node.rb +12 -0
- data/lib/theme_check/offense.rb +30 -46
- data/lib/theme_check/position.rb +77 -0
- data/lib/theme_check/position_helper.rb +37 -0
- data/lib/theme_check/remote_asset_file.rb +3 -0
- data/lib/theme_check/shopify_liquid/tag.rb +13 -0
- data/lib/theme_check/template.rb +8 -0
- data/lib/theme_check/theme.rb +7 -2
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check/visitor.rb +4 -14
- metadata +19 -4
- data/lib/theme_check/language_server/position_helper.rb +0 -27
data/lib/theme_check.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "liquid"
|
3
3
|
|
4
|
+
require_relative "theme_check/version"
|
5
|
+
require_relative "theme_check/bug"
|
6
|
+
require_relative "theme_check/exceptions"
|
4
7
|
require_relative "theme_check/analyzer"
|
5
8
|
require_relative "theme_check/check"
|
6
9
|
require_relative "theme_check/checks_tracking"
|
7
10
|
require_relative "theme_check/cli"
|
11
|
+
require_relative "theme_check/disabled_check"
|
8
12
|
require_relative "theme_check/disabled_checks"
|
9
13
|
require_relative "theme_check/liquid_check"
|
10
14
|
require_relative "theme_check/locale_diff"
|
@@ -14,6 +18,8 @@ require_relative "theme_check/regex_helpers"
|
|
14
18
|
require_relative "theme_check/json_check"
|
15
19
|
require_relative "theme_check/json_file"
|
16
20
|
require_relative "theme_check/json_helpers"
|
21
|
+
require_relative "theme_check/position_helper"
|
22
|
+
require_relative "theme_check/position"
|
17
23
|
require_relative "theme_check/language_server"
|
18
24
|
require_relative "theme_check/checks"
|
19
25
|
require_relative "theme_check/config"
|
@@ -30,6 +36,12 @@ require_relative "theme_check/template"
|
|
30
36
|
require_relative "theme_check/theme"
|
31
37
|
require_relative "theme_check/visitor"
|
32
38
|
require_relative "theme_check/corrector"
|
33
|
-
require_relative "theme_check/
|
39
|
+
require_relative "theme_check/html_node"
|
40
|
+
require_relative "theme_check/html_visitor"
|
41
|
+
require_relative "theme_check/html_check"
|
34
42
|
|
35
43
|
Dir[__dir__ + "/theme_check/checks/*.rb"].each { |file| require file }
|
44
|
+
|
45
|
+
# UTF-8 is the default internal and external encoding, like in Rails & Shopify.
|
46
|
+
Encoding.default_external = Encoding::UTF_8
|
47
|
+
Encoding.default_internal = Encoding::UTF_8
|
data/lib/theme_check/analyzer.rb
CHANGED
@@ -1,53 +1,119 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module ThemeCheck
|
3
3
|
class Analyzer
|
4
|
-
attr_reader :offenses
|
5
|
-
|
6
4
|
def initialize(theme, checks = Check.all.map(&:new), auto_correct = false)
|
7
5
|
@theme = theme
|
8
|
-
@offenses = []
|
9
6
|
@auto_correct = auto_correct
|
10
7
|
|
11
8
|
@liquid_checks = Checks.new
|
12
9
|
@json_checks = Checks.new
|
10
|
+
@html_checks = Checks.new
|
13
11
|
|
14
12
|
checks.each do |check|
|
15
13
|
check.theme = @theme
|
16
|
-
check.offenses = @offenses
|
17
14
|
|
18
15
|
case check
|
19
16
|
when LiquidCheck
|
20
17
|
@liquid_checks << check
|
21
18
|
when JsonCheck
|
22
19
|
@json_checks << check
|
20
|
+
when HtmlCheck
|
21
|
+
@html_checks << check
|
23
22
|
end
|
24
23
|
end
|
24
|
+
end
|
25
25
|
|
26
|
-
|
26
|
+
def offenses
|
27
|
+
@liquid_checks.flat_map(&:offenses) +
|
28
|
+
@json_checks.flat_map(&:offenses) +
|
29
|
+
@html_checks.flat_map(&:offenses)
|
27
30
|
end
|
28
31
|
|
29
32
|
def analyze_theme
|
30
|
-
|
31
|
-
|
33
|
+
reset
|
34
|
+
|
35
|
+
liquid_visitor = Visitor.new(@liquid_checks, @disabled_checks)
|
36
|
+
html_visitor = HtmlVisitor.new(@html_checks)
|
37
|
+
@theme.liquid.each do |template|
|
38
|
+
liquid_visitor.visit_template(template)
|
39
|
+
html_visitor.visit_template(template)
|
40
|
+
end
|
41
|
+
|
32
42
|
@theme.json.each { |json_file| @json_checks.call(:on_file, json_file) }
|
33
|
-
|
34
|
-
|
35
|
-
|
43
|
+
|
44
|
+
finish
|
45
|
+
end
|
46
|
+
|
47
|
+
def analyze_files(files)
|
48
|
+
reset
|
49
|
+
|
50
|
+
# Call all checks that run on the whole theme
|
51
|
+
liquid_visitor = Visitor.new(@liquid_checks.whole_theme, @disabled_checks)
|
52
|
+
html_visitor = HtmlVisitor.new(@html_checks.whole_theme)
|
53
|
+
@theme.liquid.each do |template|
|
54
|
+
liquid_visitor.visit_template(template)
|
55
|
+
html_visitor.visit_template(template)
|
56
|
+
end
|
57
|
+
@theme.json.each { |json_file| @json_checks.whole_theme.call(:on_file, json_file) }
|
58
|
+
|
59
|
+
# Call checks that run on a single files, only on specified file
|
60
|
+
liquid_visitor = Visitor.new(@liquid_checks.single_file, @disabled_checks)
|
61
|
+
html_visitor = HtmlVisitor.new(@html_checks.single_file)
|
62
|
+
files.each do |file|
|
63
|
+
if file.liquid?
|
64
|
+
liquid_visitor.visit_template(file)
|
65
|
+
html_visitor.visit_template(file)
|
66
|
+
elsif file.json?
|
67
|
+
@json_checks.single_file.call(:on_file, file)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
finish
|
36
72
|
end
|
37
73
|
|
38
74
|
def uncorrectable_offenses
|
39
75
|
unless @auto_correct
|
40
|
-
return
|
76
|
+
return offenses
|
41
77
|
end
|
42
78
|
|
43
|
-
|
79
|
+
offenses.select { |offense| !offense.correctable? }
|
44
80
|
end
|
45
81
|
|
46
82
|
def correct_offenses
|
47
83
|
if @auto_correct
|
48
|
-
|
84
|
+
offenses.each(&:correct)
|
49
85
|
@theme.liquid.each(&:write)
|
50
86
|
end
|
51
87
|
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def reset
|
92
|
+
@disabled_checks = DisabledChecks.new
|
93
|
+
|
94
|
+
@liquid_checks.each do |check|
|
95
|
+
check.offenses.clear
|
96
|
+
end
|
97
|
+
|
98
|
+
@html_checks.each do |check|
|
99
|
+
check.offenses.clear
|
100
|
+
end
|
101
|
+
|
102
|
+
@json_checks.each do |check|
|
103
|
+
check.offenses.clear
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def finish
|
108
|
+
@liquid_checks.call(:on_end)
|
109
|
+
@html_checks.call(:on_end)
|
110
|
+
@json_checks.call(:on_end)
|
111
|
+
|
112
|
+
@disabled_checks.remove_disabled_offenses(@liquid_checks)
|
113
|
+
@disabled_checks.remove_disabled_offenses(@json_checks)
|
114
|
+
@disabled_checks.remove_disabled_offenses(@html_checks)
|
115
|
+
|
116
|
+
offenses
|
117
|
+
end
|
52
118
|
end
|
53
119
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'theme_check/version'
|
3
|
+
|
4
|
+
module ThemeCheck
|
5
|
+
BUG_POSTAMBLE = <<~EOS
|
6
|
+
Theme Check Version: #{VERSION}
|
7
|
+
Ruby Version: #{RUBY_VERSION}
|
8
|
+
Platform: #{RUBY_PLATFORM}
|
9
|
+
Muffin mode: activated
|
10
|
+
|
11
|
+
------------------------
|
12
|
+
Whoops! It looks like you found a bug in Theme Check.
|
13
|
+
Please report it at https://github.com/Shopify/theme-check/issues, and include the message above.
|
14
|
+
Or cross your fingers real hard, and try again.
|
15
|
+
EOS
|
16
|
+
|
17
|
+
def self.bug(message)
|
18
|
+
abort(message + BUG_POSTAMBLE)
|
19
|
+
end
|
20
|
+
end
|
data/lib/theme_check/check.rb
CHANGED
@@ -6,8 +6,8 @@ module ThemeCheck
|
|
6
6
|
include JsonHelpers
|
7
7
|
|
8
8
|
attr_accessor :theme
|
9
|
-
attr_accessor :
|
10
|
-
|
9
|
+
attr_accessor :options, :ignored_patterns
|
10
|
+
attr_writer :offenses
|
11
11
|
|
12
12
|
SEVERITIES = [
|
13
13
|
:error,
|
@@ -19,6 +19,7 @@ module ThemeCheck
|
|
19
19
|
:liquid,
|
20
20
|
:translation,
|
21
21
|
:performance,
|
22
|
+
:html,
|
22
23
|
:json,
|
23
24
|
:performance,
|
24
25
|
]
|
@@ -67,6 +68,21 @@ module ThemeCheck
|
|
67
68
|
end
|
68
69
|
defined?(@can_disable) ? @can_disable : true
|
69
70
|
end
|
71
|
+
|
72
|
+
def single_file(single_file = nil)
|
73
|
+
unless single_file.nil?
|
74
|
+
@single_file = single_file
|
75
|
+
end
|
76
|
+
defined?(@single_file) ? @single_file : !method_defined?(:on_end)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def offenses
|
81
|
+
@offenses ||= []
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_offense(message, node: nil, template: node&.template, markup: nil, line_number: nil, &block)
|
85
|
+
offenses << Offense.new(check: self, message: message, template: template, node: node, markup: markup, line_number: line_number, correction: block)
|
70
86
|
end
|
71
87
|
|
72
88
|
def severity
|
@@ -89,10 +105,6 @@ module ThemeCheck
|
|
89
105
|
@ignored = true
|
90
106
|
end
|
91
107
|
|
92
|
-
def unignore!
|
93
|
-
@ignored = false
|
94
|
-
end
|
95
|
-
|
96
108
|
def ignored?
|
97
109
|
defined?(@ignored) && @ignored
|
98
110
|
end
|
@@ -101,9 +113,26 @@ module ThemeCheck
|
|
101
113
|
self.class.can_disable
|
102
114
|
end
|
103
115
|
|
116
|
+
def single_file?
|
117
|
+
self.class.single_file
|
118
|
+
end
|
119
|
+
|
120
|
+
def whole_theme?
|
121
|
+
!single_file?
|
122
|
+
end
|
123
|
+
|
124
|
+
def ==(other)
|
125
|
+
other.is_a?(Check) && code_name == other.code_name
|
126
|
+
end
|
127
|
+
|
104
128
|
def to_s
|
105
129
|
s = +"#{code_name}:\n"
|
106
|
-
properties = {
|
130
|
+
properties = {
|
131
|
+
severity: severity,
|
132
|
+
categories: categories,
|
133
|
+
doc: doc,
|
134
|
+
ignored_patterns: ignored_patterns,
|
135
|
+
}.merge(options)
|
107
136
|
properties.each_pair do |name, value|
|
108
137
|
s << " #{name}: #{value}\n" if value
|
109
138
|
end
|
data/lib/theme_check/checks.rb
CHANGED
@@ -1,22 +1,61 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require "pp"
|
3
|
+
require "timeout"
|
4
|
+
|
2
5
|
module ThemeCheck
|
3
6
|
class Checks < Array
|
7
|
+
CHECK_METHOD_TIMEOUT = 5 # sec
|
8
|
+
|
4
9
|
def call(method, *args)
|
5
10
|
each do |check|
|
6
|
-
|
7
|
-
check.send(method, *args)
|
8
|
-
end
|
11
|
+
call_check_method(check, method, *args)
|
9
12
|
end
|
10
13
|
end
|
11
14
|
|
12
|
-
def
|
13
|
-
self.class.new(
|
15
|
+
def disableable
|
16
|
+
@disableable ||= self.class.new(select(&:can_disable?))
|
17
|
+
end
|
18
|
+
|
19
|
+
def whole_theme
|
20
|
+
@whole_theme ||= self.class.new(select(&:whole_theme?))
|
21
|
+
end
|
22
|
+
|
23
|
+
def single_file
|
24
|
+
@single_file ||= self.class.new(select(&:single_file?))
|
14
25
|
end
|
15
26
|
|
16
|
-
|
17
|
-
|
27
|
+
private
|
28
|
+
|
29
|
+
def call_check_method(check, method, *args)
|
30
|
+
return unless check.respond_to?(method) && !check.ignored?
|
31
|
+
|
32
|
+
Timeout.timeout(CHECK_METHOD_TIMEOUT) do
|
33
|
+
check.send(method, *args)
|
34
|
+
end
|
35
|
+
rescue Liquid::Error
|
36
|
+
# Pass-through Liquid errors
|
37
|
+
raise
|
38
|
+
rescue => e
|
39
|
+
node = args.first
|
40
|
+
template = node.respond_to?(:template) ? node.template.relative_path : "?"
|
41
|
+
markup = node.respond_to?(:markup) ? node.markup : ""
|
42
|
+
node_class = node.respond_to?(:value) ? node.value.class : "?"
|
43
|
+
|
44
|
+
ThemeCheck.bug(<<~EOS)
|
45
|
+
Exception while running `#{check.code_name}##{method}`:
|
46
|
+
```
|
47
|
+
#{e.class}: #{e.message}
|
48
|
+
#{e.backtrace.join("\n ")}
|
49
|
+
```
|
18
50
|
|
19
|
-
|
51
|
+
Template: `#{template}`
|
52
|
+
Node: `#{node_class}`
|
53
|
+
Markup:
|
54
|
+
```
|
55
|
+
#{markup}
|
56
|
+
```
|
57
|
+
Check options: `#{check.options.pretty_inspect}`
|
58
|
+
EOS
|
20
59
|
end
|
21
60
|
end
|
22
61
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class AssetUrlFilters < LiquidCheck
|
4
|
+
severity :suggestion
|
5
|
+
categories :liquid, :performance
|
6
|
+
doc docs_url(__FILE__)
|
7
|
+
|
8
|
+
HTML_FILTERS = [
|
9
|
+
'stylesheet_tag',
|
10
|
+
'script_tag',
|
11
|
+
'img_tag',
|
12
|
+
]
|
13
|
+
ASSET_URL_FILTERS = [
|
14
|
+
'asset_url',
|
15
|
+
'asset_img_url',
|
16
|
+
'file_img_url',
|
17
|
+
'file_url',
|
18
|
+
'global_asset_url',
|
19
|
+
'img_url',
|
20
|
+
'payment_type_img_url',
|
21
|
+
'shopify_asset_url',
|
22
|
+
]
|
23
|
+
|
24
|
+
def on_variable(node)
|
25
|
+
record_variable_offense(node)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def record_variable_offense(variable_node)
|
31
|
+
# We flag HTML tags with URLs not hosted by Shopify
|
32
|
+
return if !html_resource_drop?(variable_node) || variable_hosted_by_shopify?(variable_node)
|
33
|
+
add_offense("Use one of the asset_url filters to serve assets", node: variable_node)
|
34
|
+
end
|
35
|
+
|
36
|
+
def html_resource_drop?(variable_node)
|
37
|
+
variable_node.value.filters
|
38
|
+
.any? { |(filter_name, *_filter_args)| HTML_FILTERS.include?(filter_name) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def variable_hosted_by_shopify?(variable_node)
|
42
|
+
variable_node.value.filters
|
43
|
+
.any? { |(filter_name, *_filter_args)| ASSET_URL_FILTERS.include?(filter_name) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ThemeCheck
|
3
|
+
class ContentForHeaderModification < LiquidCheck
|
4
|
+
severity :error
|
5
|
+
category :liquid
|
6
|
+
doc docs_url(__FILE__)
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@in_assign = false
|
10
|
+
@in_capture = false
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_variable(node)
|
14
|
+
return unless node.value.name.is_a?(Liquid::VariableLookup)
|
15
|
+
return unless node.value.name.name == "content_for_header"
|
16
|
+
|
17
|
+
if @in_assign || @in_capture || node.value.filters.any?
|
18
|
+
add_offense(
|
19
|
+
"Do not rely on the content of `content_for_header`",
|
20
|
+
node: node,
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_assign(_node)
|
26
|
+
@in_assign = true
|
27
|
+
end
|
28
|
+
|
29
|
+
def after_assign(_node)
|
30
|
+
@in_assign = false
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_capture(_node)
|
34
|
+
@in_capture = true
|
35
|
+
end
|
36
|
+
|
37
|
+
def after_capture(_node)
|
38
|
+
@in_capture = false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,41 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module ThemeCheck
|
3
3
|
# Reports errors when trying to use parser-blocking script tags
|
4
|
-
class ImgWidthAndHeight <
|
5
|
-
include RegexHelpers
|
4
|
+
class ImgWidthAndHeight < HtmlCheck
|
6
5
|
severity :error
|
7
|
-
categories :
|
6
|
+
categories :html, :performance
|
8
7
|
doc docs_url(__FILE__)
|
9
8
|
|
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
9
|
ENDS_IN_CSS_UNIT = /(cm|mm|in|px|pt|pc|em|ex|ch|rem|vw|vh|vmin|vmax|%)$/i
|
18
10
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
record_offenses
|
23
|
-
end
|
11
|
+
def on_img(node)
|
12
|
+
width = node.attributes["width"]&.value
|
13
|
+
height = node.attributes["height"]&.value
|
24
14
|
|
25
|
-
|
15
|
+
record_units_in_field_offenses("width", width, node: node)
|
16
|
+
record_units_in_field_offenses("height", height, node: node)
|
26
17
|
|
27
|
-
|
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
|
18
|
+
return if node.attributes["src"].nil? || (width && height)
|
39
19
|
missing_width = width.nil?
|
40
20
|
missing_height = height.nil?
|
41
21
|
error_message = if missing_width && missing_height
|
@@ -46,29 +26,18 @@ module ThemeCheck
|
|
46
26
|
"Missing height attribute"
|
47
27
|
end
|
48
28
|
|
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
|
-
)
|
29
|
+
add_offense(error_message, node: node)
|
55
30
|
end
|
56
31
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
32
|
+
private
|
33
|
+
|
34
|
+
def record_units_in_field_offenses(attribute, value, node:)
|
35
|
+
return unless value =~ ENDS_IN_CSS_UNIT
|
36
|
+
value_without_units = value.gsub(ENDS_IN_CSS_UNIT, '')
|
37
|
+
add_offense(
|
38
|
+
"The #{attribute} attribute does not take units. Replace with \"#{value_without_units}\".",
|
39
|
+
node: node,
|
40
|
+
)
|
72
41
|
end
|
73
42
|
end
|
74
43
|
end
|