File Coverage

blib/lib/Perl/Critic/Annotation.pm
Criterion Covered Total %
statement 110 113 97.3
branch 30 38 78.9
condition 17 29 58.6
subroutine 19 19 100.0
pod 8 8 100.0
total 184 207 88.8


line stmt bran cond sub pod time code
1             package Perl::Critic::Annotation;
2              
3 40     40   121809 use 5.010001;
  40         193  
4 40     40   271 use strict;
  40         110  
  40         829  
5 40     40   230 use warnings;
  40         112  
  40         1157  
6              
7 40     40   277 use Carp qw(confess);
  40         188  
  40         2297  
8 40     40   1309 use English qw(-no_match_vars);
  40         7994  
  40         337  
9              
10 40     40   16413 use Perl::Critic::PolicyFactory;
  40         126  
  40         398  
11 40     40   308 use Perl::Critic::Utils qw(:characters hashify);
  40         149  
  40         2583  
12 40     40   9453 use Readonly;
  40         146  
  40         58664  
13              
14             #-----------------------------------------------------------------------------
15              
16             our $VERSION = '1.146';
17              
18             Readonly::Scalar my $LAST_ELEMENT => -1;
19              
20             #=============================================================================
21             # CLASS methods
22              
23             sub create_annotations {
24 2728     2728 1 6408 my ($class, $doc) = @_;
25              
26 2728         5190 my @annotations = ();
27 2728   100     8056 my $comment_elements_ref = $doc->find('PPI::Token::Comment') || return;
28 2538         10002 my $annotation_rx = qr{\A (?: [#]! .*? )? \s* [#][#] \s* no \s+ critic}xms;
29 2538         5289 for my $annotation_element ( grep { $_ =~ $annotation_rx } @{$comment_elements_ref} ) {
  4504         24506  
  2538         5767  
30 102         808 push @annotations, Perl::Critic::Annotation->new( -element => $annotation_element);
31             }
32              
33 2538         25595 return @annotations;
34             }
35              
36             #-----------------------------------------------------------------------------
37              
38             sub new {
39 102     102 1 328 my ($class, @args) = @_;
40 102         302 my $self = bless {}, $class;
41 102         361 $self->_init(@args);
42 102         342 return $self;
43             }
44              
45             #=============================================================================
46             # OBJECT methods
47              
48             sub _init {
49 102     102   276 my ($self, %args) = @_;
50 102   33     405 my $annotation_element = $args{-element} || confess '-element argument is required';
51 102         294 $self->{_element} = $annotation_element;
52              
53 102         331 my %disabled_policies = _parse_annotation( $annotation_element );
54 102 100       361 $self->{_disables_all_policies} = %disabled_policies ? 0 : 1;
55 102         267 $self->{_disabled_policies} = \%disabled_policies;
56              
57             # Grab surrounding nodes to determine the context.
58             # This determines whether the annotation applies to
59             # the current line or the block that follows.
60 102         398 my $annotation_line = $annotation_element->logical_line_number();
61 102         1933 my $parent = $annotation_element->parent();
62 102 50       891 my $grandparent = $parent ? $parent->parent() : undef;
63              
64             # Handle case when it appears on the shebang line. In this
65             # situation, it only affects the first line, not the whole doc
66 102 100       525 if ( $annotation_element =~ m{\A [#]!}xms) {
67 3         29 $self->{_effective_range} = [$annotation_line, $annotation_line];
68 3         11 return $self;
69             }
70              
71             # Handle single-line usage on simple statements. In this
72             # situation, it only affects the line that it appears on.
73 99 100       623 if ( _is_single_line_annotation_on_simple_statement( $annotation_element )
74             ) {
75 45         1076 $self->{_effective_range} = [$annotation_line, $annotation_line];
76 45         138 return $self;
77             }
78              
79             # Handle single-line usage on compound statements. In this
80             # situation -- um -- I'm not sure how this works, but it does.
81 54 100       1560 if ( ref $parent eq 'PPI::Structure::Block' ) {
82 20 50 66     154 if ( ref $grandparent eq 'PPI::Statement::Compound'
83             || ref $grandparent eq 'PPI::Statement::Sub' ) {
84 20 100       78 if ( $parent->logical_line_number() == $annotation_line ) {
85 15         442 my $grandparent_line = $grandparent->logical_line_number();
86 15         340 $self->{_effective_range} = [$grandparent_line, $grandparent_line];
87 15         71 return $self;
88             }
89             }
90             }
91              
92              
93             # Handle multi-line usage. This is either a "no critic" ..
94             # "use critic" region or a block where "no critic" is in effect
95             # until the end of the scope. The start is the always the "no
96             # critic" which we already found. So now we have to search for the end.
97 39         251 my $end = $annotation_element;
98 39         162 my $use_critic = qr{\A \s* [#][#] \s* use \s+ critic}xms;
99              
100             SIB:
101 39         174 while ( my $esib = $end->next_sibling() ) {
102 349         11779 $end = $esib; # keep track of last sibling encountered in this scope
103 349 100 100     1670 last SIB if $esib->isa('PPI::Token::Comment') && $esib =~ $use_critic;
104             }
105              
106             # PPI parses __END__ as a PPI::Statement::End, and everything following is
107             # a child of that statement. That means if we encounter an __END__, we
108             # need to descend into it and continue the analysis.
109 39 100 66     1243 if ( $end->isa( 'PPI::Statement::End' ) and my $kid = $end->child( 0 ) ) {
110 3         39 $end = $kid;
111             SIB:
112 3         20 while ( my $esib = $end->next_sibling() ) {
113 11         205 $end = $esib;
114 11 50 33     79 last SIB if $esib->isa( 'PPI::Token::Comment' ) &&
115             $esib->content() =~ $use_critic;
116             }
117             }
118              
119             # We either found an end or hit the end of the scope.
120 39         200 my $ending_line = $end->logical_line_number();
121 39         678 $self->{_effective_range} = [$annotation_line, $ending_line];
122 39         149 return $self;
123             }
124              
125             #-----------------------------------------------------------------------------
126              
127             sub element {
128 13     13 1 21 my ($self) = @_;
129 13         25 return $self->{_element};
130             }
131              
132             #-----------------------------------------------------------------------------
133              
134             sub effective_range {
135 101     101 1 181 my $self = shift;
136 101         164 return @{ $self->{_effective_range} };
  101         313  
137             }
138              
139             #-----------------------------------------------------------------------------
140              
141             sub disabled_policies {
142 44     44 1 88 my $self = shift;
143 44         60 return keys %{ $self->{_disabled_policies} };
  44         189  
144             }
145              
146             #-----------------------------------------------------------------------------
147              
148             sub disables_policy {
149 8     8 1 19 my ($self, $policy_name) = @_;
150 8 50       71 return 1 if $self->{_disabled_policies}->{$policy_name};
151 0 0       0 return 1 if $self->disables_all_policies();
152 0         0 return 0;
153             }
154              
155             #-----------------------------------------------------------------------------
156              
157             sub disables_all_policies {
158 126     126 1 14882 my ($self) = @_;
159 126         444 return $self->{_disables_all_policies};
160             }
161              
162             #-----------------------------------------------------------------------------
163              
164             sub disables_line {
165 13     13 1 992 my ($self, $line_number) = @_;
166 13         25 my $effective_range = $self->{_effective_range};
167 13 50 33     118 return 1 if $line_number >= $effective_range->[0]
168             and $line_number <= $effective_range->[$LAST_ELEMENT];
169 0         0 return 0;
170             }
171              
172             #-----------------------------------------------------------------------------
173              
174             # Recognize a single-line annotation on a simple statement.
175             sub _is_single_line_annotation_on_simple_statement {
176 99     99   221 my ( $annotation_element ) = @_;
177 99         259 my $annotation_line = $annotation_element->logical_line_number();
178              
179             # If there is no sibling, we are clearly not a single-line annotation of
180             # any sort.
181 99 100       1513 my $sib = $annotation_element->sprevious_sibling()
182             or return 0;
183              
184             # The easy case: the sibling (whatever it is) is on the same line as the
185             # annotation.
186 57 100       3468 $sib->logical_line_number() == $annotation_line
187             and return 1;
188              
189             # If the sibling is a node, we may have an annotation on one line of a
190             # statement that was split over multiple lines. So we descend through the
191             # children, keeping the last significant child of each, until we bottom
192             # out. If the ultimate significant descendant is on the same line as the
193             # annotation, we accept the annotation as a single-line annotation.
194 13 50 33     460 if ( $sib->isa( 'PPI::Node' ) &&
195             $sib->logical_line_number() < $annotation_line
196             ) {
197 13         287 my $neighbor = $sib;
198 13   66     103 while ( $neighbor->isa( 'PPI::Node' )
199             and my $kid = $neighbor->schild( $LAST_ELEMENT ) ) {
200 13         292 $neighbor = $kid;
201             }
202 13 100 66     100 if ( $neighbor &&
203             $neighbor->logical_line_number() == $annotation_line
204             ) {
205 1         19 return 1;
206             }
207             }
208              
209             # We do not understand any other sort of single-line annotation. Accepting
210             # the annotation as such (if it is) is Someone Else's Problem.
211 12         276 return 0;
212             }
213              
214             #-----------------------------------------------------------------------------
215              
216             sub _parse_annotation {
217              
218 102     102   215 my ($annotation_element) = @_;
219              
220             #############################################################################
221             # This regex captures the list of Policy name patterns that are to be
222             # disabled. It is generally assumed that the element has already been
223             # verified as a no-critic annotation. So if this regex does not match,
224             # then it implies that all Policies are to be disabled.
225             #
226 102         326 my $no_critic = qr{\#\# \s* no \s+ critic \s* (?:qw)? [(["'] ([\s\w:,]+) }xms;
227             # -------------------------- ------- ----- -----------
228             # | | | |
229             # "## no critic" with optional spaces | | |
230             # | | |
231             # Policy list may be prefixed with "qw" | |
232             # | |
233             # Optional Policy list must begin with one of these |
234             # |
235             # Capture entire Policy list (with delimiters) here
236             #
237             #############################################################################
238              
239 102         221 my @disabled_policy_names = ();
240 102 100       485 if ( my ($patterns_string) = $annotation_element =~ $no_critic ) {
241              
242             # Compose the specified modules into a regex alternation. Wrap each
243             # in a no-capturing group to permit "|" in the modules specification.
244              
245 55         676 my @policy_name_patterns = grep { $_ ne $EMPTY }
  95         271  
246             split m{\s *[,\s] \s*}xms, $patterns_string;
247 55         120 my $re = join $PIPE, map {"(?:$_)"} @policy_name_patterns;
  92         277  
248 55         225 my @site_policy_names = Perl::Critic::PolicyFactory::site_policy_names();
249 55         160 @disabled_policy_names = grep {m/$re/ixms} @site_policy_names;
  7975         22156  
250              
251             # It is possible that the Policy patterns listed in the annotation do not
252             # match any of the site policy names. This could happen when running
253             # on a machine that does not have the same set of Policies as the author.
254             # So we must return something here, otherwise all Policies will be
255             # disabled. We probably need to add a mechanism to (optionally) warn
256             # about this, just to help the author avoid writing invalid Policy names.
257              
258 55 100       414 if (not @disabled_policy_names) {
259 14         111 @disabled_policy_names = @policy_name_patterns;
260             }
261             }
262              
263 102         675 return hashify(@disabled_policy_names);
264             }
265              
266             #-----------------------------------------------------------------------------
267              
268             1;
269              
270             __END__
271              
272             =pod
273              
274             =head1 NAME
275              
276             Perl::Critic::Annotation - A "## no critic" annotation in a document.
277              
278              
279             =head1 SYNOPSIS
280              
281             use Perl::Critic::Annotation;
282             $annotation = Perl::Critic::Annotation->new( -element => $no_critic_ppi_element );
283              
284             $bool = $annotation->disables_line( $number );
285             $bool = $annotation->disables_policy( $policy_object );
286             $bool = $annotation->disables_all_policies();
287              
288             ($start, $end) = $annotation->effective_range();
289             @disabled_policy_names = $annotation->disabled_policies();
290              
291              
292             =head1 DESCRIPTION
293              
294             C<Perl::Critic::Annotation> represents a single C<"## no critic">
295             annotation in a L<PPI::Document>. The Annotation takes care of parsing
296             the annotation and keeps track of which lines and Policies it affects.
297             It is intended to encapsulate the details of the no-critic
298             annotations, and to provide a way for Policy objects to interact with
299             the annotations (via a L<Perl::Critic::Document|Perl::Critic::Document>).
300              
301              
302             =head1 INTERFACE SUPPORT
303              
304             This is considered to be a non-public class. Its interface is subject
305             to change without notice.
306              
307              
308             =head1 CLASS METHODS
309              
310             =over
311              
312             =item create_annotations( -doc => $doc )
313              
314             Given a L<Perl::Critic::Document|Perl::Critic::Document>, finds all the C<"## no critic">
315             annotations and constructs a new C<Perl::Critic::Annotation> for each
316             one and returns them. The order of the returned objects is not
317             defined. It is generally expected that clients will use this
318             interface rather than calling the C<Perl::Critic::Annotation>
319             constructor directly.
320              
321              
322             =back
323              
324              
325             =head1 CONSTRUCTOR
326              
327             =over
328              
329             =item C<< new( -element => $ppi_annotation_element ) >>
330              
331             Returns a reference to a new Annotation object. The B<-element>
332             argument is required and should be a C<PPI::Token::Comment> that
333             conforms to the C<"## no critic"> syntax.
334              
335              
336             =back
337              
338              
339             =head1 METHODS
340              
341             =over
342              
343             =item C<< disables_line( $line ) >>
344              
345             Returns true if this Annotation disables C<$line> for any (or all)
346             Policies.
347              
348              
349             =item C<< disables_policy( $policy_object ) >>
350              
351             =item C<< disables_policy( $policy_name ) >>
352              
353             Returns true if this Annotation disables C<$polciy_object> or
354             C<$policy_name> at any (or all) lines.
355              
356              
357             =item C<< disables_all_policies() >>
358              
359             Returns true if this Annotation disables all Policies at any (or all)
360             lines. If this method returns true, C<disabled_policies> will return
361             an empty list.
362              
363              
364             =item C<< effective_range() >>
365              
366             Returns a two-element list, representing the first and last line
367             numbers where this Annotation has effect.
368              
369              
370             =item C<< disabled_policies() >>
371              
372             Returns a list of the names of the Policies that are affected by this
373             Annotation. If this list is empty, then it means that all Policies
374             are affected by this Annotation, and C<disables_all_policies()> should
375             return true.
376              
377              
378             =item C<< element() >>
379              
380             Returns the L<PPI::Element|PPI::Element> where this annotation started. This is
381             typically an instance of L<PPI::Token::Comment|PPI::Token::Comment>.
382              
383              
384             =back
385              
386              
387             =head1 AUTHOR
388              
389             Jeffrey Ryan Thalhammer <jeff@imaginative-software.com>
390              
391              
392             =head1 COPYRIGHT
393              
394             Copyright (c) 2005-2011 Imaginative Software Systems. All rights reserved.
395              
396             This program is free software; you can redistribute it and/or modify
397             it under the same terms as Perl itself. The full text of this license
398             can be found in the LICENSE file included with this module.
399              
400             =cut
401              
402             ##############################################################################
403             # Local Variables:
404             # mode: cperl
405             # cperl-indent-level: 4
406             # fill-column: 78
407             # indent-tabs-mode: nil
408             # c-indentation-style: bsd
409             # End:
410             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :