File Coverage

blib/lib/Perl/ToPerl6/Transformation.pm
Criterion Covered Total %
statement 145 147 98.6
branch 24 30 80.0
condition 16 26 61.5
subroutine 45 47 95.7
pod 20 20 100.0
total 250 270 92.5


line stmt bran cond sub pod time code
1             package Perl::ToPerl6::Transformation;
2              
3 26     26   1701 use 5.006001;
  26         75  
4 26     26   116 use strict;
  26         34  
  26         521  
5 26     26   98 use warnings;
  26         37  
  26         894  
6              
7 26     26   117 use English qw< -no_match_vars >;
  26         34  
  26         175  
8 26     26   9255 use Readonly;
  26         37  
  26         1324  
9              
10 26     26   130 use File::Basename qw< basename >;
  26         34  
  26         1612  
11 26     26   131 use IO::String qw< >;
  26         35  
  26         392  
12 26     26   1863 use Pod::PlainText qw< >;
  26         18950  
  26         474  
13 26     26   117 use Scalar::Util qw< blessed >;
  26         43  
  26         1254  
14 26     26   1740 use String::Format qw< stringf >;
  26         1578  
  26         1297  
15              
16 26     26   123 use overload ( q{""} => 'to_string', cmp => '_compare' );
  26         60  
  26         205  
17              
18 26     26   1908 use Perl::ToPerl6::Utils qw< :characters :internal_lookup >;
  26         66  
  26         1458  
19 26         1566 use Perl::ToPerl6::Utils::POD qw<
20             get_pod_section_for_module
21             trim_pod_section
22 26     26   7265 >;
  26         43  
23 26     26   1558 use Perl::ToPerl6::Exception::Fatal::Internal qw< throw_internal >;
  26         42  
  26         24007  
