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   32695 use 5.006;
  40         160  
20 40     40   221 use strict;
  40         95  
  40         895  
21 40     40   242 use warnings;
  40         88  
  40         1050  
22 40     40   214 use base 'Perl::Critic::Policy';
  40         89  
  40         4403  
23 40     40   181607 use Perl::Critic::Utils;
  40         92  
  40         698  
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 = 99;
44              
45 40     40   36100 use constant supported_parameters => ();
  40         98  
  40         2715  
46 40     40   262 use constant default_severity => $Perl::Critic::Utils::SEVERITY_LOWEST;
  40         105  
  40         2351  
47 40     40   276 use constant default_themes => qw(pulp cosmetic);
  40         96  
  40         2430  
48 40     40   266 use constant applies_to => 'PPI::Document';
  40         88  
  40         5428  
49              
50             sub violates {
51 19     19 1 576206 my ($self, $elem, $document) = @_;
52             ### ProhibitAdjacentLinks on: $elem->content
53              
54 19         509 my $parser = Perl::Critic::Policy::Documentation::ProhibitAdjacentLinks::Parser->new
55             (policy => $self);
56 19         123 $parser->parse_from_elem ($elem);
57 19         121 return $parser->violations;
58             }
59              
60             #------------------------------------------------------------------------------
61             package Perl::Critic::Policy::Documentation::ProhibitAdjacentLinks::Parser;
62 40     40   309 use strict;
  40         88  
  40         1054  
63 40     40   221 use warnings;
  40         96  
  40         1348  
64 40     40   774 use Pod::ParseLink;
  40         967  
  40         2125  
65 40     40   265 use base 'Perl::Critic::Pulp::PodParser';
  40         102  
  40         26036  
66              
67             my %command_non_text = (for => 1,
68             begin => 1,
69             end => 1,
70             cut => 1);
71             sub command {
72 23     23   2846 my $self = shift;
73 23         94 my ($command, $text, $linenum, $paraobj) = @_;
74 23         170 $self->SUPER::command(@_); # maintain 'in_begin'
75              
76 23 100       114 if ($command_non_text{$command}) {
77             # skip directives
78 4         158 return '';
79             }
80 19         95 $self->textblock ($text, $linenum, $paraobj);
81 19         317 return '';
82             }
83              
84             sub textblock {
85 40     40   1537 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     232 unless ($self->{'in_begin'} eq '' || $self->{'in_begin'} =~ /^:/) {
91 1         11 return '';
92             }
93              
94 39         5318 my $expand = $self->interpolate ($text, $linenum);
95             ### $expand
96 39         2409 my $ptree = $self->parse_text ($text, $linenum);
97 39         198 my @pending = reverse $ptree->children;
98 39         82 my $last_L = 0;
99 39         81 my $last_L_name = '';
100 39         88 my $last_L_display;
101 39         83 my $last_L_linenum = 0;
102              
103 39         134 while (@pending) {
104 74         164 my $obj = pop @pending;
105 74 100       216 if (! ref $obj) {
106             # plain text
107 36 100       190 if ($obj !~ /^\s*$/) {
108             # some text, not just whitespace
109 4         14 $last_L = 0;
110             }
111              
112             } else {
113             # a Pod::InteriorSequence
114 38         173 my $cmd = $obj->cmd_name;
115              
116 38 100 33     126 if ($cmd eq 'L') {
    50          
117 37         220 (undef, $linenum) = $obj->file_line;
118              
119             my $obj_text = join ('',
120 37 50       191 map {ref $_ ? $_->raw_text : $_}
  37         308  
121             $obj->parse_tree);
122 37         171 my ($display, $inferred, $name, $section, $type)
123             = Pod::ParseLink::parselink ($obj_text);
124             ### $obj_text
125             ### $display
126             ### $name
127 37 100       1919 if (! defined $name) { $name = ''; }
  2         6  
128              
129 37 100 100     296 if ($last_L
      100        
130             && ! ($name eq $last_L_name
131             && (defined $display || defined $last_L_display))) {
132 11         83 $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         79 $last_L = 1;
137 37         85 $last_L_name = $name;
138 37         85 $last_L_display = $display;
139 37         133 $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       8 if (my $subtree = $obj->parse_tree) {
147 1         8 push @pending, reverse $subtree->children;
148             }
149             }
150             }
151             }
152 39 50       158 if ($text !~ /^\s.*$/) {
153 39         123 $self->{'last'} = '';
154             }
155             ### last now: $self->{'last'}
156 39         1177 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