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 35     35   247 use strict;
  35         98  
  35         1568  
4 35     35   181 use warnings;
  35         63  
  35         2527  
5              
6 35     35   17285 use Code::TidyAll::Util::Zglob qw(zglobs_to_regex);
  35         117  
  35         2984  
7 35     35   18815 use File::Which qw(which);
  35         54574  
  35         2734  
8 35     35   19117 use IPC::Run3 qw(run3);
  35         517120  
  35         3142  
9 35     35   366 use Scalar::Util qw(weaken);
  35         76  
  35         2009  
10 35     35   246 use Specio::Declare;
  35         81  
  35         483  
11 35     35   8943 use Specio::Library::Builtins;
  35         96  
  35         464  
12 35     35   389534 use Specio::Library::Numeric;
  35         497368  
  35         541  
13 35     35   301519 use Specio::Library::String;
  35         88  
  35         342  
14 35     35   91583 use Text::Diff 1.44 qw(diff);
  35         117556  
  35         3877  
15              
16 35     35   317 use Moo;
  35         105  
  35         460  
17              
18             our $VERSION = '0.85';
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 95     95   1346 my ($self) = @_;
116 95         2270 return $self->_parse_zglob_list( $self->select );
117             }
118              
119             sub _build_select_regex {
120 65     65   1079 my ($self) = @_;
121 65         149 return zglobs_to_regex( @{ $self->selects } );
  65         1605  
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 34     34   353 my ($self) = @_;
131 34 100 66     846 return ( $self->can('validate_source') || $self->can('validate_file') ) ? 1 : 0;
132             }
133              
134             # default weight
135             sub _build_weight {
136 34     34   1124 my ($self) = @_;
137 34 100       786 return 60 if $self->is_validator;
138 30         1478 return 50;
139             }
140              
141             sub BUILD {
142 99     99 0 18383 my ( $self, $params ) = @_;
143              
144             # Strict constructor
145             #
146 99         591 $self->validate_params($params);
147             }
148              
149             sub validate_params {
150 99     99 0 380 my ( $self, $params ) = @_;
151              
152 99         243 delete( $params->{only_modes} );
153 99         240 delete( $params->{except_modes} );
154 99 100       448 if ( my @bad_params = grep { !$self->can($_) } keys(%$params) ) {
  372         5536  
155             die sprintf(
156             q{unknown option%s %s for plugin '%s'},
157             @bad_params > 1 ? 's' : q{},
158 2 100       12 join( ', ', sort map {qq['$_']} @bad_params ),
  3         132  
159             $self->name
160             );
161             }
162             }
163              
164             # No-ops by default; may be overridden in subclass
165             sub preprocess_source {
166 138     138 1 532 return $_[1];
167             }
168              
169             sub postprocess_source {
170 120     120 1 337 return $_[1];
171             }
172              
173             sub process_source_or_file {
174 139     139 0 515 my ( $self, $orig_source, $rel_path, $check_only ) = @_;
175              
176 139         370 my $new_source = $orig_source;
177 139 100       1025 if ( $self->can('transform_source') ) {
178 112         960 foreach my $iter ( 1 .. $self->tidyall->iterations ) {
179 113         630 $new_source = $self->transform_source($new_source);
180             }
181             }
182 131 100       3873 if ( $self->can('transform_file') ) {
183 11         47 my $tempfile = $self->_write_temp_file( $rel_path, $new_source );
184 11         205 foreach my $iter ( 1 .. $self->tidyall->iterations ) {
185 11         82 $self->transform_file($tempfile);
186             }
187 10         5826 $new_source = $tempfile->slurp_raw;
188             }
189 130 100       3035 if ( $self->can('validate_source') ) {
190 4         24 $self->validate_source($new_source);
191             }
192 129 100       926 if ( $self->can('validate_file') ) {
193 10         65 my $tempfile = $self->_write_temp_file( $rel_path, $new_source );
194 10         62 $self->validate_file($tempfile);
195             }
196              
197 124         385 my $diff;
198 124 100 66     516 if ( $check_only && $new_source ne $orig_source ) {
199 5         56 $diff = $self->_maybe_diff( $orig_source, $new_source, $rel_path );
200             }
201              
202 124         2424 return ( $new_source, $diff );
203             }
204              
205             sub _maybe_diff {
206 5     5   16 my $self = shift;
207              
208 5 100       33 return unless $self->diff_on_tidy_error;
209              
210 2         3 my $orig = shift;
211 2         5 my $new = shift;
212 2         3 my $rel_path = shift;
213              
214 2         11 my $orig_file = $self->_write_temp_file( $rel_path . '.orig', $orig );
215 2         7 my $new_file = $self->_write_temp_file( $rel_path . '.new', $new );
216              
217 2         28 return diff( $orig_file->stringify, $new_file->stringify, { Style => 'Unified' } );
218             }
219              
220             sub _write_temp_file {
221 25     25   127 my ( $self, $rel_path, $source ) = @_;
222              
223 25         1017 my $tempfile = $self->tidyall->_tempdir->child($rel_path);
224 25         26434 $tempfile->parent->mkpath( { mode => 0755 } );
225 25         4674 $tempfile->spew_raw($source);
226 25         27104 return $tempfile;
227             }
228              
229             sub matches_path {
230 69     69 0 707 my ( $self, $path ) = @_;
231              
232             return
233 69   66     2319 $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.85
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 - 2025 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