Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ public function run(): int
$overridePaths = $this->resolveOverridePaths($overridePaths);
}

$diffLines = null;
$diff = null;

if ($options['diff'] !== null) {
try {
$diffContent = $this->readDiff($options['diff']);
$diffLines = (new DiffParser())->parse($diffContent);
$diff = (new DiffParser())->parse($diffContent);
} catch (\Throwable $e) {
$this->writeError('Error reading diff: ' . $e->getMessage() . PHP_EOL);

Expand All @@ -100,7 +100,7 @@ public function run(): int

try {
$runner = new SniffRunner($progress);
$report = $runner->run($config, $overridePaths, $diffLines);
$report = $runner->run($config, $overridePaths, $diff);
} catch (\Throwable $e) {
$this->writeError('Runtime error: ' . $e->getMessage() . PHP_EOL);

Expand Down
31 changes: 31 additions & 0 deletions src/Diff/Diff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace DocbookCS\Diff;

final readonly class Diff
Comment thread
NickSdot marked this conversation as resolved.
{
/** @param list<FileChange> $fileChanges */
public function __construct(public array $fileChanges)
{
}

public function changeFor(string $filePath): ?FileChange
{
$normalisedPath = str_replace('\\', '/', $filePath);

foreach ($this->fileChanges as $fileChange) {
$normalisedDiffPath = str_replace('\\', '/', $fileChange->filePath);

if (
$normalisedPath === $normalisedDiffPath
|| str_ends_with($normalisedPath, '/' . ltrim($normalisedDiffPath, '/'))
) {
return $fileChange;
}
}

return null;
}
}
20 changes: 13 additions & 7 deletions src/Diff/DiffParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ final class DiffParser
{
private const string NO_FINAL_LINE_MARKER = '\ No newline at end of file';

/** @return array<string, list<int>> */
public function parse(string $diff): array
public function parse(string $diff): Diff
{
$result = [];
/** @var array<string, list<int>> $changedLinesByFile */
$changedLinesByFile = [];
$currentFile = null;
$deleted = false;
$newLineNumber = 0;
Expand Down Expand Up @@ -41,8 +41,8 @@ public function parse(string $diff): array
}
$currentFile = $path !== '/dev/null' ? $path : null;
$inHunk = false;
if ($currentFile !== null && !isset($result[$currentFile])) {
$result[$currentFile] = [];
if ($currentFile !== null && !isset($changedLinesByFile[$currentFile])) {
$changedLinesByFile[$currentFile] = [];
}
continue;
}
Expand All @@ -67,7 +67,7 @@ public function parse(string $diff): array
}

if (str_starts_with($line, '+')) {
$result[$currentFile][] = $newLineNumber;
$changedLinesByFile[$currentFile][] = $newLineNumber;
$newLineNumber++;
$newLinesRemaining--;
} elseif (str_starts_with($line, '-')) {
Expand All @@ -84,6 +84,12 @@ public function parse(string $diff): array
}
}

return $result;
$fileChanges = [];

foreach ($changedLinesByFile as $filePath => $lineNumbers) {
$fileChanges[] = new FileChange($filePath, $lineNumbers);
}

return new Diff($fileChanges);
}
}
15 changes: 15 additions & 0 deletions src/Diff/FileChange.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace DocbookCS\Diff;

final readonly class FileChange
{
/** @param list<int> $addedLineNumbers */
public function __construct(
public string $filePath,
public array $addedLineNumbers,
) {
}
}
71 changes: 8 additions & 63 deletions src/Runner/SniffRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use DocbookCS\Config\ConfigData;
use DocbookCS\Config\SniffEntry;
use DocbookCS\Diff\Diff;
use DocbookCS\Path\EntityResolver;
use DocbookCS\Path\PathLoader;
use DocbookCS\Path\PathMatcher;
Expand All @@ -25,11 +26,10 @@ public function __construct(?ProgressInterface $progress = null)

/**
* @param list<string>|null $overridePaths
* @param array<string, list<int>>|null $diffLines
* @throws \RuntimeException if a sniff class cannot be found or does not implement SniffInterface.
* @throws \UnexpectedValueException if no files are found to scan.
*/
public function run(ConfigData $config, ?array $overridePaths = null, ?array $diffLines = null): Report
public function run(ConfigData $config, ?array $overridePaths = null, ?Diff $diff = null): Report
{
$startTime = microtime(true);

Expand All @@ -45,8 +45,11 @@ public function run(ConfigData $config, ?array $overridePaths = null, ?array $di
$pathLoader = new PathLoader($includePaths, $matcher);
$files = $pathLoader->loadPaths();

if ($diffLines !== null) {
$files = $this->filterByDiff($files, array_keys($diffLines));
if ($diff !== null) {
$files = array_values(array_filter(
Comment thread
NickSdot marked this conversation as resolved.
$files,
static fn(string $file): bool => $diff->changeFor($file) !== null,
));
}

$report = new Report();
Expand All @@ -60,9 +63,7 @@ public function run(ConfigData $config, ?array $overridePaths = null, ?array $di
foreach ($files as $index => $file) {
$report->incrementFilesScanned();

$changedLines = $diffLines !== null
? $this->getChangedLinesForFile($file, $diffLines)
: null;
$changedLines = $diff?->changeFor($file)?->addedLineNumbers;

$fileReport = $processor->processFile(
$file,
Expand Down Expand Up @@ -125,40 +126,6 @@ private function instantiateSniffs(array $entries): array
return $sniffs;
}

/**
* @param list<string> $files
* @param list<string> $diffPaths
* @return list<string>
*/
private function filterByDiff(array $files, array $diffPaths): array
{
return array_values(
array_filter(
$files,
fn(string $file) => $this->matchesDiffPath($file, $diffPaths),
)
);
}

/** @param list<string> $diffPaths */
private function matchesDiffPath(string $absolutePath, array $diffPaths): bool
{
$normalized = str_replace('\\', '/', $absolutePath);

foreach ($diffPaths as $diffPath) {
$normalizedDiff = str_replace('\\', '/', $diffPath);

if (
$normalized === $normalizedDiff
|| str_ends_with($normalized, '/' . ltrim($normalizedDiff, '/'))
) {
return true;
}
}

return false;
}

private function makeRelative(string $absolutePath): string
{
$cwd = getcwd();
Expand All @@ -175,26 +142,4 @@ private function makeRelative(string $absolutePath): string

return $absolutePath; // @codeCoverageIgnore
}

/**
* @param array<string, list<int>> $diffLines
* @return list<int>
*/
private function getChangedLinesForFile(string $absolutePath, array $diffLines): array
{
$normalized = str_replace('\\', '/', $absolutePath);

foreach ($diffLines as $diffPath => $lines) {
$normalizedDiff = str_replace('\\', '/', $diffPath);

if (
$normalized === $normalizedDiff
|| str_ends_with($normalized, '/' . ltrim($normalizedDiff, '/'))
) {
return $lines;
}
}

return []; // @codeCoverageIgnore
}
}
5 changes: 5 additions & 0 deletions tests/Unit/ApplicationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

use DocbookCS\Application;
use DocbookCS\Config\ConfigData;
use DocbookCS\Diff\Diff;
use DocbookCS\Diff\DiffParser;
use DocbookCS\Diff\FileChange;
use DocbookCS\Config\ConfigParser;
use DocbookCS\Config\ConfigParserException;
use DocbookCS\Config\SniffEntry;
Expand All @@ -25,6 +27,7 @@
use DocbookCS\Sniff\ExceptionNameSniff;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\UsesClass;
use PHPUnit\Framework\TestCase;

#[
Expand All @@ -47,6 +50,8 @@
CoversClass(SniffEntry::class),
CoversClass(SniffRunner::class),
CoversClass(XmlFileProcessor::class),
UsesClass(Diff::class),
UsesClass(FileChange::class),
]
final class ApplicationTest extends TestCase
{
Expand Down
40 changes: 29 additions & 11 deletions tests/Unit/Diff/DiffParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@

namespace DocbookCS\Tests\Unit\Diff;

use DocbookCS\Diff\Diff;
use DocbookCS\Diff\DiffParser;
use DocbookCS\Diff\FileChange;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\UsesClass;
use PHPUnit\Framework\TestCase;

#[
CoversClass(DiffParser::class),
UsesClass(Diff::class),
UsesClass(FileChange::class),
]
final class DiffParserTest extends TestCase
{
Expand All @@ -24,7 +29,7 @@ protected function setUp(): void
#[Test]
public function itReturnsEmptyArrayForEmptyDiff(): void
{
self::assertSame([], $this->parser->parse(''));
self::assertSame([], $this->lineNumbersByFile($this->parser->parse('')));
}

#[Test]
Expand All @@ -41,7 +46,7 @@ public function itParsesAddedLineNumbers(): void
line3
DIFF;

$result = $this->parser->parse($diff);
$result = $this->lineNumbersByFile($this->parser->parse($diff));

self::assertArrayHasKey('reference/file.xml', $result);
self::assertSame([2], $result['reference/file.xml']);
Expand All @@ -62,7 +67,7 @@ public function itParsesMultipleAddedLines(): void
last line
DIFF;

$result = $this->parser->parse($diff);
$result = $this->lineNumbersByFile($this->parser->parse($diff));

self::assertSame([6, 7], $result['doc/chapter.xml']);
}
Expand All @@ -79,7 +84,7 @@ public function itStripsTheBPrefix(): void
+added
DIFF;

$result = $this->parser->parse($diff);
$result = $this->lineNumbersByFile($this->parser->parse($diff));

self::assertArrayHasKey('src/file.xml', $result);
self::assertArrayNotHasKey('b/src/file.xml', $result);
Expand All @@ -99,7 +104,7 @@ public function itExcludesDeletedFiles(): void
-line3
DIFF;

self::assertSame([], $this->parser->parse($diff));
self::assertSame([], $this->lineNumbersByFile($this->parser->parse($diff)));
}

#[Test]
Expand All @@ -116,7 +121,7 @@ public function itHandlesNewlyCreatedFiles(): void
+line3
DIFF;

$result = $this->parser->parse($diff);
$result = $this->lineNumbersByFile($this->parser->parse($diff));

self::assertArrayHasKey('new.xml', $result);
self::assertSame([1, 2, 3], $result['new.xml']);
Expand All @@ -142,7 +147,7 @@ public function itHandlesMultipleFilesInOneDiff(): void
unchanged
DIFF;

$result = $this->parser->parse($diff);
$result = $this->lineNumbersByFile($this->parser->parse($diff));

self::assertArrayHasKey('first.xml', $result);
self::assertArrayHasKey('second.xml', $result);
Expand All @@ -164,7 +169,7 @@ public function itIgnoresRemovedLines(): void
line3
DIFF;

$result = $this->parser->parse($diff);
$result = $this->lineNumbersByFile($this->parser->parse($diff));

// No lines added, so the changed set is empty (not absent — the file is tracked).
self::assertArrayHasKey('file.xml', $result);
Expand All @@ -185,7 +190,7 @@ public function itIgnoresTheMissingFinalNewlineMarker(): void
+second
DIFF;

$result = $this->parser->parse($diff);
$result = $this->lineNumbersByFile($this->parser->parse($diff));

self::assertSame([1, 2], $result['file.xml']);
}
Expand All @@ -209,7 +214,7 @@ public function itTracksLineNumbersAcrossMultipleHunks(): void
line12
DIFF;

$result = $this->parser->parse($diff);
$result = $this->lineNumbersByFile($this->parser->parse($diff));

self::assertSame([2, 12], $result['file.xml']);
}
Expand All @@ -225,8 +230,21 @@ public function itHandlesHunkWithNoContext(): void
+only line
DIFF;

$result = $this->parser->parse($diff);
$result = $this->lineNumbersByFile($this->parser->parse($diff));

self::assertSame([1], $result['file.xml']);
}

// TODO: avoids test diff churn; remove when fixers merged
/** @return array<string, list<int>> */
private function lineNumbersByFile(Diff $diff): array
{
$lineNumbersByFile = [];

foreach ($diff->fileChanges as $fileChange) {
$lineNumbersByFile[$fileChange->filePath] = $fileChange->addedLineNumbers;
}

return $lineNumbersByFile;
}
}
Loading