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   1916 use strict;
  3         5  
  3         72  
3 3     3   8 use warnings;
  3         4  
  3         72  
4 3     3   476 use PPI;
  3         85578  
  3         67  
5 3     3   840 use App::PRT::Util::DestinationFile;
  3         6  
  3         70  
6 3     3   10 use Path::Class;
  3         4  
  3         2605  
7              
8             sub new {
9 14     14 0 44731 my ($class) = @_;
10 14         64 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 106 my ($self, @arguments) = @_;
22              
23 3 100       31 die "source and destination class are required" unless @arguments >= 2;
24              
25 1         4 $self->register(shift @arguments => shift @arguments);
26              
27 1         4 @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 39 my ($self, $source_class_name, $destination_class_name) = @_;
37              
38 11         35 $self->{source_class_name} = $source_class_name;
39 11         25 $self->{destination_class_name} = $destination_class_name;
40             }
41              
42             sub source_class_name {
43 2156     2156 0 4279 my ($self) = @_;
44              
45 2156         3375 $self->{source_class_name};
46             }
47              
48             sub destination_class_name {
49 63     63 0 63 my ($self) = @_;
50              
51 63         222 $self->{destination_class_name};
52             }
53              
54             # refactor a file
55             # argumensts:
56             # $file: filename for refactoring
57             sub execute {
58 24     24 0 8211 my ($self, $file) = @_;
59              
60 24         31 my $replaced = 0;
61              
62 24         144 my $document = PPI::Document->new($file);
63              
64             # When parse failed
65 24 100       144897 return unless $document;
66              
67 23         83 my $should_rename = $self->_try_rename_package_statement($document);
68              
69 23         1218 $replaced += $self->_try_rename_includes($document);
70              
71 23         79 $replaced += $self->_try_rename_parent_class($document);
72              
73 23         77 $replaced += $self->_try_rename_quotes($document);
74              
75 23         77 $replaced += $self->_try_rename_tokens($document);
76              
77 23         77 $replaced += $self->_try_rename_symbols($document);
78              
79 23 100       63 if ($should_rename) {
80 5         14 my $dest_file = App::PRT::Util::DestinationFile::destination_file($self->source_class_name, $self->destination_class_name, $file);
81 5         395 my $dest_dir = file($dest_file)->dir;
82 5         244 $dest_dir->mkpath;
83 5         945 $document->save($dest_file);
84 5         5494 unlink($file);
85 5         50 $dest_file;
86             } else {
87 18 100       70 return unless $replaced;
88 10         51 $document->save($file);
89 10         7663 $file;
90             }
91             }
92              
93             # returns: should rename this document?
94             sub _try_rename_package_statement {
95 23     23   37 my ($self, $document) = @_;
96              
97 23         85 my $package = $document->find_first('PPI::Statement::Package');
98              
99 23 100       14757 return unless $package;
100 17 100       84 return unless $package->namespace eq $self->source_class_name;
101              
102 7         18 my $namespace = $package->schild(1);
103              
104 7 50       72 return unless $namespace->isa('PPI::Token::Word');
105              
106 7         17 $namespace->set_content($self->destination_class_name);
107              
108             # rename this file when the first token is package (heuristic)
109 7         29 return $document->find_first('PPI::Token') eq 'package';
110             }
111              
112             sub _try_rename_includes {
113 23     23   34 my ($self, $document) = @_;
114              
115 23         29 my $replaced = 0;
116              
117 23         85 my $statements = $document->find('PPI::Statement::Include');
118 23 100       43354 return 0 unless $statements;
119              
120 21         52 for my $statement (@$statements) {
121 55 50       153 next unless defined $statement->module;
122 55 100       871 next unless $statement->module eq $self->source_class_name;
123              
124 3         8 my $module = $statement->schild(1);
125              
126 3 50       33 return unless $module->isa('PPI::Token::Word');
127              
128 3         10 $module->set_content($self->destination_class_name);
129 3         10 $replaced++;
130             }
131              
132 21         44 $replaced;
133             }
134              
135             sub _try_rename_quotes {
136 23     23   34 my ($self, $document) = @_;
137              
138 23         29 my $replaced = 0;
139              
140 23         51 my $quotes = $document->find('PPI::Token::Quote');
141 23 100       43048 return 0 unless $quotes;
142              
143 21         63 for my $quote (@$quotes) {
144 68 100       153 next unless $quote->string eq $self->source_class_name;
145 10         13 $quote->set_content("'@{[ $self->destination_class_name ]}'");
  10         17  
146              
147 10         27 $replaced++;
148             }
149              
150 21         44 $replaced;
151             }
152              
153             # TODO: too complicated
154             sub _try_rename_parent_class {
155 23     23   60 my ($self, $document) = @_;
156              
157 23         28 my $replaced = 0;
158              
159 23         52 my $includes = $document->find('PPI::Statement::Include');
160 23 100       43008 return 0 unless $includes;
161              
162 21         56 for my $statement (@$includes) {
163 55 50       536 next unless defined $statement->pragma;
164 55 100       1122 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         191 my $parent = $statement->schild(2);
168              
169 10 100       146 if ($parent->isa('PPI::Token::Quote')) {
    50          
170             # The 'literal' method is not implemented by ::Quote::Double or ::Quote::Interpolate.
171 6 100       42 my $string = $parent->can('literal') ? $parent->literal : $parent->string;
172              
173 6 100       57 if ($string eq $self->source_class_name) {
174 5         3 $parent->set_content("'@{[ $self->destination_class_name ]}'");
  5         7  
175 5         13 $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         5 my $_replaced = 0;
181             my @new_literal = map {
182 4 100       23 if ($_ eq $self->source_class_name) {
  6         74  
183 1         2 $_replaced++;
184 1         3 $self->destination_class_name;
185             } else {
186 5         17 $_;
187             }
188             } $parent->literal;
189 4 100       14 if ($_replaced) {
190 1         11 $parent->set_content('qw(' . join(' ', @new_literal) . ')');
191 1         4 $replaced++;
192             }
193             }
194             }
195              
196 21         361 $replaced;
197             }
198              
199             # discussions:
200             # seems too wild
201             sub _try_rename_tokens {
202 23     23   34 my ($self, $document) = @_;
203              
204 23         27 my $replaced = 0;
205              
206 23         58 my $tokens = $document->find('PPI::Token');
207 23 50       43370 return 0 unless $tokens;
208              
209 23         51 for my $token (@$tokens) {
210 1980 100       2277 next unless $token->content eq $self->source_class_name;
211 13         31 $token->set_content($self->destination_class_name);
212 13         26 $replaced++;
213             }
214              
215 23         88 $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   33 my ($self, $document) = @_;
225              
226 23         28 my $replaced = 0;
227              
228 23         58 my $symbols = $document->find('PPI::Token::Symbol');
229 23 100       42723 return 0 unless $symbols;
230              
231 17         49 my $source_class_name = $self->source_class_name;
232 17         43 my $destination_class_name = $self->destination_class_name;
233              
234 17         45 for my $symbol (@$symbols) {
235 113         169 my $content = $symbol->content;
236 113         249 my $sigil = substr $content, 0, 1, '';
237              
238 113 100 100     468 if ($content =~ s/\A${source_class_name}::// && scalar(split /::/, $content) == 1) {
239 5         34 $symbol->set_content($sigil . $destination_class_name . '::' . $content);
240 5         18 $replaced++;
241             }
242             }
243              
244 17         41 $replaced;
245             }
246              
247             1;
248