File Coverage

blib/lib/Perl/Critic/Annotation.pm
Criterion Covered Total %
statement 105 110 95.4
branch 30 38 78.9
condition 17 29 58.6
subroutine 17 18 94.4
pod 8 8 100.0
total 177 203 87.1


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