html-proofer 4.0.0.rc3 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/htmlproofer +6 -3
- data/lib/html-proofer.rb +1 -1
- data/lib/html_proofer/attribute/url.rb +186 -174
- data/lib/html_proofer/cache.rb +128 -85
- data/lib/html_proofer/check/favicon.rb +29 -24
- data/lib/html_proofer/check/images.rb +87 -47
- data/lib/html_proofer/check/links.rb +109 -98
- data/lib/html_proofer/check/open_graph.rb +30 -25
- data/lib/html_proofer/check/scripts.rb +36 -28
- data/lib/html_proofer/check.rb +11 -10
- data/lib/html_proofer/configuration.rb +16 -15
- data/lib/html_proofer/element.rb +41 -19
- data/lib/html_proofer/log.rb +19 -19
- data/lib/html_proofer/reporter/cli.rb +22 -18
- data/lib/html_proofer/reporter.rb +3 -3
- data/lib/html_proofer/runner.rb +45 -44
- data/lib/html_proofer/url_validator/external.rb +157 -152
- data/lib/html_proofer/url_validator/internal.rb +72 -62
- data/lib/html_proofer/utils.rb +5 -5
- data/lib/html_proofer/version.rb +1 -1
- data/lib/html_proofer.rb +11 -9
- metadata +8 -7
@@ -1,118 +1,129 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
3
|
+
module HTMLProofer
|
4
|
+
class Check
|
5
|
+
class Links < HTMLProofer::Check
|
6
|
+
def run
|
7
|
+
@html.css("a, link, source").each do |node|
|
8
|
+
@link = create_element(node)
|
9
|
+
|
10
|
+
next if @link.ignore?
|
11
|
+
|
12
|
+
if !allow_hash_href? && @link.node["href"] == "#"
|
13
|
+
add_failure("linking to internal hash #, which points to nowhere", line: @link.line, content: @link.content)
|
14
|
+
next
|
15
|
+
end
|
16
|
+
|
17
|
+
# is there even an href?
|
18
|
+
if blank?(@link.url.raw_attribute)
|
19
|
+
next if allow_missing_href?
|
20
|
+
|
21
|
+
add_failure("'#{@link.node.name}' tag is missing a reference", line: @link.line, content: @link.content)
|
22
|
+
next
|
23
|
+
end
|
24
|
+
|
25
|
+
# is it even a valid URL?
|
26
|
+
unless @link.url.valid?
|
27
|
+
add_failure("#{@link.href} is an invalid URL", line: @link.line, content: @link.content)
|
28
|
+
next
|
29
|
+
end
|
30
|
+
|
31
|
+
check_schemes
|
32
|
+
|
33
|
+
# intentionally down here because we still want valid? & missing_href? to execute
|
34
|
+
next if @link.url.non_http_remote?
|
35
|
+
|
36
|
+
if !@link.url.internal? && @link.url.remote?
|
37
|
+
check_sri if @runner.check_sri? && @link.link_tag?
|
38
|
+
|
39
|
+
# we need to skip these for now; although the domain main be valid,
|
40
|
+
# curl/Typheous inaccurately return 404s for some links. cc https://git.io/vyCFx
|
41
|
+
next if @link.node["rel"] == "dns-prefetch"
|
42
|
+
|
43
|
+
unless @link.url.path?
|
44
|
+
add_failure("#{@link.url.raw_attribute} is an invalid URL", line: @link.line, content: @link.content)
|
45
|
+
next
|
46
|
+
end
|
47
|
+
|
48
|
+
add_to_external_urls(@link.url, @link.line)
|
49
|
+
elsif @link.url.internal?
|
50
|
+
# does the local directory have a trailing slash?
|
51
|
+
if @link.url.unslashed_directory?(@link.url.absolute_path)
|
52
|
+
add_failure("internally linking to a directory #{@link.url.raw_attribute} without trailing slash",
|
53
|
+
line: @link.line, content: @link.content)
|
54
|
+
next
|
55
|
+
end
|
56
|
+
|
57
|
+
add_to_internal_urls(@link.url, @link.line)
|
58
|
+
end
|
59
|
+
end
|
13
60
|
end
|
14
61
|
|
15
|
-
|
16
|
-
|
17
|
-
next if allow_missing_href?
|
18
|
-
|
19
|
-
add_failure("'#{@link.node.name}' tag is missing a reference", line: @link.line, content: @link.content)
|
20
|
-
next
|
62
|
+
def allow_missing_href?
|
63
|
+
@runner.options[:allow_missing_href]
|
21
64
|
end
|
22
65
|
|
23
|
-
|
24
|
-
|
25
|
-
add_failure("#{@link.href} is an invalid URL", line: @link.line, content: @link.content)
|
26
|
-
next
|
66
|
+
def allow_hash_href?
|
67
|
+
@runner.options[:allow_hash_href]
|
27
68
|
end
|
28
69
|
|
29
|
-
check_schemes
|
30
|
-
|
31
|
-
|
32
|
-
|
70
|
+
def check_schemes
|
71
|
+
case @link.url.scheme
|
72
|
+
when "mailto"
|
73
|
+
handle_mailto
|
74
|
+
when "tel"
|
75
|
+
handle_tel
|
76
|
+
when "http"
|
77
|
+
return unless @runner.options[:enforce_https]
|
33
78
|
|
34
|
-
|
35
|
-
check_sri if @runner.check_sri? && @link.link_tag?
|
36
|
-
|
37
|
-
# we need to skip these for now; although the domain main be valid,
|
38
|
-
# curl/Typheous inaccurately return 404s for some links. cc https://git.io/vyCFx
|
39
|
-
next if @link.node['rel'] == 'dns-prefetch'
|
40
|
-
|
41
|
-
unless @link.url.path?
|
42
|
-
add_failure("#{@link.url.raw_attribute} is an invalid URL", line: @link.line, content: @link.content)
|
43
|
-
next
|
79
|
+
add_failure("#{@link.url.raw_attribute} is not an HTTPS link", line: @link.line, content: @link.content)
|
44
80
|
end
|
45
|
-
|
46
|
-
add_to_external_urls(@link.url, @link.line)
|
47
|
-
elsif @link.url.internal?
|
48
|
-
# does the local directory have a trailing slash?
|
49
|
-
add_failure("internally linking to a directory #{@link.url.raw_attribute} without trailing slash", line: @link.line, content: @link.content) if @link.url.unslashed_directory?(@link.url.absolute_path)
|
50
|
-
|
51
|
-
add_to_internal_urls(@link.url, @link.line)
|
52
81
|
end
|
53
|
-
end
|
54
|
-
|
55
|
-
external_urls
|
56
|
-
end
|
57
|
-
|
58
|
-
def allow_missing_href?
|
59
|
-
@runner.options[:allow_missing_href]
|
60
|
-
end
|
61
|
-
|
62
|
-
def allow_hash_href?
|
63
|
-
@runner.options[:allow_hash_href]
|
64
|
-
end
|
65
|
-
|
66
|
-
def check_schemes
|
67
|
-
case @link.url.scheme
|
68
|
-
when 'mailto'
|
69
|
-
handle_mailto
|
70
|
-
when 'tel'
|
71
|
-
handle_tel
|
72
|
-
when 'http'
|
73
|
-
return unless @runner.options[:enforce_https]
|
74
|
-
|
75
|
-
add_failure("#{@link.url.raw_attribute} is not an HTTPS link", line: @link.line, content: @link.content)
|
76
|
-
end
|
77
|
-
end
|
78
82
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
83
|
+
def handle_mailto
|
84
|
+
if @link.url.path.empty?
|
85
|
+
add_failure("#{@link.url.raw_attribute} contains no email address", line: @link.line,
|
86
|
+
content: @link.content) unless ignore_empty_mailto?
|
87
|
+
elsif !/#{URI::MailTo::EMAIL_REGEXP}/o.match?(@link.url.path)
|
88
|
+
add_failure("#{@link.url.raw_attribute} contains an invalid email address", line: @link.line,
|
89
|
+
content: @link.content)
|
90
|
+
end
|
91
|
+
end
|
86
92
|
|
87
|
-
|
88
|
-
|
89
|
-
|
93
|
+
def handle_tel
|
94
|
+
add_failure("#{@link.url.raw_attribute} contains no phone number", line: @link.line,
|
95
|
+
content: @link.content) if @link.url.path.empty?
|
96
|
+
end
|
90
97
|
|
91
|
-
|
92
|
-
|
93
|
-
|
98
|
+
def ignore_empty_mailto?
|
99
|
+
@runner.options[:ignore_empty_mailto]
|
100
|
+
end
|
94
101
|
|
95
|
-
|
96
|
-
|
97
|
-
|
102
|
+
# Allowed elements from Subresource Integrity specification
|
103
|
+
# https://w3c.github.io/webappsec-subresource-integrity/#link-element-for-stylesheets
|
104
|
+
SRI_REL_TYPES = %(stylesheet)
|
105
|
+
|
106
|
+
def check_sri
|
107
|
+
return unless SRI_REL_TYPES.include?(@link.node["rel"])
|
108
|
+
|
109
|
+
if blank?(@link.node["integrity"]) && blank?(@link.node["crossorigin"])
|
110
|
+
add_failure("SRI and CORS not provided in: #{@link.url.raw_attribute}", line: @link.line,
|
111
|
+
content: @link.content)
|
112
|
+
elsif blank?(@link.node["integrity"])
|
113
|
+
add_failure("Integrity is missing in: #{@link.url.raw_attribute}", line: @link.line, content: @link.content)
|
114
|
+
elsif blank?(@link.node["crossorigin"])
|
115
|
+
add_failure("CORS not provided for external resource in: #{@link.link.url.raw_attribute}", line: @link.line,
|
116
|
+
content: @link.content)
|
117
|
+
end
|
118
|
+
end
|
98
119
|
|
99
|
-
|
100
|
-
|
120
|
+
private def source_tag?
|
121
|
+
@link.node.name == "source"
|
122
|
+
end
|
101
123
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
add_failure("Integrity is missing in: #{@link.url.raw_attribute}", line: @link.line, content: @link.content)
|
106
|
-
elsif blank?(@link.node['crossorigin'])
|
107
|
-
add_failure("CORS not provided for external resource in: #{@link.link.url.raw_attribute}", line: @link.line, content: @link.content)
|
124
|
+
private def anchor_tag?
|
125
|
+
@link.node.name == "a"
|
126
|
+
end
|
108
127
|
end
|
109
128
|
end
|
110
|
-
|
111
|
-
private def source_tag?
|
112
|
-
@link.node.name == 'source'
|
113
|
-
end
|
114
|
-
|
115
|
-
private def anchor_tag?
|
116
|
-
@link.node.name == 'a'
|
117
|
-
end
|
118
129
|
end
|
@@ -1,34 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
module HTMLProofer
|
4
|
+
class Check
|
5
|
+
class OpenGraph < HTMLProofer::Check
|
6
|
+
def run
|
7
|
+
@html.css('meta[property="og:url"], meta[property="og:image"]').each do |node|
|
8
|
+
@open_graph = create_element(node)
|
7
9
|
|
8
|
-
|
10
|
+
next if @open_graph.ignore?
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
12
|
+
# does the open_graph exist?
|
13
|
+
if missing_content?
|
14
|
+
add_failure("open graph has no content attribute", line: @open_graph.line, content: @open_graph.content)
|
15
|
+
elsif empty_content?
|
16
|
+
add_failure("open graph content attribute is empty", line: @open_graph.line, content: @open_graph.content)
|
17
|
+
elsif !@open_graph.url.valid?
|
18
|
+
add_failure("#{@open_graph.src} is an invalid URL", line: @open_graph.line)
|
19
|
+
elsif @open_graph.url.remote?
|
20
|
+
add_to_external_urls(@open_graph.url, @open_graph.line)
|
21
|
+
else
|
22
|
+
add_failure("internal open graph #{@open_graph.url.raw_attribute} does not exist", line: @open_graph.line,
|
23
|
+
content: @open_graph.content) unless @open_graph.url.exists?
|
24
|
+
end
|
25
|
+
end
|
23
26
|
|
24
|
-
|
25
|
-
|
27
|
+
external_urls
|
28
|
+
end
|
26
29
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
+
private def missing_content?
|
31
|
+
@open_graph.node["content"].nil?
|
32
|
+
end
|
30
33
|
|
31
|
-
|
32
|
-
|
34
|
+
private def empty_content?
|
35
|
+
@open_graph.node["content"].empty?
|
36
|
+
end
|
37
|
+
end
|
33
38
|
end
|
34
39
|
end
|
@@ -1,38 +1,46 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
module HTMLProofer
|
4
|
+
class Check
|
5
|
+
class Scripts < HTMLProofer::Check
|
6
|
+
def run
|
7
|
+
@html.css("script").each do |node|
|
8
|
+
@script = create_element(node)
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
+
next if @script.ignore?
|
11
|
+
next unless @script.content.strip.empty?
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
13
|
+
# does the script exist?
|
14
|
+
if missing_src?
|
15
|
+
add_failure("script is empty and has no src attribute", line: @script.line, content: @script.content)
|
16
|
+
elsif @script.url.remote?
|
17
|
+
add_to_external_urls(@script.src, @script.line)
|
18
|
+
check_sri if @runner.check_sri?
|
19
|
+
elsif !@script.url.exists?
|
20
|
+
add_failure("internal script reference #{@script.src} does not exist", line: @script.line,
|
21
|
+
content: @script.content)
|
22
|
+
end
|
23
|
+
end
|
21
24
|
|
22
|
-
|
23
|
-
|
25
|
+
external_urls
|
26
|
+
end
|
24
27
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
+
def missing_src?
|
29
|
+
@script.node["src"].nil?
|
30
|
+
end
|
28
31
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
def check_sri
|
33
|
+
if blank?(@script.node["integrity"]) && blank?(@script.node["crossorigin"])
|
34
|
+
add_failure("SRI and CORS not provided in: #{@script.url.raw_attribute}", line: @script.line,
|
35
|
+
content: @script.content)
|
36
|
+
elsif blank?(@script.node["integrity"])
|
37
|
+
add_failure("Integrity is missing in: #{@script.url.raw_attribute}", line: @script.line,
|
38
|
+
content: @script.content)
|
39
|
+
elsif blank?(@script.node["crossorigin"])
|
40
|
+
add_failure("CORS not provided for external resource in: #{@script.url.raw_attribute}", line: @script.line,
|
41
|
+
content: @script.content)
|
42
|
+
end
|
43
|
+
end
|
36
44
|
end
|
37
45
|
end
|
38
46
|
end
|
data/lib/html_proofer/check.rb
CHANGED
@@ -21,11 +21,12 @@ module HTMLProofer
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def run
|
24
|
-
raise NotImplementedError,
|
24
|
+
raise NotImplementedError, "HTMLProofer::Check subclasses must implement #run"
|
25
25
|
end
|
26
26
|
|
27
27
|
def add_failure(description, line: nil, status: nil, content: nil)
|
28
|
-
@failures << Failure.new(@runner.
|
28
|
+
@failures << Failure.new(@runner.current_filename, short_name, description, line: line, status: status,
|
29
|
+
content: content)
|
29
30
|
end
|
30
31
|
|
31
32
|
def self.subchecks(runner_options)
|
@@ -43,11 +44,11 @@ module HTMLProofer
|
|
43
44
|
end
|
44
45
|
|
45
46
|
def short_name
|
46
|
-
self.class.name.split(
|
47
|
+
self.class.name.split("::").last
|
47
48
|
end
|
48
49
|
|
49
50
|
def self.short_name
|
50
|
-
name.split(
|
51
|
+
name.split("::").last
|
51
52
|
end
|
52
53
|
|
53
54
|
def add_to_internal_urls(url, line)
|
@@ -57,10 +58,10 @@ module HTMLProofer
|
|
57
58
|
|
58
59
|
metadata = {
|
59
60
|
source: @runner.current_source,
|
60
|
-
|
61
|
+
filename: @runner.current_filename,
|
61
62
|
line: line,
|
62
63
|
base_url: base_url,
|
63
|
-
found:
|
64
|
+
found: false,
|
64
65
|
}
|
65
66
|
@internal_urls[url_string] << metadata
|
66
67
|
end
|
@@ -70,21 +71,21 @@ module HTMLProofer
|
|
70
71
|
|
71
72
|
@external_urls[url_string] = [] if @external_urls[url_string].nil?
|
72
73
|
|
73
|
-
@external_urls[url_string] << { filename: @runner.
|
74
|
+
@external_urls[url_string] << { filename: @runner.current_filename, line: line }
|
74
75
|
end
|
75
76
|
|
76
77
|
private def base_url
|
77
78
|
return @base_url if defined?(@base_url)
|
78
79
|
|
79
|
-
return (@base_url =
|
80
|
+
return (@base_url = "") if (base = @html.at_css("base")).nil?
|
80
81
|
|
81
|
-
@base_url = base[
|
82
|
+
@base_url = base["href"]
|
82
83
|
end
|
83
84
|
|
84
85
|
private def remove_ignored(html)
|
85
86
|
return if html.nil?
|
86
87
|
|
87
|
-
html.css(
|
88
|
+
html.css("code, pre, tt").each(&:unlink)
|
88
89
|
html
|
89
90
|
end
|
90
91
|
end
|
@@ -2,45 +2,46 @@
|
|
2
2
|
|
3
3
|
module HTMLProofer
|
4
4
|
module Configuration
|
5
|
-
DEFAULT_TESTS =
|
5
|
+
DEFAULT_TESTS = ["Links", "Images", "Scripts"].freeze
|
6
6
|
|
7
7
|
PROOFER_DEFAULTS = {
|
8
8
|
allow_hash_href: true,
|
9
9
|
allow_missing_href: false,
|
10
|
-
assume_extension:
|
10
|
+
assume_extension: ".html",
|
11
11
|
check_external_hash: true,
|
12
12
|
checks: DEFAULT_TESTS,
|
13
|
-
directory_index_file:
|
13
|
+
directory_index_file: "index.html",
|
14
14
|
disable_external: false,
|
15
|
+
ignore_empty_alt: true,
|
15
16
|
ignore_empty_mailto: false,
|
16
17
|
ignore_files: [],
|
17
18
|
ignore_missing_alt: false,
|
18
19
|
ignore_status_codes: [],
|
19
20
|
ignore_urls: [],
|
20
21
|
enforce_https: true,
|
21
|
-
extensions: [
|
22
|
+
extensions: [".html"],
|
22
23
|
log_level: :info,
|
23
24
|
only_4xx: false,
|
24
25
|
swap_attributes: {},
|
25
|
-
swap_urls: {}
|
26
|
+
swap_urls: {},
|
26
27
|
}.freeze
|
27
28
|
|
28
29
|
TYPHOEUS_DEFAULTS = {
|
29
30
|
followlocation: true,
|
30
31
|
headers: {
|
31
|
-
|
32
|
-
|
32
|
+
"User-Agent" => "Mozilla/5.0 (compatible; HTML Proofer/#{HTMLProofer::VERSION}; +https://github.com/gjtorikian/html-proofer)",
|
33
|
+
"Accept" => "application/xml,application/xhtml+xml,text/html;q=0.9, text/plain;q=0.8,image/png,*/*;q=0.5",
|
33
34
|
},
|
34
35
|
connecttimeout: 10,
|
35
|
-
timeout: 30
|
36
|
+
timeout: 30,
|
36
37
|
}.freeze
|
37
38
|
|
38
39
|
HYDRA_DEFAULTS = {
|
39
|
-
max_concurrency: 50
|
40
|
+
max_concurrency: 50,
|
40
41
|
}.freeze
|
41
42
|
|
42
43
|
PARALLEL_DEFAULTS = {
|
43
|
-
enable: true
|
44
|
+
enable: true,
|
44
45
|
}.freeze
|
45
46
|
|
46
47
|
CACHE_DEFAULTS = {}.freeze
|
@@ -60,20 +61,20 @@ module HTMLProofer
|
|
60
61
|
end
|
61
62
|
|
62
63
|
def self.to_regex?(item)
|
63
|
-
if item.start_with?(
|
64
|
-
Regexp.new
|
64
|
+
if item.start_with?("/") && item.end_with?("/")
|
65
|
+
Regexp.new(item[1...-1])
|
65
66
|
else
|
66
67
|
item
|
67
68
|
end
|
68
69
|
end
|
69
70
|
|
70
71
|
def self.parse_json_option(option_name, config, symbolize_names: true)
|
71
|
-
raise ArgumentError,
|
72
|
-
raise ArgumentError,
|
72
|
+
raise ArgumentError, "Must provide an option name in string format." unless option_name.is_a?(String)
|
73
|
+
raise ArgumentError, "Must provide an option name in string format." if option_name.strip.empty?
|
73
74
|
|
74
75
|
return {} if config.nil?
|
75
76
|
|
76
|
-
raise ArgumentError,
|
77
|
+
raise ArgumentError, "Must provide a JSON configuration in string format." unless config.is_a?(String)
|
77
78
|
|
78
79
|
return {} if config.strip.empty?
|
79
80
|
|
data/lib/html_proofer/element.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "addressable/uri"
|
4
4
|
|
5
5
|
module HTMLProofer
|
6
6
|
# Represents the element currently being processed
|
@@ -26,66 +26,88 @@ module HTMLProofer
|
|
26
26
|
|
27
27
|
def meta_content
|
28
28
|
return nil unless meta_tag?
|
29
|
-
return swap_attributes(
|
29
|
+
return swap_attributes("content") if attribute_swapped?
|
30
30
|
|
31
|
-
@node[
|
31
|
+
@node["content"]
|
32
32
|
end
|
33
33
|
|
34
34
|
def meta_tag?
|
35
|
-
@node.name ==
|
35
|
+
@node.name == "meta"
|
36
36
|
end
|
37
37
|
|
38
38
|
def src
|
39
39
|
return nil if !img_tag? && !script_tag? && !source_tag?
|
40
|
-
return swap_attributes(
|
40
|
+
return swap_attributes("src") if attribute_swapped?
|
41
41
|
|
42
|
-
@node[
|
42
|
+
@node["src"]
|
43
43
|
end
|
44
44
|
|
45
45
|
def img_tag?
|
46
|
-
@node.name ==
|
46
|
+
@node.name == "img"
|
47
47
|
end
|
48
48
|
|
49
49
|
def script_tag?
|
50
|
-
@node.name ==
|
50
|
+
@node.name == "script"
|
51
51
|
end
|
52
52
|
|
53
53
|
def srcset
|
54
54
|
return nil if !img_tag? && !source_tag?
|
55
|
-
return swap_attributes(
|
55
|
+
return swap_attributes("srcset") if attribute_swapped?
|
56
56
|
|
57
|
-
@node[
|
57
|
+
@node["srcset"]
|
58
58
|
end
|
59
59
|
|
60
60
|
def source_tag?
|
61
|
-
@node.name ==
|
61
|
+
@node.name == "source"
|
62
62
|
end
|
63
63
|
|
64
64
|
def href
|
65
65
|
return nil if !a_tag? && !link_tag?
|
66
|
-
return swap_attributes(
|
66
|
+
return swap_attributes("href") if attribute_swapped?
|
67
67
|
|
68
|
-
@node[
|
68
|
+
@node["href"]
|
69
69
|
end
|
70
70
|
|
71
71
|
def a_tag?
|
72
|
-
@node.name ==
|
72
|
+
@node.name == "a"
|
73
73
|
end
|
74
74
|
|
75
75
|
def link_tag?
|
76
|
-
@node.name ==
|
76
|
+
@node.name == "link"
|
77
77
|
end
|
78
78
|
|
79
79
|
def aria_hidden?
|
80
|
-
@node.attributes[
|
80
|
+
@node.attributes["aria-hidden"]&.value == "true"
|
81
81
|
end
|
82
82
|
|
83
83
|
def multiple_srcsets?
|
84
|
-
!blank?(srcset) && srcset.split(
|
84
|
+
!blank?(srcset) && srcset.split(",").size > 1
|
85
|
+
end
|
86
|
+
|
87
|
+
def srcsets
|
88
|
+
return nil if blank?(srcset)
|
89
|
+
|
90
|
+
srcset.split(",").map(&:strip)
|
91
|
+
end
|
92
|
+
|
93
|
+
def multiple_sizes?
|
94
|
+
return false if blank?(srcsets)
|
95
|
+
|
96
|
+
srcsets.any? do |srcset|
|
97
|
+
!blank?(srcset) && srcset.split(" ").size > 1
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def srcsets_wo_sizes
|
102
|
+
return nil if blank?(srcsets)
|
103
|
+
|
104
|
+
srcsets.map do |srcset|
|
105
|
+
srcset.split(" ").first
|
106
|
+
end
|
85
107
|
end
|
86
108
|
|
87
109
|
def ignore?
|
88
|
-
return true if @node.attributes[
|
110
|
+
return true if @node.attributes["data-proofer-ignore"]
|
89
111
|
return true if ancestors_ignorable?
|
90
112
|
|
91
113
|
return true if url&.ignore?
|
@@ -116,7 +138,7 @@ module HTMLProofer
|
|
116
138
|
private def ancestors_ignorable?
|
117
139
|
ancestors_attributes = @node.ancestors.map { |a| a.respond_to?(:attributes) && a.attributes }
|
118
140
|
ancestors_attributes.pop # remove document at the end
|
119
|
-
ancestors_attributes.any? { |a| !a[
|
141
|
+
ancestors_attributes.any? { |a| !a["data-proofer-ignore"].nil? }
|
120
142
|
end
|
121
143
|
end
|
122
144
|
end
|