Skip to content
Permalink
Browse files

Add initial ideas for moving static methods

Ref #1595
  • Loading branch information...
muglug committed Jun 1, 2019
1 parent 01f4e39 commit cc89b9254d4b4a76003b8cf315ecdc251f0d7723
@@ -177,6 +177,16 @@ class Codebase
*/
public $diff_methods = false;
/**
* @var array<string, string>
*/
public $method_migrations = [];
/**
* @var array<string, string>
*/
public $call_transforms = [];
/**
* @var bool
*/
@@ -17,7 +17,7 @@ class FileManipulation
* @param int $end
* @param string $insertion_text
*/
public function __construct($start, $end, $insertion_text)
public function __construct(int $start, int $end, string $insertion_text)
{
$this->start = $start;
$this->end = $end;
@@ -1015,9 +1015,7 @@ function (FunctionLikeParameter $param) {
$error_location = $property_storage->location;
if ($storage->declaring_property_ids[$property_name] !== $fq_class_name) {
$error_location = $this->class->name
? new CodeLocation($this, $this->class->name)
: $storage->location;
$error_location = $storage->location ?: $storage->stmt_location;
}
if ($fq_class_name !== $constructor_appearing_fqcln
@@ -194,7 +194,7 @@ public function getFunctionLikeAnalyzer(string $method_name) : ?FunctionLikeAnal
*/
public static function checkFullyQualifiedClassLikeName(
StatementsSource $statements_source,
$fq_class_name,
string $fq_class_name,
CodeLocation $code_location,
array $suppressed_issues,
bool $inferred = true,
@@ -5,6 +5,7 @@
use Psalm\Config;
use Psalm\Context;
use Psalm\Exception\UnsupportedIssueToFixException;
use Psalm\FileManipulation;
use Psalm\Internal\LanguageServer\{LanguageServer, ProtocolStreamReader, ProtocolStreamWriter};
use Psalm\Internal\Provider\ClassLikeStorageProvider;
use Psalm\Internal\Provider\FileProvider;
@@ -523,6 +524,64 @@ public function checkClassReferences()
);
}
public function prepareMigration() : void
{
if (!$this->codebase->alter_code) {
throw new \UnexpectedValueException('Should not be checking references');
}
$this->codebase->classlikes->refactorMethods(
$this->codebase->methods,
$this->progress
);
}
public function migrateCode() : void
{
if (!$this->codebase->alter_code) {
throw new \UnexpectedValueException('Should not be checking references');
}
$migration_manipulations = \Psalm\Internal\FileManipulation\FileManipulationBuffer::getMigrationManipulations(
$this->codebase->file_provider
);
if (!$migration_manipulations) {
return;
}
foreach ($migration_manipulations as $file_path => $file_manipulations) {
usort(
$file_manipulations,
/**
* @return int
*/
function (FileManipulation $a, FileManipulation $b) {
if ($a->start === $b->start) {
if ($b->end === $a->end) {
return $b->insertion_text > $a->insertion_text ? 1 : -1;
}
return $b->end > $a->end ? 1 : -1;
}
return $b->start > $a->start ? 1 : -1;
}
);
$existing_contents = $this->codebase->file_provider->getContents($file_path);
foreach ($file_manipulations as $manipulation) {
$existing_contents
= substr($existing_contents, 0, $manipulation->start)
. $manipulation->insertion_text
. substr($existing_contents, $manipulation->end);
}
$this->codebase->file_provider->setContents($file_path, $existing_contents);
}
}
/**
* @param string $symbol
*
@@ -64,7 +64,7 @@ public static function analyze(
? $codebase->classlike_storage_provider->get($child_fq_class_name)
: null;
if (!$class_storage || !$class_storage->parent_classes) {
if (!$class_storage || !$class_storage->parent_class) {
if (IssueBuffer::accepts(
new ParentNotFound(
'Cannot call method on parent as this class does not extend another',
@@ -78,7 +78,7 @@ public static function analyze(
return;
}
$fq_class_name = reset($class_storage->parent_classes);
$fq_class_name = $class_storage->parent_class;
$class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
@@ -821,6 +821,40 @@ function (Assertion $assertion) use ($found_generic_params) : Assertion {
}
}
foreach ($codebase->call_transforms as $original_pattern => $transformation) {
if ($declaring_method_id
&& strtolower($declaring_method_id) . '\((.*\))' === $original_pattern
) {
if (strpos($transformation, '($1)') === strlen($transformation) - 4
&& $stmt->class instanceof PhpParser\Node\Name
) {
$new_method_id = substr($transformation, 0, -4);
list($new_fq_class_name, $new_method_name) = explode('::', $new_method_id);
$file_manipulations = [];
$file_manipulations[] = new \Psalm\FileManipulation(
(int) $stmt->class->getAttribute('startFilePos'),
(int) $stmt->class->getAttribute('endFilePos') + 1,
Type::getStringFromFQCLN(
$new_fq_class_name,
$statements_analyzer->getNamespace(),
$statements_analyzer->getAliasedClassesFlipped(),
null
)
);
$file_manipulations[] = new \Psalm\FileManipulation(
(int) $stmt->name->getAttribute('startFilePos'),
(int) $stmt->name->getAttribute('endFilePos') + 1,
$new_method_name
);
FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations);
}
}
}
if ($config->after_method_checks) {
$file_manipulations = [];
@@ -140,6 +140,30 @@ public static function analyzeClassConst(
}
}
if ($codebase->method_migrations
&& $context->calling_method_id
&& isset($codebase->method_migrations[strtolower($context->calling_method_id)])
&& strtolower(explode('::', $context->calling_method_id)[0]) === strtolower($fq_class_name)
) {
$file_manipulations = [];
$file_manipulations[] = new \Psalm\FileManipulation(
(int) $stmt->class->getAttribute('startFilePos'),
(int) $stmt->class->getAttribute('endFilePos') + 1,
Type::getStringFromFQCLN(
$fq_class_name,
$statements_analyzer->getNamespace(),
$statements_analyzer->getAliasedClassesFlipped(),
null
)
);
\Psalm\Internal\FileManipulation\FileManipulationBuffer::add(
$statements_analyzer->getFilePath(),
$file_manipulations
);
}
if ($stmt->name instanceof PhpParser\Node\Identifier && $stmt->name->name === 'class') {
$stmt->inferredType = Type::getLiteralClassString($fq_class_name);
@@ -1094,10 +1094,14 @@ public function updateFile($file_path, $dry_run, $output_changes = false)
{
$new_return_type_manipulations = FunctionDocblockManipulator::getManipulationsForFile($file_path);
$other_manipulations = FileManipulationBuffer::getForFile($file_path);
$other_manipulations = FileManipulationBuffer::getManipulationsForFile($file_path);
$file_manipulations = array_merge($new_return_type_manipulations, $other_manipulations);
if (!$file_manipulations) {
return;
}
usort(
$file_manipulations,
/**
@@ -1116,8 +1120,6 @@ function (FileManipulation $a, FileManipulation $b) {
}
);
$docblock_update_count = count($file_manipulations);
$existing_contents = $this->file_provider->getContents($file_path);
foreach ($file_manipulations as $manipulation) {
@@ -1127,28 +1129,26 @@ function (FileManipulation $a, FileManipulation $b) {
. substr($existing_contents, $manipulation->end);
}
if ($docblock_update_count) {
if ($dry_run) {
echo $file_path . ':' . "\n";
$differ = new \SebastianBergmann\Diff\Differ(
new \SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder([
'fromFile' => 'Original',
'toFile' => 'New',
])
);
if ($dry_run) {
echo $file_path . ':' . "\n";
echo (string) $differ->diff($this->file_provider->getContents($file_path), $existing_contents);
$differ = new \SebastianBergmann\Diff\Differ(
new \SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder([
'fromFile' => $file_path,
'toFile' => $file_path,
])
);
return;
}
echo (string) $differ->diff($this->file_provider->getContents($file_path), $existing_contents);
if ($output_changes) {
echo 'Altering ' . $file_path . "\n";
}
return;
}
$this->file_provider->setContents($file_path, $existing_contents);
if ($output_changes) {
echo 'Altering ' . $file_path . "\n";
}
$this->file_provider->setContents($file_path, $existing_contents);
}
/**
@@ -705,6 +705,86 @@ public function checkClassReferences(Methods $methods, Progress $progress = null
}
}
/**
* @return void
*/
public function refactorMethods(Methods $methods, Progress $progress = null)
{
if ($progress === null) {
$progress = new VoidProgress();
}
$project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance();
$codebase = $project_analyzer->getCodebase();
if (!$codebase->method_migrations) {
return;
}
$progress->debug('Refacting methods ' . PHP_EOL);
$code_migrations = [];
foreach ($codebase->method_migrations as $original => $eventual) {
try {
$original_method_storage = $methods->getStorage($original);
} catch (\InvalidArgumentException $e) {
continue;
}
list($eventual_fq_class_name, $eventual_name) = explode('::', $eventual);
try {
$classlike_storage = $this->classlike_storage_provider->get($eventual_fq_class_name);
} catch (\InvalidArgumentException $e) {
continue;
}
if ($classlike_storage->stmt_location
&& $this->config->isInProjectDirs($classlike_storage->stmt_location->file_path)
&& $original_method_storage->stmt_location
&& $original_method_storage->stmt_location->file_path
&& $original_method_storage->location
) {
$new_class_bounds = $classlike_storage->stmt_location->getSnippetBounds();
$old_method_bounds = $original_method_storage->stmt_location->getSnippetBounds();
$old_method_name_bounds = $original_method_storage->location->getSelectionBounds();
FileManipulationBuffer::add(
$original_method_storage->stmt_location->file_path,
[
new \Psalm\FileManipulation(
$old_method_name_bounds[0],
$old_method_name_bounds[1],
$eventual_name
)
]
);
$selection = $classlike_storage->stmt_location->getSnippet();
$insert_pos = strrpos($selection, "\n", -1);
if (!$insert_pos) {
$insert_pos = strlen($selection) - 1;
} else {
$insert_pos++;
}
$code_migrations[] = new \Psalm\Internal\FileManipulation\CodeMigration(
$original_method_storage->stmt_location->file_path,
$old_method_bounds[0],
$old_method_bounds[1],
$classlike_storage->stmt_location->file_path,
$new_class_bounds[0] + $insert_pos
);
}
}
FileManipulationBuffer::addCodeMigrations($code_migrations);
}
/**
* @param string $class_name
* @param mixed $visibility

0 comments on commit cc89b92

Please sign in to comment.
You can’t perform that action at this time.