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   120493 use 5.010001;
  40         197  
4 40     40   265 use strict;
  40         125  
  40         909  
5 40     40   310 use warnings;
  40         118  
  40         1301  
6              
7 40     40   286 use Carp qw(confess);
  40         128  
  40         2216  
8 40     40   1342 use English qw(-no_match_vars);
  40         7525  
  40         322  
9              
10 40     40   16371 use Perl::Critic::PolicyFactory;
  40         181  
  40         368  
11 40     40   325 use Perl::Critic::Utils qw(:characters hashify);
  40         122  
  40         2221  
12 40     40   9283 use Readonly;
  40         129  
  40         57708  
13              
14             #-----------------------------------------------------------------------------
15              
16             our $VERSION = '1.148';
17              
18             Readonly::Scalar my $LAST_ELEMENT => -1;
19              
20             #=============================================================================
21             # CLASS methods
22              
23             sub create_annotations {
24 2728     2728 1 6191 my ($class, $doc) = @_;
25              
26 2728         5166 my @annotations = ();
27 2728   100     8372 my $comment_elements_ref = $doc->find('PPI::Token::Comment') || return;
28 2538         9964 my $annotation_rx = qr{\A (?: [#]! .*? )? \s* [#][#] \s* no \s+ critic}xms;
29 2538         4429 for my $annotation_element ( grep { $_ =~ $annotation_rx } @{$comment_elements_ref} ) {
  4504         23755  
  2538         5923  
30 102         761 push @annotations, Perl::Critic::Annotation->new( -element => $annotation_element);
31             }
32              
33 2538         23983 return @annotations;
34             }
35              
36             #-----------------------------------------------------------------------------
37              
38             sub new {
39 102     102 1 362 my ($class, @args) = @_;
40 102         257 my $self = bless {}, $class;
41 102         373 $self->_init(@args);
42 102         381 return $self;
43             }
44              
45             #=============================================================================
46             # OBJECT methods
47              
48             sub _init {
49 102     102   282 my ($self, %args) = @_;
50 102   33     449 my $annotation_element = $args{-element} || confess '-element argument is required';
51 102         305 $self->{_element} = $annotation_element;
52              
53 102         329 my %disabled_policies = _parse_annotation( $annotation_element );
54 102 100       396 $self->{_disables_all_policies} = %disabled_policies ? 0 : 1;
55 102         259 $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         435 my $annotation_line = $annotation_element->logical_line_number();
61 102         2088 my $parent = $annotation_element->parent();
62 102 50       896 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       511 if ( $annotation_element =~ m{\A [#]!}xms) {
67 3         33 $self->{_effective_range} = [$annotation_line, $annotation_line];
68 3         13 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       685 if ( _is_single_line_annotation_on_simple_statement( $annotation_element )
74             ) {
75 45         1059 $self->{_effective_range} = [$annotation_line, $annotation_line];
76 45         129 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       1654 if ( ref $parent eq 'PPI::Structure::Block' ) {
82 20 50 66     119 if ( ref $grandparent eq 'PPI::Statement::Compound'
83             || ref $grandparent eq 'PPI::Statement::Sub' ) {
84 20 100       71 if ( $parent->logical_line_number() == $annotation_line ) {
85 15         475 my $grandparent_line = $grandparent->logical_line_number();
86 15         347 $self->{_effective_range} = [$grandparent_line, $grandparent_line];
87 15         42 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         254 my $end = $annotation_element;
98 39         140 my $use_critic = qr{\A \s* [#][#] \s* use \s+ critic}xms;
99              
100             SIB:
101 39         158 while ( my $esib = $end->next_sibling() ) {
102 349         12088 $end = $esib; # keep track of last sibling encountered in this scope
103 349 100 100     1777 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     1269 if ( $end->isa( 'PPI::Statement::End' ) and my $kid = $end->child( 0 ) ) {
110 3         40 $end = $kid;
111             SIB:
112 3         26 while ( my $esib = $end->next_sibling() ) {
113 11         212 $end = $esib;
114 11 50 33     82 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         182 my $ending_line = $end->logical_line_number();
121 39         650 $self->{_effective_range} = [$annotation_line, $ending_line];
122 39         154 return $self;
123             }
124              
125             #-----------------------------------------------------------------------------
126              
127             sub element {
128 13     13 1 24 my ($self) = @_;
129 13         28 return $self->{_element};
130             }
131              
132             #-----------------------------------------------------------------------------
133              
134             sub effective_range {
135 101     101 1 204 my $self = shift;
136 101         146 return @{ $self->{_effective_range} };
  101         315  
137             }
138              
139             #-----------------------------------------------------------------------------
140              
141             sub disabled_policies {
142 44     44 1 68 my $self = shift;
143 44         75 return keys %{ $self->{_disabled_policies} };
  44         231  
144             }
145              
146             #-----------------------------------------------------------------------------
147              
148             sub disables_policy {
149 8     8 1 19 my ($self, $policy_name) = @_;
150 8 50       62 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 14566 my ($self) = @_;
159 126         460 return $self->{_disables_all_policies};
160             }
161              
162             #-----------------------------------------------------------------------------
163              
164             sub disables_line {
165 13     13 1 976 my ($self, $line_number) = @_;
166 13         22 my $effective_range = $self->{_effective_range};
167 13 50 33     120 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   219 my ( $annotation_element ) = @_;
177 99         239 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       1493 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       3705 $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     429 if ( $sib->isa( 'PPI::Node' ) &&
195             $sib->logical_line_number() < $annotation_line
196             ) {
197 13         287 my $neighbor = $sib;
198 13   66     104 while ( $neighbor->isa( 'PPI::Node' )
199             and my $kid = $neighbor->schild( $LAST_ELEMENT ) ) {
200 13         308 $neighbor = $kid;
201             }
202 13 100 66     101 if ( $neighbor &&
203             $neighbor->logical_line_number() == $annotation_line
204             ) {
205 1         18 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         232 return 0;
212             }
213              
214             #-----------------------------------------------------------------------------
215              
216             sub _parse_annotation {
217              
218 102     102   237 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         345 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         257 my @disabled_policy_names = ();
240 102 100       496 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         690 my @policy_name_patterns = grep { $_ ne $EMPTY }
  95         287  
246             split m{\s *[,\s] \s*}xms, $patterns_string;
247 55         140 my $re = join $PIPE, map {"(?:$_)"} @policy_name_patterns;
  92         298  
248 55         209 my @site_policy_names = Perl::Critic::PolicyFactory::site_policy_names();
249 55         162 @disabled_policy_names = grep {m/$re/ixms} @site_policy_names;
  7975         23155  
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       416 if (not @disabled_policy_names) {
259 14         98 @disabled_policy_names = @policy_name_patterns;
260             }
261             }
262              
263 102         670 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 :