html-proofer 2.4.2 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|