Skip to content

Commit

Permalink
Add an option --include-dev to check the development code.
Browse files Browse the repository at this point in the history
  • Loading branch information
guillaume-perreal committed Nov 18, 2019
1 parent 832281d commit ef14320
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 12 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ If this is already done, run it like this:
composer-require-checker check /path/to/your/project/composer.json
```

### Check development code and dependencies (optional)

By default, Composer require checker only checks the source code listed in the `autoload` section of `composer.json`
against the dependencies listed in the `require` section. This checks that there are no indirect dependencies in the
*production* code.

To check the *development* code, use the `--include-dev` option. This will scan the source code from both the
`autoload` and `autoload-dev` section and will search for symbols in dependencies from both the `require` and `require-dev`
section.

Please note these two checks are really differents: a successful `--include-dev` check does not guarantee there are no
no indirect dependencies in the *production* code. You probably want to do both.

## Configuration

Composer require checker is configured to whitelist some symbols per default. Have a look at the
Expand Down
12 changes: 10 additions & 2 deletions src/ComposerRequireChecker/Cli/CheckCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ protected function configure()
InputOption::VALUE_NONE,
'this will cause ComposerRequireChecker to ignore errors when files cannot be parsed, otherwise'
. ' errors will be thrown'
)
->addOption(
'include-dev',
null,
InputOption::VALUE_NONE,
'also check the development sources and the development dependencies'
);
}

Expand All @@ -63,9 +69,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
$composerData = $this->getComposerData($composerJson);

$includeDev = (bool)$input->getOption('include-dev');

$options = $this->getCheckOptions($input);

$getPackageSourceFiles = new LocateComposerPackageSourceFiles();
$getPackageSourceFiles = new LocateComposerPackageSourceFiles($includeDev);
$getAdditionalSourceFiles = new LocateFilesByGlobPattern();

$sourcesASTs = $this->getASTFromFilesLocator($input);
Expand All @@ -75,7 +83,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
(new ComposeGenerators())->__invoke(
$getAdditionalSourceFiles($options->getScanFiles(), dirname($composerJson)),
$getPackageSourceFiles($composerData, dirname($composerJson)),
(new LocateComposerPackageDirectDependenciesSourceFiles())->__invoke($composerJson)
(new LocateComposerPackageDirectDependenciesSourceFiles($includeDev))->__invoke($composerJson)
)
));
$this->verbose("found " . count($definedVendorSymbols) . " symbols.", $output, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@

final class LocateComposerPackageDirectDependenciesSourceFiles
{
/**
* @var bool
*/
private $includeDev;

/**
* LocateComposerPackageDirectDependenciesSourceFiles constructor.
*
* @param bool $includeDev Wether to include packages from require-dev or not.
*/
public function __construct(bool $includeDev)
{
$this->includeDev = $includeDev;
}

public function __invoke(string $composerJsonPath): Generator
{
$packageDir = dirname($composerJsonPath);
Expand All @@ -21,7 +36,12 @@ public function __invoke(string $composerJsonPath): Generator
$vendorDirs = [];
foreach ($composerJson['require'] ?? [] as $vendorName => $vendorRequiredVersion) {
$vendorDirs[$vendorName] = $packageDir . '/' . $configVendorDir . '/' . $vendorName;
};
}
if ($this->includeDev) {
foreach ($composerJson['require-dev'] ?? [] as $vendorName => $vendorRequiredVersion) {
$vendorDirs[$vendorName] = $packageDir . '/' . $configVendorDir . '/' . $vendorName;
}
}

$installedPackages = $this->getInstalledPackages($packageDir . '/' . $configVendorDir);

Expand All @@ -30,7 +50,7 @@ public function __invoke(string $composerJsonPath): Generator
continue;
}

yield from (new LocateComposerPackageSourceFiles())->__invoke($installedPackages[$vendorName], $vendorDir);
yield from (new LocateComposerPackageSourceFiles(false))->__invoke($installedPackages[$vendorName], $vendorDir);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@

final class LocateComposerPackageSourceFiles
{
/**
* @var bool
*/
private $includeDev;

/**
* LocateComposerPackageSourceFiles constructor.
*
* @param bool $includeDev Wether to liste the files from the autoload-dev configuration.
*/
public function __construct(bool $includeDev)
{
$this->includeDev = $includeDev;
}

/**
* @param mixed[] $composerData The contents of composer.json for a package
* @param string $packageDir The path to package
Expand All @@ -14,22 +29,30 @@ final class LocateComposerPackageSourceFiles
*/
public function __invoke(array $composerData, string $packageDir): Generator
{
$blacklist = $composerData['autoload']['exclude-from-classmap'] ?? null;
yield from $this->iterateAutoload($composerData['autoload'] ?? [], $packageDir);
if ($this->includeDev) {
yield from $this->iterateAutoload($composerData['autoload-dev'] ?? [], $packageDir);
}
}

private function iterateAutoload(array $autoloadData, string $packageDir): Generator
{
$blacklist = $autoloadData['exclude-from-classmap'] ?? null;

yield from $this->locateFilesInClassmapDefinitions(
$this->getFilePaths($composerData['autoload']['classmap'] ?? [], $packageDir),
$this->getFilePaths($autoloadData['classmap'] ?? [], $packageDir),
$blacklist
);
yield from $this->locateFilesInFilesInFilesDefinitions(
$this->getFilePaths($composerData['autoload']['files'] ?? [], $packageDir),
$this->getFilePaths($autoloadData['files'] ?? [], $packageDir),
$blacklist
);
yield from $this->locateFilesInPsr0Definitions(
$this->getFilePaths($composerData['autoload']['psr-0'] ?? [], $packageDir),
$this->getFilePaths($autoloadData['psr-0'] ?? [], $packageDir),
$blacklist
);
yield from $this->locateFilesInPsr4Definitions(
$this->getFilePaths($composerData['autoload']['psr-4'] ?? [], $packageDir),
$this->getFilePaths($autoloadData['psr-4'] ?? [], $packageDir),
$blacklist
);
}
Expand Down
17 changes: 17 additions & 0 deletions test/ComposerRequireCheckerTest/Cli/CheckCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,23 @@ public function testSelfCheckShowsNoErrors(): void
$this->assertNotRegExp('/Collecting used symbols... found \d+ symbols./', $this->commandTester->getDisplay());
}

public function testSelfCheckIncludingDevShowsNoErrors(): void
{
$this->commandTester->execute([
// that's our own composer.json, lets be sure our self check does not throw errors
'composer-json' => dirname(__DIR__, 3) . '/composer.json'
], [
'include-dev' => true
]);

$this->assertSame(0, $this->commandTester->getStatusCode());
$this->assertStringContainsString('no unknown symbols found', $this->commandTester->getDisplay());

// verbose output should not be shown
$this->assertNotRegExp('/Collecting defined (vendor|extension) symbols... found \d+ symbols./', $this->commandTester->getDisplay());
$this->assertNotRegExp('/Collecting used symbols... found \d+ symbols./', $this->commandTester->getDisplay());
}

public function testVerboseSelfCheckShowsCounts(): void
{
$this->commandTester->execute([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected function setUp(): void
{
parent::setUp();

$this->locator = new LocateComposerPackageDirectDependenciesSourceFiles();
$this->locator = new LocateComposerPackageDirectDependenciesSourceFiles(false);
$this->root = vfsStream::setup();
}

Expand Down Expand Up @@ -182,11 +182,91 @@ public function testOldInstalledJsonUsedAsFallback(): void
$this->assertFalse($this->root->hasChild('vendor/foo/bar/composer.json'));
}

public function testWithoutDevDependencies(): void
{
vfsStream::create([
'composer.json' => '{"require":{"foo/bar": "^1.0"},"require-dev":{"foo/baz": "^1.0"}}',
'vendor' => [
'composer' => [
'installed.json' =>
'{"packages":[' .
'{"name": "foo/bar", "autoload":{"psr-4":{"":"src"}}},' .
'{"name": "foo/baz", "autoload":{"psr-4":{"":"src"}}}' .
']}',
],
'foo' => [
'bar' => [
'src' => [
'MyClass.php' => '',
],
],
'baz' => [
'src' => [
'BazClass.php' => '',
],
],
],
],
]);

$files = $this->locate($this->root->getChild('composer.json')->url());

$this->assertCount(1, $files);

$expectedFile = $this->root->getChild('vendor/foo/bar/src/MyClass.php')->url();
$actualFile = str_replace('\\', '/', reset($files));
$this->assertSame($expectedFile, $actualFile);
}

public function testWithDevDependencies(): void
{
$this->locator = new LocateComposerPackageDirectDependenciesSourceFiles(true);

vfsStream::create([
'composer.json' => '{"require":{"foo/bar": "^1.0"},"require-dev":{"foo/baz": "^1.0"}}',
'vendor' => [
'composer' => [
'installed.json' => <<<'INSTALLEDJSON'
{
"packages": [
{"name": "foo/bar", "autoload": {"psr-4":{"":"src"}}},
{"name": "foo/baz", "autoload": {"psr-4":{"":"src"}}}
]
}
INSTALLEDJSON
],
'foo' => [
'bar' => [
'src' => [
'MyClass.php' => '',
],
],
'baz' => [
'src' => [
'BazClass.php' => '',
],
],
],
],
]);

$files = $this->locate($this->root->getChild('composer.json')->url());

$this->assertCount(2, $files);

$this->assertContains($this->root->getChild('vendor/foo/bar/src/MyClass.php')->url(), $files);
$this->assertContains($this->root->getChild('vendor/foo/baz/src/BazClass.php')->url(), $files);
}
/**
* @return string[]
*/
private function locate(string $composerJson): array
{
return iterator_to_array(($this->locator)($composerJson));
$files = [];
$generator = ($this->locator)($composerJson);
foreach ($generator as $file) {
$files[] = $file;
}
return $files;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ protected function setUp(): void
{
parent::setUp();

$this->locator = new LocateComposerPackageSourceFiles();
$this->locator = new LocateComposerPackageSourceFiles(false);
$this->root = vfsStream::setup();
}

Expand Down Expand Up @@ -180,6 +180,57 @@ public function testFromPsr4WithExcludeFromClassmap(array $excludedPattern, arra
}
}

public function testFromClassmapWithoutDev(): void
{
vfsStream::create([
'composer.json' => <<<'COMPOSERJSON'
{
"autoload": {"classmap": ["src/MyClassA.php"]},
"autoload-dev": {"classmap": ["tests/MyClassATest.php"]}
}
COMPOSERJSON
,
'src' => [
'MyClassA.php' => '<?php class MyClassA {}',
],
'tests' => [
'MyClassATest.php' => '<?php class MyClassATest {}',
]
]);

$files = $this->files($this->root->getChild('composer.json')->url());

$this->assertCount(1, $files);
$this->assertContains($this->root->getChild('src/MyClassA.php')->url(), $files);
}

public function testFromClassmapWithDev(): void
{
$this->locator = new LocateComposerPackageSourceFiles(true);

vfsStream::create([
'composer.json' => <<<'COMPOSERJSON'
{
"autoload": {"classmap": ["src/MyClassA.php"]},
"autoload-dev": {"classmap": ["tests/MyClassATest.php"]}
}
COMPOSERJSON
,
'src' => [
'MyClassA.php' => '<?php class MyClassA {}',
],
'tests' => [
'MyClassATest.php' => '<?php class MyClassATest {}',
]
]);

$files = $this->files($this->root->getChild('composer.json')->url());

$this->assertCount(2, $files);
$this->assertContains($this->root->getChild('src/MyClassA.php')->url(), $files);
$this->assertContains($this->root->getChild('tests/MyClassATest.php')->url(), $files);
}

/**
* @return array[]
*/
Expand Down

0 comments on commit ef14320

Please sign in to comment.