Skip to content

Commit 8fa1abd

Browse files
committed
warn about links with empty href destination
I often make blank links as a note to myself to remember to fill in a URL. If I forget, though, there will be a link that just brings the user to the top of the page, which isn't what I want. So now, we warn on these empty links. If you want to bring the user to the top of the page, use an explicit `#` character. Fixes #492
1 parent ce9b108 commit 8fa1abd

File tree

5 files changed

+28
-5
lines changed

5 files changed

+28
-5
lines changed

lib/Statocles/Plugin/LinkCheck.pm

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ sub check_pages {
3939

4040
my %page_paths = ();
4141
my %links = ();
42+
my %empty = (); # Pages with empty links
4243
for my $page ( @{ $event->pages } ) {
4344
$page_paths{ $page->path } = 1;
4445
if ( $page->DOES( 'Statocles::Page::Document' ) ) {
@@ -47,8 +48,13 @@ sub check_pages {
4748
for my $attr ( qw( src href ) ) {
4849
for my $el ( $dom->find( "[$attr]" )->each ) {
4950
my $url = $el->attr( $attr );
51+
52+
if ( !$url ) {
53+
push @{ $empty{ $page->path } }, $el;
54+
}
55+
5056
$url =~ s{#.*$}{};
51-
next unless $url;
57+
next unless $url; # Skip checking fragment-internal links for now
5258
next if $url =~ m{^(?:[a-z][a-z0-9+.-]*):}i;
5359
next if $url =~ m{^//};
5460
if ( $url !~ m{^/} ) {
@@ -68,18 +74,25 @@ sub check_pages {
6874
}
6975
}
7076

71-
my @missing; # Array of arrayrefs of [ link => page ] pairs
77+
my @missing; # Array of arrayrefs of [ link_url, page_path, link_element ]
7278
for my $link_url ( keys %links ) {
7379
$link_url .= 'index.html' if $link_url =~ m{/$};
7480
next if $page_paths{ $link_url } || $page_paths{ "$link_url/index.html" };
7581
next if grep { $link_url =~ /^$_/ } @{ $self->ignore };
7682
push @missing, [ $link_url, $_ ] for keys %{ $links{ $link_url } };
7783
}
7884

85+
for my $page_url ( keys %empty ) {
86+
push @missing, map { [ '', $page_url, $_ ] } @{ $empty{ $page_url } };
87+
}
88+
7989
if ( @missing ) {
8090
# Sort by page url and then missing link url
8191
for my $m ( sort { $a->[1] cmp $b->[1] || $a->[0] cmp $b->[0] } @missing ) {
82-
$event->emitter->log->warn( "URL broken on $m->[1]: '$m->[0]' not found" );
92+
my $msg = $m->[0] ? sprintf( q{'%s' not found}, $m->[0] )
93+
: sprintf( q{Link with text "%s" has no destination}, $m->[2]->text )
94+
;
95+
$event->emitter->log->warn( "URL broken on $m->[1]: $msg" );
8396
}
8497
}
8598
}

t/app/blog/pages.t

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ my @page_tests = (
6666
"Full URLs without schema are not broken" => '//example.com',
6767
"Fixed relative URL" => '/blog/2014/06/02/more_tags/docs.html',
6868
"Test a mailto: link" => 'mailto:user@example.com',
69+
'No link destination' => '/blog/2014/06/02/more_tags/',
6970
);
7071
my @links = $dom->find( 'article .content a' )->each;
7172
is scalar @links, scalar keys %links, 'all links found';
@@ -813,6 +814,7 @@ my @page_tests = (
813814
"Full URLs without schema are not broken" => '//example.com',
814815
"Fixed relative URL" => 'docs.html',
815816
"Test a mailto: link" => 'mailto:user@example.com',
817+
'No link destination' => '',
816818
);
817819
my @links = $dom->find( 'section a' )->each;
818820
is scalar @links, scalar keys %links, 'all links found';

t/plugin/link_check.t

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ subtest 'check links' => sub {
4141
[ ignore(), warn => re(qr{\QURL broken on /blog/2014/06/02/more_tags/docs.html: '/missing/favicon.png' not found} ) ],
4242
[ ignore(), warn => re(qr{\QURL broken on /blog/2014/06/02/more_tags/docs.html: '/missing/script.js' not found} ) ],
4343
[ ignore(), warn => re(qr{\QURL broken on /blog/2014/06/02/more_tags/docs.html: '/missing/stylesheet.css' not found} ) ],
44+
[ ignore(), warn => re(qr{\QURL broken on /blog/2014/06/02/more_tags/index.html: Link with text "No link destination" has no destination}) ],
4445
[ ignore(), warn => re(qr{\QURL broken on /blog/2014/06/02/more_tags/index.html: '/blog/2014/06/02/does_not_exist' not found}) ],
4546
[ ignore(), warn => re(qr{\QURL broken on /blog/2014/06/02/more_tags/index.html: '/blog/2014/06/02/more_tags/current_does_not_exist' not found}) ],
4647
[ ignore(), warn => re(qr{\QURL broken on /blog/2014/06/02/more_tags/index.html: '/blog/2014/06/02/more_tags/does_not_exist' not found}) ],
@@ -89,6 +90,7 @@ subtest 'ignore patterns' => sub {
8990

9091
cmp_deeply $site->log->history,
9192
bag(
93+
[ ignore(), warn => re(qr{\QURL broken on /blog/2014/06/02/more_tags/index.html: Link with text "No link destination" has no destination}) ],
9294
[ ignore(), warn => re(qr{\QURL broken on /blog/2014/06/02/more_tags/index.html: '/blog/2014/06/02/does_not_exist' not found}) ],
9395
[ ignore(), warn => re(qr{\QURL broken on /blog/2014/06/02/more_tags/index.html: '/blog/2014/06/02/more_tags/current_does_not_exist' not found}) ],
9496
[ ignore(), 'warn', re(qr{\QURL broken on $page: '/blog/2014/06/02/more_tags/does_not_exist' not found}) ],
@@ -116,7 +118,10 @@ subtest 'ignore patterns' => sub {
116118

117119
my $page = '/blog/2014/06/02/more_tags/index.html';
118120

119-
cmp_deeply $site->log->history, [],
121+
cmp_deeply $site->log->history,
122+
[
123+
[ ignore(), warn => re(qr{\QURL broken on /blog/2014/06/02/more_tags/index.html: Link with text "No link destination" has no destination}) ],
124+
],
120125
'all broken links ignored'
121126
or diag explain $site->log->history;
122127
};

t/share/app/blog/2014/06/02/more_tags/index.markdown

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,6 @@ links:
3131
[Fixed relative URL](docs.html)
3232

3333
[Test a mailto: link](mailto:user@example.com)
34+
35+
[No link destination]()
36+

t/share/theme/site/layout.html.ep

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
</nav>
2424
<%= $content %>
2525
<footer>
26-
<a href="<%= $site->data->{profile_url} // '' %>">Profile</a>
26+
<a href="<%= $site->data->{profile_url} // '#' %>">Profile</a>
2727
<div id="app-info"><%= $self->app->data->{info} // '' %></div>
2828
</footer>
2929
</body>

0 commit comments

Comments
 (0)