File Coverage

blib/lib/Code/TidyAll/Plugin.pm
Criterion Covered Total %
statement 91 94 96.8
branch 20 22 90.9
condition 6 12 50.0
subroutine 24 26 92.3
pod 2 6 33.3
total 143 160 89.3


line stmt bran cond sub pod time code
1              
2             use strict;
3 27     27   171 use warnings;
  27         52  
  27         815  
4 27     27   130  
  27         63  
  27         750  
5             use Code::TidyAll::Util::Zglob qw(zglobs_to_regex);
6 27     27   9492 use File::Which qw(which);
  27         71  
  27         1491  
7 27     27   10408 use IPC::Run3 qw(run3);
  27         23150  
  27         1364  
8 27     27   11781 use Scalar::Util qw(weaken);
  27         249008  
  27         1802  
9 27     27   231 use Specio::Declare;
  27         55  
  27         1030  
10 27     27   193 use Specio::Library::Builtins;
  27         58  
  27         245  
11 27     27   5269 use Specio::Library::Numeric;
  27         54  
  27         237  
12 27     27   226223 use Specio::Library::String;
  27         297022  
  27         206  
13 27     27   183955 use Text::Diff 1.44 qw(diff);
  27         69  
  27         230  
14 27     27   50185  
  27         21528  
  27         1992  
15             use Moo;
16 27     27   187  
  27         77  
  27         236  
17             our $VERSION = '0.82';
18              
19             has argv => (
20             is => 'ro',
21             isa => t('Str'),
22             default => q{}
23             );
24              
25             # This really belongs in Code::TidyAll::Role::RunsCommand but moving it there
26             # breaks plugins not in the core distro that expect to just inherit from this
27             # module and be able to specify a cmd attribute.
28             has cmd => (
29             is => 'ro',
30             isa => t('NonEmptyStr'),
31             lazy => 1,
32             builder => '_build_cmd',
33             );
34              
35             has diff_on_tidy_error => (
36             is => 'ro',
37             isa => t('Bool'),
38             default => 0
39             );
40              
41             has is_tidier => (
42             is => 'lazy',
43             isa => t('Bool'),
44             );
45              
46             has is_validator => (
47             is => 'lazy',
48             isa => t('Bool'),
49             );
50              
51             has name => (
52             is => 'ro',
53             required => 1
54             );
55              
56             has select => (
57             is => 'lazy',
58             isa => t( 'ArrayRef', of => t('NonEmptyStr') ),
59             );
60              
61             has select_regex => (
62             is => 'lazy',
63             isa => t('RegexpRef'),
64             );
65              
66             has selects => (
67             is => 'lazy',
68             isa => t( 'ArrayRef', of => t('NonEmptyStr') ),
69             );
70              
71             has shebang => (
72             is => 'ro',
73             isa => t( 'ArrayRef', of => t('NonEmptyStr') ),
74             );
75              
76             has tidyall => (
77             is => 'ro',
78              
79             # This should be "object_isa_type( class => 'Code::TidyAll' )" but then
80             # we'd need to load Code::TidyAll, leading to a circular require
81             # situation.
82             isa => t('Object'),
83             required => 1,
84             weak_ref => 1
85             );
86              
87             has weight => (
88             is => 'lazy',
89             isa => t('PositiveOrZeroInt'),
90             );
91              
92             with 'Code::TidyAll::Role::HasIgnore';
93              
94             around BUILDARGS => sub {
95             my $orig = shift;
96             my $class = shift;
97              
98             my $args = $class->$orig(@_);
99              
100             for my $key (qw( ignore select shebang )) {
101             if ( defined $args->{$key} && !ref $args->{$key} ) {
102             $args->{$key} = [ $args->{$key} ];
103             }
104             }
105              
106             return $args;
107             };
108              
109             die 'no default cmd specified';
110             }
111 0     0   0  
112             my ($self) = @_;
113             return $self->_parse_zglob_list( $self->select );
114             }
115 74     74   706  
116 74         1086 my ($self) = @_;
117             return zglobs_to_regex( @{ $self->selects } );
118             }
119              
120 45     45   489 my ($self) = @_;
121 45         60 return ( $self->can('transform_source') || $self->can('transform_file') ) ? 1 : 0;
  45         699  
