From 988b30688047bde3cbfb98f31af421757cd516d3 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 27 May 2026 17:09:57 +0700 Subject: [PATCH 1/5] Make Psr4SourcePathsRule detect non-existent directory registration in psr4 autoload --- .../Rules/Composer/Psr4SourcePathsRule.php | 23 ++++++++++- .../Rule/Composer/Psr4SourcePathsRuleTest.php | 39 ++++++++++++++++--- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/Rule/Rules/Composer/Psr4SourcePathsRule.php b/src/Rule/Rules/Composer/Psr4SourcePathsRule.php index 2922c830..7275dfb9 100644 --- a/src/Rule/Rules/Composer/Psr4SourcePathsRule.php +++ b/src/Rule/Rules/Composer/Psr4SourcePathsRule.php @@ -13,6 +13,7 @@ use function file_exists; use function implode; use function in_array; +use function is_dir; use function rtrim; use function sprintf; use function str_replace; @@ -61,12 +62,30 @@ public function evaluateProject(string $basePath, Architecture $architecture, ar ); } + $autoloadPaths = $this->psr4PathResolver->paths($basePath); + $nonExistentPaths = []; + + foreach ($autoloadPaths as $autoloadPath) { + if (! is_dir(rtrim($basePath, '/') . '/' . $autoloadPath)) { + $nonExistentPaths[] = $autoloadPath; + } + } + + if ($nonExistentPaths !== []) { + return $this->violation( + sprintf( + 'PSR-4 source path(s) [%s] declared in composer.json do not exist on disk', + implode(', ', $nonExistentPaths) + ), + $composerFile + ); + } + if ($this->sourcePaths === null) { return null; } - $autoloadPaths = $this->psr4PathResolver->paths($basePath); - $missingPaths = []; + $missingPaths = []; foreach ($this->normalisePaths($this->sourcePaths) as $sourcePath) { if (! in_array($sourcePath, $autoloadPaths, true)) { diff --git a/tests/Rule/Composer/Psr4SourcePathsRuleTest.php b/tests/Rule/Composer/Psr4SourcePathsRuleTest.php index 5ceee621..ab9a1a0c 100644 --- a/tests/Rule/Composer/Psr4SourcePathsRuleTest.php +++ b/tests/Rule/Composer/Psr4SourcePathsRuleTest.php @@ -12,6 +12,7 @@ use PHPUnit\Framework\TestCase; use function file_put_contents; +use function mkdir; #[CoversClass(Psr4SourcePathsRule::class)] final class Psr4SourcePathsRuleTest extends TestCase @@ -33,7 +34,7 @@ public function testPassesWhenSourcePathsExistInComposerPsr4Autoloads(): void } } } -JSON); +JSON, ['src', 'tests']); $psr4SourcePathsRule = new Psr4SourcePathsRule(['src', 'tests']); @@ -53,7 +54,7 @@ public function testFailsWhenSourcePathIsMissingFromComposerPsr4Autoloads(): voi } } } -JSON); +JSON, ['src']); $psr4SourcePathsRule = new Psr4SourcePathsRule(['src/', 'tests/']); @@ -98,7 +99,7 @@ public function testPassesWhenComposerPsr4MappingUsesPathList(): void } } } -JSON); +JSON, ['src', 'tests']); $psr4SourcePathsRule = new Psr4SourcePathsRule(['src/', 'tests/']); @@ -121,7 +122,7 @@ public function testSkipsComposerPsr4SectionWithInvalidShape(): void } } } -JSON); +JSON, ['tests']); $psr4SourcePathsRule = new Psr4SourcePathsRule(['tests/']); @@ -146,7 +147,7 @@ public function testReadsSourcePathsFromComposerWhenSourcePathsAreNotConfigured( } } } -JSON); +JSON, ['app', 'tests', 'specs']); $psr4SourcePathsRule = new Psr4SourcePathsRule(null); @@ -157,6 +158,25 @@ public function testReadsSourcePathsFromComposerWhenSourcePathsAreNotConfigured( $this->assertSame(['app', 'tests', 'specs'], $psr4SourcePathsRule->sourcePathsFor($basePath)); } + public function testFailsWhenPsr4PathDirectoryDoesNotExistOnDisk(): void + { + $basePath = $this->makeTempProject(<<<'JSON' +{ + "autoload": { + "psr-4": { + "View\\": "directory/not/exists" + } + } +} +JSON); + + $violation = (new Psr4SourcePathsRule(null))->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 testNormalisesExplicitSourcePaths(): void { $psr4SourcePathsRule = new Psr4SourcePathsRule([' src/ ', 'tests\\']); @@ -164,11 +184,18 @@ public function testNormalisesExplicitSourcePaths(): void $this->assertSame(['src', 'tests'], $psr4SourcePathsRule->sourcePathsFor($this->makeTempDir())); } - private function makeTempProject(string $composerJson): string + /** + * @param list $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; } From 63e0a0efd0ec5ba9675352f949b45fd0641de310 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 27 May 2026 17:14:52 +0700 Subject: [PATCH 2/5] include composer.json hash to cache --- src/Cache/AnalysisCacheMetadataFactory.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Cache/AnalysisCacheMetadataFactory.php b/src/Cache/AnalysisCacheMetadataFactory.php index b27928dd..b3ee2658 100644 --- a/src/Cache/AnalysisCacheMetadataFactory.php +++ b/src/Cache/AnalysisCacheMetadataFactory.php @@ -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; @@ -33,6 +35,7 @@ public function metadata(string $basePath, string $configPath, array $scanPaths, 'configPath' => $configPath, 'configHash' => $this->fileHash($configPath), 'composerGeneratedVersionHash' => $this->composerGeneratedVersionHash(), + 'composerHash' => $this->composerHash($basePath), 'scanPaths' => $scanPaths, 'filesHash' => $this->filesHash($files), ]; @@ -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) From 98af262f70e323ca09d858a7d154dda5f173cdcd Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 27 May 2026 17:16:29 +0700 Subject: [PATCH 3/5] update cache metadata version --- src/Cache/AnalysisCacheMetadataFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cache/AnalysisCacheMetadataFactory.php b/src/Cache/AnalysisCacheMetadataFactory.php index b3ee2658..559f91fe 100644 --- a/src/Cache/AnalysisCacheMetadataFactory.php +++ b/src/Cache/AnalysisCacheMetadataFactory.php @@ -30,7 +30,7 @@ public function metadata(string $basePath, string $configPath, array $scanPaths, sort($files); return [ - 'version' => 1, + 'version' => 2, 'basePath' => $basePath, 'configPath' => $configPath, 'configHash' => $this->fileHash($configPath), From 0be86f16227d3711a86f97b37b5e5c318eb04495 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 27 May 2026 17:26:50 +0700 Subject: [PATCH 4/5] move to separate rule: Psr4DirectoryExistsRule --- src/Preset/Presets/Psr1Preset.php | 4 + src/Preset/Presets/Psr4Preset.php | 4 + .../Composer/Psr4DirectoryExistsRule.php | 76 ++++++++++++ .../Rules/Composer/Psr4SourcePathsRule.php | 23 +--- tests/Preset/PresetTest.php | 5 +- .../Composer/Psr4DirectoryExistsRuleTest.php | 112 ++++++++++++++++++ .../Rule/Composer/Psr4SourcePathsRuleTest.php | 39 +----- 7 files changed, 207 insertions(+), 56 deletions(-) create mode 100644 src/Rule/Rules/Composer/Psr4DirectoryExistsRule.php create mode 100644 tests/Rule/Composer/Psr4DirectoryExistsRuleTest.php diff --git a/src/Preset/Presets/Psr1Preset.php b/src/Preset/Presets/Psr1Preset.php index 75995bff..9c488712 100644 --- a/src/Preset/Presets/Psr1Preset.php +++ b/src/Preset/Presets/Psr1Preset.php @@ -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; @@ -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'; @@ -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, diff --git a/src/Preset/Presets/Psr4Preset.php b/src/Preset/Presets/Psr4Preset.php index ef04dff2..191e8e93 100644 --- a/src/Preset/Presets/Psr4Preset.php +++ b/src/Preset/Presets/Psr4Preset.php @@ -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; @@ -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'; /** @@ -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()); } } diff --git a/src/Rule/Rules/Composer/Psr4DirectoryExistsRule.php b/src/Rule/Rules/Composer/Psr4DirectoryExistsRule.php new file mode 100644 index 00000000..3cd16c2c --- /dev/null +++ b/src/Rule/Rules/Composer/Psr4DirectoryExistsRule.php @@ -0,0 +1,76 @@ +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', + ); + } +} diff --git a/src/Rule/Rules/Composer/Psr4SourcePathsRule.php b/src/Rule/Rules/Composer/Psr4SourcePathsRule.php index 7275dfb9..2922c830 100644 --- a/src/Rule/Rules/Composer/Psr4SourcePathsRule.php +++ b/src/Rule/Rules/Composer/Psr4SourcePathsRule.php @@ -13,7 +13,6 @@ use function file_exists; use function implode; use function in_array; -use function is_dir; use function rtrim; use function sprintf; use function str_replace; @@ -62,30 +61,12 @@ public function evaluateProject(string $basePath, Architecture $architecture, ar ); } - $autoloadPaths = $this->psr4PathResolver->paths($basePath); - $nonExistentPaths = []; - - foreach ($autoloadPaths as $autoloadPath) { - if (! is_dir(rtrim($basePath, '/') . '/' . $autoloadPath)) { - $nonExistentPaths[] = $autoloadPath; - } - } - - if ($nonExistentPaths !== []) { - return $this->violation( - sprintf( - 'PSR-4 source path(s) [%s] declared in composer.json do not exist on disk', - implode(', ', $nonExistentPaths) - ), - $composerFile - ); - } - if ($this->sourcePaths === null) { return null; } - $missingPaths = []; + $autoloadPaths = $this->psr4PathResolver->paths($basePath); + $missingPaths = []; foreach ($this->normalisePaths($this->sourcePaths) as $sourcePath) { if (! in_array($sourcePath, $autoloadPaths, true)) { diff --git a/tests/Preset/PresetTest.php b/tests/Preset/PresetTest.php index 4183d0fd..73107834 100644 --- a/tests/Preset/PresetTest.php +++ b/tests/Preset/PresetTest.php @@ -199,14 +199,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); diff --git a/tests/Rule/Composer/Psr4DirectoryExistsRuleTest.php b/tests/Rule/Composer/Psr4DirectoryExistsRuleTest.php new file mode 100644 index 00000000..cfbe4f33 --- /dev/null +++ b/tests/Rule/Composer/Psr4DirectoryExistsRuleTest.php @@ -0,0 +1,112 @@ +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 $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'); + } +} diff --git a/tests/Rule/Composer/Psr4SourcePathsRuleTest.php b/tests/Rule/Composer/Psr4SourcePathsRuleTest.php index ab9a1a0c..5ceee621 100644 --- a/tests/Rule/Composer/Psr4SourcePathsRuleTest.php +++ b/tests/Rule/Composer/Psr4SourcePathsRuleTest.php @@ -12,7 +12,6 @@ use PHPUnit\Framework\TestCase; use function file_put_contents; -use function mkdir; #[CoversClass(Psr4SourcePathsRule::class)] final class Psr4SourcePathsRuleTest extends TestCase @@ -34,7 +33,7 @@ public function testPassesWhenSourcePathsExistInComposerPsr4Autoloads(): void } } } -JSON, ['src', 'tests']); +JSON); $psr4SourcePathsRule = new Psr4SourcePathsRule(['src', 'tests']); @@ -54,7 +53,7 @@ public function testFailsWhenSourcePathIsMissingFromComposerPsr4Autoloads(): voi } } } -JSON, ['src']); +JSON); $psr4SourcePathsRule = new Psr4SourcePathsRule(['src/', 'tests/']); @@ -99,7 +98,7 @@ public function testPassesWhenComposerPsr4MappingUsesPathList(): void } } } -JSON, ['src', 'tests']); +JSON); $psr4SourcePathsRule = new Psr4SourcePathsRule(['src/', 'tests/']); @@ -122,7 +121,7 @@ public function testSkipsComposerPsr4SectionWithInvalidShape(): void } } } -JSON, ['tests']); +JSON); $psr4SourcePathsRule = new Psr4SourcePathsRule(['tests/']); @@ -147,7 +146,7 @@ public function testReadsSourcePathsFromComposerWhenSourcePathsAreNotConfigured( } } } -JSON, ['app', 'tests', 'specs']); +JSON); $psr4SourcePathsRule = new Psr4SourcePathsRule(null); @@ -158,25 +157,6 @@ public function testReadsSourcePathsFromComposerWhenSourcePathsAreNotConfigured( $this->assertSame(['app', 'tests', 'specs'], $psr4SourcePathsRule->sourcePathsFor($basePath)); } - public function testFailsWhenPsr4PathDirectoryDoesNotExistOnDisk(): void - { - $basePath = $this->makeTempProject(<<<'JSON' -{ - "autoload": { - "psr-4": { - "View\\": "directory/not/exists" - } - } -} -JSON); - - $violation = (new Psr4SourcePathsRule(null))->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 testNormalisesExplicitSourcePaths(): void { $psr4SourcePathsRule = new Psr4SourcePathsRule([' src/ ', 'tests\\']); @@ -184,18 +164,11 @@ public function testNormalisesExplicitSourcePaths(): void $this->assertSame(['src', 'tests'], $psr4SourcePathsRule->sourcePathsFor($this->makeTempDir())); } - /** - * @param list $dirs - */ - private function makeTempProject(string $composerJson, array $dirs = []): string + private function makeTempProject(string $composerJson): string { $basePath = $this->makeTempDir(); file_put_contents($basePath . '/composer.json', $composerJson); - foreach ($dirs as $dir) { - mkdir($basePath . '/' . $dir, 0777, true); - } - return $basePath; } From f0e98e47a65a86815dd838c0900436774ebbd0e5 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 27 May 2026 17:27:43 +0700 Subject: [PATCH 5/5] more tests --- tests/Preset/PresetTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/Preset/PresetTest.php b/tests/Preset/PresetTest.php index 73107834..90e22d91 100644 --- a/tests/Preset/PresetTest.php +++ b/tests/Preset/PresetTest.php @@ -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); @@ -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); @@ -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 @@ -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