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   121792 use 5.010001;
  40         203  
4 40     40   287 use strict;
  40         122  
  40         911  
5 40     40   256 use warnings;
  40         152  
  40         1310  
6              
7 40     40   344 use Carp qw(confess);
  40         146  
  40         2614  
8              
9 40     40   1282 use Perl::Critic::PolicyFactory;
  40         116  
  40         398  
10 40     40   365 use Perl::Critic::Utils qw(:characters hashify);
  40         156  
  40         2359  
11 40     40   9463 use Readonly;
  40         142  
  40         57415  
12              
13             #-----------------------------------------------------------------------------
14              
15             our $VERSION = '1.150';
16              
17             Readonly::Scalar my $LAST_ELEMENT => -1;
18              
19             #=============================================================================
20             # CLASS methods
21              
22             sub create_annotations {
23 88     88 1 274 my ($class, $doc) = @_;
24              
25 88         173 my @annotations;
26 88   100     687 my $comment_elements_ref = $doc->find('PPI::Token::Comment') || return;
27 79         314 my $annotation_rx = qr{\A (?: [#]! .*? )? \s* [#][#] \s* no \s+ critic}xms;
28 79         156 for my $annotation_element ( grep { m/$annotation_rx/smx } @{$comment_elements_ref} ) {
  191         1194  
  79         182  
29 72         516 push @annotations, Perl::Critic::Annotation->new( -element => $annotation_element);
30             }
31              
32 79         499 return @annotations;
33             }
34              
35             #-----------------------------------------------------------------------------
36              
37             sub new {
38 72     72 1 173 my ($class, @args) = @_;
39 72         142 my $self = bless {}, $class;
40 72         237 $self->_init(@args);
41 72         224 return $self;
42             }
43              
44             #=============================================================================
45             # OBJECT methods
46              
47             sub _init {
48 72     72   163 my ($self, %args) = @_;
49 72   33     221 my $annotation_element = $args{-element} || confess '-element argument is required';
50 72         162 $self->{_element} = $annotation_element;
51              
52 72         188 my %disabled_policies = _parse_annotation( $annotation_element );
53 72 100       271 $self->{_disables_all_policies} = %disabled_policies ? 0 : 1;
54 72         183 $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         231 my $annotation_line = $annotation_element->logical_line_number();
60 72         1281 my $parent = $annotation_element->parent();
61 72 50       533 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       300 if ( $annotation_element =~ m{\A [#]!}xms) {
66 2         15 $self->{_effective_range} = [$annotation_line, $annotation_line];
67 2         7 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       390 if ( _is_single_line_annotation_on_simple_statement( $annotation_element )
73             ) {
74 35         705 $self->{_effective_range} = [$annotation_line, $annotation_line];
75 35         88 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       887 if ( ref $parent eq 'PPI::Structure::Block' ) {
81 11 50 66     71 if ( ref $grandparent eq 'PPI::Statement::Compound'
82             || ref $grandparent eq 'PPI::Statement::Sub' ) {
83 11 100       48 if ( $parent->logical_line_number() == $annotation_line ) {
84 6         159 my $grandparent_line = $grandparent->logical_line_number();
85 6         118 $self->{_effective_range} = [$grandparent_line, $grandparent_line];
86 6         15 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         211 my $end = $annotation_element;
97 29         115 my $use_critic = qr{\A \s* [#][#] \s* use \s+ critic}xms;
98              
99             SIB:
100 29         124 while ( my $esib = $end->next_sibling() ) {
101 165         4065 $end = $esib; # keep track of last sibling encountered in this scope
102 165 100 100     735 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     724 if ( $end->isa( 'PPI::Statement::End' ) and my $kid = $end->child( 0 ) ) {
109 1         26 $end = $kid;
110             SIB:
111 1         9 while ( my $esib = $end->next_sibling() ) {
112 3         54 $end = $esib;
113 3 50 33     27 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         111 my $ending_line = $end->logical_line_number();
120 29         460 $self->{_effective_range} = [$annotation_line, $ending_line];
121 29         99 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 132 my $self = shift;
135 71         108 return @{ $self->{_effective_range} };
  71         190  
136             }
137              
138             #-----------------------------------------------------------------------------
139              
140             sub disabled_policies {
141 29     29 1 38 my $self = shift;
142 29         35 return keys %{ $self->{_disabled_policies} };
  29         123  
143             }
144              
145             #-----------------------------------------------------------------------------
146              
147             sub disables_policy {
148 8     8 1 21 my ($self, $policy_name) = @_;
149 8 50       69 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 14716 my ($self) = @_;
158 71         264 return $self->{_disables_all_policies};
159             }
160              
161             #-----------------------------------------------------------------------------
162              
163             sub disables_line {
164 13     13 1 1013 my ($self, $line_number) = @_;
165 13         30 my $effective_range = $self->{_effective_range};
166 13 50 33     129 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   136 my ( $annotation_element ) = @_;
176 70         138 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       883 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       2579 $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     374 if ( $sib->isa( 'PPI::Node' ) &&
194             $sib->logical_line_number() < $annotation_line
195             ) {
196 12         223 my $neighbor = $sib;
197 12   66     79 while ( $neighbor->isa( 'PPI::Node' )
198             and my $kid = $neighbor->schild( $LAST_ELEMENT ) ) {
199 12         228 $neighbor = $kid;
200             }
201 12 100 66     97 if ( $neighbor &&
202             $neighbor->logical_line_number() == $annotation_line
203             ) {
204 1         20 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         263 return 0;
211             }
212              
213             #-----------------------------------------------------------------------------
214              
215             sub _parse_annotation {
216              
217 72     72   141 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         273 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         119 my @disabled_policy_names;
239 72 100       342 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         431 my @policy_name_patterns = grep { $_ ne $EMPTY }
  80         208  
245             split m{\s *[,\s] \s*}xms, $patterns_string;
246 40         83 my $re = join $PIPE, map {"(?:$_)"} @policy_name_patterns;
  77         214  
247 40         150 my @site_policy_names = Perl::Critic::PolicyFactory::site_policy_names();
248 40         104 @disabled_policy_names = grep {m/$re/ixms} @site_policy_names;
  5800         15765  
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       285 if (not @disabled_policy_names) {
258 2         16 @disabled_policy_names = @policy_name_patterns;
259             }
260             }
261              
262 72         378 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 :