2018-09-25 23:52:27 +02:00
|
|
|
<?php
|
2018-11-06 03:57:36 +01:00
|
|
|
namespace Psalm\Internal\Diff;
|
2018-09-25 23:52:27 +02:00
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
use function count;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function get_class;
|
2019-07-05 22:24:00 +02:00
|
|
|
use PhpParser;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function strpos;
|
|
|
|
use function strtolower;
|
2019-07-05 22:24:00 +02:00
|
|
|
use function substr;
|
|
|
|
use function trim;
|
2018-09-25 23:52:27 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2018-10-26 06:59:14 +02:00
|
|
|
class ClassStatementsDiffer extends AstDiffer
|
2018-09-25 23:52:27 +02:00
|
|
|
{
|
|
|
|
/**
|
|
|
|
* 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
|
2018-10-23 19:38:12 +02:00
|
|
|
* @param array<int, PhpParser\Node\Stmt> $a
|
|
|
|
* @param array<int, PhpParser\Node\Stmt> $b
|
2018-09-25 23:52:27 +02:00
|
|
|
*
|
2018-09-26 23:54:08 +02:00
|
|
|
* @return array{
|
|
|
|
* 0: array<int, string>,
|
2018-09-27 19:32:08 +02:00
|
|
|
* 1: array<int, string>,
|
2018-09-26 23:54:08 +02:00
|
|
|
* 2: array<int, string>,
|
|
|
|
* 3: array<int, array{0: int, 1: int, 2: int, 3: int}>
|
|
|
|
* }
|
2018-09-25 23:52:27 +02:00
|
|
|
*/
|
2018-09-27 19:32:08 +02:00
|
|
|
public static function diff($name, array $a, array $b, $a_code, $b_code)
|
2018-09-25 23:52:27 +02:00
|
|
|
{
|
2018-09-26 22:33:59 +02:00
|
|
|
$diff_map = [];
|
|
|
|
|
2018-09-25 23:52:27 +02:00
|
|
|
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
|
|
|
|
*/
|
2018-09-25 23:52:27 +02:00
|
|
|
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) {
|
2018-09-25 23:52:27 +02:00
|
|
|
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> */
|
2018-09-25 23:52:27 +02:00
|
|
|
$a_comments = $a->getComments();
|
2019-10-09 16:04:34 +02:00
|
|
|
/** @var list<PhpParser\Comment> */
|
2018-09-25 23:52:27 +02:00
|
|
|
$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;
|
|
|
|
|
2019-04-23 03:50:58 +02:00
|
|
|
if ($a_size === $b_size
|
|
|
|
&& substr($a_code, $a_start, $a_size) === substr($b_code, $b_start, $b_size)
|
|
|
|
) {
|
2018-10-26 06:59:14 +02:00
|
|
|
$start_diff = $b_start - $a_start;
|
|
|
|
$line_diff = $b->getLine() - $a->getLine();
|
|
|
|
|
2020-06-25 06:24:37 +02:00
|
|
|
/** @psalm-suppress MixedArrayAssignment */
|
2019-01-08 06:54:48 +01:00
|
|
|
$diff_map[] = [$a_start, $a_end, $start_diff, $line_diff];
|
2018-09-26 22:33:59 +02:00
|
|
|
|
2018-09-25 23:52:27 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-04-23 03:50:58 +02:00
|
|
|
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;
|
2018-09-25 23:52:27 +02:00
|
|
|
|
2019-04-23 03:50:58 +02:00
|
|
|
$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);
|
2018-09-25 23:52:27 +02:00
|
|
|
|
2018-10-07 04:58:21 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-09-26 23:54:08 +02:00
|
|
|
} 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) {
|
2018-09-26 23:54:08 +02:00
|
|
|
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);
|
2018-09-26 22:33:59 +02:00
|
|
|
} else {
|
|
|
|
$signature_change = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$signature_change && !$body_change) {
|
2020-06-25 06:24:37 +02:00
|
|
|
/** @psalm-suppress MixedArrayAssignment */
|
2018-09-26 22:33:59 +02:00
|
|
|
$diff_map[] = [$a_start, $a_end, $b_start - $a_start, $b->getLine() - $a->getLine()];
|
2018-09-25 23:52:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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 = [];
|
2018-09-25 23:52:27 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2018-10-03 19:58:32 +02:00
|
|
|
} elseif ($diff_elem->old instanceof PhpParser\Node\Stmt\TraitUse) {
|
|
|
|
foreach ($diff_elem->old->traits as $trait) {
|
2020-03-02 21:49:05 +01:00
|
|
|
$keep[] = strtolower($name . '&' . (string) $trait->getAttribute('resolvedName'));
|
2018-10-03 19:58:32 +02:00
|
|
|
}
|
2018-09-25 23:52:27 +02:00
|
|
|
}
|
|
|
|
} 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-09-26 23:54:08 +02:00
|
|
|
}
|
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-09-26 23:54:08 +02:00
|
|
|
}
|
2018-10-04 00:16:33 +02:00
|
|
|
} elseif ($affected_elem instanceof PhpParser\Node\Stmt\TraitUse) {
|
|
|
|
foreach ($affected_elem->traits as $trait) {
|
2020-03-02 21:49:05 +01:00
|
|
|
$add_or_delete[] = strtolower($name . '&' . (string) $trait->getAttribute('resolvedName'));
|
2018-10-03 19:58:32 +02:00
|
|
|
}
|
2018-09-26 23:54:08 +02:00
|
|
|
}
|
2018-09-25 23:52:27 +02:00
|
|
|
}
|
2019-07-05 22:24:00 +02:00
|
|
|
}
|
2018-09-25 23:52:27 +02:00
|
|
|
|
2019-09-17 00:42:44 +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];
|
2018-09-25 23:52:27 +02:00
|
|
|
}
|
|
|
|
}
|