html-proofer 2.4.2 → 2.5.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/README.md +56 -46
- data/bin/htmlproof +4 -2
- data/lib/html/proofer.rb +20 -13
- data/lib/html/proofer/cache.rb +16 -0
- data/lib/html/proofer/check_runner.rb +39 -6
- data/lib/html/proofer/checkable.rb +5 -5
- data/lib/html/proofer/checks/favicon.rb +8 -8
- data/lib/html/proofer/checks/html.rb +6 -4
- data/lib/html/proofer/checks/images.rb +9 -12
- data/lib/html/proofer/checks/links.rb +47 -32
- data/lib/html/proofer/checks/scripts.rb +6 -5
- data/lib/html/proofer/log.rb +6 -2
- data/lib/html/proofer/url_validator.rb +6 -6
- data/lib/html/proofer/utils.rb +17 -15
- data/lib/html/proofer/version.rb +1 -1
- data/spec/html/proofer/fixtures/links/check_just_once.html +8 -0
- data/spec/html/proofer/fixtures/vcr_cassettes/links/check_just_once_html.yml +87 -0
- data/spec/html/proofer/fixtures/vcr_cassettes/sorting/status_verbosity_info_typhoeus_followlocation_false_error_sort_status_.yml +244 -0
- data/spec/html/proofer/links_spec.rb +6 -0
- data/spec/html/proofer/utils_spec.rb +3 -3
- data/spec/html/proofer_spec.rb +3 -3
- data/spec/spec_helper.rb +1 -0
- metadata +9 -2
@@ -1,21 +1,21 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
class FaviconCheckable < ::HTML::Proofer::Checkable
|
4
|
-
|
5
|
-
@rel
|
6
|
-
end
|
4
|
+
attr_reader :rel
|
7
5
|
end
|
8
6
|
|
9
7
|
class FaviconCheck < ::HTML::Proofer::CheckRunner
|
10
|
-
|
11
8
|
def run
|
12
|
-
|
13
|
-
|
9
|
+
found = false
|
10
|
+
@html.xpath('//link[not(ancestor::pre or ancestor::code)]').each do |node|
|
11
|
+
favicon = FaviconCheckable.new(node, self)
|
14
12
|
next if favicon.ignore?
|
15
|
-
|
13
|
+
found = true if favicon.rel.split(' ').last.eql? 'icon'
|
14
|
+
break if found
|
16
15
|
end
|
17
16
|
|
17
|
+
return if found
|
18
|
+
|
18
19
|
add_issue 'no favicon specified'
|
19
20
|
end
|
20
|
-
|
21
21
|
end
|
@@ -27,16 +27,18 @@ class HtmlCheck < ::HTML::Proofer::CheckRunner
|
|
27
27
|
rect set stop switch symbol text textPath tref tspan use
|
28
28
|
view vkern)
|
29
29
|
|
30
|
+
SCRIPT_EMBEDS_MSG = /Element script embeds close tag/
|
31
|
+
|
30
32
|
def run
|
31
|
-
@html.errors.each do |
|
32
|
-
message =
|
33
|
-
line =
|
33
|
+
@html.errors.each do |error|
|
34
|
+
message = error.message
|
35
|
+
line = error.line
|
34
36
|
# Nokogiri (or rather libxml2 underhood) only recognizes html4 tags,
|
35
37
|
# so we need to skip errors caused by the new tags in html5
|
36
38
|
next if HTML5_TAGS.include? message[/Tag ([\w-]+) invalid/o, 1]
|
37
39
|
|
38
40
|
# tags embedded in scripts are used in templating languages: http://git.io/vOovv
|
39
|
-
next if @validation_opts[:ignore_script_embeds] && message =~
|
41
|
+
next if @validation_opts[:ignore_script_embeds] && message =~ SCRIPT_EMBEDS_MSG
|
40
42
|
|
41
43
|
add_issue(message, line)
|
42
44
|
end
|
@@ -1,12 +1,9 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
class ImageCheckable < ::HTML::Proofer::Checkable
|
4
|
-
|
5
4
|
SCREEN_SHOT_REGEX = /Screen(?: |%20)Shot(?: |%20)\d+-\d+-\d+(?: |%20)at(?: |%20)\d+.\d+.\d+/
|
6
5
|
|
7
|
-
|
8
|
-
@alt
|
9
|
-
end
|
6
|
+
attr_reader :alt
|
10
7
|
|
11
8
|
def empty_alt_tag?
|
12
9
|
alt.strip.empty?
|
@@ -23,32 +20,32 @@ class ImageCheckable < ::HTML::Proofer::Checkable
|
|
23
20
|
def missing_src?
|
24
21
|
!src
|
25
22
|
end
|
26
|
-
|
27
23
|
end
|
28
24
|
|
29
25
|
class ImageCheck < ::HTML::Proofer::CheckRunner
|
30
26
|
def run
|
31
|
-
@html.css('img').each do |
|
32
|
-
img = ImageCheckable.new
|
27
|
+
@html.css('img').each do |node|
|
28
|
+
img = ImageCheckable.new(node, self)
|
29
|
+
line = node.line
|
33
30
|
|
34
31
|
next if img.ignore?
|
35
32
|
|
36
33
|
# screenshot filenames should return because of terrible names
|
37
|
-
next add_issue("image has a terrible filename (#{img.src})",
|
34
|
+
next add_issue("image has a terrible filename (#{img.src})", line) if img.terrible_filename?
|
38
35
|
|
39
36
|
# does the image exist?
|
40
37
|
if img.missing_src?
|
41
|
-
add_issue('image has no src or srcset attribute',
|
38
|
+
add_issue('image has no src or srcset attribute', line)
|
42
39
|
else
|
43
40
|
if img.remote?
|
44
|
-
add_to_external_urls
|
41
|
+
add_to_external_urls(img.src, line)
|
45
42
|
else
|
46
|
-
add_issue("internal image #{img.src} does not exist",
|
43
|
+
add_issue("internal image #{img.src} does not exist", line) unless img.exists?
|
47
44
|
end
|
48
45
|
end
|
49
46
|
|
50
47
|
if img.alt.nil? || (img.empty_alt_tag? && !img.ignore_empty_alt?)
|
51
|
-
add_issue("image #{img.src} does not have an alt attribute",
|
48
|
+
add_issue("image #{img.src} does not have an alt attribute", line)
|
52
49
|
end
|
53
50
|
end
|
54
51
|
|
@@ -19,15 +19,15 @@ class LinkCheckable < ::HTML::Proofer::Checkable
|
|
19
19
|
def placeholder?
|
20
20
|
(id || name) && href.nil?
|
21
21
|
end
|
22
|
-
|
23
22
|
end
|
24
23
|
|
25
24
|
class LinkCheck < ::HTML::Proofer::CheckRunner
|
26
|
-
include HTML::Utils
|
25
|
+
include HTML::Proofer::Utils
|
27
26
|
|
28
27
|
def run
|
29
|
-
@html.css('a, link').each do |
|
30
|
-
link = LinkCheckable.new
|
28
|
+
@html.css('a, link').each do |node|
|
29
|
+
link = LinkCheckable.new(node, self)
|
30
|
+
line = node.line
|
31
31
|
|
32
32
|
next if link.ignore?
|
33
33
|
next if link.href =~ /^javascript:/ # can't put this in ignore? because the URI does not parse
|
@@ -35,24 +35,15 @@ class LinkCheck < ::HTML::Proofer::CheckRunner
|
|
35
35
|
|
36
36
|
# is it even a valid URL?
|
37
37
|
unless link.valid?
|
38
|
-
add_issue("#{link.href} is an invalid URL",
|
38
|
+
add_issue("#{link.href} is an invalid URL", line)
|
39
39
|
next
|
40
40
|
end
|
41
41
|
|
42
|
-
|
43
|
-
when 'mailto'
|
44
|
-
if link.path.empty?
|
45
|
-
add_issue("#{link.href} contains no email address", l.line)
|
46
|
-
elsif !link.path.include?('@')
|
47
|
-
add_issue("#{link.href} contains an invalid email address", l.line)
|
48
|
-
end
|
49
|
-
when 'tel'
|
50
|
-
add_issue("#{link.href} contains no phone number", l.line) if link.path.empty?
|
51
|
-
end
|
42
|
+
check_schemes(link, line)
|
52
43
|
|
53
44
|
# is there even a href?
|
54
45
|
if link.missing_href?
|
55
|
-
add_issue('anchor has no href attribute',
|
46
|
+
add_issue('anchor has no href attribute', line)
|
56
47
|
next
|
57
48
|
end
|
58
49
|
|
@@ -61,47 +52,71 @@ class LinkCheck < ::HTML::Proofer::CheckRunner
|
|
61
52
|
|
62
53
|
# does the file even exist?
|
63
54
|
if link.remote?
|
64
|
-
add_to_external_urls
|
55
|
+
add_to_external_urls(link.href, line)
|
65
56
|
next
|
66
57
|
elsif !link.internal?
|
67
|
-
add_issue("internally linking to #{link.href}, which does not exist",
|
58
|
+
add_issue("internally linking to #{link.href}, which does not exist", line) unless link.exists?
|
68
59
|
end
|
69
60
|
|
70
61
|
# does the local directory have a trailing slash?
|
71
62
|
if link.unslashed_directory? link.absolute_path
|
72
|
-
add_issue("internally linking to a directory #{link.absolute_path} without trailing slash",
|
63
|
+
add_issue("internally linking to a directory #{link.absolute_path} without trailing slash", line)
|
73
64
|
next
|
74
65
|
end
|
75
66
|
|
76
67
|
# verify the target hash
|
77
|
-
if link.hash
|
78
|
-
if link.internal?
|
79
|
-
unless hash_check @html, link.hash
|
80
|
-
add_issue("linking to internal hash ##{link.hash} that does not exist", l.line)
|
81
|
-
end
|
82
|
-
elsif link.external?
|
83
|
-
external_link_check(link)
|
84
|
-
end
|
85
|
-
end
|
68
|
+
handle_hash(link, line) if link.hash
|
86
69
|
end
|
87
70
|
|
88
71
|
external_urls
|
89
72
|
end
|
90
73
|
|
91
|
-
def
|
74
|
+
def check_schemes(link, line)
|
75
|
+
case link.scheme
|
76
|
+
when 'mailto'
|
77
|
+
handle_mailto(link, line)
|
78
|
+
when 'tel'
|
79
|
+
handle_tel(link, line)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def handle_mailto(link, line)
|
84
|
+
if link.path.empty?
|
85
|
+
add_issue("#{link.href} contains no email address", line)
|
86
|
+
elsif !link.path.include?('@')
|
87
|
+
add_issue("#{link.href} contains an invalid email address", line)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def handle_tel(link, line)
|
92
|
+
add_issue("#{link.href} contains no phone number", line) if link.path.empty?
|
93
|
+
end
|
94
|
+
|
95
|
+
def handle_hash(link, line)
|
96
|
+
if link.internal?
|
97
|
+
unless hash_check @html, link.hash
|
98
|
+
add_issue("linking to internal hash ##{link.hash} that does not exist", line)
|
99
|
+
end
|
100
|
+
elsif link.external?
|
101
|
+
external_link_check(link, line)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def external_link_check(link, line)
|
92
106
|
if !link.exists?
|
93
|
-
add_issue("trying to find hash of #{link.href}, but #{link.absolute_path} does not exist",
|
107
|
+
add_issue("trying to find hash of #{link.href}, but #{link.absolute_path} does not exist", line)
|
94
108
|
else
|
95
109
|
target_html = create_nokogiri link.absolute_path
|
96
110
|
unless hash_check target_html, link.hash
|
97
|
-
add_issue("linking to #{link.href}, but #{link.hash} does not exist",
|
111
|
+
add_issue("linking to #{link.href}, but #{link.hash} does not exist", line)
|
98
112
|
end
|
99
113
|
end
|
100
114
|
end
|
101
115
|
|
102
116
|
def hash_check(html, href_hash)
|
103
117
|
html.xpath("//*[case_insensitive_equals(@id, '#{href_hash}')]", \
|
104
|
-
"//*[case_insensitive_equals(@name, '#{href_hash}')]",
|
118
|
+
"//*[case_insensitive_equals(@name, '#{href_hash}')]", \
|
119
|
+
HTML::Proofer::XpathFunctions.new).length > 0
|
105
120
|
end
|
106
121
|
|
107
122
|
end
|
@@ -18,19 +18,20 @@ end
|
|
18
18
|
|
19
19
|
class ScriptCheck < ::HTML::Proofer::CheckRunner
|
20
20
|
def run
|
21
|
-
@html.css('script').each do |
|
22
|
-
script = ScriptCheckable.new
|
21
|
+
@html.css('script').each do |node|
|
22
|
+
script = ScriptCheckable.new(node, self)
|
23
|
+
line = node.line
|
23
24
|
|
24
25
|
next if script.ignore?
|
25
26
|
next unless script.blank?
|
26
27
|
|
27
28
|
# does the script exist?
|
28
29
|
if script.missing_src?
|
29
|
-
add_issue('script is empty and has no src attribute',
|
30
|
+
add_issue('script is empty and has no src attribute', line)
|
30
31
|
elsif script.remote?
|
31
|
-
add_to_external_urls
|
32
|
+
add_to_external_urls(script.src, line)
|
32
33
|
else
|
33
|
-
add_issue("internal script #{script.src} does not exist",
|
34
|
+
add_issue("internal script #{script.src} does not exist", line) unless script.exists?
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
data/lib/html/proofer/log.rb
CHANGED
@@ -6,8 +6,12 @@ module HTML
|
|
6
6
|
class Log
|
7
7
|
include Yell::Loggable
|
8
8
|
|
9
|
-
def initialize(verbose)
|
10
|
-
log_level =
|
9
|
+
def initialize(verbose, verbosity = nil)
|
10
|
+
log_level = if verbosity.nil?
|
11
|
+
verbose ? :debug : :info
|
12
|
+
else
|
13
|
+
verbosity
|
14
|
+
end
|
11
15
|
|
12
16
|
@logger = Yell.new(:format => false, \
|
13
17
|
:name => 'HTML::Proofer', \
|
@@ -5,7 +5,7 @@ require_relative './utils'
|
|
5
5
|
module HTML
|
6
6
|
class Proofer
|
7
7
|
class UrlValidator
|
8
|
-
include Utils
|
8
|
+
include HTML::Proofer::Utils
|
9
9
|
|
10
10
|
attr_accessor :logger, :external_urls, :hydra
|
11
11
|
|
@@ -74,6 +74,7 @@ module HTML
|
|
74
74
|
href = response.request.base_url.to_s
|
75
75
|
method = response.request.options[:method]
|
76
76
|
response_code = response.code
|
77
|
+
|
77
78
|
debug_msg = "Received a #{response_code} for #{href}"
|
78
79
|
debug_msg << " in #{filenames.join(' ')}" unless filenames.nil?
|
79
80
|
logger.log :debug, :yellow, debug_msg
|
@@ -87,7 +88,7 @@ module HTML
|
|
87
88
|
else
|
88
89
|
return if @options[:only_4xx] && !response_code.between?(400, 499)
|
89
90
|
# Received a non-successful http response.
|
90
|
-
|
91
|
+
add_external_issue(filenames, "External link #{href} failed: #{response_code} #{response.return_message}", response_code)
|
91
92
|
end
|
92
93
|
end
|
93
94
|
|
@@ -108,15 +109,15 @@ module HTML
|
|
108
109
|
|
109
110
|
return unless body_doc.xpath(xpath).empty?
|
110
111
|
|
111
|
-
|
112
|
+
add_external_issue filenames, "External link #{href} failed: #{effective_url} exists, but the hash '#{hash}' does not", response.code
|
112
113
|
end
|
113
114
|
|
114
115
|
def handle_timeout(href, filenames, response_code)
|
115
116
|
return if @options[:only_4xx]
|
116
|
-
|
117
|
+
add_external_issue filenames, "External link #{href} failed: got a time out", response_code
|
117
118
|
end
|
118
119
|
|
119
|
-
def
|
120
|
+
def add_external_issue(filenames, desc, status = nil)
|
120
121
|
if filenames.nil?
|
121
122
|
@failed_tests << CheckRunner::Issue.new('', desc, nil, status)
|
122
123
|
else
|
@@ -129,7 +130,6 @@ module HTML
|
|
129
130
|
rescue URI::InvalidURIError
|
130
131
|
nil
|
131
132
|
end
|
132
|
-
|
133
133
|
end
|
134
134
|
end
|
135
135
|
end
|
data/lib/html/proofer/utils.rb
CHANGED
@@ -1,24 +1,26 @@
|
|
1
1
|
require 'nokogiri'
|
2
2
|
|
3
3
|
module HTML
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
class Proofer
|
5
|
+
module Utils
|
6
|
+
def create_nokogiri(path)
|
7
|
+
if File.exist? path
|
8
|
+
content = File.open(path).read
|
9
|
+
else
|
10
|
+
content = path
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
Nokogiri::HTML(content)
|
14
|
+
end
|
15
|
+
module_function :create_nokogiri
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
def swap(href, replacement)
|
18
|
+
replacement.each do |link, replace|
|
19
|
+
href = href.gsub(link, replace)
|
20
|
+
end
|
21
|
+
href
|
19
22
|
end
|
20
|
-
|
23
|
+
module_function :swap
|
21
24
|
end
|
22
|
-
module_function :swap
|
23
25
|
end
|
24
26
|
end
|
data/lib/html/proofer/version.rb
CHANGED
@@ -0,0 +1,8 @@
|
|
1
|
+
<a href="https://github.com/contact?form%5Bsubject%5D=New+Assigned+Events">First</a>
|
2
|
+
|
3
|
+
<a href="https://github.com/contact?form%5Bsubject%5D=Organization+and+Team+Membership+APIs">Ignored</a>
|
4
|
+
|
5
|
+
<a href="https://github.com/contact#false?form%5Bsubject%5D=Organization+and+Team+Membership+APIs">Second, because of hash</a>
|
6
|
+
|
7
|
+
|
8
|
+
<a href="https://github.com/contact?form%5Bsubject%5D=Blah+and+blah+blah+APIs">Ignored</a>
|
@@ -0,0 +1,87 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: head
|
5
|
+
uri: https://github.com/contact#false?form%5Bsubject%5D=Organization+and+Team+Membership+APIs
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: ''
|
9
|
+
headers:
|
10
|
+
User-Agent:
|
11
|
+
- Mozilla/5.0 (compatible; HTML Proofer/2.4.2; +https://github.com/gjtorikian/html-proofer)
|
12
|
+
response:
|
13
|
+
status:
|
14
|
+
code: 0
|
15
|
+
message:
|
16
|
+
headers: {}
|
17
|
+
body:
|
18
|
+
encoding: UTF-8
|
19
|
+
string: ''
|
20
|
+
http_version:
|
21
|
+
adapter_metadata:
|
22
|
+
effective_url: https://github.com/contact
|
23
|
+
recorded_at: Fri, 04 Sep 2015 21:28:18 GMT
|
24
|
+
- request:
|
25
|
+
method: head
|
26
|
+
uri: https://github.com/contact?form%5Bsubject%5D=New+Assigned+Events
|
27
|
+
body:
|
28
|
+
encoding: US-ASCII
|
29
|
+
string: ''
|
30
|
+
headers:
|
31
|
+
User-Agent:
|
32
|
+
- Mozilla/5.0 (compatible; HTML Proofer/2.4.2; +https://github.com/gjtorikian/html-proofer)
|
33
|
+
response:
|
34
|
+
status:
|
35
|
+
code: 0
|
36
|
+
message:
|
37
|
+
headers: {}
|
38
|
+
body:
|
39
|
+
encoding: UTF-8
|
40
|
+
string: ''
|
41
|
+
http_version:
|
42
|
+
adapter_metadata:
|
43
|
+
effective_url: https://github.com/contact?form%5Bsubject%5D=New+Assigned+Events
|
44
|
+
recorded_at: Fri, 04 Sep 2015 21:28:18 GMT
|
45
|
+
- request:
|
46
|
+
method: get
|
47
|
+
uri: https://github.com/contact#false?form%5Bsubject%5D=Organization+and+Team+Membership+APIs
|
48
|
+
body:
|
49
|
+
encoding: US-ASCII
|
50
|
+
string: ''
|
51
|
+
headers:
|
52
|
+
User-Agent:
|
53
|
+
- Mozilla/5.0 (compatible; HTML Proofer/2.4.2; +https://github.com/gjtorikian/html-proofer)
|
54
|
+
response:
|
55
|
+
status:
|
56
|
+
code: 0
|
57
|
+
message:
|
58
|
+
headers: {}
|
59
|
+
body:
|
60
|
+
encoding: UTF-8
|
61
|
+
string: ''
|
62
|
+
http_version:
|
63
|
+
adapter_metadata:
|
64
|
+
effective_url: https://github.com/contact
|
65
|
+
recorded_at: Fri, 04 Sep 2015 21:28:18 GMT
|
66
|
+
- request:
|
67
|
+
method: get
|
68
|
+
uri: https://github.com/contact?form%5Bsubject%5D=New+Assigned+Events
|
69
|
+
body:
|
70
|
+
encoding: US-ASCII
|
71
|
+
string: ''
|
72
|
+
headers:
|
73
|
+
User-Agent:
|
74
|
+
- Mozilla/5.0 (compatible; HTML Proofer/2.4.2; +https://github.com/gjtorikian/html-proofer)
|
75
|
+
response:
|
76
|
+
status:
|
77
|
+
code: 0
|
78
|
+
message:
|
79
|
+
headers: {}
|
80
|
+
body:
|
81
|
+
encoding: UTF-8
|
82
|
+
string: ''
|
83
|
+
http_version:
|
84
|
+
adapter_metadata:
|
85
|
+
effective_url: https://github.com/contact?form%5Bsubject%5D=New+Assigned+Events
|
86
|
+
recorded_at: Fri, 04 Sep 2015 21:28:18 GMT
|
87
|
+
recorded_with: VCR 2.9.3
|