File Coverage

lib/App/PRT/Command/RenameClass.pm
Criterion Covered Total %
statement 124 124 100.0
branch 46 52 88.4
condition 3 3 100.0
subroutine 17 17 100.0
pod 0 6 0.0
total 190 202 94.0


line stmt bran cond sub pod time code
1             package App::PRT::Command::RenameClass;
2 3     3   2825 use strict;
  3         8  
  3         120  
3 3     3   17 use warnings;
  3         6  
  3         92  
4 3     3   985 use PPI;
  3         154676  
  3         99  
5 3     3   4130 use App::PRT::Util::DestinationFile;
  3         7  
  3         94  
6 3     3   18 use Path::Class;
  3         7  
  3         4039  
7              
8             sub new {
9 14     14 0 177637 my ($class) = @_;
10 14         112 bless {
11             rule => undef,
12             }, $class;
13             }
14              
15             # parse arguments from CLI
16             # arguments:
17             # @arguments
18             # returns:
19             # @rest_arguments
20             sub parse_arguments {
21 3     3 0 152 my ($self, @arguments) = @_;
22              
23 3 100       41 die "source and destination class are required" unless @arguments >= 2;
24              
25 1         6 $self->register(shift @arguments => shift @arguments);
26              
27 1         5 @arguments;
28             }
29              
30              
31             # register a replacing rule
32             # arguments:
33             # $source: source class name
34             # $dest: destination class name
35             sub register {
36 11     11 0 65 my ($self, $source_class_name, $destination_class_name) = @_;
37              
38 11         53 $self->{source_class_name} = $source_class_name;
39 11         38 $self->{destination_class_name} = $destination_class_name;
40             }
41              
42             sub source_class_name {
43 2156     2156 0 9362 my ($self) = @_;
44              
45 2156         6525 $self->{source_class_name};
46             }
47              
48             sub destination_class_name {
49 63     63 0 106 my ($self) = @_;
50              
51 63         598 $self->{destination_class_name};
52             }
53              
54             # refactor a file
55             # argumensts:
56             # $file: filename for refactoring
57             sub execute {
58 24     24 0 25293 my ($self, $file) = @_;
59              
60 24         41 my $replaced = 0;
61              
62 24         224 my $document = PPI::Document->new($file);
63              
64             # When parse failed
65 24 100       287725 return unless $document;
66              
67 23         112 my $should_rename = $self->_try_rename_package_statement($document);
68              
69 23         2193 $replaced += $self->_try_rename_includes($document);
70              
71 23         94 $replaced += $self->_try_rename_parent_class($document);
72              
73 23         94 $replaced += $self->_try_rename_quotes($document);
74              
75 23         95 $replaced += $self->_try_rename_tokens($document);
76              
77 23         94 $replaced += $self->_try_rename_symbols($document);
78              
79 23 100       78 if ($should_rename) {
80 5         17 my $dest_file = App::PRT::Util::DestinationFile::destination_file($self->source_class_name, $self->destination_class_name, $file);
81 5         756 my $dest_dir = file($dest_file)->dir;
82 5         1062 $dest_dir->mkpath;
83 5         2004 $document->save($dest_file);
84 5         11910 unlink($file);
85 5         73 $dest_file;
86             } else {
87 18 100       86 return unless $replaced;
88 10         74 $document->save($file);
89 10         13899 $file;
90             }
91             }
92              
93             # returns: should rename this document?
94             sub _try_rename_package_statement {
95 23     23   44 my ($self, $document) = @_;
96              
97 23         127 my $package = $document->find_first('PPI::Statement::Package');
98              
99 23 100       38542 return unless $package;
100 17 100       114 return unless $package->namespace eq $self->source_class_name;
101              
102 7         28 my $namespace = $package->schild(1);
103              
104 7 50       113 return unless $namespace->isa('PPI::Token::Word');
105              
106 7         25 $namespace->set_content($self->destination_class_name);
107              
108             # rename this file when the first token is package (heuristic)
109 7         41 return $document->find_first('PPI::Token') eq 'package';
110             }
111              
112             sub _try_rename_includes {
113 23     23   89 my ($self, $document) = @_;
114              
115 23         46 my $replaced = 0;
116              
117 23         119 my $statements = $document->find('PPI::Statement::Include');
118 23 100       88933 return 0 unless $statements;
119              
120 21         71 for my $statement (@$statements) {
121 55 50       231 next unless defined $statement->module;
122 55 100       1631 next unless $statement->module eq $self->source_class_name;
123              
124 3         13 my $module = $statement->schild(1);
125              
126 3 50       81 return unless $module->isa('PPI::Token::Word');
127              
128 3         14 $module->set_content($self->destination_class_name);
129 3         19 $replaced++;
130             }
131              
132 21         75 $replaced;
133             }
134              
135             sub _try_rename_quotes {
136 23     23   49 my ($self, $document) = @_;
137              
138 23         43 my $replaced = 0;
139              
140 23         81 my $quotes = $document->find('PPI::Token::Quote');
141 23 100       84755 return 0 unless $quotes;
142              
143 21         70 for my $quote (@$quotes) {
144 68 100       302 next unless $quote->string eq $self->source_class_name;
145 10         19 $quote->set_content("'@{[ $self->destination_class_name ]}'");
  10         30  
146              
147 10         42 $replaced++;
148             }
149              
150 21         64 $replaced;
151             }
152              
153             # TODO: too complicated
154             sub _try_rename_parent_class {
155 23     23   101 my ($self, $document) = @_;
156              
157 23         42 my $replaced = 0;
158              
159 23         86 my $includes = $document->find('PPI::Statement::Include');
160 23 100       83754 return 0 unless $includes;
161              
162 21         79 for my $statement (@$includes) {
163 55 50       928 next unless defined $statement->pragma;
164 55 100       1862 next unless $statement->pragma =~ /^parent|base$/; # only 'use parent' and 'use base' are supported
165              
166             # schild(2) is 'Foo' of use parent Foo
167 10         316 my $parent = $statement->schild(2);
168              
169 10 100       230 if ($parent->isa('PPI::Token::Quote')) {
    50          
170             # The 'literal' method is not implemented by ::Quote::Double or ::Quote::Interpolate.
171 6 100       96 my $string = $parent->can('literal') ? $parent->literal : $parent->string;
172              
173 6 100       92 if ($string eq $self->source_class_name) {
174 5         7 $parent->set_content("'@{[ $self->destination_class_name ]}'");
  5         13  
175 5         25 $replaced++;
176             }
177             } elsif ($parent->isa('PPI::Token::QuoteLike::Words')) {
178             # use parent qw(A B C) pattern
179             # literal is array when QuoteLike::Words
180 4         10 my $_replaced = 0;
181             my @new_literal = map {
182 4 100       33 if ($_ eq $self->source_class_name) {
  6         112  
183 1         3 $_replaced++;
184 1         4 $self->destination_class_name;
185             } else {
186 5         21 $_;
187             }
188             } $parent->literal;
189 4 100       22 if ($_replaced) {
190 1         16 $parent->set_content('qw(' . join(' ', @new_literal) . ')');
191 1         9 $replaced++;
192             }
193             }
194             }
195              
196 21         629 $replaced;
197             }
198              
199             # discussions:
200             # seems too wild
201             sub _try_rename_tokens {
202 23     23   43 my ($self, $document) = @_;
203              
204 23         41 my $replaced = 0;
205              
206 23         82 my $tokens = $document->find('PPI::Token');
207 23 50       85930 return 0 unless $tokens;
208              
209 23         99 for my $token (@$tokens) {
210 1980 100       5126 next unless $token->content eq $self->source_class_name;
211 13         51 $token->set_content($self->destination_class_name);
212 13         50 $replaced++;
213             }
214              
215 23         139 $replaced;
216             }
217              
218             # e.g.
219             # $Foo::Bar::GLOBAL_VAR
220             # ~~~~~~~~ Rename here
221             #
222             # $Foo::Bar::Buz::GLOBAL_VAR <= Don't rename because it's not the same class
223             sub _try_rename_symbols {
224 23     23   51 my ($self, $document) = @_;
225              
226 23         42 my $replaced = 0;
227              
228 23         125 my $symbols = $document->find('PPI::Token::Symbol');
229 23 100       84935 return 0 unless $symbols;
230              
231 17         83 my $source_class_name = $self->source_class_name;
232 17         64 my $destination_class_name = $self->destination_class_name;
233              
234 17         50 for my $symbol (@$symbols) {
235 113         317 my $content = $symbol->content;
236 113         503 my $sigil = substr $content, 0, 1, '';
237              
238 113 100 100     673 if ($content =~ s/\A${source_class_name}::// && scalar(split /::/, $content) == 1) {
239 5         46 $symbol->set_content($sigil . $destination_class_name . '::' . $content);
240 5         29 $replaced++;
241             }
242             }
243              
244 17         58 $replaced;
245             }
246              
247             1;
248