Skip to content
Open
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ jobs:
- name: Create release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release create "${RELEASE_TAG}" --title "sebastian/cli-parser ${RELEASE_TAG}" --notes-file release-notes.md
run: gh release create "${RELEASE_TAG}" --title "sebastian/diff ${RELEASE_TAG}" --notes-file release-notes.md
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
parameters:
level: 5
level: 10
paths:
- src
- tests
Expand Down
94 changes: 33 additions & 61 deletions src/Differ.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,15 @@
*/
namespace SebastianBergmann\Diff;

use const PHP_INT_SIZE;
use const PREG_SPLIT_DELIM_CAPTURE;
use const PREG_SPLIT_NO_EMPTY;
use function array_any;
use function array_shift;
use function array_unshift;
use function array_values;
use function count;
use function current;
use function end;
use function is_string;
use function key;
use function min;
use function preg_split;
use function prev;
use function reset;
Expand All @@ -44,21 +40,23 @@ public function __construct(DiffOutputBuilderInterface $outputBuilder)
}

/**
* @param list<string>|string $from
* @param list<string>|string $to
* @param array<int|string, int|string>|string $from
* @param array<int|string, int|string>|string $to
*/
public function diff(array|string $from, array|string $to, ?LongestCommonSubsequenceCalculator $lcs = null): string
public function diff(array|string $from, array|string $to): string
{
$diff = $this->diffToArray($from, $to, $lcs);
$diff = $this->diffToArray($from, $to);

return $this->outputBuilder->getDiff($diff);
}

/**
* @param list<string>|string $from
* @param list<string>|string $to
* @param array<int|string, int|string>|string $from
* @param array<int|string, int|string>|string $to
*
* @return list<array{0: mixed, 1: int}>
*/
public function diffToArray(array|string $from, array|string $to, ?LongestCommonSubsequenceCalculator $lcs = null): array
public function diffToArray(array|string $from, array|string $to): array
{
if (is_string($from)) {
$from = $this->splitStringByLines($from);
Expand All @@ -70,41 +68,14 @@ public function diffToArray(array|string $from, array|string $to, ?LongestCommon

[$from, $to, $start, $end] = self::getArrayDiffParted($from, $to);

if ($lcs === null) {
$lcs = $this->selectLcsImplementation($from, $to);
}

$common = $lcs->calculate(array_values($from), array_values($to));
$diff = [];
$diff = [];

foreach ($start as $token) {
$diff[] = [$token, self::OLD];
}

reset($from);
reset($to);

foreach ($common as $token) {
while ((/* from-token */ reset($from)) !== $token) {
$diff[] = [array_shift($from), self::REMOVED];
}

while ((/* to-token */ reset($to)) !== $token) {
$diff[] = [array_shift($to), self::ADDED];
}

$diff[] = [$token, self::OLD];

array_shift($from);
array_shift($to);
}

while (($token = array_shift($from)) !== null) {
$diff[] = [$token, self::REMOVED];
}

while (($token = array_shift($to)) !== null) {
$diff[] = [$token, self::ADDED];
foreach ((new MyersDiff)->calculate(array_values($from), array_values($to)) as $entry) {
$diff[] = $entry;
}

foreach ($end as $token) {
Expand All @@ -118,46 +89,39 @@ public function diffToArray(array|string $from, array|string $to, ?LongestCommon
return $diff;
}

/**
* @return list<string>
*/
private function splitStringByLines(string $input): array
{
return preg_split('/(.*\R)/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
}
$result = preg_split('/(.*\R)/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);

private function selectLcsImplementation(array $from, array $to): LongestCommonSubsequenceCalculator
{
// We do not want to use the time-efficient implementation if its memory
// footprint will probably exceed this value. Note that the footprint
// calculation is only an estimation for the matrix and the LCS method
// will typically allocate a bit more memory than this.
$memoryLimit = 100 * 1024 * 1024;

if ($this->calculateEstimatedFootprint($from, $to) > $memoryLimit) {
return new MemoryEfficientLongestCommonSubsequenceCalculator;
if ($result === false) {
return [];
}

return new TimeEfficientLongestCommonSubsequenceCalculator;
}

private function calculateEstimatedFootprint(array $from, array $to): int
{
$itemSize = PHP_INT_SIZE === 4 ? 76 : 144;

return $itemSize * min(count($from), count($to)) ** 2;
return $result;
}

/**
* @param list<array{0: mixed, 1: int}> $diff
*/
private function detectUnmatchedLineEndings(array $diff): bool
{
$newLineBreaks = ['' => true];
$oldLineBreaks = ['' => true];

foreach ($diff as $entry) {
if (self::OLD === $entry[1]) {
/** @phpstan-ignore argument.type */
$ln = $this->getLinebreak($entry[0]);
$oldLineBreaks[$ln] = true;
$newLineBreaks[$ln] = true;
} elseif (self::ADDED === $entry[1]) {
/** @phpstan-ignore argument.type */
$newLineBreaks[$this->getLinebreak($entry[0])] = true;
} elseif (self::REMOVED === $entry[1]) {
/** @phpstan-ignore argument.type */
$oldLineBreaks[$this->getLinebreak($entry[0])] = true;
}
}
Expand Down Expand Up @@ -198,6 +162,12 @@ private function getLinebreak(int|string $line): string
return "\n";
}

/**
* @param array<int|string, int|string> $from
* @param array<int|string, int|string> $to
*
* @return array{0: array<int|string, int|string>, 1: array<int|string, int|string>, 2: array<int|string, int|string>, 3: array<int|string, int|string>}
*/
private static function getArrayDiffParted(array &$from, array &$to): array
{
$start = [];
Expand All @@ -208,6 +178,7 @@ private static function getArrayDiffParted(array &$from, array &$to): array
foreach ($from as $k => $v) {
$toK = key($to);

/** @phpstan-ignore offsetAccess.notFound */
if ($toK === $k && $v === $to[$k]) {
$start[$k] = $v;

Expand All @@ -231,6 +202,7 @@ private static function getArrayDiffParted(array &$from, array &$to): array
prev($from);
prev($to);

/** @phpstan-ignore offsetAccess.notFound */
$end = [$fromK => $from[$fromK]] + $end;
unset($from[$fromK], $to[$toK]);
} while (true);
Expand Down
1 change: 1 addition & 0 deletions src/Exception/ConfigurationException.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public function __construct(string $option, string $expected, mixed $value, int
'Option "%s" must be %s, got "%s".',
$option,
$expected,
/** @phpstan-ignore binaryOp.invalid */
is_object($value) ? $value::class : (null === $value ? '<null>' : gettype($value) . '#' . $value),
),
$code,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
*/
namespace SebastianBergmann\Diff;

use LogicException;

/**
* @deprecated https://github.com/sebastianbergmann/diff/pull/138
* @codeCoverageIgnore
*/
interface LongestCommonSubsequenceCalculator
final class NoMiddleSnakeFoundException extends LogicException implements Exception
{
/**
* Calculates the longest common subsequence of two arrays.
*/
public function calculate(array $from, array $to): array;
public function __construct()
{
parent::__construct('No middle snake found; input invariants violated');
}
}
99 changes: 0 additions & 99 deletions src/MemoryEfficientLongestCommonSubsequenceCalculator.php

This file was deleted.

Loading