diff --git a/.gitignore b/.gitignore index 1b2bc847..553c78fe 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ phpdox.xml clover.xml example/test-data phing-latest.phar -build/ +build/ \ No newline at end of file diff --git a/src/ComposerRequireChecker/Cli/CheckCommand.php b/src/ComposerRequireChecker/Cli/CheckCommand.php index 3cc4255f..41ee9b7b 100644 --- a/src/ComposerRequireChecker/Cli/CheckCommand.php +++ b/src/ComposerRequireChecker/Cli/CheckCommand.php @@ -40,12 +40,18 @@ protected function configure() 'the composer.json of your package, that should be checked', './composer.json' ) - ->addOption( + ->addOption( 'ignore-parse-errors', null, InputOption::VALUE_NONE, 'this will cause ComposerRequireChecker to ignore errors when files cannot be parsed, otherwise' . ' errors will be thrown' + ) + ->addOption( + 'register-namespace', + null, + InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, + 'vendor/package:namespace:path as if it was psr4' ); } @@ -64,13 +70,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int $options = $this->getCheckOptions($input); $getPackageSourceFiles = new LocateComposerPackageSourceFiles(); + $manualRegistered = $this->buildManualList($input, $composerJson); $sourcesASTs = $this->getASTFromFilesLocator($input); $definedVendorSymbols = (new LocateDefinedSymbolsFromASTRoots())->__invoke($sourcesASTs( (new ComposeGenerators())->__invoke( $getPackageSourceFiles($composerJson), - (new LocateComposerPackageDirectDependenciesSourceFiles())->__invoke($composerJson) + (new LocateComposerPackageDirectDependenciesSourceFiles())->__invoke($composerJson, $manualRegistered) ) )); @@ -145,4 +152,21 @@ private function getASTFromFilesLocator(InputInterface $input): LocateASTFromFil return $sourcesASTs; } + /** + * @return array + */ + private function buildManualList(InputInterface $input, $composerJson) + { + if (!$input->hasOption('register-namespace')) { + return []; + } + $packageDir = dirname($composerJson); + $namespaces = []; + foreach ($input->getOption('register-namespace') as $option) { + if (preg_match('!^([^/:]+(/[^/:]+)+):([^:]+):([^:]+)$!i', $option, $matches)) { + $namespaces[$packageDir . '/vendor/' . $matches[1]][$matches[3]] = $matches[4]; + } + } + return $namespaces; + } } diff --git a/src/ComposerRequireChecker/FileLocator/LocateComposerPackageDirectDependenciesSourceFiles.php b/src/ComposerRequireChecker/FileLocator/LocateComposerPackageDirectDependenciesSourceFiles.php index dacd871b..7cb8d3f3 100644 --- a/src/ComposerRequireChecker/FileLocator/LocateComposerPackageDirectDependenciesSourceFiles.php +++ b/src/ComposerRequireChecker/FileLocator/LocateComposerPackageDirectDependenciesSourceFiles.php @@ -6,7 +6,7 @@ final class LocateComposerPackageDirectDependenciesSourceFiles { - public function __invoke(string $composerJsonPath): Generator + public function __invoke(string $composerJsonPath, array $manual = []): Generator { $packageDir = dirname($composerJsonPath); @@ -22,7 +22,10 @@ function (string $vendorName) use ($packageDir) { continue; } - yield from (new LocateComposerPackageSourceFiles())->__invoke($vendorDir . '/composer.json'); + yield from (new LocateComposerPackageSourceFiles())->__invoke( + $vendorDir . '/composer.json', + $manual[$vendorDir] ?? [] + ); } } } diff --git a/src/ComposerRequireChecker/FileLocator/LocateComposerPackageSourceFiles.php b/src/ComposerRequireChecker/FileLocator/LocateComposerPackageSourceFiles.php index 874a54e4..0bdf1c8a 100644 --- a/src/ComposerRequireChecker/FileLocator/LocateComposerPackageSourceFiles.php +++ b/src/ComposerRequireChecker/FileLocator/LocateComposerPackageSourceFiles.php @@ -6,7 +6,7 @@ final class LocateComposerPackageSourceFiles { - public function __invoke(string $composerJsonPath): Generator + public function __invoke(string $composerJsonPath, array $manual = []): Generator { $packageDir = dirname($composerJsonPath); $composerData = json_decode(file_get_contents($composerJsonPath), true); @@ -29,6 +29,10 @@ public function __invoke(string $composerJsonPath): Generator $this->getFilePaths($composerData['autoload']['psr-4'] ?? [], $packageDir), $blacklist ); + yield from $this->locateFilesInFilesInFilesDefinitions( + $this->getFilePaths($manual, $packageDir), + $blacklist + ); } private function getFilePaths(array $sourceDirs, string $packageDir): array diff --git a/test/ComposerRequireCheckerTest/Cli/CheckCommandGetManualListTest.php b/test/ComposerRequireCheckerTest/Cli/CheckCommandGetManualListTest.php new file mode 100644 index 00000000..5fdd76d2 --- /dev/null +++ b/test/ComposerRequireCheckerTest/Cli/CheckCommandGetManualListTest.php @@ -0,0 +1,62 @@ +getMethod('buildManualList'); + $method->setAccessible(true); + return [ + [$this->getInputMockForManualList(), $method, []], + [$this->getInputMockForManualList( + ['abc', 'nope', 'abc/hi:Abc\\:src', 'abc/def/qoi:Qui\\:src/lib/ext', 'abc/def/:Qui\\:src/lib/ext']), + $method, + [__DIR__ . '/vendor/abc/def/qoi', __DIR__ . '/vendor/abc/hi'] + ], + ]; + } + + /** + * @param array $return + * @return InputInterface + */ + private function getInputMockForManualList(array $return = []):InputInterface + { + $input = $this->getMockBuilder(InputInterface::class)->getMock(); + $hasReturn = count($return) > 0; + $input->expects($this->once()) + ->method('hasOption') + ->with(/** @scrutinizer ignore-type */'register-namespace') + ->willReturn($hasReturn); + $input->expects($hasReturn ? $this->once() : $this->never()) + ->method('getOption') + ->with(/** @scrutinizer ignore-type */'register-namespace') + ->willReturn($return); + return $input; + } + + /** + * @dataProvider provideBuildManualList + * Since the command does so much, there's no reasonable way to supply test data + * @test + */ + public function testBuildManualList(InputInterface $input, ReflectionMethod $method, array $expected) + { + $instance = new CheckCommand(); + $result = $method->invoke($instance, $input, __FILE__); + $this->assertInternalType('array', $result); + $this->assertCount(count($expected), $result); + $this->assertCount(0, array_diff(array_keys($result), $expected)); + } +} \ No newline at end of file diff --git a/test/ComposerRequireCheckerTest/Cli/CheckCommandTest.php b/test/ComposerRequireCheckerTest/Cli/CheckCommandTest.php index c955f5ba..788ae501 100644 --- a/test/ComposerRequireCheckerTest/Cli/CheckCommandTest.php +++ b/test/ComposerRequireCheckerTest/Cli/CheckCommandTest.php @@ -3,6 +3,7 @@ namespace ComposerRequireCheckerTest\Cli; use ComposerRequireChecker\Cli\Application; +use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Tester\CommandTester; @@ -24,7 +25,7 @@ public function setUp() public function testExceptionIfComposerJsonNotFound() { - self::expectException(\InvalidArgumentException::class); + self::expectException(InvalidArgumentException::class); $this->commandTester->execute([ 'composer-json' => 'this-will-not-be-found.json'