122             }
123              
124             my ($self) = @_;
125 0     0   0 return ( $self->can('validate_source') || $self->can('validate_file') ) ? 1 : 0;
126 0 0 0     0 }
127              
128             # default weight
129             my ($self) = @_;
130 22     22   185 return 60 if $self->is_validator;
131 22 100 66     396 return 50;
132             }
133              
134             my ( $self, $params ) = @_;
135              
136 22     22   581 # Strict constructor
137 22 100       341 #
138 18         637 $self->validate_params($params);
139             }
140              
141             my ( $self, $params ) = @_;
142 79     79 0 9110  
143             delete( $params->{only_modes} );
144             delete( $params->{except_modes} );
145             if ( my @bad_params = grep { !$self->can($_) } keys(%$params) ) {
146 79         319 die sprintf(
147             q{unknown option%s %s for plugin '%s'},
148             @bad_params > 1 ? 's' : q{},
149             join( ', ', sort map {qq['$_']} @bad_params ),
150 79     79 0 160 $self->name
151             );
152 79         142 }
153 79         117 }
154 79 100       247  
  291         2420  
155             # No-ops by default; may be overridden in subclass
156             return $_[1];
157             }
158 2 100       16  
  3         63  
159             return $_[1];
160             }
161              
162             my ( $self, $orig_source, $rel_path, $check_only ) = @_;
163              
164             my $new_source = $orig_source;
165             if ( $self->can('transform_source') ) {
166 97     97 1 255 foreach my $iter ( 1 .. $self->tidyall->iterations ) {
167             $new_source = $self->transform_source($new_source);
168             }
169             }
170 83     83 1 205 if ( $self->can('transform_file') ) {
171             my $tempfile = $self->_write_temp_file( $rel_path, $new_source );
172             foreach my $iter ( 1 .. $self->tidyall->iterations ) {
173             $self->transform_file($tempfile);
174 98     98 0 242 }
175             $new_source = $tempfile->slurp_raw;
176 98         136 }
177 98 100       352 if ( $self->can('validate_source') ) {
178 71         367 $self->validate_source($new_source);
179 72         246 }
180             if ( $self->can('validate_file') ) {
181             my $tempfile = $self->_write_temp_file( $rel_path, $new_source );
182 92 100       1688 $self->validate_file($tempfile);
183 11         35 }
184 11         64  
185 11         36 my $diff;
186             if ( $check_only && $new_source ne $orig_source ) {
187 10         5030 $diff = $self->_maybe_diff( $orig_source, $new_source, $rel_path );
188             }
189 91 100       2618  
190 4         10 return ( $new_source, $diff );
191             }
192 90 100       291  
193 10         33 my $self = shift;
194 10         35  
195             return unless $self->diff_on_tidy_error;
196              
197 85         235 my $orig = shift;
198 85 100 66     241 my $new = shift;
199 4         27 my $rel_path = shift;
200              
201             my $orig_file = $self->_write_temp_file( $rel_path . '.orig', $orig );
202 85         1116 my $new_file = $self->_write_temp_file( $rel_path . '.new', $new );
203              
204             return diff( $orig_file->stringify, $new_file->stringify, { Style => 'Unified' } );
205             }
206 4     4   10  
207             my ( $self, $rel_path, $source ) = @_;
208 4 100       51  
209             my $tempfile = $self->tidyall->_tempdir->child($rel_path);
210 1         3 $tempfile->parent->mkpath( { mode => 0755 } );
211 1         3 $tempfile->spew_raw($source);
212 1         2 return $tempfile;
213             }
214 1         5  
215 1         6 my ( $self, $path ) = @_;
216              
217 1         7 return
218             $path =~ $self->select_regex
219             && $path !~ $self->tidyall->ignore_regex
220             && $path !~ $self->ignore_regex;
221 23     23   65 }
222              
223 23         520 1;
224 23         12106  
225 23         2346 # ABSTRACT: Create plugins for tidying or validating code
226 23         8288  
227              
228             =pod
229              
230 49     49 0 104 =encoding UTF-8
231              
232             =head1 NAME
233 49   66     849  
234             Code::TidyAll::Plugin - Create plugins for tidying or validating code
235              
236             =head1 VERSION
237              
238             version 0.82
239              
240             =head1 SYNOPSIS
241              
242             package Code::TidyAll::Plugin::SomeTidier;
243             use Moo;
244             extends 'Code::TidyAll::Plugin';
245              
246             sub transform_source {
247             my ( $self, $source ) = @_;
248             ...
249             return $source;
250             }
251              
252              
253             package Code::TidyAll::Plugin::SomeValidator;
254             use Moo;
255             extends 'Code::TidyAll::Plugin';
256              
257             sub validate_file {
258             my ( $self, $file ) = @_;
259             die 'not valid' if ...;
260             }
261              
262             =head1 DESCRIPTION
263              
264             To use a tidier or validator with C<tidyall> it must have a corresponding
265             plugin class that inherits from this class. This document describes how to
266             implement a new plugin.
267              
268             The easiest way to start is to look at existing plugins, such as
269             L<Code::TidyAll::Plugin::PerlTidy> and L<Code::TidyAll::Plugin::PerlCritic>.
270              
271             =head1 NAMING
272              
273             If you are going to publicly release your plugin, call it C<<
274             Code::TidyAll::Plugin::I<something> >> so that users can find it easily and
275             refer to it by its short name in configuration.
276              
277             If it's an internal plugin, you can call it whatever you like and refer to it
278             with a plus sign prefix in the config file, e.g.
279              
280             [+My::Tidier::Class]
281             select = **/*.{pl,pm,t}
282              
283             =head1 CONSTRUCTOR AND ATTRIBUTES
284              
285             Your plugin constructor will be called with the configuration key/value pairs
286             as parameters. e.g. given
287              
288             [PerlCritic]
289             select = lib/**/*.pm
290             ignore = lib/UtterHack.pm
291             argv = -severity 3
292              
293             then L<Code::TidyAll::Plugin::PerlCritic> would be constructed with parameters
294              
295             Code::TidyAll::Plugin::PerlCritic->new(
296             select => 'lib/**/*.pm',
297             ignore => 'lib/UtterHack.pm',
298             argv => '-severity 3',
299             );
300              
301             The following attributes are part of this base class. Your subclass can declare
302             others, of course.
303              
304             =head2 argv
305              
306             A standard attribute for passing command line arguments.
307              
308             =head2 diff_on_tidy_error
309              
310             This only applies to plugins which transform source. If this is true, then when
311             the plugin is run in check mode it will include a diff in the return value from
312             C<process_source_or_file> when the source is not tidy.
313              
314             =head2 is_validator
315              
316             An attribute that indicates if this is a validator or not; By default this
317             returns true if either C<validate_source> or C<validate_file> methods have been
318             implemented.
319              
320             =head2 name
321              
322             Name of the plugin to be used in error messages etc.
323              
324             =head2 tidyall
325              
326             A weak reference back to the L<Code::TidyAll> object.
327              
328             =head2 weight
329              
330             A number indicating the relative weight of the plugin, used to calculate the
331             order the plugins will execute in. The lower the number the sooner the plugin
332             will be executed.
333              
334             By default the weight will be C<50> for non validators (anything where
335             C<is_validator> returns false) and C<60> for validators (anything where
336             C<is_validator> returns true.)
337              
338             The order of plugin execution is determined first by the value of the C<weight>
339             attribute, and then (if multiple plugins have the same weight>) by sorting by
340             the name of module.
341              
342             =head1 METHODS
343              
344             Your plugin may define one or more of these methods. They are all no-ops by
345             default.
346              
347             =head2 $plugin->preprocess_source($source)
348              
349             Receives source code as a string; returns the processed string, or dies with
350             error. This runs on all plugins I<before> any of the other methods.
351              
352             =head2 $plugin->transform_source($source)
353              
354             Receives source code as a string; returns the transformed string, or dies with
355             error. This is repeated multiple times if --iterations was passed or specified
356             in the configuration file.
357              
358             =head2 $plugin->transform_file($file)
359              
360             Receives filename; transforms the file in place, or dies with error. Note that
361             the file will be a temporary copy of the user's file with the same basename;
362             your changes will only propagate back if there was no error reported from any
363             plugin. This is repeated multiple times if --iterations was passed or specified
364             in the configuration file.
365              
366             =head2 $plugin->validate_source($source)
367              
368             Receives source code as a string; dies with error if invalid. Return value will
369             be ignored.
370              
371             =head2 $plugin->validate_file($file)
372              
373             Receives filename; validates file and dies with error if invalid. Should not
374             modify file! Return value will be ignored.
375              
376             =head2 $plugin->postprocess_source($source)
377              
378             Receives source code as a string; returns the processed string, or dies with
379             error. This runs on all plugins I<after> any of the other methods.
380              
381             =head1 SUPPORT
382              
383             Bugs may be submitted at L<https://github.com/houseabsolute/perl-code-tidyall/issues>.
384              
385             =head1 SOURCE
386              
387             The source code repository for Code-TidyAll can be found at L<https://github.com/houseabsolute/perl-code-tidyall>.
388              
389             =head1 AUTHORS
390              
391             =over 4
392              
393             =item *
394              
395             Jonathan Swartz <swartz@pobox.com>
396              
397             =item *
398              
399             Dave Rolsky <autarch@urth.org>
400              
401             =back
402              
403             =head1 COPYRIGHT AND LICENSE
404              
405             This software is copyright (c) 2011 - 2022 by Jonathan Swartz.
406              
407             This is free software; you can redistribute it and/or modify it under
408             the same terms as the Perl 5 programming language system itself.
409              
410             The full text of the license can be found in the
411             F<LICENSE> file included with this distribution.
412              
413             =cut