From 259ca6d9268b585050d3a5d7e3ab42369723ffae Mon Sep 17 00:00:00 2001 From: Ariffin Ismail Date: Sat, 9 May 2026 01:59:15 +0800 Subject: [PATCH] Pass explicit paths to PHPCS even when project has a custom config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since 3.4.0, PHP_CodeSniffer ignores any paths the user passes to `duster lint` / `duster fix` whenever a custom phpcs config exists at the project root (`.phpcs.xml`, `phpcs.xml`, `.phpcs.xml.dist`, `phpcs.xml.dist`). The dirty list resolved by `App\Project::paths()` is correctly stored in `DusterConfig`, but `PhpCodeSniffer::lint()` and `fix()` short-circuit on `hasCustomConfig()` and call `process()` with an empty array, so PHPCS falls back to the `` directives in the ruleset and scans the whole project. This silently breaks `--dirty` (and `--diff`) for any project that has a custom phpcs config — every commit re-lints the entire codebase through pre-commit hooks instead of just the changed files. Refactors the path resolution into a single `resolvePaths()` helper with three outcomes: - `null` — skip PHPCS entirely (e.g. only Blade files were passed explicitly; preserves PR #199). - `[]` — defer to the ruleset's `` directives (no explicit paths, custom config present; preserves existing 3.4.x behaviour). - `array` — pass paths through so PHPCS uses them instead of the ruleset's `` list. Restores `--dirty` / `--diff` / explicit `path` argument behaviour for projects with a custom phpcs config. Adds a regression test (`it lints only the explicit paths when project has a custom phpcs config`) and pins the fall-back-to-ruleset behaviour so it does not regress in future. Fixes #206 Co-authored-by: Cursor --- app/Support/PhpCodeSniffer.php | 60 ++++++++++++++----- .../PhpCodeSnifferConfigOverrideTest.php | 28 +++++++++ .../.phpcs.xml.dist | 8 +++ .../BadClass.php | 9 +++ .../CleanClass.php | 8 +++ 5 files changed, 98 insertions(+), 15 deletions(-) create mode 100644 tests/Fixtures/PhpCodeSnifferProjectConfigWithExplicitFile/.phpcs.xml.dist create mode 100644 tests/Fixtures/PhpCodeSnifferProjectConfigWithExplicitFile/BadClass.php create mode 100644 tests/Fixtures/PhpCodeSnifferProjectConfigWithExplicitFile/CleanClass.php diff --git a/app/Support/PhpCodeSniffer.php b/app/Support/PhpCodeSniffer.php index 3f901d7..60f6e7f 100644 --- a/app/Support/PhpCodeSniffer.php +++ b/app/Support/PhpCodeSniffer.php @@ -14,25 +14,23 @@ public function lint(): int { $this->heading('Linting using PHP_CodeSniffer'); - if (! $this->hasCustomConfig()) { - if (empty($paths = $this->getPaths())) { - return 0; - } + $paths = $this->resolvePaths(); + + if ($paths === null) { + return 0; } - return $this->process('runPHPCS', $paths ?? []); + return $this->process('runPHPCS', $paths); } public function fix(): int { $this->heading('Fixing using PHP_CodeSniffer'); - if ($this->hasCustomConfig()) { - $paths = []; - } else { - if (empty($paths = $this->getPaths())) { - return 0; - } + $paths = $this->resolvePaths(); + + if ($paths === null) { + return 0; } $fix = $this->process('runPHPCBF', $paths); @@ -88,13 +86,45 @@ private function process(string $tool, array $params = []): int } /** - * @return array + * Resolve the paths to hand to PHP_CodeSniffer. + * + * Returns: + * - `null` when PHPCS should be skipped entirely (e.g. only Blade + * files were passed explicitly, or there is nothing to lint). + * - An empty array when PHPCS should fall back to the project's custom + * ruleset (custom config present and no explicit paths). + * - A list of paths otherwise. + * + * @return array|null */ - private function getPaths(): array + private function resolvePaths(): ?array { - $paths = $this->dusterConfig->get('paths') === [Project::path()] - ? $this->getDefaultDirectories() : $this->dusterConfig->get('paths'); + if ($this->hasExplicitPaths()) { + $paths = $this->filterBladeFiles($this->dusterConfig->get('paths')); + + return empty($paths) ? null : $paths; + } + + if ($this->hasCustomConfig()) { + return []; + } + $paths = $this->filterBladeFiles($this->getDefaultDirectories()); + + return empty($paths) ? null : $paths; + } + + private function hasExplicitPaths(): bool + { + return $this->dusterConfig->get('paths') !== [Project::path()]; + } + + /** + * @param array $paths + * @return array + */ + private function filterBladeFiles(array $paths): array + { return array_values(array_filter($paths, function ($path) { if (is_dir($path)) { return true; diff --git a/tests/Feature/PhpCodeSnifferConfigOverrideTest.php b/tests/Feature/PhpCodeSnifferConfigOverrideTest.php index fe6d4d4..cc80eae 100644 --- a/tests/Feature/PhpCodeSnifferConfigOverrideTest.php +++ b/tests/Feature/PhpCodeSnifferConfigOverrideTest.php @@ -12,3 +12,31 @@ ->toContain('Linting using PHP_CodeSniffer') ->toContain('Comment refers to a TODO task'); }); + +it('lints only the explicit paths when project has a custom phpcs config', function () { + chdir(__DIR__ . '/../Fixtures/PhpCodeSnifferProjectConfigWithExplicitFile'); + + [$statusCode, $output] = run('lint', [ + 'path' => base_path('tests/Fixtures/PhpCodeSnifferProjectConfigWithExplicitFile/CleanClass.php'), + '--using' => 'phpcs', + ]); + + expect($statusCode)->toBe(0) + ->and($output) + ->toContain('Linting using PHP_CodeSniffer') + ->not->toContain('Comment refers to a TODO task'); +}); + +it('falls back to ruleset file directives when no explicit paths are provided', function () { + chdir(__DIR__ . '/../Fixtures/PhpCodeSnifferProjectConfigWithExplicitFile'); + + [$statusCode, $output] = run('lint', [ + 'path' => base_path('tests/Fixtures/PhpCodeSnifferProjectConfigWithExplicitFile'), + '--using' => 'phpcs', + ]); + + expect($statusCode)->toBe(1) + ->and($output) + ->toContain('Linting using PHP_CodeSniffer') + ->toContain('Comment refers to a TODO task'); +}); diff --git a/tests/Fixtures/PhpCodeSnifferProjectConfigWithExplicitFile/.phpcs.xml.dist b/tests/Fixtures/PhpCodeSnifferProjectConfigWithExplicitFile/.phpcs.xml.dist new file mode 100644 index 0000000..ff1b140 --- /dev/null +++ b/tests/Fixtures/PhpCodeSnifferProjectConfigWithExplicitFile/.phpcs.xml.dist @@ -0,0 +1,8 @@ + + + BadClass.php + + + + + diff --git a/tests/Fixtures/PhpCodeSnifferProjectConfigWithExplicitFile/BadClass.php b/tests/Fixtures/PhpCodeSnifferProjectConfigWithExplicitFile/BadClass.php new file mode 100644 index 0000000..b4993ae --- /dev/null +++ b/tests/Fixtures/PhpCodeSnifferProjectConfigWithExplicitFile/BadClass.php @@ -0,0 +1,9 @@ +