Skip to content
Closed
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
18 changes: 16 additions & 2 deletions src/Analyser/Analyser.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ public function analyse(
$globalSkipPaths = $architecture->getSkipPaths();
$ruleSkipPaths = $architecture->getRuleSkipPaths();
$skippedRuleKeys = $this->skippedRuleKeyMap($architecture->getSkippedRuleKeys());
$files ??= $this->filesForAnalysis($architecture, $scanPaths, $layers);

// Composer rules follow the same scope as every other rule kind: they
// only run when the root composer.json is part of the file list, which
// a layer-wide scan includes and an explicit path argument may not.
$composerJsonFile = Path::normalise(Path::resolve('composer.json', $this->basePath), canonicalise: true);
$isAnalysesComposerJson = in_array($composerJsonFile, $files, true);

$projectRuleViolations = [];
$fileAnalysisRules = [];
Expand Down Expand Up @@ -140,6 +147,10 @@ public function analyse(
continue;
}

if ($rule instanceof ComposerJsonRuleInterface && ! $isAnalysesComposerJson) {
continue;
}

$projectRuleViolations[$key] = [];

if ($rule instanceof FileAnalysisRuleInterface) {
Expand Down Expand Up @@ -168,7 +179,6 @@ public function analyse(
$layerPatterns = $architecture->getLayerPatterns();
$chainLayerResolver = ChainLayerResolver::fromLayerConfig($layers, $this->basePath, $layerPatterns);

$files ??= $this->filesForAnalysis($architecture, $scanPaths, $layers);
$withFileAnalysis = $fileAnalysisRules !== [];
$extractionResult = $this->collectAnalysisNodes(
$files,
Expand Down Expand Up @@ -1407,9 +1417,13 @@ public function filesForAnalysis(Architecture $architecture, array $scanPaths =
$layers ??= $this->resolveLayers($architecture);
$files = [];
$skipPathMatcher = SkipPathMatcher::compile($this->basePath, $architecture->getSkipPaths());
$isExplicitScan = $scanPaths !== [];
$scanPaths = $this->scanPaths($layers, $scanPaths);

if ($this->shouldAnalyseComposerJson($architecture)) {
// The root composer.json joins a layer-wide scan automatically; an
// explicit path argument keeps the file list to what was named, so
// composer rules only run there when composer.json itself is named.
if (! $isExplicitScan && $this->shouldAnalyseComposerJson($architecture)) {
$scanPaths[] = 'composer.json';
}

Expand Down
40 changes: 40 additions & 0 deletions tests/Analyser/AnalyserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use Boundwize\StructArmed\Rule\FunctionRuleInterface;
use Boundwize\StructArmed\Rule\Rules\Class_\AnonymousClassMayNotHaveEmptyParenthesesRule;
use Boundwize\StructArmed\Rule\Rules\Class_\MustBeFinalRule;
use Boundwize\StructArmed\Rule\Rules\Composer\Psr4DirectoryExistsRule;
use Boundwize\StructArmed\Rule\Rules\Composer\Psr4SourcePathsRule;
use Boundwize\StructArmed\Rule\Rules\File\Psr1PhpTagsRule;
use Boundwize\StructArmed\Rule\Rules\File\Psr1SymbolsOrSideEffectsRule;
Expand Down Expand Up @@ -2686,6 +2687,45 @@ public function testFilesForAnalysisDoesNotIncludeRootComposerJsonWhenComposerJs
$this->assertStringEndsWith('/src/Foo.php', $files[0]);
}

public function testFilesForAnalysisDoesNotIncludeRootComposerJsonForExplicitScanPath(): void
{
$basePath = $this->makeTempProject([
'composer.json' => '{"autoload":{"psr-4":{"App\\\\":"src/"}}}',
'src/Foo.php' => '<?php namespace App; final class Foo {}',
]);

$architecture = Architecture::define()
->layer('Domain', 'src/')
->rule('composer.source_paths', new Psr4SourcePathsRule(['src/']));

$analyser = new Analyser($basePath);

$files = array_map($this->normalisePath(...), $analyser->filesForAnalysis($architecture, ['src/Foo.php']));

$this->assertCount(1, $files);
$this->assertStringEndsWith('/src/Foo.php', $files[0]);

$this->assertCount(2, $analyser->filesForAnalysis($architecture));
}

public function testAnalyserScopesComposerRulesToExplicitScanPaths(): void
{
$basePath = $this->makeTempProject([
'composer.json' => '{"autoload":{"psr-4":{"App\\\\":"src/","Missing\\\\":"missing/"}}}',
'src/Foo.php' => '<?php namespace App; final class Foo {}',
]);

$architecture = Architecture::define()
->layer('Domain', 'src/')
->rule('composer.directory_exists', new Psr4DirectoryExistsRule());

$analyser = new Analyser($basePath);

$this->assertCount(0, $analyser->analyse($architecture, ['src/Foo.php'])->forRule('composer.directory_exists'));
$this->assertCount(1, $analyser->analyse($architecture, ['composer.json'])->forRule('composer.directory_exists'));
$this->assertCount(1, $analyser->analyse($architecture)->forRule('composer.directory_exists'));
}

public function testFilesForAnalysisDoesNotIncludeRootComposerJsonWhenComposerJsonRuleIsSkipped(): void
{
$basePath = $this->makeTempProject([
Expand Down
Loading