diff --git a/src/Analyser/FileAnalysisProvider.php b/src/Analyser/FileAnalysisProvider.php index d0c40131..b5ebe055 100644 --- a/src/Analyser/FileAnalysisProvider.php +++ b/src/Analyser/FileAnalysisProvider.php @@ -303,6 +303,20 @@ private function fileState(array $nodes): array if ($this->isSymbolDeclaration($node)) { $declaresSymbols = true; + + // A define() argument can itself be effectful, e.g. define('X', include 'config.php'). + if ($node instanceof Expression) { + $argumentSideEffectLine = $this->intrinsicSideEffectLineInExpression($node->expr); + + if ($argumentSideEffectLine !== null) { + if (! $hasSideEffects) { + $sideEffectLine = $argumentSideEffectLine; + } + + $hasSideEffects = true; + } + } + continue; } diff --git a/tests/Analyser/FileAnalysisProviderTest.php b/tests/Analyser/FileAnalysisProviderTest.php index 804ec687..d1cb3bf9 100644 --- a/tests/Analyser/FileAnalysisProviderTest.php +++ b/tests/Analyser/FileAnalysisProviderTest.php @@ -410,6 +410,13 @@ public static function defineStatementProvider(): iterable yield 'define() method call' => ["\$container->define('X', 1);", false, true]; yield 'defined() on a variable' => ["\$guard || define('X', 1);", false, true]; yield 'assignment' => ['$version = 1;', false, true]; + yield 'define() with include argument' => ["define('X', include 'config.php');", true, true]; + yield 'define() with assignment argument' => ["define('X', \$config = 1);", true, true]; + yield 'guarded define() with include argument' => [ + "defined('X') || define('X', include 'config.php');", + true, + true, + ]; } #[DataProvider('defineStatementProvider')] diff --git a/tests/Rule/File/Psr1SymbolsOrSideEffectsRuleTest.php b/tests/Rule/File/Psr1SymbolsOrSideEffectsRuleTest.php index b6fdab69..93cf6bb5 100644 --- a/tests/Rule/File/Psr1SymbolsOrSideEffectsRuleTest.php +++ b/tests/Rule/File/Psr1SymbolsOrSideEffectsRuleTest.php @@ -338,6 +338,31 @@ public function testViolatesEchoNextToDefineAndClass(): void } } + public function testViolatesDefineWithSideEffectArgumentNextToClass(): void + { + $basePath = $this->makeTempDir(); + + try { + mkdir($basePath . '/src'); + file_put_contents( + $basePath . '/src/Foo.php', + "evaluateProjectAll( + $basePath, + Architecture::define() + ); + + $this->assertCount(1, $violations); + $this->assertSame(2, $violations[0]->line); + } finally { + unlink($basePath . '/src/Foo.php'); + rmdir($basePath . '/src'); + rmdir($basePath); + } + } + public function testViolatesDefineMixedWithSideEffectCall(): void { $basePath = $this->makeTempDir();