1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-16 11:26:55 +01:00
psalm/src/Psalm/Internal/Diff/ClassStatementsDiffer.php

242 lines
9.6 KiB
PHP
Raw Normal View History

<?php
2018-11-06 03:57:36 +01:00
namespace Psalm\Internal\Diff;
2019-07-05 22:24:00 +02:00
use function count;
use function get_class;
2019-07-05 22:24:00 +02:00
use PhpParser;
use function strpos;
use function strtolower;
2019-07-05 22:24:00 +02:00
use function substr;
use function trim;
/**
* @internal
*/
class ClassStatementsDiffer extends AstDiffer
{
/**
* Calculate diff (edit script) from $a to $b.
*
2018-09-27 19:32:08 +02:00
* @param string $name
* @param string $a_code
* @param string $b_code
* @param array<int, PhpParser\Node\Stmt> $a
* @param array<int, PhpParser\Node\Stmt> $b
*
* @return array{
* 0: array<int, string>,
2018-09-27 19:32:08 +02:00
* 1: array<int, string>,
* 2: array<int, string>,
* 3: array<int, array{0: int, 1: int, 2: int, 3: int}>
* }
*/
2018-09-27 19:32:08 +02:00
public static function diff($name, array $a, array $b, $a_code, $b_code)
{
$diff_map = [];
list($trace, $x, $y, $bc) = self::calculateTrace(
2018-09-27 19:32:08 +02:00
/**
* @param string $a_code
* @param string $b_code
* @param bool $body_change
*
* @return bool
*/
function (
PhpParser\Node\Stmt $a,
PhpParser\Node\Stmt $b,
2018-09-27 19:32:08 +02:00
$a_code,
$b_code,
&$body_change = false
) use (&$diff_map) {
if (get_class($a) !== get_class($b)) {
return false;
}
$a_start = (int)$a->getAttribute('startFilePos');
$a_end = (int)$a->getAttribute('endFilePos');
$b_start = (int)$b->getAttribute('startFilePos');
$b_end = (int)$b->getAttribute('endFilePos');
$a_comments_end = $a_start;
$b_comments_end = $b_start;
2019-10-09 16:04:34 +02:00
/** @var list<PhpParser\Comment> */
$a_comments = $a->getComments();
2019-10-09 16:04:34 +02:00
/** @var list<PhpParser\Comment> */
$b_comments = $b->getComments();
$signature_change = false;
$body_change = false;
if ($a_comments) {
if (!$b_comments) {
$signature_change = true;
}
$a_start = $a_comments[0]->getFilePos();
}
if ($b_comments) {
if (!$a_comments) {
$signature_change = true;
}
$b_start = $b_comments[0]->getFilePos();
}
$a_size = $a_end - $a_start;
$b_size = $b_end - $b_start;
if ($a_size === $b_size
&& substr($a_code, $a_start, $a_size) === substr($b_code, $b_start, $b_size)
) {
$start_diff = $b_start - $a_start;
$line_diff = $b->getLine() - $a->getLine();
$diff_map[] = [$a_start, $a_end, $start_diff, $line_diff];
return true;
}
if (!$signature_change
&& substr($a_code, $a_start, $a_comments_end - $a_start)
!== substr($b_code, $b_start, $b_comments_end - $b_start)
) {
$signature_change = true;
}
if ($a instanceof PhpParser\Node\Stmt\ClassMethod && $b instanceof PhpParser\Node\Stmt\ClassMethod) {
if ((string) $a->name !== (string) $b->name) {
return false;
}
if ($a->stmts) {
$first_stmt = $a->stmts[0];
$a_stmts_start = (int) $first_stmt->getAttribute('startFilePos');
if ($a_stmt_comments = $first_stmt->getComments()) {
$a_stmts_start = $a_stmt_comments[0]->getFilePos();
}
} else {
$a_stmts_start = $a_end;
}
if ($b->stmts) {
$first_stmt = $b->stmts[0];
$b_stmts_start = (int) $first_stmt->getAttribute('startFilePos');
if ($b_stmt_comments = $first_stmt->getComments()) {
$b_stmts_start = $b_stmt_comments[0]->getFilePos();
}
} else {
$b_stmts_start = $b_end;
}
$a_body_size = $a_end - $a_stmts_start;
$b_body_size = $b_end - $b_stmts_start;
$body_change = $a_body_size !== $b_body_size
|| substr($a_code, $a_stmts_start, $a_end - $a_stmts_start)
!== substr($b_code, $b_stmts_start, $b_end - $b_stmts_start);
if (!$signature_change) {
$a_signature = substr($a_code, $a_start, $a_stmts_start - $a_start);
$b_signature = substr($b_code, $b_start, $b_stmts_start - $b_start);
if ($a_signature !== $b_signature) {
$a_signature = trim($a_signature);
$b_signature = trim($b_signature);
if (strpos($a_signature, $b_signature) === false
&& strpos($b_signature, $a_signature) === false
) {
$signature_change = true;
}
}
}
} elseif ($a instanceof PhpParser\Node\Stmt\Property && $b instanceof PhpParser\Node\Stmt\Property) {
if (count($a->props) !== 1 || count($b->props) !== 1) {
return false;
}
2018-09-30 17:33:40 +02:00
if ((string) $a->props[0]->name !== (string) $b->props[0]->name || $a->flags !== $b->flags) {
return false;
}
$body_change = substr($a_code, $a_comments_end, $a_end - $a_comments_end)
!== substr($b_code, $b_comments_end, $b_end - $b_comments_end);
} else {
$signature_change = true;
}
if (!$signature_change && !$body_change) {
$diff_map[] = [$a_start, $a_end, $b_start - $a_start, $b->getLine() - $a->getLine()];
}
return !$signature_change;
},
$a,
$b,
$a_code,
$b_code
);
$diff = self::extractDiff($trace, $x, $y, $a, $b, $bc);
$keep = [];
$keep_signature = [];
2018-10-04 00:16:33 +02:00
$add_or_delete = [];
foreach ($diff as $diff_elem) {
if ($diff_elem->type === DiffElem::TYPE_KEEP) {
if ($diff_elem->old instanceof PhpParser\Node\Stmt\ClassMethod) {
$keep[] = strtolower($name) . '::' . strtolower((string) $diff_elem->old->name);
} elseif ($diff_elem->old instanceof PhpParser\Node\Stmt\Property) {
foreach ($diff_elem->old->props as $prop) {
$keep[] = strtolower($name) . '::$' . $prop->name;
}
} elseif ($diff_elem->old instanceof PhpParser\Node\Stmt\ClassConst) {
foreach ($diff_elem->old->consts as $const) {
$keep[] = strtolower($name) . '::' . $const->name;
}
} elseif ($diff_elem->old instanceof PhpParser\Node\Stmt\TraitUse) {
foreach ($diff_elem->old->traits as $trait) {
$keep[] = strtolower($name . '&' . $trait->getAttribute('resolvedName'));
}
}
} elseif ($diff_elem->type === DiffElem::TYPE_KEEP_SIGNATURE) {
if ($diff_elem->old instanceof PhpParser\Node\Stmt\ClassMethod) {
$keep_signature[] = strtolower($name) . '::' . strtolower((string) $diff_elem->old->name);
} elseif ($diff_elem->old instanceof PhpParser\Node\Stmt\Property) {
foreach ($diff_elem->old->props as $prop) {
$keep_signature[] = strtolower($name) . '::$' . $prop->name;
}
}
2018-10-04 00:16:33 +02:00
} elseif ($diff_elem->type === DiffElem::TYPE_REMOVE || $diff_elem->type === DiffElem::TYPE_ADD) {
/** @psalm-suppress MixedAssignment */
$affected_elem = $diff_elem->type === DiffElem::TYPE_REMOVE ? $diff_elem->old : $diff_elem->new;
if ($affected_elem instanceof PhpParser\Node\Stmt\ClassMethod) {
$add_or_delete[] = strtolower($name) . '::' . strtolower((string) $affected_elem->name);
} elseif ($affected_elem instanceof PhpParser\Node\Stmt\Property) {
foreach ($affected_elem->props as $prop) {
$add_or_delete[] = strtolower($name) . '::$' . $prop->name;
}
2018-10-04 00:16:33 +02:00
} elseif ($affected_elem instanceof PhpParser\Node\Stmt\ClassConst) {
foreach ($affected_elem->consts as $const) {
$add_or_delete[] = strtolower($name) . '::' . $const->name;
}
2018-10-04 00:16:33 +02:00
} elseif ($affected_elem instanceof PhpParser\Node\Stmt\TraitUse) {
foreach ($affected_elem->traits as $trait) {
$add_or_delete[] = strtolower($name . '&' . $trait->getAttribute('resolvedName'));
}
}
}
2019-07-05 22:24:00 +02:00
}
/** @var array<int, array{0: int, 1: int, 2: int, 3: int}> $diff_map */
2018-10-04 00:16:33 +02:00
return [$keep, $keep_signature, $add_or_delete, $diff_map];
}
}