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