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 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   31239 use 5.006;
  40         155  
20 40     40   234 use strict;
  40         89  
  40         842  
21 40     40   204 use warnings;
  40         95  
  40         995  
22 40     40   207 use base 'Perl::Critic::Policy';
  40         80  
  40         4274  
23 40     40   149824 use Perl::Critic::Utils;
  40         89  
  40         744  
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 = 98;
44              
45 40     40   35088 use constant supported_parameters => ();
  40         95  
  40         2753  
46 40     40   268 use constant default_severity => $Perl::Critic::Utils::SEVERITY_LOWEST;
  40         106  
  40         2348  
47 40     40   254 use constant default_themes => qw(pulp cosmetic);
  40         94  
  40         2275  
48 40     40   251 use constant applies_to => 'PPI::Document';
  40         113  
  40         5165  
49              
50             sub violates {
51 19     19 1 440272 my ($self, $elem, $document) = @_;
52             ### ProhibitAdjacentLinks on: $elem->content
53              
54 19         234 my $parser = Perl::Critic::Policy::Documentation::ProhibitAdjacentLinks::Parser->new
55             (policy => $self);
56 19         69 $parser->parse_from_elem ($elem);
57 19         69 return $parser->violations;
58             }
59              
60             #------------------------------------------------------------------------------
61             package Perl::Critic::Policy::Documentation::ProhibitAdjacentLinks::Parser;
62 40     40   303 use strict;
  40         110  
  40         1029  
63 40     40   222 use warnings;
  40         96  
  40         1308  
64 40     40   672 use Pod::ParseLink;
  40         842  
  40         2070  
65 40     40   261 use base 'Perl::Critic::Pulp::PodParser';
  40         110  
  40         24829  
66              
67             my %command_non_text = (for => 1,
68             begin => 1,
69             end => 1,
70             cut => 1);
71             sub command {
72 23     23   1746 my $self = shift;
73 23         51 my ($command, $text, $linenum, $paraobj) = @_;
74 23         89 $self->SUPER::command(@_); # maintain 'in_begin'
75              
76 23 100       63 if ($command_non_text{$command}) {
77             # skip directives
78 4         67 return '';
79             }
80 19         48 $self->textblock ($text, $linenum, $paraobj);
81 19         211 return '';
82             }
83              
84             sub textblock {
85 40     40   1237 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     118 unless ($self->{'in_begin'} eq '' || $self->{'in_begin'} =~ /^:/) {
91 1         7 return '';
92             }
93              
94 39         3138 my $expand = $self->interpolate ($text, $linenum);
95             ### $expand
96 39         1864 my $ptree = $self->parse_text ($text, $linenum);
97 39         142 my @pending = reverse $ptree->children;
98 39         68 my $last_L = 0;
99 39         58 my $last_L_name = '';
100 39         57 my $last_L_display;
101 39         53 my $last_L_linenum = 0;
102              
103 39         85 while (@pending) {
104 74         120 my $obj = pop @pending;
105 74 100       146 if (! ref $obj) {
106             # plain text
107 36 100       128 if ($obj !~ /^\s*$/) {
108             # some text, not just whitespace
109 4         9 $last_L = 0;
110             }
111              
112             } else {
113             # a Pod::InteriorSequence
114 38         112 my $cmd = $obj->cmd_name;
115              
116 38 100 33     76 if ($cmd eq 'L') {
    50          
117 37         131 (undef, $linenum) = $obj->file_line;
118              
119             my $obj_text = join ('',
120 37 50       96 map {ref $_ ? $_->raw_text : $_}
  37         184  
121             $obj->parse_tree);
122 37         123 my ($display, $inferred, $name, $section, $type)
123             = Pod::ParseLink::parselink ($obj_text);
124             ### $obj_text
125             ### $display
126             ### $name
127 37 100       1322 if (! defined $name) { $name = ''; }
  2         4  
128              
129 37 100 100     135 if ($last_L
      100        
130             && ! ($name eq $last_L_name
131             && (defined $display || defined $last_L_display))) {
132 11         50 $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         72 $last_L = 1;
137 37         51 $last_L_name = $name;
138 37         52 $last_L_display = $display;
139 37         90 $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         6 push @pending, reverse $subtree->children;
148             }
149             }
150             }
151             }
152 39 50       101 if ($text !~ /^\s.*$/) {
153 39         78 $self->{'last'} = '';
154             }
155             ### last now: $self->{'last'}
156 39         801 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 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