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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ chmod +x phparkitect.phar
./phparkitect.phar check
```

When you run phparkitect as phar and you have custom rules in need of autoloading the project classes you'll need to specify the option `--autoload=[AUTOLOAD_FILE]`.

# Usage

To use this tool you need to launch a command via Bash:
Expand Down Expand Up @@ -473,6 +475,7 @@ phparkitect check --config=/project/yourConfigFile.php
* `--target-php-version`: With this parameter, you can specify which PHP version should use the parser. This can be useful to debug problems and to understand if there are problems with a different PHP version.
Supported PHP versions are: 7.4, 8.0, 8.1, 8.2 8.3
* `--stop-on-failure`: With this option the process will end immediately after the first violation.
* `--autoload`: specify the path of an autoload file to be loaded when running phparkitect.

## Run only a specific rule
For some reasons, you might want to run only a specific rule, you can do it using `runOnlyThis` like this:
Expand Down
60 changes: 54 additions & 6 deletions src/CLI/Command/Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@

use Arkitect\CLI\Baseline;
use Arkitect\CLI\ConfigBuilder;
use Arkitect\CLI\Printer\Printer;
use Arkitect\CLI\Printer\PrinterFactory;
use Arkitect\CLI\Progress\DebugProgress;
use Arkitect\CLI\Progress\Progress;
use Arkitect\CLI\Progress\ProgressBarProgress;
use Arkitect\CLI\Runner;
use Arkitect\CLI\TargetPhpVersion;
Expand All @@ -16,6 +18,7 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Webmozart\Assert\Assert;

class Check extends Command
{
Expand All @@ -26,6 +29,7 @@ class Check extends Command
private const SKIP_BASELINE_PARAM = 'skip-baseline';
private const IGNORE_BASELINE_LINENUMBERS_PARAM = 'ignore-baseline-linenumbers';
private const FORMAT_PARAM = 'format';
private const AUTOLOAD_PARAM = 'autoload';

private const GENERATE_BASELINE_PARAM = 'generate-baseline';
private const DEFAULT_RULES_FILENAME = 'phparkitect.php';
Expand Down Expand Up @@ -95,6 +99,12 @@ protected function configure(): void
InputOption::VALUE_OPTIONAL,
'Output format: text (default), json, gitlab',
'text'
)
->addOption(
self::AUTOLOAD_PARAM,
'a',
InputOption::VALUE_REQUIRED,
'Specify an autoload file to use',
);
}

Expand Down Expand Up @@ -123,20 +133,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$this->printHeadingLine($output);

$config = ConfigBuilder::loadFromFile($rulesFilename)
->autoloadFilePath($input->getOption(self::AUTOLOAD_PARAM))
->stopOnFailure($stopOnFailure)
->targetPhpVersion(TargetPhpVersion::create($phpVersion))
->baselineFilePath(Baseline::resolveFilePath($useBaseline, self::DEFAULT_BASELINE_FILENAME))
->ignoreBaselineLinenumbers($ignoreBaselineLinenumbers)
->skipBaseline($skipBaseline)
->format($format);

$printer = PrinterFactory::create($config->getFormat());

$progress = $verbose ? new DebugProgress($output) : new ProgressBarProgress($output);
$this->requireAutoload($output, $config->getAutoloadFilePath());
$printer = $this->createPrinter($output, $config->getFormat());
$progress = $this->createProgress($output, $verbose);
$baseline = $this->createBaseline($output, $config->isSkipBaseline(), $config->getBaselineFilePath());

$baseline = Baseline::create($config->isSkipBaseline(), $config->getBaselineFilePath());

$baseline->getFilename() && $output->writeln("Baseline file '{$baseline->getFilename()}' found");
$output->writeln("Config file '$rulesFilename' found\n");

$runner = new Runner();
Expand Down Expand Up @@ -177,6 +186,45 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
}

/**
* @psalm-suppress UnresolvableInclude
*/
protected function requireAutoload(OutputInterface $output, ?string $filePath): void
{
if (null === $filePath) {
return;
}

Assert::file($filePath, "Cannot find '$filePath'");

require_once $filePath;

$output->writeln("Autoload file '$filePath' added");
}

protected function createPrinter(OutputInterface $output, string $format): Printer
{
$output->writeln("Output format: $format");

return PrinterFactory::create($format);
}

protected function createProgress(OutputInterface $output, bool $verbose): Progress
{
$output->writeln('Progress: '.($verbose ? 'debug' : 'bar'));

return $verbose ? new DebugProgress($output) : new ProgressBarProgress($output);
}

protected function createBaseline(OutputInterface $output, bool $skipBaseline, ?string $baselineFilePath): Baseline
{
$baseline = Baseline::create($skipBaseline, $baselineFilePath);

$baseline->getFilename() && $output->writeln("Baseline file '{$baseline->getFilename()}' found");

return $baseline;
}

protected function printHeadingLine(OutputInterface $output): void
{
$app = $this->getApplication();
Expand Down
15 changes: 15 additions & 0 deletions src/CLI/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class Config

private string $format;

private ?string $autoloadFilePath;

private TargetPhpVersion $targetPhpVersion;

public function __construct()
Expand All @@ -39,6 +41,7 @@ public function __construct()
$this->baselineFilePath = null;
$this->ignoreBaselineLinenumbers = false;
$this->format = PrinterFactory::default();
$this->autoloadFilePath = null;
$this->targetPhpVersion = TargetPhpVersion::latest();
}

Expand Down Expand Up @@ -152,4 +155,16 @@ public function isSkipBaseline(): bool
{
return $this->skipBaseline;
}

public function autoloadFilePath(?string $autoloadFilePath): self
{
$this->autoloadFilePath = $autoloadFilePath;

return $this;
}

public function getAutoloadFilePath(): ?string
{
return $this->autoloadFilePath;
}
}
17 changes: 16 additions & 1 deletion tests/E2E/Cli/CheckCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -252,16 +252,27 @@ public function test_gitlab_format_output_no_errors(): void
self::assertJsonStringEqualsJsonString($expectedJson, $cmdTester->getDisplay());
}

public function test_autoload_file(): void
{
$configFilePath = __DIR__.'/../_fixtures/autoload/phparkitect.php';

$cmdTester = $this->runCheck($configFilePath, null, null, false, false, false, 'text', __DIR__.'/../_fixtures/autoload/autoload.php');

self::assertCommandWasSuccessful($cmdTester);
}

protected function runCheck(
$configFilePath = null,
?bool $stopOnFailure = null,
?string $useBaseline = null,
$generateBaseline = false,
bool $skipBaseline = false,
bool $ignoreBaselineNumbers = false,
string $format = 'text'
string $format = 'text',
?string $autoloadFilePath = null
): ApplicationTester {
$input = ['check'];

if (null !== $configFilePath) {
$input['--config'] = $configFilePath;
}
Expand All @@ -286,6 +297,10 @@ protected function runCheck(

$input['--format'] = $format;

if ($autoloadFilePath) {
$input['--autoload'] = $autoloadFilePath;
}

$app = new PhpArkitectApplication();
$app->setAutoExit(false);

Expand Down
22 changes: 22 additions & 0 deletions tests/E2E/_fixtures/autoload/autoload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);

spl_autoload_register(function ($class): void {
$classmap = [
'Autoload\Services\UserService' => __DIR__.'/src/Service/UserService.php',
'Autoload\Model\User' => __DIR__.'/src/Model/User.php',
'Autoload\Model\UserInterface' => __DIR__.'/src/Model/UserInterface.php',
];

$path = $classmap[$class] ?? null;

if (null === $path) {
return;
}

if (!file_exists($path)) {
return;
}

require_once $path;
});
57 changes: 57 additions & 0 deletions tests/E2E/_fixtures/autoload/phparkitect.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);

use Arkitect\Analyzer\ClassDescription;
use Arkitect\ClassSet;
use Arkitect\CLI\Config;
use Arkitect\Expression\Description;
use Arkitect\Expression\Expression;
use Arkitect\Expression\ForClasses\ResideInOneOfTheseNamespaces;
use Arkitect\Rules\Rule;
use Arkitect\Rules\Violation;
use Arkitect\Rules\ViolationMessage;
use Arkitect\Rules\Violations;

return static function (Config $config): void {
// a dummy rule to check if the class is autoloaded
// is_a with 'true' passed as the third parameter triggers the autoloader
$autoload_rule = new class('Autoload\Model\UserInterface') implements Expression {
public string $implements;

public function __construct(string $implements)
{
$this->implements = $implements;
}

public function describe(ClassDescription $theClass, string $because): Description
{
return new Description("{$theClass->getFQCN()} should implement {$this->implements}", $because);
}

public function evaluate(ClassDescription $theClass, Violations $violations, string $because): void
{
if (is_a($theClass->getFQCN(), $this->implements, true)) {
return;
}

$violation = Violation::create(
$theClass->getFQCN(),
ViolationMessage::selfExplanatory($this->describe($theClass, $because)),
$theClass->getFilePath()
);

$violations->add($violation);
}
};

$class_set = ClassSet::fromDir(__DIR__.'/src');

$rule = Rule::allClasses()
->except('Autoload\Model\UserInterface')
->that(new ResideInOneOfTheseNamespaces('Autoload\Model'))
->should($autoload_rule)
->because('we want check if the class is autoloaded');

$config
->add($class_set, $rule);
};
8 changes: 8 additions & 0 deletions tests/E2E/_fixtures/autoload/src/Model/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php
declare(strict_types=1);

namespace Autoload\Model;

class User implements UserInterface
{
}
8 changes: 8 additions & 0 deletions tests/E2E/_fixtures/autoload/src/Model/UserInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php
declare(strict_types=1);

namespace Autoload\Model;

interface UserInterface
{
}
8 changes: 8 additions & 0 deletions tests/E2E/_fixtures/autoload/src/Service/UserService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php
declare(strict_types=1);

namespace Autoload\Services;

class UserService
{
}
29 changes: 27 additions & 2 deletions tests/Unit/CLI/Printer/GitlabPrinterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,33 @@ public function test_print_with_violations(): void

$result = $printer->print($violationsCollection);

self::assertSame(<<<JSON
[{"description":"Some error message","check_name":"RuleA.some-error-message","fingerprint":"7ddcfd42f5f2af3d00864ef959a0327f508cb5227aedca96d919d681a5dcde4a","severity":"major","location":{"path":"tests\/Unit\/CLI\/Printer\/GitlabPrinterTest.php","lines":{"begin":42}}},{"description":"Another error message","check_name":"RuleB.another-error-message","fingerprint":"800c2ceafbf4023e401200186ecabdfe59891c5d6670e86571e3c50339df07dc","severity":"major","location":{"path":"tests\/Unit\/CLI\/Printer\/GitlabPrinterTest.php","lines":{"begin":1}}}]
self::assertJsonStringEqualsJsonString(<<<JSON
[
{
"description": "Some error message",
"check_name": "RuleA.some-error-message",
"fingerprint": "7ddcfd42f5f2af3d00864ef959a0327f508cb5227aedca96d919d681a5dcde4a",
"severity": "major",
"location": {
"path": "tests\/Unit\/CLI\/Printer\/GitlabPrinterTest.php",
"lines": {
"begin": 42
}
}
},
{
"description": "Another error message",
"check_name": "RuleB.another-error-message",
"fingerprint": "800c2ceafbf4023e401200186ecabdfe59891c5d6670e86571e3c50339df07dc",
"severity": "major",
"location": {
"path": "tests\/Unit\/CLI\/Printer\/GitlabPrinterTest.php",
"lines": {
"begin": 1
}
}
}
]
JSON, $result);
}

Expand Down