mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Add ability to move classes
This commit is contained in:
parent
13779e760e
commit
f309c755f8
@ -207,6 +207,11 @@ class Codebase
|
||||
*/
|
||||
public $class_constants_to_rename = [];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public $classes_to_move = [];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
@ -222,6 +227,11 @@ class Codebase
|
||||
*/
|
||||
public $class_constant_transforms = [];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public $class_transforms = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
|
@ -12,20 +12,43 @@ class FileManipulation
|
||||
/** @var string */
|
||||
public $insertion_text;
|
||||
|
||||
/** @var bool */
|
||||
public $preserve_indentation;
|
||||
|
||||
/**
|
||||
* @param int $start
|
||||
* @param int $end
|
||||
* @param string $insertion_text
|
||||
*/
|
||||
public function __construct(int $start, int $end, string $insertion_text)
|
||||
public function __construct(int $start, int $end, string $insertion_text, bool $preserve_indentation = false)
|
||||
{
|
||||
$this->start = $start;
|
||||
$this->end = $end;
|
||||
$this->insertion_text = $insertion_text;
|
||||
$this->preserve_indentation = $preserve_indentation;
|
||||
}
|
||||
|
||||
public function getKey() : string
|
||||
{
|
||||
return sha1($this->start . ':' . $this->insertion_text);
|
||||
}
|
||||
|
||||
public function transform(string $existing_contents) : string
|
||||
{
|
||||
if ($this->preserve_indentation) {
|
||||
$newline_pos = strrpos($existing_contents, "\n", $this->start - strlen($existing_contents));
|
||||
|
||||
$newline_pos = $newline_pos !== false ? $newline_pos + 1 : 0;
|
||||
|
||||
$indentation = substr($existing_contents, $newline_pos, $this->start - $newline_pos);
|
||||
|
||||
if (trim($indentation) === '') {
|
||||
$this->insertion_text = $this->insertion_text . $indentation;
|
||||
}
|
||||
}
|
||||
|
||||
return substr($existing_contents, 0, $this->start)
|
||||
. $this->insertion_text
|
||||
. substr($existing_contents, $this->end);
|
||||
}
|
||||
}
|
||||
|
@ -131,6 +131,64 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
$project_analyzer = $this->file_analyzer->project_analyzer;
|
||||
$codebase = $this->getCodebase();
|
||||
|
||||
if ($codebase->alter_code && $class->name && $codebase->classes_to_move) {
|
||||
if (isset($codebase->classes_to_move[strtolower($this->fq_class_name)])) {
|
||||
$destination_class = $codebase->classes_to_move[strtolower($this->fq_class_name)];
|
||||
|
||||
$source_class_parts = explode('\\', $this->fq_class_name);
|
||||
$destination_class_parts = explode('\\', $destination_class);
|
||||
|
||||
array_pop($source_class_parts);
|
||||
array_pop($destination_class_parts);
|
||||
|
||||
$source_ns = implode('\\', $source_class_parts);
|
||||
$destination_ns = implode('\\', $destination_class_parts);
|
||||
|
||||
if (strtolower($source_ns) !== strtolower($destination_ns)) {
|
||||
if ($storage->namespace_name_location) {
|
||||
$bounds = $storage->namespace_name_location->getSelectionBounds();
|
||||
|
||||
$file_manipulations = [
|
||||
new \Psalm\FileManipulation(
|
||||
$bounds[0],
|
||||
$bounds[1],
|
||||
$destination_ns
|
||||
)
|
||||
];
|
||||
|
||||
\Psalm\Internal\FileManipulation\FileManipulationBuffer::add(
|
||||
$this->getFilePath(),
|
||||
$file_manipulations
|
||||
);
|
||||
} elseif (!$source_ns) {
|
||||
$class_start_pos = (int) $class->getAttribute('startFilePos');
|
||||
|
||||
$file_manipulations = [
|
||||
new \Psalm\FileManipulation(
|
||||
$class_start_pos,
|
||||
$class_start_pos,
|
||||
'namespace ' . $destination_ns . ';' . "\n\n",
|
||||
true
|
||||
)
|
||||
];
|
||||
|
||||
\Psalm\Internal\FileManipulation\FileManipulationBuffer::add(
|
||||
$this->getFilePath(),
|
||||
$file_manipulations
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$codebase->classlikes->handleClassLikeReferenceInMigration(
|
||||
$codebase,
|
||||
$this,
|
||||
$class->name,
|
||||
$this->fq_class_name,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
$classlike_storage_provider = $codebase->classlike_storage_provider;
|
||||
|
||||
$parent_fq_class_name = $this->parent_fq_class_name;
|
||||
@ -152,6 +210,16 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($codebase->alter_code && $codebase->classes_to_move) {
|
||||
$codebase->classlikes->handleClassLikeReferenceInMigration(
|
||||
$codebase,
|
||||
$this,
|
||||
$class->extends,
|
||||
$parent_fq_class_name,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$parent_class_storage = $classlike_storage_provider->get($parent_fq_class_name);
|
||||
|
||||
@ -279,6 +347,14 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
);
|
||||
}
|
||||
|
||||
$codebase->classlikes->handleClassLikeReferenceInMigration(
|
||||
$codebase,
|
||||
$this,
|
||||
$interface_name,
|
||||
$fq_interface_name,
|
||||
null
|
||||
);
|
||||
|
||||
try {
|
||||
$interface_storage = $classlike_storage_provider->get($fq_interface_name);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
@ -681,22 +757,47 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
break;
|
||||
}
|
||||
|
||||
$property_id = strtolower($this->fq_class_name) . '::$' . $prop->name;
|
||||
if ($codebase->alter_code) {
|
||||
$property_id = strtolower($this->fq_class_name) . '::$' . $prop->name;
|
||||
|
||||
foreach ($codebase->properties_to_rename as $original_property_id => $new_property_name) {
|
||||
if ($property_id === $original_property_id) {
|
||||
$file_manipulations = [
|
||||
new \Psalm\FileManipulation(
|
||||
(int) $prop->name->getAttribute('startFilePos'),
|
||||
(int) $prop->name->getAttribute('endFilePos') + 1,
|
||||
'$' . $new_property_name
|
||||
)
|
||||
];
|
||||
$property_storage = $codebase->properties->getStorage($property_id);
|
||||
|
||||
\Psalm\Internal\FileManipulation\FileManipulationBuffer::add(
|
||||
$this->getFilePath(),
|
||||
$file_manipulations
|
||||
if ($property_storage->type
|
||||
&& $property_storage->type_location
|
||||
&& $property_storage->type_location !== $property_storage->signature_type_location
|
||||
) {
|
||||
$replace_type = ExpressionAnalyzer::fleshOutType(
|
||||
$codebase,
|
||||
$property_storage->type,
|
||||
$this->getFQCLN(),
|
||||
$this->getFQCLN(),
|
||||
$this->getParentFQCLN()
|
||||
);
|
||||
|
||||
$codebase->classlikes->handleDocblockTypeInMigration(
|
||||
$codebase,
|
||||
$this,
|
||||
$replace_type,
|
||||
$property_storage->type_location,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($codebase->properties_to_rename as $original_property_id => $new_property_name) {
|
||||
if ($property_id === $original_property_id) {
|
||||
$file_manipulations = [
|
||||
new \Psalm\FileManipulation(
|
||||
(int) $prop->name->getAttribute('startFilePos'),
|
||||
(int) $prop->name->getAttribute('endFilePos') + 1,
|
||||
'$' . $new_property_name
|
||||
)
|
||||
];
|
||||
|
||||
\Psalm\Internal\FileManipulation\FileManipulationBuffer::add(
|
||||
$this->getFilePath(),
|
||||
$file_manipulations
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -361,12 +361,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
|
||||
$check_stmts = true;
|
||||
|
||||
if ($codebase->methods_to_move
|
||||
&& $context->calling_method_id
|
||||
&& isset($codebase->methods_to_move[strtolower($context->calling_method_id)])
|
||||
) {
|
||||
$destination_method_id = $codebase->methods_to_move[strtolower($context->calling_method_id)];
|
||||
|
||||
if ($codebase->alter_code) {
|
||||
foreach ($this->function->params as $param) {
|
||||
$param_name_node = null;
|
||||
|
||||
@ -379,7 +374,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
}
|
||||
|
||||
if ($param_name_node) {
|
||||
$resolved_name = (string) $param_name_node->getAttribute('resolvedName');
|
||||
$resolved_name = ClassLikeAnalyzer::getFQCLNFromNameObject($param_name_node, $this->getAliases());
|
||||
|
||||
$parent_fqcln = $this->getParentFQCLN();
|
||||
|
||||
@ -389,12 +384,12 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
$resolved_name = $parent_fqcln;
|
||||
}
|
||||
|
||||
$codebase->classlikes->airliftClassLikeReference(
|
||||
$codebase->classlikes->handleClassLikeReferenceInMigration(
|
||||
$codebase,
|
||||
$this,
|
||||
$param_name_node,
|
||||
$resolved_name,
|
||||
explode('::', $destination_method_id)[0],
|
||||
$statements_analyzer->getFilePath(),
|
||||
(int) $param_name_node->getAttribute('startFilePos'),
|
||||
(int) $param_name_node->getAttribute('endFilePos') + 1
|
||||
$context->calling_method_id
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -411,7 +406,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
}
|
||||
|
||||
if ($return_name_node) {
|
||||
$resolved_name = (string) $return_name_node->getAttribute('resolvedName');
|
||||
$resolved_name = ClassLikeAnalyzer::getFQCLNFromNameObject($return_name_node, $this->getAliases());
|
||||
|
||||
$parent_fqcln = $this->getParentFQCLN();
|
||||
|
||||
@ -421,12 +416,12 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
$resolved_name = $parent_fqcln;
|
||||
}
|
||||
|
||||
$codebase->classlikes->airliftClassLikeReference(
|
||||
$codebase->classlikes->handleClassLikeReferenceInMigration(
|
||||
$codebase,
|
||||
$this,
|
||||
$return_name_node,
|
||||
$resolved_name,
|
||||
explode('::', $destination_method_id)[0],
|
||||
$statements_analyzer->getFilePath(),
|
||||
(int) $return_name_node->getAttribute('startFilePos'),
|
||||
(int) $return_name_node->getAttribute('endFilePos') + 1
|
||||
$context->calling_method_id
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -435,8 +430,6 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
&& $storage->return_type_location
|
||||
&& $storage->return_type_location !== $storage->signature_return_type_location
|
||||
) {
|
||||
$bounds = $storage->return_type_location->getSelectionBounds();
|
||||
|
||||
$replace_type = ExpressionAnalyzer::fleshOutType(
|
||||
$codebase,
|
||||
$storage->return_type,
|
||||
@ -445,12 +438,12 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
$this->getParentFQCLN()
|
||||
);
|
||||
|
||||
$codebase->classlikes->airliftDocblockType(
|
||||
$codebase->classlikes->handleDocblockTypeInMigration(
|
||||
$codebase,
|
||||
$this,
|
||||
$replace_type,
|
||||
explode('::', $destination_method_id)[0],
|
||||
$statements_analyzer->getFilePath(),
|
||||
$bounds[0],
|
||||
$bounds[1]
|
||||
$storage->return_type_location,
|
||||
$context->calling_method_id
|
||||
);
|
||||
}
|
||||
|
||||
@ -459,8 +452,6 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
&& $function_param->type_location
|
||||
&& $function_param->type_location !== $function_param->signature_type_location
|
||||
) {
|
||||
$bounds = $function_param->type_location->getSelectionBounds();
|
||||
|
||||
$replace_type = ExpressionAnalyzer::fleshOutType(
|
||||
$codebase,
|
||||
$function_param->type,
|
||||
@ -469,12 +460,12 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
$this->getParentFQCLN()
|
||||
);
|
||||
|
||||
$codebase->classlikes->airliftDocblockType(
|
||||
$codebase->classlikes->handleDocblockTypeInMigration(
|
||||
$codebase,
|
||||
$this,
|
||||
$replace_type,
|
||||
explode('::', $destination_method_id)[0],
|
||||
$statements_analyzer->getFilePath(),
|
||||
$bounds[0],
|
||||
$bounds[1]
|
||||
$function_param->type_location,
|
||||
$context->calling_method_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -539,16 +539,77 @@ class ProjectAnalyzer
|
||||
$source_parts = explode('::', $source);
|
||||
$destination_parts = explode('::', $destination);
|
||||
|
||||
if (count($source_parts) === 1 || count($destination_parts) === 1) {
|
||||
throw new \Psalm\Exception\RefactorException('Cannot yet refactor classes');
|
||||
}
|
||||
|
||||
if (!$this->codebase->classlikes->classExists($source_parts[0])) {
|
||||
if (!$this->codebase->classlikes->hasFullyQualifiedClassName($source_parts[0])) {
|
||||
throw new \Psalm\Exception\RefactorException(
|
||||
'Source class ' . $source_parts[0] . ' doesn’t exist'
|
||||
);
|
||||
}
|
||||
|
||||
if (count($source_parts) === 1 && count($destination_parts) === 1) {
|
||||
if ($this->codebase->classlikes->hasFullyQualifiedClassName($destination_parts[0])) {
|
||||
throw new \Psalm\Exception\RefactorException(
|
||||
'Destination class ' . $destination_parts[0] . ' already exists'
|
||||
);
|
||||
}
|
||||
|
||||
$source_class_storage = $this->codebase->classlike_storage_provider->get($source_parts[0]);
|
||||
|
||||
foreach ($source_class_storage->methods as $method_name => $method_storage) {
|
||||
if ($method_storage->is_static) {
|
||||
$old_method_id = strtolower($source_parts[0] . '::' . $method_name);
|
||||
$new_method_id = $destination_parts[0] . '::' . $method_name;
|
||||
|
||||
$this->codebase->call_transforms[$old_method_id . '\((.*\))'] = $new_method_id . '($1)';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($source_class_storage->properties as $property_name => $property_storage) {
|
||||
if ($property_storage->is_static) {
|
||||
$old_property_id = strtolower($source_parts[0]) . '::' . $property_name;
|
||||
$new_property_id = $destination_parts[0] . '::' . $property_name;
|
||||
|
||||
$this->codebase->property_transforms[$old_property_id] = $new_property_id;
|
||||
}
|
||||
}
|
||||
|
||||
$all_class_consts = array_merge(
|
||||
$source_class_storage->public_class_constants,
|
||||
$source_class_storage->protected_class_constants,
|
||||
$source_class_storage->private_class_constants
|
||||
);
|
||||
|
||||
foreach ($all_class_consts as $const_name => $_) {
|
||||
$old_const_id = strtolower($source_parts[0]) . '::' . $const_name;
|
||||
$new_const_id = $destination_parts[0] . '::' . $const_name;
|
||||
|
||||
$this->codebase->class_constant_transforms[$old_const_id] = $new_const_id;
|
||||
}
|
||||
|
||||
$destination_parts = explode('\\', $destination);
|
||||
|
||||
array_pop($destination_parts);
|
||||
$destination_ns = implode('\\', $destination_parts);
|
||||
|
||||
$this->codebase->classes_to_move[strtolower($source)] = $destination;
|
||||
|
||||
$destination_class_storage = $this->codebase->classlike_storage_provider->create($destination);
|
||||
|
||||
$destination_class_storage->name = $destination;
|
||||
|
||||
if ($source_class_storage->aliases) {
|
||||
$destination_class_storage->aliases = clone $source_class_storage->aliases;
|
||||
$destination_class_storage->aliases->namespace = $destination_ns;
|
||||
}
|
||||
|
||||
$destination_class_storage->location = $source_class_storage->location;
|
||||
$destination_class_storage->stmt_location = $source_class_storage->stmt_location;
|
||||
$destination_class_storage->populated = true;
|
||||
|
||||
$this->codebase->class_transforms[strtolower($source)] = $destination;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->codebase->methods->methodExists($source)) {
|
||||
if ($this->codebase->methods->methodExists($destination)) {
|
||||
throw new \Psalm\Exception\RefactorException(
|
||||
@ -631,7 +692,7 @@ class ProjectAnalyzer
|
||||
);
|
||||
|
||||
if (isset($source_class_constants[$source_parts[1]])) {
|
||||
if (!$this->codebase->classlikes->classExists($destination_parts[0])) {
|
||||
if (!$this->codebase->classlikes->hasFullyQualifiedClassName($destination_parts[0])) {
|
||||
throw new \Psalm\Exception\RefactorException(
|
||||
'Destination class ' . $destination_parts[0] . ' doesn’t exist'
|
||||
);
|
||||
@ -682,9 +743,11 @@ class ProjectAnalyzer
|
||||
$this->progress
|
||||
);
|
||||
|
||||
$this->codebase->classlikes->moveConstants(
|
||||
$this->codebase->classlikes->moveClassConstants(
|
||||
$this->progress
|
||||
);
|
||||
|
||||
$this->codebase->classlikes->moveClasses();
|
||||
}
|
||||
|
||||
public function migrateCode() : void
|
||||
@ -723,10 +786,7 @@ class ProjectAnalyzer
|
||||
$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);
|
||||
$existing_contents = $manipulation->transform($existing_contents);
|
||||
}
|
||||
|
||||
$this->codebase->file_provider->setContents($file_path, $existing_contents);
|
||||
|
@ -110,6 +110,26 @@ class ForeachAnalyzer
|
||||
$statements_analyzer->getParentFQCLN()
|
||||
);
|
||||
|
||||
if ($var_comment->type_start
|
||||
&& $var_comment->type_end
|
||||
&& $var_comment->line_number
|
||||
) {
|
||||
$type_location = new CodeLocation\DocblockTypeLocation(
|
||||
$statements_analyzer,
|
||||
$var_comment->type_start,
|
||||
$var_comment->type_end,
|
||||
$var_comment->line_number,
|
||||
);
|
||||
|
||||
$codebase->classlikes->handleDocblockTypeInMigration(
|
||||
$codebase,
|
||||
$statements_analyzer,
|
||||
$comment_type,
|
||||
$type_location,
|
||||
$context->calling_method_id
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($context->vars_in_scope[$var_comment->var_id])
|
||||
|| $statements_analyzer->isSuperGlobal($var_comment->var_id)
|
||||
) {
|
||||
|
@ -901,22 +901,6 @@ class PropertyAssignmentAnalyzer
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if ($stmt->class instanceof PhpParser\Node\Name
|
||||
&& $codebase->methods_to_move
|
||||
&& $context->calling_method_id
|
||||
&& isset($codebase->methods_to_move[strtolower($context->calling_method_id)])
|
||||
) {
|
||||
$destination_method_id = $codebase->methods_to_move[strtolower($context->calling_method_id)];
|
||||
|
||||
$codebase->classlikes->airliftClassLikeReference(
|
||||
$fq_class_name,
|
||||
explode('::', $destination_method_id)[0],
|
||||
$statements_analyzer->getFilePath(),
|
||||
(int) $stmt->class->getAttribute('startFilePos'),
|
||||
(int) $stmt->class->getAttribute('endFilePos') + 1
|
||||
);
|
||||
}
|
||||
|
||||
$prop_name = $stmt->name;
|
||||
|
||||
if (!$prop_name instanceof PhpParser\Node\Identifier) {
|
||||
@ -964,35 +948,45 @@ class PropertyAssignmentAnalyzer
|
||||
|
||||
$declaring_property_id = strtolower((string) $declaring_property_class) . '::$' . $prop_name;
|
||||
|
||||
foreach ($codebase->property_transforms as $original_pattern => $transformation) {
|
||||
if ($declaring_property_id === $original_pattern
|
||||
&& $stmt->class instanceof PhpParser\Node\Name
|
||||
) {
|
||||
list($old_declaring_fq_class_name) = explode('::$', $declaring_property_id);
|
||||
list($new_fq_class_name, $new_property_name) = explode('::$', $transformation);
|
||||
if ($codebase->alter_code && $stmt->class instanceof PhpParser\Node\Name) {
|
||||
$moved_class = $codebase->classlikes->handleClassLikeReferenceInMigration(
|
||||
$codebase,
|
||||
$statements_analyzer,
|
||||
$stmt->class,
|
||||
$fq_class_name,
|
||||
$context->calling_method_id
|
||||
);
|
||||
|
||||
$file_manipulations = [];
|
||||
if (!$moved_class) {
|
||||
foreach ($codebase->property_transforms as $original_pattern => $transformation) {
|
||||
if ($declaring_property_id === $original_pattern) {
|
||||
list($old_declaring_fq_class_name) = explode('::$', $declaring_property_id);
|
||||
list($new_fq_class_name, $new_property_name) = explode('::$', $transformation);
|
||||
|
||||
if (strtolower($new_fq_class_name) !== strtolower($old_declaring_fq_class_name)) {
|
||||
$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 = [];
|
||||
|
||||
if (strtolower($new_fq_class_name) !== strtolower($old_declaring_fq_class_name)) {
|
||||
$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_property_name
|
||||
);
|
||||
|
||||
FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations);
|
||||
}
|
||||
}
|
||||
|
||||
$file_manipulations[] = new \Psalm\FileManipulation(
|
||||
(int) $stmt->name->getAttribute('startFilePos'),
|
||||
(int) $stmt->name->getAttribute('endFilePos') + 1,
|
||||
'$' . $new_property_name
|
||||
);
|
||||
|
||||
FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,21 +120,23 @@ class AssignmentAnalyzer
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
);
|
||||
|
||||
if ($codebase->methods_to_move
|
||||
&& $context->calling_method_id
|
||||
&& isset($codebase->methods_to_move[strtolower($context->calling_method_id)])
|
||||
&& $var_comment->type_start
|
||||
if ($var_comment->type_start
|
||||
&& $var_comment->type_end
|
||||
&& $var_comment->line_number
|
||||
) {
|
||||
$destination_method_id = $codebase->methods_to_move[strtolower($context->calling_method_id)];
|
||||
|
||||
$codebase->classlikes->airliftDocblockType(
|
||||
$var_comment_type,
|
||||
explode('::', $destination_method_id)[0],
|
||||
$statements_analyzer->getFilePath(),
|
||||
$type_location = new CodeLocation\DocblockTypeLocation(
|
||||
$statements_analyzer,
|
||||
$var_comment->type_start,
|
||||
$var_comment->type_end
|
||||
$var_comment->type_end,
|
||||
$var_comment->line_number,
|
||||
);
|
||||
|
||||
$codebase->classlikes->handleDocblockTypeInMigration(
|
||||
$codebase,
|
||||
$statements_analyzer,
|
||||
$var_comment_type,
|
||||
$type_location,
|
||||
$context->calling_method_id
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -246,19 +246,13 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
|
||||
}
|
||||
|
||||
if ($fq_class_name) {
|
||||
if ($stmt->class instanceof PhpParser\Node\Name
|
||||
&& $codebase->methods_to_move
|
||||
&& $context->calling_method_id
|
||||
&& isset($codebase->methods_to_move[strtolower($context->calling_method_id)])
|
||||
) {
|
||||
$destination_method_id = $codebase->methods_to_move[strtolower($context->calling_method_id)];
|
||||
|
||||
$codebase->classlikes->airliftClassLikeReference(
|
||||
if ($codebase->alter_code) {
|
||||
$codebase->classlikes->handleClassLikeReferenceInMigration(
|
||||
$codebase,
|
||||
$statements_analyzer,
|
||||
$stmt->class,
|
||||
$fq_class_name,
|
||||
explode('::', $destination_method_id)[0],
|
||||
$statements_analyzer->getFilePath(),
|
||||
(int) $stmt->class->getAttribute('startFilePos'),
|
||||
(int) $stmt->class->getAttribute('endFilePos') + 1
|
||||
$context->calling_method_id
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1030,35 +1030,45 @@ class PropertyFetchAnalyzer
|
||||
|
||||
$declaring_property_id = strtolower((string) $declaring_property_class) . '::$' . $prop_name;
|
||||
|
||||
foreach ($codebase->property_transforms as $original_pattern => $transformation) {
|
||||
if ($declaring_property_id === $original_pattern
|
||||
&& $stmt->class instanceof PhpParser\Node\Name
|
||||
) {
|
||||
list($old_declaring_fq_class_name) = explode('::$', $declaring_property_id);
|
||||
list($new_fq_class_name, $new_property_name) = explode('::$', $transformation);
|
||||
if ($codebase->alter_code && $stmt->class instanceof PhpParser\Node\Name) {
|
||||
$moved_class = $codebase->classlikes->handleClassLikeReferenceInMigration(
|
||||
$codebase,
|
||||
$statements_analyzer,
|
||||
$stmt->class,
|
||||
$fq_class_name,
|
||||
$context->calling_method_id
|
||||
);
|
||||
|
||||
$file_manipulations = [];
|
||||
if (!$moved_class) {
|
||||
foreach ($codebase->property_transforms as $original_pattern => $transformation) {
|
||||
if ($declaring_property_id === $original_pattern) {
|
||||
list($old_declaring_fq_class_name) = explode('::$', $declaring_property_id);
|
||||
list($new_fq_class_name, $new_property_name) = explode('::$', $transformation);
|
||||
|
||||
if (strtolower($new_fq_class_name) !== strtolower($old_declaring_fq_class_name)) {
|
||||
$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 = [];
|
||||
|
||||
if (strtolower($new_fq_class_name) !== strtolower($old_declaring_fq_class_name)) {
|
||||
$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_property_name
|
||||
);
|
||||
|
||||
FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations);
|
||||
}
|
||||
}
|
||||
|
||||
$file_manipulations[] = new \Psalm\FileManipulation(
|
||||
(int) $stmt->name->getAttribute('startFilePos'),
|
||||
(int) $stmt->name->getAttribute('endFilePos') + 1,
|
||||
'$' . $new_property_name
|
||||
);
|
||||
|
||||
FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -579,6 +579,16 @@ class ExpressionAnalyzer
|
||||
$fq_class_name
|
||||
);
|
||||
}
|
||||
|
||||
if ($codebase->alter_code) {
|
||||
$codebase->classlikes->handleClassLikeReferenceInMigration(
|
||||
$codebase,
|
||||
$statements_analyzer,
|
||||
$stmt->class,
|
||||
$fq_class_name,
|
||||
$context->calling_method_id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -950,22 +950,23 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource
|
||||
$this->getSuppressedIssues()
|
||||
);
|
||||
|
||||
if ($codebase->methods_to_move
|
||||
&& $context->calling_method_id
|
||||
&& isset($codebase->methods_to_move[strtolower($context->calling_method_id)])
|
||||
&& $var_comment->type_start
|
||||
if ($var_comment->type_start
|
||||
&& $var_comment->type_end
|
||||
&& $var_comment->line_number
|
||||
) {
|
||||
$destination_method_id
|
||||
= $codebase->methods_to_move[strtolower($context->calling_method_id)];
|
||||
|
||||
$codebase->classlikes->airliftDocblockType(
|
||||
$var_comment_type,
|
||||
explode('::', $destination_method_id)[0],
|
||||
$this->getFilePath(),
|
||||
$type_location = new CodeLocation\DocblockTypeLocation(
|
||||
$this,
|
||||
$var_comment->type_start,
|
||||
$var_comment->type_end
|
||||
$var_comment->type_end,
|
||||
$var_comment->line_number
|
||||
);
|
||||
|
||||
$codebase->classlikes->handleDocblockTypeInMigration(
|
||||
$codebase,
|
||||
$this,
|
||||
$var_comment_type,
|
||||
$type_location,
|
||||
$context->calling_method_id
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1156,10 +1156,7 @@ class Analyzer
|
||||
$existing_contents = $this->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);
|
||||
$existing_contents = $manipulation->transform($existing_contents);
|
||||
}
|
||||
|
||||
if ($dry_run) {
|
||||
|
@ -889,7 +889,7 @@ class ClassLikes
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function moveConstants(Progress $progress = null)
|
||||
public function moveClassConstants(Progress $progress = null)
|
||||
{
|
||||
if ($progress === null) {
|
||||
$progress = new VoidProgress();
|
||||
@ -959,6 +959,199 @@ class ClassLikes
|
||||
FileManipulationBuffer::addCodeMigrations($code_migrations);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function moveClasses()
|
||||
{
|
||||
}
|
||||
|
||||
public function handleClassLikeReferenceInMigration(
|
||||
\Psalm\Codebase $codebase,
|
||||
\Psalm\StatementsSource $source,
|
||||
PhpParser\Node $class_name_node,
|
||||
string $fq_class_name,
|
||||
?string $calling_method_id
|
||||
) : bool {
|
||||
|
||||
$calling_fq_class_name = $source->getFQCLN();
|
||||
|
||||
// if we're inside a moved class static method
|
||||
if ($codebase->methods_to_move
|
||||
&& $calling_fq_class_name
|
||||
&& $calling_method_id
|
||||
&& isset($codebase->methods_to_move[strtolower($calling_method_id)])
|
||||
) {
|
||||
$destination_class = explode('::', $codebase->methods_to_move[strtolower($calling_method_id)])[0];
|
||||
|
||||
$intended_fq_class_name = strtolower($calling_fq_class_name) === strtolower($fq_class_name)
|
||||
&& isset($codebase->classes_to_move[strtolower($calling_fq_class_name)])
|
||||
? $destination_class
|
||||
: $fq_class_name;
|
||||
|
||||
$this->airliftClassLikeReference(
|
||||
$intended_fq_class_name,
|
||||
$destination_class,
|
||||
$source->getFilePath(),
|
||||
(int) $class_name_node->getAttribute('startFilePos'),
|
||||
(int) $class_name_node->getAttribute('endFilePos') + 1
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// if we're inside a moved class (could be a method, could be a property/class const default)
|
||||
if ($codebase->classes_to_move
|
||||
&& $calling_fq_class_name
|
||||
&& isset($codebase->classes_to_move[strtolower($calling_fq_class_name)])
|
||||
) {
|
||||
$destination_class = $codebase->classes_to_move[strtolower($calling_fq_class_name)];
|
||||
|
||||
if ($class_name_node instanceof PhpParser\Node\Identifier) {
|
||||
$destination_parts = explode('\\', $destination_class);
|
||||
|
||||
$destination_class_name = array_pop($destination_parts);
|
||||
$file_manipulations = [];
|
||||
|
||||
$file_manipulations[] = new \Psalm\FileManipulation(
|
||||
(int) $class_name_node->getAttribute('startFilePos'),
|
||||
(int) $class_name_node->getAttribute('endFilePos') + 1,
|
||||
$destination_class_name
|
||||
);
|
||||
|
||||
FileManipulationBuffer::add($source->getFilePath(), $file_manipulations);
|
||||
} else {
|
||||
$this->airliftClassLikeReference(
|
||||
strtolower($calling_fq_class_name) === strtolower($fq_class_name)
|
||||
? $destination_class
|
||||
: $fq_class_name,
|
||||
$destination_class,
|
||||
$source->getFilePath(),
|
||||
(int) $class_name_node->getAttribute('startFilePos'),
|
||||
(int) $class_name_node->getAttribute('endFilePos') + 1
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// if we're outside a moved class, but we're changing all references to a class
|
||||
foreach ($codebase->class_transforms as $old_fq_class_name => $new_fq_class_name) {
|
||||
if (strtolower($fq_class_name) === $old_fq_class_name) {
|
||||
$file_manipulations = [];
|
||||
|
||||
$file_manipulations[] = new \Psalm\FileManipulation(
|
||||
(int) $class_name_node->getAttribute('startFilePos'),
|
||||
(int) $class_name_node->getAttribute('endFilePos') + 1,
|
||||
Type::getStringFromFQCLN(
|
||||
$new_fq_class_name,
|
||||
$source->getNamespace(),
|
||||
$source->getAliasedClassesFlipped(),
|
||||
$calling_fq_class_name
|
||||
)
|
||||
);
|
||||
|
||||
FileManipulationBuffer::add($source->getFilePath(), $file_manipulations);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleDocblockTypeInMigration(
|
||||
\Psalm\Codebase $codebase,
|
||||
\Psalm\StatementsSource $source,
|
||||
Type\Union $type,
|
||||
CodeLocation $type_location,
|
||||
?string $calling_method_id
|
||||
) : void {
|
||||
|
||||
$calling_fq_class_name = $source->getFQCLN();
|
||||
|
||||
$moved_type = false;
|
||||
|
||||
// if we're inside a moved class static method
|
||||
if ($codebase->methods_to_move
|
||||
&& $calling_fq_class_name
|
||||
&& $calling_method_id
|
||||
&& isset($codebase->methods_to_move[strtolower($calling_method_id)])
|
||||
) {
|
||||
$bounds = $type_location->getSelectionBounds();
|
||||
|
||||
$destination_class = explode('::', $codebase->methods_to_move[strtolower($calling_method_id)])[0];
|
||||
|
||||
$this->airliftDocblockType(
|
||||
$type,
|
||||
$destination_class,
|
||||
$source->getFilePath(),
|
||||
$bounds[0],
|
||||
$bounds[1]
|
||||
);
|
||||
|
||||
$moved_type = true;
|
||||
}
|
||||
|
||||
// if we're inside a moved class (could be a method, could be a property/class const default)
|
||||
if (!$moved_type
|
||||
&& $codebase->classes_to_move
|
||||
&& $calling_fq_class_name
|
||||
&& isset($codebase->classes_to_move[strtolower($calling_fq_class_name)])
|
||||
) {
|
||||
$bounds = $type_location->getSelectionBounds();
|
||||
|
||||
$destination_class = $codebase->classes_to_move[strtolower($calling_fq_class_name)];
|
||||
|
||||
if ($type->containsClassLike(strtolower($calling_fq_class_name))) {
|
||||
$type = clone $type;
|
||||
|
||||
$type->replaceClassLike(strtolower($calling_fq_class_name), $destination_class);
|
||||
}
|
||||
|
||||
$this->airliftDocblockType(
|
||||
$type,
|
||||
$destination_class,
|
||||
$source->getFilePath(),
|
||||
$bounds[0],
|
||||
$bounds[1]
|
||||
);
|
||||
|
||||
$moved_type = true;
|
||||
}
|
||||
|
||||
// if we're outside a moved class, but we're changing all references to a class
|
||||
if (!$moved_type) {
|
||||
foreach ($codebase->class_transforms as $old_fq_class_name => $new_fq_class_name) {
|
||||
if ($type->containsClassLike($old_fq_class_name)) {
|
||||
$type = clone $type;
|
||||
|
||||
$type->replaceClassLike($old_fq_class_name, $new_fq_class_name);
|
||||
|
||||
$bounds = $type_location->getSelectionBounds();
|
||||
|
||||
$file_manipulations = [];
|
||||
|
||||
$file_manipulations[] = new \Psalm\FileManipulation(
|
||||
$bounds[0],
|
||||
$bounds[1],
|
||||
$type->toNamespacedString(
|
||||
$source->getNamespace(),
|
||||
$source->getAliasedClassesFlipped(),
|
||||
null,
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
FileManipulationBuffer::add(
|
||||
$source->getFilePath(),
|
||||
$file_manipulations
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function airliftClassLikeReference(
|
||||
string $fq_class_name,
|
||||
string $destination_fq_class_name,
|
||||
|
@ -93,6 +93,9 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
/** @var int */
|
||||
private $php_minor_version;
|
||||
|
||||
/** @var PhpParser\Node\Name|null */
|
||||
private $namespace_name;
|
||||
|
||||
/**
|
||||
* @var array<string, array<int, string>>
|
||||
*/
|
||||
@ -161,6 +164,9 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
|
||||
if ($node instanceof PhpParser\Node\Stmt\Namespace_) {
|
||||
$this->file_aliases = $this->aliases;
|
||||
|
||||
$this->namespace_name = $node->name;
|
||||
|
||||
$this->aliases = new Aliases(
|
||||
$node->name ? implode('\\', $node->name->parts) : '',
|
||||
$this->aliases->uses,
|
||||
@ -818,6 +824,9 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
|
||||
$storage->stmt_location = $class_location;
|
||||
$storage->location = $name_location;
|
||||
if ($this->namespace_name) {
|
||||
$storage->namespace_name_location = new CodeLocation($this->file_scanner, $this->namespace_name);
|
||||
}
|
||||
$storage->user_defined = !$this->codebase->register_stub_files;
|
||||
$storage->stubbed = $this->codebase->register_stub_files;
|
||||
$storage->aliases = $this->aliases;
|
||||
|
@ -176,6 +176,11 @@ class ClassLikeStorage
|
||||
*/
|
||||
public $stmt_location;
|
||||
|
||||
/**
|
||||
* @var CodeLocation|null
|
||||
*/
|
||||
public $namespace_name_location;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
|
@ -1039,6 +1039,22 @@ abstract class Type
|
||||
return $aliased_classes[strtolower($value)];
|
||||
}
|
||||
|
||||
if (strpos($value, '\\')) {
|
||||
$parts = explode('\\', $value);
|
||||
|
||||
$suffix = array_pop($parts);
|
||||
|
||||
while ($parts) {
|
||||
$left = implode('\\', $parts);
|
||||
|
||||
if (isset($aliased_classes[strtolower($left)])) {
|
||||
return $aliased_classes[strtolower($left)] . '\\' . $suffix;
|
||||
}
|
||||
|
||||
$suffix = array_pop($parts) . '\\' . $suffix;
|
||||
}
|
||||
}
|
||||
|
||||
return '\\' . $value;
|
||||
}
|
||||
|
||||
|
@ -709,6 +709,148 @@ abstract class Atomic
|
||||
}
|
||||
}
|
||||
|
||||
public function containsClassLike(string $fq_classlike_name) : bool
|
||||
{
|
||||
if ($this instanceof TNamedObject) {
|
||||
if (strtolower($this->value) === $fq_classlike_name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this instanceof TNamedObject
|
||||
|| $this instanceof TIterable
|
||||
|| $this instanceof TTemplateParam
|
||||
) {
|
||||
if ($this->extra_types) {
|
||||
foreach ($this->extra_types as $extra_type) {
|
||||
if ($extra_type->containsClassLike($fq_classlike_name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this instanceof TScalarClassConstant) {
|
||||
if (strtolower($this->fq_classlike_name) === $fq_classlike_name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this instanceof TClassString && $this->as !== 'object') {
|
||||
if (strtolower($this->as) === $fq_classlike_name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this instanceof TTemplateParam) {
|
||||
if ($this->as->containsClassLike($fq_classlike_name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this instanceof TLiteralClassString) {
|
||||
if (strtolower($this->value) === $fq_classlike_name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this instanceof Type\Atomic\TArray
|
||||
|| $this instanceof Type\Atomic\TGenericObject
|
||||
|| $this instanceof Type\Atomic\TIterable
|
||||
) {
|
||||
foreach ($this->type_params as $type_param) {
|
||||
if ($type_param->containsClassLike($fq_classlike_name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this instanceof Type\Atomic\TFn
|
||||
|| $this instanceof Type\Atomic\TCallable
|
||||
) {
|
||||
if ($this->params) {
|
||||
foreach ($this->params as $param) {
|
||||
if ($param->type && $param->type->containsClassLike($fq_classlike_name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->return_type && $this->return_type->containsClassLike($fq_classlike_name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function replaceClassLike(string $old, string $new) : void
|
||||
{
|
||||
if ($this instanceof TNamedObject) {
|
||||
if (strtolower($this->value) === $old) {
|
||||
$this->value = $new;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this instanceof TNamedObject
|
||||
|| $this instanceof TIterable
|
||||
|| $this instanceof TTemplateParam
|
||||
) {
|
||||
if ($this->extra_types) {
|
||||
foreach ($this->extra_types as $extra_type) {
|
||||
$extra_type->replaceClassLike($old, $new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this instanceof TScalarClassConstant) {
|
||||
if (strtolower($old) === $new) {
|
||||
$this->fq_classlike_name = $new;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this instanceof TClassString && $this->as !== 'object') {
|
||||
if (strtolower($this->as) === $old) {
|
||||
$this->as = $new;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this instanceof TTemplateParam) {
|
||||
$this->as->replaceClassLike($old, $new);
|
||||
}
|
||||
|
||||
if ($this instanceof TLiteralClassString) {
|
||||
if (strtolower($this->value) === $old) {
|
||||
$this->value = $new;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this instanceof Type\Atomic\TArray
|
||||
|| $this instanceof Type\Atomic\TGenericObject
|
||||
|| $this instanceof Type\Atomic\TIterable
|
||||
) {
|
||||
foreach ($this->type_params as $type_param) {
|
||||
$type_param->replaceClassLike($old, $new);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this instanceof Type\Atomic\TFn
|
||||
|| $this instanceof Type\Atomic\TCallable
|
||||
) {
|
||||
if ($this->params) {
|
||||
foreach ($this->params as $param) {
|
||||
if ($param->type) {
|
||||
$param->type->replaceClassLike($old, $new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->return_type) {
|
||||
$this->return_type->replaceClassLike($old, $new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Atomic $other
|
||||
*
|
||||
|
@ -1674,6 +1674,30 @@ class Union
|
||||
}
|
||||
}
|
||||
|
||||
public function containsClassLike(string $fq_class_like_name) : bool
|
||||
{
|
||||
foreach ($this->types as $atomic_type) {
|
||||
if ($atomic_type->containsClassLike($fq_class_like_name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function replaceClassLike(string $old, string $new) : void
|
||||
{
|
||||
foreach ($this->types as $key => $atomic_type) {
|
||||
$atomic_type->replaceClassLike($old, $new);
|
||||
|
||||
unset($this->types[$key]);
|
||||
|
||||
$this->types[$atomic_type->getKey()] = $atomic_type;
|
||||
}
|
||||
|
||||
$this->id = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
|
363
tests/FileManipulation/ClassMoveTest.php
Normal file
363
tests/FileManipulation/ClassMoveTest.php
Normal file
@ -0,0 +1,363 @@
|
||||
<?php
|
||||
namespace Psalm\Tests\FileManipulation;
|
||||
|
||||
use Psalm\Context;
|
||||
use Psalm\Internal\Analyzer\FileAnalyzer;
|
||||
use Psalm\Tests\Internal\Provider;
|
||||
use Psalm\Tests\TestConfig;
|
||||
|
||||
class ClassMoveTest extends \Psalm\Tests\TestCase
|
||||
{
|
||||
/** @var \Psalm\Internal\Analyzer\ProjectAnalyzer */
|
||||
protected $project_analyzer;
|
||||
|
||||
public function setUp() : void
|
||||
{
|
||||
FileAnalyzer::clearCache();
|
||||
\Psalm\Internal\FileManipulation\FunctionDocblockManipulator::clearCache();
|
||||
|
||||
$this->file_provider = new Provider\FakeFileProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerValidCodeParse
|
||||
*
|
||||
* @param string $input_code
|
||||
* @param string $output_code
|
||||
* @param array<string, string> $constants_to_move
|
||||
* @param array<string, string> $call_transforms
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testValidCode(
|
||||
string $input_code,
|
||||
string $output_code,
|
||||
array $constants_to_move
|
||||
) {
|
||||
$test_name = $this->getTestName();
|
||||
if (strpos($test_name, 'SKIPPED-') !== false) {
|
||||
$this->markTestSkipped('Skipped due to a bug.');
|
||||
}
|
||||
|
||||
$config = new TestConfig();
|
||||
|
||||
$this->project_analyzer = new \Psalm\Internal\Analyzer\ProjectAnalyzer(
|
||||
$config,
|
||||
new \Psalm\Internal\Provider\Providers(
|
||||
$this->file_provider,
|
||||
new Provider\FakeParserCacheProvider()
|
||||
)
|
||||
);
|
||||
|
||||
$context = new Context();
|
||||
|
||||
$file_path = self::$src_dir_path . 'somefile.php';
|
||||
|
||||
$this->addFile(
|
||||
$file_path,
|
||||
$input_code
|
||||
);
|
||||
|
||||
$codebase = $this->project_analyzer->getCodebase();
|
||||
|
||||
$this->project_analyzer->refactorCodeAfterCompletion($constants_to_move);
|
||||
|
||||
$this->analyzeFile($file_path, $context);
|
||||
|
||||
$this->project_analyzer->prepareMigration();
|
||||
|
||||
$codebase->analyzer->updateFile($file_path, false);
|
||||
|
||||
$this->project_analyzer->migrateCode();
|
||||
|
||||
$this->assertSame($output_code, $codebase->getFileContents($file_path));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,array{string,string,array<string, string>}>
|
||||
*/
|
||||
public function providerValidCodeParse()
|
||||
{
|
||||
return [
|
||||
'renameEmptyClass' => [
|
||||
'<?php
|
||||
namespace Ns;
|
||||
|
||||
class A {}
|
||||
|
||||
class C extends A {
|
||||
/**
|
||||
* @var A
|
||||
*/
|
||||
public $one;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param A $a
|
||||
* @return A
|
||||
*/
|
||||
function foo(A $a) : A {
|
||||
return $a;
|
||||
}
|
||||
|
||||
/** @var A */
|
||||
$i = new A();
|
||||
|
||||
if ($i instanceof A) {}',
|
||||
'<?php
|
||||
namespace Ns;
|
||||
|
||||
class B {}
|
||||
|
||||
class C extends B {
|
||||
/**
|
||||
* @var B
|
||||
*/
|
||||
public $one;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param B $a
|
||||
* @return B
|
||||
*/
|
||||
function foo(B $a) : B {
|
||||
return $a;
|
||||
}
|
||||
|
||||
/** @var B */
|
||||
$i = new B();
|
||||
|
||||
if ($i instanceof B) {}',
|
||||
[
|
||||
'Ns\A' => 'Ns\B',
|
||||
]
|
||||
],
|
||||
'renameClassWithInstanceMethod' => [
|
||||
'<?php
|
||||
namespace Ns;
|
||||
|
||||
class A {
|
||||
/**
|
||||
* @param self $one
|
||||
* @param A $two
|
||||
*/
|
||||
public function foo(self $one, A $two) : void {}
|
||||
}
|
||||
|
||||
function foo(A $a) : A {
|
||||
return $a->foo($a, $a);
|
||||
}',
|
||||
'<?php
|
||||
namespace Ns;
|
||||
|
||||
class B {
|
||||
/**
|
||||
* @param self $one
|
||||
* @param self $two
|
||||
*/
|
||||
public function foo(self $one, self $two) : void {}
|
||||
}
|
||||
|
||||
function foo(B $a) : B {
|
||||
return $a->foo($a, $a);
|
||||
}',
|
||||
[
|
||||
'Ns\A' => 'Ns\B',
|
||||
]
|
||||
],
|
||||
'renameClassWithStaticMethod' => [
|
||||
'<?php
|
||||
namespace Ns;
|
||||
|
||||
class A {
|
||||
/**
|
||||
* @param self $one
|
||||
* @param A $two
|
||||
*/
|
||||
public static function foo(self $one, A $two) : void {
|
||||
A::foo($one, $two);
|
||||
}
|
||||
}
|
||||
|
||||
function foo() {
|
||||
A::foo(new A(), A::foo());
|
||||
}',
|
||||
'<?php
|
||||
namespace Ns;
|
||||
|
||||
class B {
|
||||
/**
|
||||
* @param self $one
|
||||
* @param self $two
|
||||
*/
|
||||
public static function foo(self $one, self $two) : void {
|
||||
B::foo($one, $two);
|
||||
}
|
||||
}
|
||||
|
||||
function foo() {
|
||||
B::foo(new B(), B::foo());
|
||||
}',
|
||||
[
|
||||
'Ns\A' => 'Ns\B',
|
||||
]
|
||||
],
|
||||
'renameClassWithInstanceProperty' => [
|
||||
'<?php
|
||||
namespace Ns;
|
||||
|
||||
class A {
|
||||
/**
|
||||
* @var A
|
||||
*/
|
||||
public $one;
|
||||
|
||||
/**
|
||||
* @var self
|
||||
*/
|
||||
public $two;
|
||||
}',
|
||||
'<?php
|
||||
namespace Ns;
|
||||
|
||||
class B {
|
||||
/**
|
||||
* @var self
|
||||
*/
|
||||
public $one;
|
||||
|
||||
/**
|
||||
* @var self
|
||||
*/
|
||||
public $two;
|
||||
}',
|
||||
[
|
||||
'Ns\A' => 'Ns\B',
|
||||
]
|
||||
],
|
||||
'renameClassWithStaticProperty' => [
|
||||
'<?php
|
||||
namespace Ns;
|
||||
|
||||
class A {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public static $one = "one";
|
||||
}
|
||||
|
||||
echo A::$one;
|
||||
A::$one = "two";',
|
||||
'<?php
|
||||
namespace Ns;
|
||||
|
||||
class B {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public static $one = "one";
|
||||
}
|
||||
|
||||
echo B::$one;
|
||||
B::$one = "two";',
|
||||
[
|
||||
'Ns\A' => 'Ns\B',
|
||||
]
|
||||
],
|
||||
'moveClassIntoNamespace' => [
|
||||
'<?php
|
||||
class A {
|
||||
/** @var ?Exception */
|
||||
public $x;
|
||||
|
||||
/**
|
||||
* @param ArrayObject<int, A> $a
|
||||
*/
|
||||
public function foo(ArrayObject $a) : Exception {
|
||||
foreach ($a as $b) {
|
||||
$b->bar();
|
||||
}
|
||||
|
||||
return new Exception("bad");
|
||||
}
|
||||
|
||||
public function bar() : void {}
|
||||
}',
|
||||
'<?php
|
||||
namespace Foo\Bar\Baz;
|
||||
|
||||
class B {
|
||||
/** @var null|\Exception */
|
||||
public $x;
|
||||
|
||||
/**
|
||||
* @param \ArrayObject<int, self> $a
|
||||
*/
|
||||
public function foo(\ArrayObject $a) : \Exception {
|
||||
foreach ($a as $b) {
|
||||
$b->bar();
|
||||
}
|
||||
|
||||
return new \Exception("bad");
|
||||
}
|
||||
|
||||
public function bar() : void {}
|
||||
}',
|
||||
[
|
||||
'A' => 'Foo\Bar\Baz\B',
|
||||
]
|
||||
],
|
||||
'moveClassDeeperIntoNamespace' => [
|
||||
'<?php
|
||||
namespace Foo;
|
||||
|
||||
use Exception;
|
||||
use ArrayObject;
|
||||
|
||||
class A {
|
||||
/** @var ?Exception */
|
||||
public $x;
|
||||
|
||||
/**
|
||||
* @param ArrayObject<int, A> $a
|
||||
*/
|
||||
public function foo(ArrayObject $a) : Exception {
|
||||
foreach ($a as $b) {
|
||||
$b->bar();
|
||||
}
|
||||
|
||||
return new Exception("bad");
|
||||
}
|
||||
|
||||
public function bar() : void {}
|
||||
}',
|
||||
'<?php
|
||||
namespace Foo\Bar\Baz;
|
||||
|
||||
use Exception;
|
||||
use ArrayObject;
|
||||
|
||||
class B {
|
||||
/** @var null|Exception */
|
||||
public $x;
|
||||
|
||||
/**
|
||||
* @param ArrayObject<int, self> $a
|
||||
*/
|
||||
public function foo(ArrayObject $a) : Exception {
|
||||
foreach ($a as $b) {
|
||||
$b->bar();
|
||||
}
|
||||
|
||||
return new Exception("bad");
|
||||
}
|
||||
|
||||
public function bar() : void {}
|
||||
}',
|
||||
[
|
||||
'Foo\A' => 'Foo\Bar\Baz\B',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ use Psalm\Internal\Analyzer\FileAnalyzer;
|
||||
use Psalm\Tests\Internal\Provider;
|
||||
use Psalm\Tests\TestConfig;
|
||||
|
||||
class MoveMethodTest extends \Psalm\Tests\TestCase
|
||||
class MethodMoveTest extends \Psalm\Tests\TestCase
|
||||
{
|
||||
/** @var \Psalm\Internal\Analyzer\ProjectAnalyzer */
|
||||
protected $project_analyzer;
|
||||
|
@ -888,9 +888,9 @@ class ReturnTypeManipulationTest extends FileManipulationTest
|
||||
|
||||
class D {
|
||||
/**
|
||||
* @return \A\B\C[]
|
||||
* @return B\C[]
|
||||
*
|
||||
* @psalm-return array{0:\A\B\C}
|
||||
* @psalm-return array{0:B\C}
|
||||
*/
|
||||
public function getArrayOfC(): array {
|
||||
return [new \A\B\C];
|
||||
|
Loading…
Reference in New Issue
Block a user