Code-ART
view release on metacpan or search on metacpan
lib/Code/ART.pm view on Meta::CPAN
$var->{is_builtin} = 0;
if (my $std_desc = $STD_VAR_DESC{$var->{decl_name}}) {
@{$var}{'desc', 'aliases'} = @{$std_desc}{'desc', 'aliases'};
$var->{is_builtin} = 1;
}
# Check whether its name is unhelpful...
$var->{is_cacogram} = $var_name =~ /\A$CACOGRAMS_PAT\Z/ ? 1 : 0;
# Check for homograms and parograms...
my $parograms_pat = _parograms_of($var_name);
$var->{homograms} = {};
$var->{parograms} = {};
for my $other_var (values %Code::ART::varinfo) {
next if $var == $other_var || !_share_scope($var, $other_var);
my $other_name = $other_var->{raw_name};
my ($gram_type, $matcher) = $other_name eq $var_name ? ('homograms', $var_name)
: ('parograms', $parograms_pat);
if ($other_name =~ /\A$matcher\z/) {
$var->{$gram_type}{$other_name}
//= { from=>$var->{declared_at}, to=>$var->{end_of_scope} };
$var->{$gram_type}{$other_name}{from}
= min $var->{$gram_type}{$other_name}{from}, $other_var->{declared_at};
$var->{$gram_type}{$other_name}{to}
= max $var->{$gram_type}{$other_name}{to}, $other_var->{end_of_scope};
}
}
# Measure its scope...
$var->{scope_scale}
= ($var->{end_of_scope} - ($var->{declared_at} // 0)) / length($source);
}
# Return all the information acquired...
return {
vars => \%Code::ART::varinfo,
var_at => \%var_at,
use_version => $Code::ART::use_version,
}
}
1; # Magic true value required at end of module
__END__
=head1 NAME
Code::ART - Analyze/Rename/Track Perl source code
=head1 VERSION
This document describes Code::ART version 0.000005
=head1 SYNOPSIS
use Code::ART;
# Convert source code fragment to sub and call...
$refactored = refactor_to_sub( $source_code, \%options );
# or:
$refactored = hoist_to_lexical( $source_code, \%options );
# Source code of sub or lexical...
$sub_definition = $refactored->{code};
# Code to call sub with args, or to evaluate lexical...
$sub_call_syntax = $refactored->{call};
# Array of arg names (as strings, only for refactor_to_sub() )...
@sub_arg_list = @{ $refactored->{args} };
# Only if refactoring failed...
$failure_message = $refactored->{failed};
=head1 DESCRIPTION
This module provides a range of subroutines to help you refactor
valid Perl source into cleaner, better decomposed code.
The module also comes with a Vim plugin to plumb those
refactoring behaviours directly into that editor (see L<"Vim integration">).
For example, the module provides a subroutine (C<refactor_to_sub()>)
that takes a source code fragment as a string, analyzes it to determine
the unbound variables within it, then constructs the source code of an
equivalent subroutine (with the unbound variables converted to
parameters) plus the source code of a suitable call to that subroutine.
It is useful when hooked into an editor, allowing you to
(semi-)automatically convert functional code like:
my @heatmap =
map { $config{$_} }
sort {
my $a_key = $a =~ /(\d+)/ ? $1 : undef;
my $b_key = $b =~ /(\d+)/ ? $1 : undef;
defined $a_key && defined $b_key
? $a_key <=> $b_key
: $a cmp $b;
}
grep { /^heatmap/ }
keys %config;
into a much cleaner:
my @heatmap =
map { $config{$_} }
nsort
grep { /^heatmap/ }
keys %config;
plus:
sub nsort {
sort {
my $a_key = $a =~ /(\d+)/ ? $1 : undef;
my $b_key = $b =~ /(\d+)/ ? $1 : undef;
defined $a_key && defined $b_key
? $a_key <=> $b_key
: $a cmp $b;
}, @_;
}
Or to replace something long and imperative like:
my @heatmap_keys;
for my $key (keys %config) {
next if $key !~ /^heatmap/;
push @heatmap_keys, $key;
}
@heatmap_keys
= sort {
my $a_key = $a =~ /(\d+)/ ? $1 : undef;
my $b_key = $b =~ /(\d+)/ ? $1 : undef;
defined $a_key && defined $b_key
? $a_key <=> $b_key
: $a cmp $b;
} @heatmap_keys;
my @heatmap;
for (@heatmap_keys) {
push @heatmap, $config{$_};
}
with something short and imperative:
my @heatmap;
for ( get_heatmap_keys(\%config ) ) {
push @heatmap, $config{$_};
}
plus:
sub get_heatmap_keys {
my ($config_href) = @_;
my @heatmap_keys;
for my $key (keys %{$config_href}) {
next if $key !~ /^heatmap/;
push @heatmap_keys, $key;
}
@heatmap_keys = sort {
my $a_key = $a =~ /(\d+)/ ? $1 : undef;
my $b_key = $b =~ /(\d+)/ ? $1 : undef;
defined $a_key && defined $b_key
? $a_key <=> $b_key
: $a cmp $b;
} @heatmap_keys;
return @heatmap_keys;
}
=head1 INTERFACE
=head2 Refactoring a fragment of Perl code
To refactor some Perl code, call the C<refactor_to_sub()>
subroutine, which is automatically exported when the
module is loaded.
my $refactored = refactor_to_sub( $source_code_string, \%options );
Note that this subroutine does not actually rewrite the source code
with the refactoring; it merely returns the components with which you
could transform the original source yourself.
The subroutine takes a single required argument:
a string containing the complete source code within which
some element is to be refactored.
The options specify where and how to refactor that code element, as follows:
=over
=item C<< from => $starting_string_index >>
=item C<< to => $ending_string_index >>
These two options are actually required. They must be non-negative integer
values that represent the indexes in the string where the fragment you
wish to refactor begins and ends.
=item C<< name => $name_of_new_sub >>
This option allows you to specify the name of the new subroutine.
If it is not provided, the module uses a bad generic name instead
(C<__REFACTORED_SUB__>), which you'll have to change anyway,
so passing the option is strongly recommended.
=item C<< data => $name_of_the_var_to_hold_any_trailing_data >>
This option allows you to specify the name of the slurpy variable into
which any trailing arguments for the new subroutine (i.e. in addition to
those the refactorer determines are required) will be placed.
If it is not provided, the module uses a generic name instead
(C<@__EXTRA_DATA__>).
=item C<< return => $source_of_the_expr_to_be_returned >>
If this option is specified, the refactorer places its value in a
C<return> statement at the end of the refactored subroutine.
If it is not provided, no extra return statement is added.
=back
The return value of C<refactor_to_sub()> in all contexts and in all cases
is a hash reference containing one or more of the following keys:
=over
=item C<'code'>
The value for this key will be a string representing the source code for
the new subroutine into which the original code was refactored.
=item C<'call'>
The value for this key will be a string representing the source code for
the specific call to the new subroutine (including it's arguments)
that can be used to replace the original code.
=item C<'return'>
The value of this key will be a reference to an hash, whose keys are
the names of the variables present inside the original code that was
refactored, and whose values are the equivalent names of those variables
in the refactored code.
The purpose of these information is to allow your code to present the
user with a list of possible return values to select from (i.e. the keys
of the hash) and then install a suitable return statement (i.e. the
value of the selected key).
=item C<'failed'>
lib/Code/ART.pm view on Meta::CPAN
Match all instances of the variable under the cursor.
=item <CTRL-H>
Hoist all instances of the visually selected code into a lexical variable.
=item <CTRL-C>
Hoist all instances of the visually selected code into a lexical closure.
=item <CTRL-R>
Refactor all instances of the visually selected code into a parameterized subroutine.
=item <CTRL-H><CTRL-H>
=item <CTRL-C><CTRL-C>
=item <CTRL-R><CTRL-R>
Same as the single-control-character versions above, but these only refactor
the code actually selected, rather than every equivalent instance throughout
the buffer.
=back
=head1 DIAGNOSTICS
The analysis and refactoring subroutines all return a hash, in all
cases. However, if any subroutine cannot perform its task (usually
because the code it has been given is invalid), then the returned hash
will contain the key 'failed', and the corresponding value will give a
reason for the failure (if possible).
The following failure messages may be encountered:
=over
=item C<< failed => 'invalid source code' >>
The code you passed in as the first argument could not be recognized
by PPR as a valid Perl.
There is a small chance this was caused by a bug in PPR,
but it's more likely that something was wrong with the code you passed in.
=item C<< failed => 'not a valid series of statements' >>
The subset of the code you asked C<refactor_to_sub()> to refactor
could not be recognized by PPR as a refactorable sequence of Perl statements.
Check whether you caught an extra unmatched opening or closing brace, or
started in the middle of a string.
=item C<< failed => 'the code has an internal return statement' >>
If the code you're trying to put into a subroutine contains a (conditional) return
statement anywhere but at the end of the fragment, then there's no way to refactor it
cleanly into another subroutine, because the internal return will return from the newly
refactored subroutine, I<not> from the place where you'll be replacing the original
code with a call tothe newly refactored subroutine. So C<refactor_to_sub()> doesn't try.
=item C<< failed => "code has both a leading assignment and an explicit return" >>
If you're attempting to refactor a fragment of code that starts with the
rvalue of an assignment, and ends in a return, there's no way to put
both into a new subroutine and still have the previous behaviour of the
original code preserved. So C<refactor_to_sub()> doesn't try.
=item C<< failed => "because the target code is not a simple expression" >>
Only simple expressions (not full statements) can be hoisted into a lexical
variable or closure. You tried to hoist something "bigger" than that.
=item C<< failed => "because there is no variable at the specified location" >>
You called C<classify_var_at()> but gave it a position in the source code
where there was no variable. If you're doing that from within some editor,
you may have an out-by-one error if the buffer positions you're detecting
and passing back to the module start at 1 instead of zero.
=item C<< failed => 'because the apparent variable is not actually a variable' >>
You called C<classify_var_at()> but gave it a position in the source code
where there was no variable. It I<looks> like there is a variable there,
but there isn't. Is the apparent variable actually in an uninterpolated
string, or a comment, or some POD, or after the C<__DATA__> or C<__END__>
marker?
=back
API errors are signalled by throwing an exception:
=over
=item C<< "%s argument of %s must be a %s" >>
You called the specified subroutine with the wrong kind of argument.
The error message will specify which argument and what kind of value it requires.
=item C<< "Unexpected extra argument passed to %s" >>
You called the specified subroutine with an extra unexpected argument.
Did you mean to put that argument in the subroutine's options hash instead?
=item C<< "Unknown option (%s) passed to %s" >>
You passed an unexpected named argument via the specified subroutine's
options hash. Did you misspell it, perhaps?
=back
=head1 CONFIGURATION AND ENVIRONMENT
Code::ART requires no configuration files or environment variables.
=head1 DEPENDENCIES
The PPR module (version 0.000027 or later)
( run in 0.603 second using v1.01-cache-2.11-cpan-364913b4093 )