Thanks to visit codestin.com
Credit goes to github.com

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
12 changes: 11 additions & 1 deletion src/Cache/AnalysisCacheMetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
use Composer\InstalledVersions;

use function array_map;
use function file_exists;
use function hash;
use function hash_file;
use function json_encode;
use function rtrim;
use function sort;

use const JSON_INVALID_UTF8_SUBSTITUTE;
Expand All @@ -28,11 +30,12 @@ public function metadata(string $basePath, string $configPath, array $scanPaths,
sort($files);

return [
'version' => 1,
'version' => 2,
'basePath' => $basePath,
'configPath' => $configPath,
'configHash' => $this->fileHash($configPath),
'composerGeneratedVersionHash' => $this->composerGeneratedVersionHash(),
'composerHash' => $this->composerHash($basePath),
'scanPaths' => $scanPaths,
'filesHash' => $this->filesHash($files),
];
Expand Down Expand Up @@ -68,6 +71,13 @@ private function filesHash(array $files): string
], $files), JSON_INVALID_UTF8_SUBSTITUTE | JSON_THROW_ON_ERROR));
}

private function composerHash(string $basePath): string
{
$composerFile = rtrim($basePath, '/') . '/composer.json';

return file_exists($composerFile) ? $this->fileHash($composerFile) : '';
}

public function composerGeneratedVersionHash(): string
{
$version = InstalledVersions::isInstalled(Version::PACKAGE_NAME)
Expand Down
4 changes: 4 additions & 0 deletions src/Preset/Presets/Psr1Preset.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Boundwize\StructArmed\Preset\PresetInterface;
use Boundwize\StructArmed\Rule\Rules\Class_\ClassConstantNameMustBeUpperCaseRule;
use Boundwize\StructArmed\Rule\Rules\Class_\ClassNameMustBeStudlyCapsRule;
use Boundwize\StructArmed\Rule\Rules\Composer\Psr4DirectoryExistsRule;
use Boundwize\StructArmed\Rule\Rules\Composer\Psr4NamespaceRule;
use Boundwize\StructArmed\Rule\Rules\Composer\Psr4SourcePathsRule;
use Boundwize\StructArmed\Rule\Rules\File\Psr1PhpTagsRule;
Expand All @@ -29,6 +30,8 @@

public const SOURCE_PATHS_MUST_BE_IN_COMPOSER = 'psr1.source_paths.must_be_in_composer';

public const SOURCE_PATHS_MUST_EXIST_ON_DISK = 'psr1.source_paths.must_exist_on_disk';

public const CLASSES_MUST_BE_STUDLY_CAPS = 'psr1.classes.must_be_studly_caps';

public const CLASS_CONSTANTS_MUST_BE_UPPER_CASE = 'psr1.class_constants.must_be_upper_case';
Expand All @@ -54,6 +57,7 @@ public function apply(Architecture $architecture): void
);
$architecture->rule(self::CLASSES_MUST_FOLLOW_PSR4, new Psr4NamespaceRule(self::SOURCE_LAYER));
$architecture->rule(self::SOURCE_PATHS_MUST_BE_IN_COMPOSER, new Psr4SourcePathsRule($this->sourcePaths));
$architecture->rule(self::SOURCE_PATHS_MUST_EXIST_ON_DISK, new Psr4DirectoryExistsRule());
$architecture->rule(self::CLASSES_MUST_BE_STUDLY_CAPS, new ClassNameMustBeStudlyCapsRule(self::SOURCE_LAYER));
$architecture->rule(
self::CLASS_CONSTANTS_MUST_BE_UPPER_CASE,
Expand Down
4 changes: 4 additions & 0 deletions src/Preset/Presets/Psr4Preset.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Boundwize\StructArmed\Architecture;
use Boundwize\StructArmed\Preset\PresetInterface;
use Boundwize\StructArmed\Rule\Rules\Composer\Psr4DirectoryExistsRule;
use Boundwize\StructArmed\Rule\Rules\Composer\Psr4NamespaceRule;
use Boundwize\StructArmed\Rule\Rules\Composer\Psr4SourcePathsRule;

Expand All @@ -15,6 +16,8 @@

public const SOURCE_PATHS_MUST_BE_IN_COMPOSER = 'psr4.source_paths.must_be_in_composer';

public const SOURCE_PATHS_MUST_EXIST_ON_DISK = 'psr4.source_paths.must_exist_on_disk';

public const CLASSES_MUST_MATCH_COMPOSER = 'psr4.classes.must_match_composer';

/**
Expand All @@ -36,5 +39,6 @@ public function apply(Architecture $architecture): void
self::SOURCE_PATHS_MUST_BE_IN_COMPOSER,
new Psr4SourcePathsRule($this->sourcePaths)
);
$architecture->rule(self::SOURCE_PATHS_MUST_EXIST_ON_DISK, new Psr4DirectoryExistsRule());
}
}
76 changes: 76 additions & 0 deletions src/Rule/Rules/Composer/Psr4DirectoryExistsRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace Boundwize\StructArmed\Rule\Rules\Composer;

use Boundwize\StructArmed\Architecture;
use Boundwize\StructArmed\Composer\Psr4PathResolver;
use Boundwize\StructArmed\Rule\ProjectRuleInterface;
use Boundwize\StructArmed\Rule\RuleViolation;

use function file_exists;
use function implode;
use function is_dir;
use function rtrim;
use function sprintf;

final readonly class Psr4DirectoryExistsRule implements ProjectRuleInterface
{
public function __construct(
private Psr4PathResolver $psr4PathResolver = new Psr4PathResolver(),
) {
}

public function evaluateProject(string $basePath, Architecture $architecture, array $skipPaths = []): ?RuleViolation
{
$composerFile = rtrim($basePath, '/') . '/composer.json';

if (! file_exists($composerFile)) {
return $this->violation(
'composer.json was not found',
$composerFile
);
}

$composer = $this->psr4PathResolver->composerConfig($basePath);

if ($composer === null) {
return $this->violation(
'composer.json is not valid JSON',
$composerFile
);
}

$nonExistentPaths = [];

foreach ($this->psr4PathResolver->paths($basePath) as $autoloadPath) {
if (! is_dir(rtrim($basePath, '/') . '/' . $autoloadPath)) {
$nonExistentPaths[] = $autoloadPath;
}
}

if ($nonExistentPaths === []) {
return null;
}

return $this->violation(
sprintf(
'PSR-4 source path(s) [%s] declared in composer.json do not exist on disk',
implode(', ', $nonExistentPaths)
),
$composerFile
);
}

private function violation(string $message, string $file): RuleViolation
{
return new RuleViolation(
message: $message,
file: $file,
line: 1,
className: '',
layer: 'Source',
);
}
}
15 changes: 13 additions & 2 deletions tests/Preset/PresetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public function testPsr1PresetRegistersSourceLayerAndRules(): void
$this->assertArrayHasKey(Psr1Preset::FILES_SHOULD_DECLARE_SYMBOLS_OR_SIDE_EFFECTS, $rules);
$this->assertArrayHasKey(Psr1Preset::CLASSES_MUST_FOLLOW_PSR4, $rules);
$this->assertArrayHasKey(Psr1Preset::SOURCE_PATHS_MUST_BE_IN_COMPOSER, $rules);
$this->assertArrayHasKey(Psr1Preset::SOURCE_PATHS_MUST_EXIST_ON_DISK, $rules);
$this->assertArrayHasKey(Psr1Preset::CLASSES_MUST_BE_STUDLY_CAPS, $rules);
$this->assertArrayHasKey(Psr1Preset::CLASS_CONSTANTS_MUST_BE_UPPER_CASE, $rules);
$this->assertArrayHasKey(Psr1Preset::METHODS_MUST_BE_CAMEL_CASE, $rules);
Expand All @@ -61,6 +62,7 @@ public function testPsr12PresetAppliesPsr1RulesAndAddsVisibilityRules(): void
$this->assertArrayHasKey(Psr1Preset::FILES_SHOULD_DECLARE_SYMBOLS_OR_SIDE_EFFECTS, $rules);
$this->assertArrayHasKey(Psr1Preset::CLASSES_MUST_FOLLOW_PSR4, $rules);
$this->assertArrayHasKey(Psr1Preset::SOURCE_PATHS_MUST_BE_IN_COMPOSER, $rules);
$this->assertArrayHasKey(Psr1Preset::SOURCE_PATHS_MUST_EXIST_ON_DISK, $rules);
$this->assertArrayHasKey(Psr1Preset::CLASSES_MUST_BE_STUDLY_CAPS, $rules);
$this->assertArrayHasKey(Psr1Preset::CLASS_CONSTANTS_MUST_BE_UPPER_CASE, $rules);
$this->assertArrayHasKey(Psr1Preset::METHODS_MUST_BE_CAMEL_CASE, $rules);
Expand All @@ -86,6 +88,10 @@ public function testPsr4PresetRegistersSourceLayerAndRules(): void
Psr4Preset::SOURCE_PATHS_MUST_BE_IN_COMPOSER,
$architecture->getRules()
);
$this->assertArrayHasKey(
Psr4Preset::SOURCE_PATHS_MUST_EXIST_ON_DISK,
$architecture->getRules()
);
}

public function testPsr4PresetUsesComposerSourcePathsByDefault(): void
Expand All @@ -99,6 +105,10 @@ public function testPsr4PresetUsesComposerSourcePathsByDefault(): void
Psr4Preset::SOURCE_PATHS_MUST_BE_IN_COMPOSER,
$architecture->getRules()
);
$this->assertArrayHasKey(
Psr4Preset::SOURCE_PATHS_MUST_EXIST_ON_DISK,
$architecture->getRules()
);
}

public function testPsr15PresetRegistersSourceLayerAndRules(): void
Expand Down Expand Up @@ -199,14 +209,15 @@ public function testPsr1AndPsr12BothEnabledDoNotDuplicatePsr1Rules(): void

$rules = $architecture->getRules();

// PSR-1 has 8 rules, PSR-12 adds 3 more — total must be 11, not 19 (8+8+3 if duplicated)
$this->assertCount(11, $rules);
// PSR-1 has 9 rules, PSR-12 adds 3 more — total must be 12, not 21 (9+9+3 if duplicated)
$this->assertCount(12, $rules);

$this->assertArrayHasKey(Psr1Preset::FILES_MUST_USE_VALID_TAGS, $rules);
$this->assertArrayHasKey(Psr1Preset::FILES_MUST_USE_UTF8_WITHOUT_BOM, $rules);
$this->assertArrayHasKey(Psr1Preset::FILES_SHOULD_DECLARE_SYMBOLS_OR_SIDE_EFFECTS, $rules);
$this->assertArrayHasKey(Psr1Preset::CLASSES_MUST_FOLLOW_PSR4, $rules);
$this->assertArrayHasKey(Psr1Preset::SOURCE_PATHS_MUST_BE_IN_COMPOSER, $rules);
$this->assertArrayHasKey(Psr1Preset::SOURCE_PATHS_MUST_EXIST_ON_DISK, $rules);
$this->assertArrayHasKey(Psr1Preset::CLASSES_MUST_BE_STUDLY_CAPS, $rules);
$this->assertArrayHasKey(Psr1Preset::CLASS_CONSTANTS_MUST_BE_UPPER_CASE, $rules);
$this->assertArrayHasKey(Psr1Preset::METHODS_MUST_BE_CAMEL_CASE, $rules);
Expand Down
112 changes: 112 additions & 0 deletions tests/Rule/Composer/Psr4DirectoryExistsRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php

declare(strict_types=1);

namespace Boundwize\StructArmed\Tests\Rule\Composer;

use Boundwize\StructArmed\Architecture;
use Boundwize\StructArmed\Rule\Rules\Composer\Psr4DirectoryExistsRule;
use Boundwize\StructArmed\Rule\RuleViolation;
use Boundwize\StructArmed\Tests\Support\TemporaryDirectoryCleanupTrait;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;

use function file_put_contents;
use function mkdir;

#[CoversClass(Psr4DirectoryExistsRule::class)]
final class Psr4DirectoryExistsRuleTest extends TestCase
{
use TemporaryDirectoryCleanupTrait;

public function testPassesWhenAllPsr4DirectoriesExistOnDisk(): void
{
$basePath = $this->makeTempProject(<<<'JSON'
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
}
}
JSON, ['src', 'tests']);

$this->assertNotInstanceOf(
RuleViolation::class,
(new Psr4DirectoryExistsRule())->evaluateProject($basePath, Architecture::define())
);
}

public function testFailsWhenPsr4DirectoryDoesNotExistOnDisk(): void
{
$basePath = $this->makeTempProject(<<<'JSON'
{
"autoload": {
"psr-4": {
"View\\": "directory/not/exists"
}
}
}
JSON);

$violation = (new Psr4DirectoryExistsRule())->evaluateProject($basePath, Architecture::define());

$this->assertInstanceOf(RuleViolation::class, $violation);
$this->assertStringContainsString('directory/not/exists', $violation->message);
$this->assertStringContainsString('do not exist on disk', $violation->message);
}

public function testFailsWhenComposerJsonIsMissing(): void
{
$violation = (new Psr4DirectoryExistsRule())->evaluateProject($this->makeTempDir(), Architecture::define());

$this->assertInstanceOf(RuleViolation::class, $violation);
$this->assertStringContainsString('composer.json was not found', $violation->message);
}

public function testFailsWhenComposerJsonIsInvalid(): void
{
$violation = (new Psr4DirectoryExistsRule())->evaluateProject(
$this->makeTempProject('{not json'),
Architecture::define()
);

$this->assertInstanceOf(RuleViolation::class, $violation);
$this->assertStringContainsString('composer.json is not valid JSON', $violation->message);
}

public function testPassesWhenNoPsr4PathsAreDeclared(): void
{
$basePath = $this->makeTempProject('{}');

$this->assertNotInstanceOf(
RuleViolation::class,
(new Psr4DirectoryExistsRule())->evaluateProject($basePath, Architecture::define())
);
}

/**
* @param list<string> $dirs
*/
private function makeTempProject(string $composerJson, array $dirs = []): string
{
$basePath = $this->makeTempDir();
file_put_contents($basePath . '/composer.json', $composerJson);

foreach ($dirs as $dir) {
mkdir($basePath . '/' . $dir, 0777, true);
}

return $basePath;
}

private function makeTempDir(): string
{
return $this->makeTemporaryDirectory('structarmed-psr4-dir-exists-rule');
}
}
Loading