File Coverage

blib/lib/Perl/Critic/Policy/Documentation/ProhibitAdjacentLinks.pm
Criterion Covered Total %
statement 83 83 100.0
branch 18 22 81.8
condition 9 12 75.0
subroutine 16 16 100.0
pod 1 1 100.0
total 127 134 94.7


line stmt bran cond sub pod time code
1             # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2021 Kevin Ryde
2              
3             # This file is part of Perl-Critic-Pulp.
4              
5             # Perl-Critic-Pulp is free software; you can redistribute it and/or modify
6             # it under the terms of the GNU General Public License as published by the
7             # Free Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Perl-Critic-Pulp is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Perl-Critic-Pulp. If not, see <http://www.gnu.org/licenses/>.
17              
18             package Perl::Critic::Policy::Documentation::ProhibitAdjacentLinks;
19 40     40   335940 use 5.006;
  40         178  
20 40     40   242 use strict;
  40         114  
  40         1130  
21 40     40   202 use warnings;
  40         117  
  40         2369  
22 40     40   234 use base 'Perl::Critic::Policy';
  40         108  
  40         5902  
23 40     40   247865 use Perl::Critic::Utils;
  40         119  
  40         910  
24              
25             # uncomment this to run the ### lines
26             #use Smart::Comments;
27              
28             # perlcritic -s ProhibitAdjacentLinks ProhibitAdjacentLinks.pm
29             # perlcritic -s ProhibitAdjacentLinks /usr/share/perl5/SVG.pm
30              
31             # cf /usr/lib/perl5/Template/Context.pm
32             # L<Template> L<new()|Template#new()>
33             # the "#" separator is wrong though
34             #
35             # cf /usr/share/perl5/DBIx/Class/Storage/DBI.pm
36             # L<DBI|DBI/ATTRIBUTES_COMMON_TO_ALL_HANDLES>
37             # L<connection|DBI/Database_Handle_Attributes>
38             #
39             # /usr/share/perl5/DhMakePerl/PodParser.pm
40             # L<Pod::Parser> L<command|Pod::Parser/command>
41             #
42              
43             our $VERSION = 100;
44              
45 40     40   43249 use constant supported_parameters => ();
  40         106  
  40         3228  
46 40     40   251 use constant default_severity => $Perl::Critic::Utils::SEVERITY_LOWEST;
  40         100  
  40         2674  
47 40     40   252 use constant default_themes => qw(pulp cosmetic);
  40         143  
  40         2839  
48 40     40   256 use constant applies_to => 'PPI::Document';
  40         88  
  40         6128  
49              
50             sub violates {
51 19     19 1 634587 my ($self, $elem, $document) = @_;
52             ### ProhibitAdjacentLinks on: $elem->content
53              
54 19         364 my $parser = Perl::Critic::Policy::Documentation::ProhibitAdjacentLinks::Parser->new
55             (policy => $self);
56 19         94 $parser->parse_from_elem ($elem);
57 19         99 return $parser->violations;
58             }
59              
60             #------------------------------------------------------------------------------
61             package Perl::Critic::Policy::Documentation::ProhibitAdjacentLinks::Parser;
62 40     40   264 use strict;
  40         112  
  40         1156  
63 40     40   219 use warnings;
  40         124  
  40         2425  
64 40     40   20401 use Pod::ParseLink;
  40         35994  
  40         2921  
65 40     40   309 use base 'Perl::Critic::Pulp::PodParser';
  40         88  
  40         30421  