24              
25             our $VERSION = '0.03';
26              
27              
28             Readonly::Scalar my $LOCATION_LINE_NUMBER => 0;
29             Readonly::Scalar my $LOCATION_COLUMN_NUMBER => 1;
30             Readonly::Scalar my $LOCATION_VISUAL_COLUMN_NUMBER => 2;
31             Readonly::Scalar my $LOCATION_LOGICAL_LINE_NUMBER => 3;
32             Readonly::Scalar my $LOCATION_LOGICAL_FILENAME => 4;
33              
34              
35             # Class variables...
36             my $format = "%m at line %l, column %c. %e.\n"; # Default stringy format
37             my %diagnostics = (); # Cache of diagnostic messages
38              
39             #-----------------------------------------------------------------------------
40              
41             Readonly::Scalar my $CONSTRUCTOR_ARG_COUNT => 5;
42              
43             sub new {
44 39     39 1 23519 my ( $class, $desc, $expl, $elem, $sev ) = @_;
45              
46             # Check arguments to help out developers who might
47             # be creating new Perl::ToPerl6::Transformer modules.
48              
49 39 100       110 if ( @_ != $CONSTRUCTOR_ARG_COUNT ) {
50 1         6 throw_internal 'Wrong number of args to Transformation->new()';
51             }
52              
53 38 100       52 if ( eval { $elem->isa( 'Perl::ToPerl6::Document' ) } ) {
  38         219  
54             # break the facade, return the real PPI::Document
55 1         5 $elem = $elem->ppi_document();
56             }
57              
58 38 100       51 if ( not eval { $elem->isa( 'PPI::Element' ) } ) {
  38         132  
59 1         3 throw_internal '3rd arg to Transformation->new() must be a PPI::Element';
60             }
61              
62             # Strip punctuation. These are controlled by the user via the
63             # formats. He/She can use whatever makes sense to them.
64 37         95 ($desc, $expl) = _chomp_periods($desc, $expl);
65              
66             # Create object
67 37         105 my $self = bless {}, $class;
68 37         233 $self->{_description} = $desc;
69 37         59 $self->{_explanation} = $expl;
70 37         50 $self->{_severity} = $sev;
71 37         77 $self->{_transformer} = caller;
72              
73             # PPI eviscerates the Elements in a Document when the Document gets
74             # DESTROY()ed, and thus they aren't useful after it is gone. So we have
75             # to preemptively grab everything we could possibly want.
76 37         136 $self->{_element_class} = blessed $elem;
77              
78 37         182 my $top = $elem->top();
79 37 100       511 $self->{_filename} = $top->can('filename') ? $top->filename() : undef;
80 37         76 $self->{_source} = _line_containing_transformation( $elem );
81             $self->{_location} =
82 37   50     249 $elem->location() || [ 0, 0, 0, 0, $self->filename() ];
83              
84 37         558 return $self;
85             }
86              
87             #-----------------------------------------------------------------------------
88              
89 19     19 1 6251 sub set_format { return $format = verbosity_to_format( $_[0] ); }
90 6     6 1 23 sub get_format { return $format; }
91              
92             #-----------------------------------------------------------------------------
93              
94             sub sort_by_location {
95              
96 5 50   5 1 22 ref $_[0] || shift; # Can call as object or class method
97 5 50       12 return scalar @_ if ! wantarray; # In case we are called in scalar context
98              
99             ## TODO: What if $a and $b are not Transformation objects?
100             return
101 23         72 map {$_->[0]}
102 38 50       74 sort { ($a->[1] <=> $b->[1]) || ($a->[2] <=> $b->[2]) }
103 5   50     13 map {[$_, $_->location->[0] || 0, $_->location->[1] || 0]}
  23   50     36  
104             @_;
105             }
106              
107             #-----------------------------------------------------------------------------
108              
109             sub sort_by_severity {
110              
111 1 50   1 1 9 ref $_[0] || shift; # Can call as object or class method
112 1 50       3 return scalar @_ if ! wantarray; # In case we are called in scalar context
113              
114             ## TODO: What if $a and $b are not Transformation objects?
115             return
116 6         14 map {$_->[0]}
117 9         9 sort { $a->[1] <=> $b->[1] }
118 1   100     3 map {[$_, $_->severity() || 0]}
  6         7  
119             @_;
120             }
121              
122             #-----------------------------------------------------------------------------
123              
124             sub location {
125 101     101 1 100 my $self = shift;
126              
127 101         324 return $self->{_location};
128             }
129              
130             #-----------------------------------------------------------------------------
131              
132             sub line_number {
133 4     4 1 8 my ($self) = @_;
134              
135 4         9 return $self->location()->[$LOCATION_LINE_NUMBER];
136             }
137              
138             #-----------------------------------------------------------------------------
139              
140             sub logical_line_number {
141 14     14 1 18 my ($self) = @_;
142              
143 14         23 return $self->location()->[$LOCATION_LOGICAL_LINE_NUMBER];
144             }
145              
146             #-----------------------------------------------------------------------------
147              
148             sub column_number {
149 2     2 1 5 my ($self) = @_;
150              
151 2         6 return $self->location()->[$LOCATION_COLUMN_NUMBER];
152             }
153              
154             #-----------------------------------------------------------------------------
155              
156             sub visual_column_number {
157 12     12 1 15 my ($self) = @_;
158              
159 12         15 return $self->location()->[$LOCATION_VISUAL_COLUMN_NUMBER];
160             }
161              
162             #-----------------------------------------------------------------------------
163              
164             sub diagnostics {
165 5     5 1 41 my ($self) = @_;
166 5         11 my $transformer = $self->transformer();
167              
168 5 100       14 if ( not $diagnostics{$transformer} ) {
169 2         7 eval {
170 2   33     9 my $module_name = ref $transformer || $transformer;
171 2         11 $diagnostics{$transformer} =
172             trim_pod_section(
173             get_pod_section_for_module( $module_name, 'DESCRIPTION' )
174             );
175             };
176 2   100     22 $diagnostics{$transformer} ||= " No diagnostics available\n";
177             }
178 5         107 return $diagnostics{$transformer};
179             }
180              
181             #-----------------------------------------------------------------------------
182              
183             sub description {
184 24     24 1 81 my $self = shift;
185 24         49 return $self->{_description};
186             }
187              
188             #-----------------------------------------------------------------------------
189              
190             sub explanation {
191 26     26 1 39 my $self = shift;
192 26         27 my $expl = $self->{_explanation};
193 26 100       44 if ( !$expl ) {
194 20         22 $expl = '(no explanation)';
195             }
196 26 100       48 if ( ref $expl eq 'ARRAY' ) {
197 2 100       2 my $page = @{$expl} > 1 ? 'pages' : 'page';
  2         7  
198 2         4 $page .= $SPACE . join $COMMA, @{$expl};
  2         7  
199 2         6 $expl = "See $page of PBP";
200             }
201 26         54 return $expl;
202             }
203              
204             #-----------------------------------------------------------------------------
205              
206             sub severity {
207 55     55 1 57 my $self = shift;
208 55         315 return $self->{_severity};
209             }
210              
211             #-----------------------------------------------------------------------------
212              
213             sub transformer {
214 48     48 1 46 my $self = shift;
215 48         259 return $self->{_transformer};
216             }
217              
218             #-----------------------------------------------------------------------------
219              
220             sub filename {
221 4     4 1 5 my $self = shift;
222 4         51 return $self->{_filename};
223             }
224              
225             #-----------------------------------------------------------------------------
226              
227             sub logical_filename {
228 4     4 1 5 my ($self) = @_;
229              
230 4         7 return $self->location()->[$LOCATION_LOGICAL_FILENAME];
231             }
232              
233             #-----------------------------------------------------------------------------
234              
235             sub source {
236 4     4 1 7 my $self = shift;
237 4         15 return $self->{_source};
238             }
239              
240             #-----------------------------------------------------------------------------
241              
242             sub element_class {
243 2     2 1 4 my ($self) = @_;
244              
245 2         8 return $self->{_element_class};
246             }
247              
248             #-----------------------------------------------------------------------------
249              
250             sub to_string {
251 22     22 1 2996 my $self = shift;
252              
253 22         37 my $long_transformer = $self->transformer();
254 22         28 (my $short_transformer = $long_transformer) =~ s/ \A Perl::ToPerl6::Transformer:: //xms;
255              
256             # Wrap the more expensive ones in sub{} to postpone evaluation
257             my %fspec = (
258 2     2   62 'f' => sub { $self->logical_filename() },
259 2     2   68 'F' => sub { basename( $self->logical_filename() ) },
260 2     2   60 'g' => sub { $self->filename() },
261 2     2   60 'G' => sub { basename( $self->filename() ) },
262 12     12   474 'l' => sub { $self->logical_line_number() },
263 2     2   58 'L' => sub { $self->line_number() },
264 10     10   173 'c' => sub { $self->visual_column_number() },
265 0     0   0 'C' => sub { $self->element_class() },
266             'm' => $self->description(),
267             'e' => $self->explanation(),
268             's' => $self->severity(),
269 2     2   93 'd' => sub { $self->diagnostics() },
270 2     2   31 'r' => sub { $self->source() },
271 22         187 'P' => $long_transformer,
272             'p' => $short_transformer,
273             );
274 22         100 return stringf($format, %fspec);
275             }
276              
277             #-----------------------------------------------------------------------------
278             # Apparently, some perls do not implicitly stringify overloading
279             # objects before doing a comparison. This causes a couple of our
280             # sorting tests to fail. To work around this, we overload C<cmp> to
281             # do it explicitly.
282             #
283             # 20060503 - More information: This problem has been traced to
284             # Test::Simple versions <= 0.60, not perl itself. Upgrading to
285             # Test::Simple v0.62 will fix the problem. But rather than forcing
286             # everyone to upgrade, I have decided to leave this workaround in
287             # place.
288              
289 0     0   0 sub _compare { return "$_[0]" cmp "$_[1]" }
290              
291             #-----------------------------------------------------------------------------
292              
293             sub _line_containing_transformation {
294 37     37   49 my ( $elem ) = @_;
295              
296 37   66     139 my $stmnt = $elem->statement() || $elem;
297 37   33     567 my $code_string = $stmnt->content() || $EMPTY;
298              
299             # Split into individual lines
300 37         1618 my @lines = split qr{ \n\s* }xms, $code_string;
301              
302             # Take the line containing the element that is in transformation
303 37   50     186 my $inx = ( $elem->line_number() || 0 ) -
      50        
304             ( $stmnt->line_number() || 0 );
305 37 50       2890 $inx > @lines and return $EMPTY;
306 37         153 return $lines[$inx];
307             }
308              
309             #-----------------------------------------------------------------------------
310              
311             sub _chomp_periods {
312 38     38   468 my @args = @_;
313              
314 38         67 for (@args) {
315 80 100 100     296 next if not defined or ref;
316 74         164 s{ [.]+ \z }{}xms
317             }
318              
319 38         89 return @args;
320             }
321              
322             #-----------------------------------------------------------------------------
323              
324             1;
325              
326             #-----------------------------------------------------------------------------
327              
328             __END__
329              
330             =head1 NAME
331              
332             Perl::ToPerl6::Transformation - A transformation of a Transformer found in some source code.
333              
334              
335             =head1 SYNOPSIS
336              
337             use PPI;
338             use Perl::ToPerl6::Transformation;
339              
340             my $elem = $doc->child(0); # $doc is a PPI::Document object
341             my $desc = 'Offending code'; # Describe the transformation
342             my $expl = [1,45,67]; # Page numbers from PBP
343             my $sev = 5; # Severity level of this transformation
344              
345             my $vio = Perl::ToPerl6::Transformation->new($desc, $expl, $node, $sev);
346              
347              
348             =head1 DESCRIPTION
349              
350             Perl::ToPerl6::Transformation is the generic representation of an individual
351             Transformer transformation. Its primary purpose is to provide an abstraction
352             layer so that clients of L<Perl::ToPerl6|Perl::ToPerl6> don't have to
353             know anything about L<PPI|PPI>. The C<transformations> method of all
354             L<Perl::ToPerl6::Transformer|Perl::ToPerl6::Transformer> subclasses must return a
355             list of these Perl::ToPerl6::Transformation objects.
356              
357              
358             =head1 INTERFACE SUPPORT
359              
360             This is considered to be a public class. Any changes to its interface
361             will go through a deprecation cycle.
362              
363              
364             =head1 CONSTRUCTOR
365              
366             =over
367              
368             =item C<new( $description, $explanation, $element, $severity )>
369              
370             Returns a reference to a new C<Perl::ToPerl6::Transformation> object. The
371             arguments are a description of the transformation (as string), an
372             explanation for the transformer (as string) or a series of page numbers in
373             PBP (as an ARRAY ref), a reference to the L<PPI|PPI> element that
374             caused the transformation, and the severity of the transformation (as an
375             integer).
376              
377              
378             =back
379              
380              
381             =head1 METHODS
382              
383             =over
384              
385             =item C<description()>
386              
387             Returns a brief description of the specific transformation. In other
388             words, this value may change on a per transformation basis.
389              
390              
391             =item C<explanation()>
392              
393             Returns an explanation of the transformer as a string or as reference to an
394             array of page numbers in PBP. This value will generally not change
395             based upon the specific code violating the transformer.
396              
397              
398             =item C<location()>
399              
400             Don't use this method. Use the C<line_number()>,
401             C<logical_line_number()>, C<column_number()>,
402             C<visual_column_number()>, and C<logical_filename()> methods instead.
403              
404             Returns a five-element array reference containing the line and real &
405             virtual column and logical numbers and logical file name where this
406             Transformation occurred, as in L<PPI::Element|PPI::Element>.
407              
408              
409             =item C<line_number()>
410              
411             Returns the physical line number that the transformation was found on.
412              
413              
414             =item C<logical_line_number()>
415              
416             Returns the logical line number that the transformation was found on. This
417             can differ from the physical line number when there were C<#line>
418             directives in the code.
419              
420              
421             =item C<column_number()>
422              
423             Returns the physical column that the transformation was found at. This
424             means that hard tab characters count as a single character.
425              
426              
427             =item C<visual_column_number()>
428              
429             Returns the column that the transformation was found at, as it would appear
430             if hard tab characters were expanded, based upon the value of
431             L<PPI::Document/"tab_width [ $width ]">.
432              
433              
434             =item C<filename()>
435              
436             Returns the path to the file where this Transformation occurred. In some
437             cases, the path may be undefined because the source code was not read
438             directly from a file.
439              
440              
441             =item C<logical_filename()>
442              
443             Returns the logical path to the file where the Transformation occurred.
444             This can differ from C<filename()> when there was a C<#line> directive
445             in the code.
446              
447              
448             =item C<severity()>
449              
450             Returns the severity of this Transformation as an integer ranging from 1 to
451             5, where 5 is the "most" severe.
452              
453              
454             =item C<sort_by_severity( @transformation_objects )>
455              
456             If you need to sort Transformations by severity, use this handy routine:
457              
458             @sorted = Perl::ToPerl6::Transformation::sort_by_severity(@transformations);
459              
460              
461             =item C<sort_by_location( @transformation_objects )>
462              
463             If you need to sort Transformations by location, use this handy routine:
464              
465             @sorted = Perl::ToPerl6::Transformation::sort_by_location(@transformations);
466              
467              
468             =item C<diagnostics()>
469              
470             Returns a formatted string containing a full discussion of the
471             motivation for and details of the Transformer module that created this
472             Transformation. This information is automatically extracted from the
473             C<DESCRIPTION> section of the Transformer module's POD.
474              
475              
476             =item C<transformer()>
477              
478             Returns the name of the L<Perl::ToPerl6::Transformer|Perl::ToPerl6::Transformer>
479             that created this Transformation.
480              
481              
482             =item C<source()>
483              
484             Returns the string of source code that caused this exception. If the
485             code spans multiple lines (e.g. multi-line statements, subroutines or
486             other blocks), then only the line containing the transformation will be
487             returned.
488              
489              
490             =item C<element_class()>
491              
492             Returns the L<PPI::Element|PPI::Element> subclass of the code that caused this
493             exception.
494              
495              
496             =item C<set_format( $format )>
497              
498             Class method. Sets the format for all Transformation objects when they are
499             evaluated in string context. The default is C<'%d at line %l, column
500             %c. %e'>. See L<"OVERLOADS"> for formatting options.
501              
502              
503             =item C<get_format()>
504              
505             Class method. Returns the current format for all Transformation objects
506             when they are evaluated in string context.
507              
508              
509             =item C<to_string()>
510              
511             Returns a string representation of this transformation. The content of the
512             string depends on the current value of the C<$format> package
513             variable. See L<"OVERLOADS"> for the details.
514              
515              
516             =back
517              
518              
519             =head1 OVERLOADS
520              
521             Perl::ToPerl6::Transformation overloads the C<""> operator to produce neat
522             little messages when evaluated in string context.
523              
524             Formats are a combination of literal and escape characters similar to
525             the way C<sprintf> works. If you want to know the specific formatting
526             capabilities, look at L<String::Format|String::Format>. Valid escape
527             characters are:
528              
529             Escape Meaning
530             ------- ----------------------------------------------------------------
531             %c Column number where the transformation occurred
532             %d Full diagnostic discussion of the transformation (DESCRIPTION in POD)
533             %e Explanation of transformation or page numbers in PBP
534             %F Just the name of the logical file where the transformation occurred.
535             %f Path to the logical file where the transformation occurred.
536             %G Just the name of the physical file where the transformation occurred.
537             %g Path to the physical file where the transformation occurred.
538             %l Logical line number where the transformation occurred
539             %L Physical line number where the transformation occurred
540             %m Brief description of the transformation
541             %P Full name of the Transformer module that created the transformation
542             %p Name of the Transformer without the Perl::ToPerl6::Transformer:: prefix
543             %r The string of source code that caused the transformation
544             %C The class of the PPI::Element that caused the transformation
545             %s The severity level of the transformation
546              
547             Explanation of the C<%F>, C<%f>, C<%G>, C<%G>, C<%l>, and C<%L> formats:
548             Using C<#line> directives, you can affect what perl thinks the current line
549             number and file name are; see L<perlsyn/Plain Old Comments (Not!)> for
550             the details. Under normal circumstances, the values of C<%F>, C<%f>, and
551             C<%l> will match the values of C<%G>, C<%g>, and C<%L>, respectively. In the
552             presence of a C<#line> directive, the values of C<%F>, C<%f>, and C<%l> will
553             change to take that directive into account. The values of C<%G>, C<%g>, and
554             C<%L> are unaffected by those directives.
555              
556             Here are some examples:
557              
558             Perl::ToPerl6::Transformation::set_format("%m at line %l, column %c.\n");
559             # looks like "Mixed case variable name at line 6, column 23."
560              
561             Perl::ToPerl6::Transformation::set_format("%m near '%r'\n");
562             # looks like "Mixed case variable name near 'my $theGreatAnswer = 42;'"
563              
564             Perl::ToPerl6::Transformation::set_format("%l:%c:%p\n");
565             # looks like "6:23:NamingConventions::Capitalization"
566              
567             Perl::ToPerl6::Transformation::set_format("%m at line %l. %e. \n%d\n");
568             # looks like "Mixed case variable name at line 6. See page 44 of PBP.
569             Conway's recommended naming convention is to use lower-case words
570             separated by underscores. Well-recognized acronyms can be in ALL
571             CAPS, but must be separated by underscores from other parts of the
572             name."
573              
574              
575             =head1 AUTHOR
576              
577             Jeffrey Ryan Thalhammer <jeff@imaginative-software.com>
578              
579              
580             =head1 COPYRIGHT
581              
582             Copyright (c) 2005-2011 Imaginative Software Systems. All rights reserved.
583              
584             This program is free software; you can redistribute it and/or modify
585             it under the same terms as Perl itself. The full text of this license
586             can be found in the LICENSE file included with this module.
587              
588             =cut
589              
590             # Local Variables:
591             # mode: cperl
592             # cperl-indent-level: 4
593             # fill-column: 78
594             # indent-tabs-mode: nil
595             # c-indentation-style: bsd
596             # End:
597             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :