Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
refactor: support for violations
  • Loading branch information
nunomaduro committed Apr 1, 2023
1 parent dd0ecdc commit 8e66263
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 50 deletions.
26 changes: 16 additions & 10 deletions src/Blueprint.php
Expand Up @@ -11,7 +11,7 @@
use Pest\Arch\Support\Composer;
use Pest\Arch\ValueObjects\Dependency;
use Pest\Arch\ValueObjects\Targets;
use Pest\Arch\ValueObjects\ViolationReference;
use Pest\Arch\ValueObjects\Violation;
use PhpParser\Node\Name;
use PHPUnit\Architecture\ArchitectureAsserts;
use PHPUnit\Architecture\Elements\Layer\Layer;
Expand Down Expand Up @@ -75,7 +75,7 @@ public function expectToUse(LayerOptions $options, callable $failure): void
/**
* Expects the target to "only" use the given dependencies.
*
* @param callable(string, string, string, ViolationReference|null): mixed $failure
* @param callable(string, string, string, Violation|null): mixed $failure
*/
public function expectToOnlyUse(LayerOptions $options, callable $failure): void
{
Expand Down Expand Up @@ -115,7 +115,7 @@ public function expectToOnlyUse(LayerOptions $options, callable $failure): void
/**
* Expects the dependency to "only" be used by given targets.
*
* @param callable(string, string, ViolationReference|null): mixed $failure
* @param callable(string, string, Violation|null): mixed $failure
*/
public function expectToOnlyBeUsedIn(LayerOptions $options, callable $failure): void
{
Expand Down Expand Up @@ -171,7 +171,7 @@ public static function assertEquals(mixed $expected, mixed $actual, string $mess
Assert::assertEquals($expected, $actual, $message);
}

private function getUsagePathAndLines(Layer $layer, string $objectName, string $target): null|ViolationReference
private function getUsagePathAndLines(Layer $layer, string $objectName, string $target): null|Violation
{
$dependOnObjects = array_filter(
$layer->getIterator()->getArrayCopy(), //@phpstan-ignore-line
Expand All @@ -187,14 +187,20 @@ private function getUsagePathAndLines(Layer $layer, string $objectName, string $
);

/** @var array<int, Name> $names */
/** @phpstan-ignore-next-line */
$names = array_values(array_filter($names, static fn (Name $name): bool => $name->toString() === $target));
$names = array_values(array_filter(
$names, static fn (Name $name): bool => $name->toString() === $target, // @phpstan-ignore-line
));

if ($names !== []) {
/** @phpstan-ignore-next-line */
return new ViolationReference($dependOnObject->path, $names[0]->getAttribute('startLine'), $names[0]->getAttribute('endLine'));
if ($names === []) {
return null;
}

return null;
$startLine = $names[0]->getAttribute('startLine');
assert(is_int($startLine));

$endLine = $names[0]->getAttribute('endLine');
assert(is_int($endLine));

return new Violation($dependOnObject->path, $startLine, $endLine);
}
}
26 changes: 0 additions & 26 deletions src/Exception/ArchitectureViolationException.php

This file was deleted.

35 changes: 35 additions & 0 deletions src/Exceptions/ArchExpectationFailedException.php
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Pest\Arch\Exceptions;

use NunoMaduro\Collision\Contracts\RenderableOnCollisionEditor;
use Pest\Arch\ValueObjects\Violation;
use PHPUnit\Framework\AssertionFailedError;
use Whoops\Exception\Frame;

/**
* @internal
*/
final class ArchExpectationFailedException extends AssertionFailedError implements RenderableOnCollisionEditor // @phpstan-ignore-line
{
/**
* Creates a new exception instance.
*/
public function __construct(private readonly Violation $reference, string $message)
{
parent::__construct($message);
}

/**
* {@inheritDoc}
*/
public function toCollisionEditor(): Frame
{
return new Frame([
'file' => $this->reference->path,
'line' => $this->reference->start,
]);
}
}
10 changes: 5 additions & 5 deletions src/Expectations/ToOnlyBeUsedIn.php
Expand Up @@ -6,12 +6,12 @@

use Pest\Arch\Blueprint;
use Pest\Arch\Collections\Dependencies;
use Pest\Arch\Exception\ArchitectureViolationException;
use Pest\Arch\Exceptions\ArchExpectationFailedException;
use Pest\Arch\GroupArchExpectation;
use Pest\Arch\Options\LayerOptions;
use Pest\Arch\SingleArchExpectation;
use Pest\Arch\ValueObjects\Targets;
use Pest\Arch\ValueObjects\ViolationReference;
use Pest\Arch\ValueObjects\Violation;
use Pest\Expectation;
use PHPUnit\Framework\ExpectationFailedException;

Expand Down Expand Up @@ -41,14 +41,14 @@ public static function make(Expectation $expectation, array|string $targets): Gr
static function (LayerOptions $options) use ($blueprint): void {
$blueprint->expectToOnlyBeUsedIn(
$options,
static function (string $value, string $notAllowedDependOn, ViolationReference|null $reference): void {
if ($reference === null) {
static function (string $value, string $notAllowedDependOn, Violation|null $violation): void {
if ($violation === null) {
throw new ExpectationFailedException(
"Expecting '$value' not to be used on '$notAllowedDependOn'.",
);
}

throw new ArchitectureViolationException("Expecting '$value' not to be used on '$notAllowedDependOn'.", $reference);
throw new ArchExpectationFailedException($violation, "Expecting '$value' not to be used on '$notAllowedDependOn'.");
},
);
},
Expand Down
10 changes: 5 additions & 5 deletions src/Expectations/ToOnlyUse.php
Expand Up @@ -6,11 +6,11 @@

use Pest\Arch\Blueprint;
use Pest\Arch\Collections\Dependencies;
use Pest\Arch\Exception\ArchitectureViolationException;
use Pest\Arch\Exceptions\ArchExpectationFailedException;
use Pest\Arch\Options\LayerOptions;
use Pest\Arch\SingleArchExpectation;
use Pest\Arch\ValueObjects\Targets;
use Pest\Arch\ValueObjects\ViolationReference;
use Pest\Arch\ValueObjects\Violation;
use Pest\Expectation;
use PHPUnit\Framework\ExpectationFailedException;

Expand All @@ -35,16 +35,16 @@ public static function make(Expectation $expectation, array|string $dependencies

return SingleArchExpectation::fromExpectation($expectation, static function (LayerOptions $options) use ($blueprint): void {
$blueprint->expectToOnlyUse(
$options, static function (string $value, string $dependOn, string $notAllowedDependOn, ViolationReference|null $reference): void {
$options, static function (string $value, string $dependOn, string $notAllowedDependOn, Violation|null $violation): void {
$message = $dependOn === ''
? "Expecting '{$value}' to use nothing. However, it uses '{$notAllowedDependOn}'."
: "Expecting '{$value}' to only use '{$dependOn}'. However, it also uses '{$notAllowedDependOn}'.";

if ($reference === null) {
if ($violation === null) {
throw new ExpectationFailedException($message);
}

throw new ArchitectureViolationException($message, $reference);
throw new ArchExpectationFailedException($violation, $message);
});
});
}
Expand Down
Expand Up @@ -4,9 +4,13 @@

namespace Pest\Arch\ValueObjects;

final class ViolationReference
final class Violation
{
/**
* Creates a new violation instance.
*/
public function __construct(public readonly string $path, public readonly int $start, public readonly int $end)
{
//
}
}
7 changes: 6 additions & 1 deletion tests/Arch.php
Expand Up @@ -2,7 +2,9 @@

use Pest\Arch\ValueObjects\Dependency;
use Pest\Arch\ValueObjects\Targets;
use Pest\Arch\ValueObjects\Violation;
use Pest\Expectation;
use Whoops\Exception\Frame;

test('globals')
->expect('Pest\Arch')
Expand All @@ -26,7 +28,10 @@

test('exceptions')
->expect('Pest\Arch\Exceptions')
->toUseNothing();
->toOnlyUse([
Frame::class,
Violation::class,
])->ignoring('PHPUnit\Framework');

test('expectations')
->expect('Pest\Arch\Expectations')
Expand Down
4 changes: 2 additions & 2 deletions tests/Pest.php
@@ -1,6 +1,6 @@
<?php

use Pest\Arch\Exception\ArchitectureViolationException;
use Pest\Arch\Exceptions\ArchExpectationFailedException;

uses()->beforeEach(function () {
$this->arch()->ignore([
Expand All @@ -9,7 +9,7 @@
})->in(__DIR__);

expect()->extend('toThrowArchitectureViolation', function (string $message, string $file, int $line) {
return $this->toThrow(function (ArchitectureViolationException $exception) use ($line, $file, $message) {
return $this->toThrow(function (ArchExpectationFailedException $exception) use ($line, $file, $message) {
$frame = $exception->toCollisionEditor();
$violationFile = str_replace(DIRECTORY_SEPARATOR, '/', $frame->getFile());
$violationLine = $frame->getLine();
Expand Down

0 comments on commit 8e66263

Please sign in to comment.