mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 05:41:20 +01:00
260 lines
7.7 KiB
PHP
260 lines
7.7 KiB
PHP
<?php
|
|
namespace Psalm\Internal\FileManipulation;
|
|
|
|
use function array_merge;
|
|
use Psalm\CodeLocation;
|
|
use Psalm\FileManipulation;
|
|
use Psalm\Internal\Provider\FileProvider;
|
|
use function preg_match;
|
|
use function strlen;
|
|
use function strrpos;
|
|
use function substr;
|
|
use function substr_replace;
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
class FileManipulationBuffer
|
|
{
|
|
/** @var array<string, FileManipulation[]> */
|
|
private static $file_manipulations = [];
|
|
|
|
/** @var CodeMigration[] */
|
|
private static $code_migrations = [];
|
|
|
|
/**
|
|
* @param string $file_path
|
|
* @param FileManipulation[] $file_manipulations
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function add($file_path, array $file_manipulations)
|
|
{
|
|
if (!isset(self::$file_manipulations[$file_path])) {
|
|
self::$file_manipulations[$file_path] = [];
|
|
}
|
|
|
|
foreach ($file_manipulations as $file_manipulation) {
|
|
self::$file_manipulations[$file_path][$file_manipulation->getKey()] = $file_manipulation;
|
|
}
|
|
}
|
|
|
|
/** @param CodeMigration[] $code_migrations */
|
|
public static function addCodeMigrations(array $code_migrations) : void
|
|
{
|
|
self::$code_migrations = array_merge(self::$code_migrations, $code_migrations);
|
|
}
|
|
|
|
/**
|
|
* @return array{int, int}
|
|
*/
|
|
private static function getCodeOffsets(
|
|
string $source_file_path,
|
|
int $source_start,
|
|
int $source_end
|
|
) : array {
|
|
if (!isset(self::$file_manipulations[$source_file_path])) {
|
|
return [0, 0];
|
|
}
|
|
|
|
$start_offset = 0;
|
|
$middle_offset = 0;
|
|
|
|
foreach (self::$file_manipulations[$source_file_path] as $fm) {
|
|
$offset = strlen($fm->insertion_text) - $fm->end + $fm->start;
|
|
|
|
if ($fm->end < $source_start) {
|
|
$start_offset += $offset;
|
|
$middle_offset += $offset;
|
|
} elseif ($fm->start > $source_start
|
|
&& $fm->end < $source_end
|
|
) {
|
|
$middle_offset += $offset;
|
|
}
|
|
}
|
|
|
|
return [$start_offset, $middle_offset];
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public static function addForCodeLocation(
|
|
CodeLocation $code_location,
|
|
string $replacement_text,
|
|
bool $swallow_newlines = false
|
|
) {
|
|
$bounds = $code_location->getSnippetBounds();
|
|
|
|
if ($swallow_newlines) {
|
|
$project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance();
|
|
|
|
$codebase = $project_analyzer->getCodebase();
|
|
|
|
$file_contents = $codebase->getFileContents($code_location->file_path);
|
|
|
|
if (($file_contents[$bounds[0] - 1] ?? null) === "\n"
|
|
&& ($file_contents[$bounds[0] - 2] ?? null) === "\n"
|
|
) {
|
|
$bounds[0] -= 2;
|
|
}
|
|
}
|
|
|
|
self::add(
|
|
$code_location->file_path,
|
|
[
|
|
new FileManipulation(
|
|
$bounds[0],
|
|
$bounds[1],
|
|
$replacement_text
|
|
),
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public static function addVarAnnotationToRemove(CodeLocation\DocblockTypeLocation $code_location)
|
|
{
|
|
$bounds = $code_location->getSelectionBounds();
|
|
|
|
$project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance();
|
|
|
|
$codebase = $project_analyzer->getCodebase();
|
|
|
|
$file_contents = $codebase->getFileContents($code_location->file_path);
|
|
|
|
$comment_start = strrpos($file_contents, '/**', $bounds[0] - strlen($file_contents));
|
|
|
|
if ($comment_start === false) {
|
|
return;
|
|
}
|
|
|
|
$comment_end = \strpos($file_contents, '*/', $bounds[1]);
|
|
|
|
if ($comment_end === false) {
|
|
return;
|
|
}
|
|
|
|
$comment_end += 2;
|
|
|
|
$comment_text = substr($file_contents, $comment_start, $comment_end - $comment_start);
|
|
|
|
$var_type_comment_start = $bounds[0] - $comment_start;
|
|
$var_type_comment_end = $bounds[1] - $comment_start;
|
|
|
|
$var_start = strrpos($comment_text, '@var', $var_type_comment_start - strlen($comment_text));
|
|
$var_end = \strpos($comment_text, "\n", $var_type_comment_end);
|
|
|
|
if ($var_start && $var_end) {
|
|
$var_start = strrpos($comment_text, "\n", $var_start - strlen($comment_text)) ?: $var_start;
|
|
$comment_text = substr_replace($comment_text, '', $var_start, $var_end - $var_start);
|
|
if (preg_match('@^/\*\*\n(\s*\*\s*\n)*\s*\*?\*/$@', $comment_text)) {
|
|
$comment_text = '';
|
|
}
|
|
} else {
|
|
$comment_text = '';
|
|
}
|
|
|
|
self::add(
|
|
$code_location->file_path,
|
|
[
|
|
new FileManipulation(
|
|
$comment_start,
|
|
$comment_end,
|
|
$comment_text,
|
|
false,
|
|
$comment_text === ''
|
|
),
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param string $file_path
|
|
*
|
|
* @return FileManipulation[]
|
|
*/
|
|
public static function getManipulationsForFile($file_path)
|
|
{
|
|
if (!isset(self::$file_manipulations[$file_path])) {
|
|
return [];
|
|
}
|
|
|
|
return self::$file_manipulations[$file_path];
|
|
}
|
|
|
|
/**
|
|
* @param string $file_path
|
|
*
|
|
* @return array<string, FileManipulation[]>
|
|
*/
|
|
public static function getMigrationManipulations(FileProvider $file_provider)
|
|
{
|
|
$code_migration_manipulations = [];
|
|
|
|
foreach (self::$code_migrations as $code_migration) {
|
|
list($start_offset, $middle_offset) = self::getCodeOffsets(
|
|
$code_migration->source_file_path,
|
|
$code_migration->source_start,
|
|
$code_migration->source_end
|
|
);
|
|
|
|
if (!isset($code_migration_manipulations[$code_migration->source_file_path])) {
|
|
$code_migration_manipulations[$code_migration->source_file_path] = [];
|
|
}
|
|
|
|
if (!isset($code_migration_manipulations[$code_migration->destination_file_path])) {
|
|
$code_migration_manipulations[$code_migration->destination_file_path] = [];
|
|
}
|
|
|
|
$delete_file_manipulation = new FileManipulation(
|
|
$code_migration->source_start + $start_offset,
|
|
$code_migration->source_end + $middle_offset,
|
|
''
|
|
);
|
|
|
|
$code_migration_manipulations[$code_migration->source_file_path][] = $delete_file_manipulation;
|
|
|
|
list($destination_start_offset) = self::getCodeOffsets(
|
|
$code_migration->destination_file_path,
|
|
$code_migration->destination_start,
|
|
$code_migration->destination_start
|
|
);
|
|
|
|
$manipulation = new FileManipulation(
|
|
$code_migration->destination_start + $destination_start_offset,
|
|
$code_migration->destination_start + $destination_start_offset,
|
|
"\n" . substr(
|
|
$file_provider->getContents($code_migration->source_file_path),
|
|
$delete_file_manipulation->start,
|
|
$delete_file_manipulation->end - $delete_file_manipulation->start
|
|
) . "\n"
|
|
);
|
|
|
|
$code_migration_manipulations[$code_migration->destination_file_path][$manipulation->getKey()]
|
|
= $manipulation;
|
|
}
|
|
|
|
return $code_migration_manipulations;
|
|
}
|
|
|
|
/**
|
|
* @return array<string, FileManipulation[]>
|
|
*/
|
|
public static function getAll()
|
|
{
|
|
return self::$file_manipulations;
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public static function clearCache()
|
|
{
|
|
self::$file_manipulations = [];
|
|
self::$code_migrations = [];
|
|
}
|
|
}
|