66              
67             my %command_non_text = (for => 1,
68             begin => 1,
69             end => 1,
70             cut => 1);
71             sub command {
72 23     23   2459 my $self = shift;
73 23         86 my ($command, $text, $linenum, $paraobj) = @_;
74 23         122 $self->SUPER::command(@_); # maintain 'in_begin'
75              
76 23 100       75 if ($command_non_text{$command}) {
77             # skip directives
78 4         77 return '';
79             }
80 19         74 $self->textblock ($text, $linenum, $paraobj);
81 19         258 return '';
82             }
83              
84             sub textblock {
85 40     40   1754 my ($self, $text, $linenum, $pod_para) = @_;
86             ### textblock
87             ### $text
88              
89             # process outside =begin, and inside =begin which is ":" markup
90 40 100 66     178 unless ($self->{'in_begin'} eq '' || $self->{'in_begin'} =~ /^:/) {
91 1         9 return '';
92             }
93              
94 39         4758 my $expand = $self->interpolate ($text, $linenum);
95             ### $expand
96 39         2649 my $ptree = $self->parse_text ($text, $linenum);
97 39         193 my @pending = reverse $ptree->children;
98 39         76 my $last_L = 0;
99 39         96 my $last_L_name = '';
100 39         67 my $last_L_display;
101 39         72 my $last_L_linenum = 0;
102              
103 39         136 while (@pending) {
104 74         173 my $obj = pop @pending;
105 74 100       176 if (! ref $obj) {
106             # plain text
107 36 100       209 if ($obj !~ /^\s*$/) {
108             # some text, not just whitespace
109 4         13 $last_L = 0;
110             }
111              
112             } else {
113             # a Pod::InteriorSequence
114 38         145 my $cmd = $obj->cmd_name;
115              
116 38 100 33     100 if ($cmd eq 'L') {
    50          
117 37         182 (undef, $linenum) = $obj->file_line;
118              
119             my $obj_text = join ('',
120 37 50       178 map {ref $_ ? $_->raw_text : $_}
  37         263  
121             $obj->parse_tree);
122 37         168 my ($display, $inferred, $name, $section, $type)
123             = Pod::ParseLink::parselink ($obj_text);
124             ### $obj_text
125             ### $display
126             ### $name
127 37 100       1865 if (! defined $name) { $name = ''; }
  2         4  
128              
129 37 100 100     211 if ($last_L
      100        
130             && ! ($name eq $last_L_name
131             && (defined $display || defined $last_L_display))) {
132 11         65 $self->violation_at_linenum_and_textpos
133             ("Adjacent L<> sequences, perhaps a comma or words should be in between",
134             $last_L_linenum, '', 0);
135             }
136 37         68 $last_L = 1;
137 37         66 $last_L_name = $name;
138 37         75 $last_L_display = $display;
139 37         123 $last_L_linenum = $linenum;
140              
141             } elsif ($cmd eq 'X' || $cmd eq 'Z') {
142             # ignore X<> index entries, maybe Z<> crunched already
143              
144             } else {
145             # descend into other like C<>
146 1 50       7 if (my $subtree = $obj->parse_tree) {
147 1         7 push @pending, reverse $subtree->children;
148             }
149             }
150             }
151             }
152 39 50       158 if ($text !~ /^\s.*$/) {
153 39         116 $self->{'last'} = '';
154             }
155             ### last now: $self->{'last'}
156 39         1255 return;
157             }
158              
159             1;
160             __END__
161              
162             =for stopwords Ryde
163              
164             =head1 NAME
165              
166             Perl::Critic::Policy::Documentation::ProhibitAdjacentLinks - consecutive LE<lt>E<gt> links
167              
168             =head1 DESCRIPTION
169              
170             This policy is part of the L<C<Perl::Critic::Pulp>|Perl::Critic::Pulp>
171             add-on. It asks you not to have two adjacent LE<lt>E<gt> sequences in a
172             paragraph. For example,
173              
174             =for ProhibitVerbatimMarkup allow next 2
175              
176             =head1 SEE ALSO
177              
178             L<Foo> # bad
179             L<Bar>
180              
181             The idea is adjacent LE<lt>E<gt> like this is probably a missing comma or
182             missing text. It's easy to make this mistake in a "SEE ALSO" list.
183              
184             This is normally only very minor and on that basis this policy is lowest
185             severity and under the "cosmetic" theme (see L<Perl::Critic/POLICY THEMES>).
186              
187             =head2 Exceptions
188              
189             An exception is made for two links to the same page where one (or both) have
190             display text,
191              
192             =for ProhibitVerbatimMarkup allow next
193              
194             See L<My::Package> L<new()|My::Package/Contructors> for more.
195              
196             This hyperlinks both the package name and a function etc within it. Perhaps
197             exactly when to allow or disallow this might be loosened or tightened in the
198             future. Adjacent linking is fairly unusual though, and too much linking is
199             often not a good thing since the meaning ought to be made clear in plain
200             text too.
201              
202             =head2 Disabling
203              
204             If you don't care about this sort of thing at all you can disable
205             C<ProhibitAdjacentLinks> from your F<.perlcriticrc> in the usual way
206             (see L<Perl::Critic/CONFIGURATION>),
207              
208             [-Documentation::ProhibitAdjacentLinks]
209              
210             =head1 BUGS
211              
212             The column position of the offending adjacency is not included in the
213             violation reported. You may need to look carefully at the line to see the
214             problem, and at the following line if the adjacent link is on the next line.
215              
216             =head1 SEE ALSO
217              
218             L<Perl::Critic::Pulp>,
219             L<Perl::Critic::Policy::Documentation::ProhibitDuplicateSeeAlso>,
220             L<Perl::Critic::Policy::Documentation::ProhibitLinkToSelf>,
221             L<Perl::Critic::Policy::Documentation::RequirePodLinksIncludeText>,
222             L<Perl::Critic::Policy::Documentation::RequireLinkedURLs>
223              
224             =head1 HOME PAGE
225              
226             http://user42.tuxfamily.org/perl-critic-pulp/index.html
227              
228             =head1 COPYRIGHT
229              
230             Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2021 Kevin Ryde
231              
232             Perl-Critic-Pulp is free software; you can redistribute it and/or modify it
233             under the terms of the GNU General Public License as published by the Free
234             Software Foundation; either version 3, or (at your option) any later
235             version.
236              
237             Perl-Critic-Pulp is distributed in the hope that it will be useful, but
238             WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
239             or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
240             more details.
241              
242             You should have received a copy of the GNU General Public License along with
243             Perl-Critic-Pulp. If not, see <http://www.gnu.org/licenses/>.
244              
245             =cut