diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71ffe91..3df07a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,10 +34,10 @@ jobs: - name: "Install dependencies" run: "composer install --no-interaction --prefer-dist" - - name: "Run PHP_CodeSniffer" + - name: "Run PHP-CS-Fixer" run: | set -o pipefail - vendor/bin/phpcs -q --report=checkstyle | cs2pr + vendor/bin/php-cs-fixer check --diff --format=checkstyle | cs2pr stan: name: "Static Analysis" diff --git a/.gitignore b/.gitignore index 29bcd8b..939b569 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,3 @@ -/.phpcs-cache.json -/.phpstan.cache/ -/.phpunit.cache/ - /vendor/ /composer.lock diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..8bb6445 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,20 @@ +setRiskyAllowed(true) + ->setRules([ + '@PER-CS3x0' => true, + '@PHP8x4Migration' => true, + ]) + ->setFinder(new PhpCsFixer\Finder() + ->in(__DIR__ . '/src') + ->append([ + __FILE__, + __DIR__ . '/bin/docbook-cs', + ])) + ->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect()) + ->setCacheFile(__DIR__ . '/var/.php-cs-fixer.cache') + ->setUnsupportedPhpVersionAllowed(true) +; diff --git a/README.md b/README.md index 75053e2..13c3c6b 100644 --- a/README.md +++ b/README.md @@ -19,17 +19,23 @@ A static-analysis linter for DocBook XML files. It scans XML documentation sourc composer install ``` -### Running checks +### Tooling ```bash -# Tests -vendor/bin/phpunit +# run tests +composer test -# Static analysis -vendor/bin/phpstan +# run static analysis +composer phpstan -# Code style -vendor/bin/phpcs +# format all files +composer fixer -- fix + +# format all dirty files +composer fixer-fresh + +# check all files; do not apply +composer fixer -- check ``` ### Writing a sniff diff --git a/composer.json b/composer.json index e7d8725..e11c2f9 100644 --- a/composer.json +++ b/composer.json @@ -11,12 +11,12 @@ "ext-simplexml": "*" }, "require-dev": { - "phpunit/phpunit": "^13.2.4", + "friendsofphp/php-cs-fixer": "^3.95", "phpstan/phpstan": "^2.2.5", - "phpunit/php-code-coverage": "^14.2.3", - "squizlabs/php_codesniffer": "^4.0.2", - "phpstan/phpstan-strict-rules": "^2.0.12", "phpstan/phpstan-phpunit": "^2.0.18", + "phpstan/phpstan-strict-rules": "^2.0.12", + "phpunit/phpunit": "^13.2.4", + "phpunit/php-code-coverage": "^14.2.3", "shipmonk/dead-code-detector": "^1.3.2" }, "autoload": { @@ -32,9 +32,10 @@ "scripts": { "test": "@php -r \"exit(extension_loaded('xdebug') || extension_loaded('pcov') ? 0 : 1);\" && XDEBUG_MODE=coverage ./vendor/bin/phpunit --configuration phpunit.xml.dist || ./vendor/bin/phpunit --configuration phpunit.xml.dist --no-coverage", "phpstan": "./vendor/bin/phpstan analyse --no-progress", - "phpcs": "./vendor/bin/phpcs", + "fixer": "./vendor/bin/php-cs-fixer", + "fixer-fresh": "files=($(git --no-pager diff --name-only --diff-filter=ACMR HEAD | grep -E '\\.php$')); if [ ${#files[@]} -gt 0 ]; then vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --path-mode=intersection -- \"${files[@]}\"; fi", "quality": [ - "@phpcs", + "@fixer-fresh", "@phpstan", "@test" ] diff --git a/phpcs.xml b/phpcs.xml deleted file mode 100644 index 12bbfd2..0000000 --- a/phpcs.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - DocbookCS Coding Standard - - - tests/* - - - - - - - - . - - (^|/)\.* - /vendor/* - diff --git a/src/Application.php b/src/Application.php index 934d1f6..6138d95 100644 --- a/src/Application.php +++ b/src/Application.php @@ -43,9 +43,9 @@ public static function withArguments(array $argv): self return new self($argv, unifiedDiff: $stdin); } - $type = $stat['mode'] & 0170000; + $type = $stat['mode'] & 0o170000; - if ($type === 0010000 || $type === 0100000) { + if ($type === 0o010000 || $type === 0o100000) { $stdin = stream_get_contents(STDIN); if ($stdin === false) { @@ -350,42 +350,42 @@ private function isInteractive($stream): bool private function printHelp(): void { $help = <<<'HELP' -DocbookCS - DocBook Code Sniffer - -Usage: - docbook-cs [options] [ ...] - -Options: - -h, --help Show this help message and exit. - -v, --version Show version information and exit. - -q, --quiet Suppress progress output. - --config= Path to configuration file (default: docbookcs.xml). - --report= Output format: console (default), checkstyle, json. - --colors Force ANSI color output. - --no-colors Disable ANSI color output. - --fix Automatically fix violations when fixers exist - (experimental). - --wide Check whole selected files and recursively include - referenced XML files. - -Arguments: - One or more files or directories to scan. - Paths cannot be combined with diff input. - -Examples: - docbook-cs - docbook-cs --config=myconfig.xml reference/ - docbook-cs --report=checkstyle --no-colors > report.xml - docbook-cs . --fix - docbook-cs reference/ - docbook-cs reference/strings/functions/strlen.xml - docbook-cs reference/strings/functions/strlen.xml --wide - docbook-cs reference/strings/functions/strlen.xml --wide --fix - git diff HEAD | docbook-cs - git diff HEAD | docbook-cs --wide - git diff HEAD | docbook-cs --wide --fix --report=checkstyle - -HELP; + DocbookCS - DocBook Code Sniffer + + Usage: + docbook-cs [options] [ ...] + + Options: + -h, --help Show this help message and exit. + -v, --version Show version information and exit. + -q, --quiet Suppress progress output. + --config= Path to configuration file (default: docbookcs.xml). + --report= Output format: console (default), checkstyle, json. + --colors Force ANSI color output. + --no-colors Disable ANSI color output. + --fix Automatically fix violations when fixers exist + (experimental). + --wide Check whole selected files and recursively include + referenced XML files. + + Arguments: + One or more files or directories to scan. + Paths cannot be combined with diff input. + + Examples: + docbook-cs + docbook-cs --config=myconfig.xml reference/ + docbook-cs --report=checkstyle --no-colors > report.xml + docbook-cs . --fix + docbook-cs reference/ + docbook-cs reference/strings/functions/strlen.xml + docbook-cs reference/strings/functions/strlen.xml --wide + docbook-cs reference/strings/functions/strlen.xml --wide --fix + git diff HEAD | docbook-cs + git diff HEAD | docbook-cs --wide + git diff HEAD | docbook-cs --wide --fix --report=checkstyle + + HELP; $this->write($help); } diff --git a/src/Config/ConfigData.php b/src/Config/ConfigData.php index 0cfb46b..b038641 100644 --- a/src/Config/ConfigData.php +++ b/src/Config/ConfigData.php @@ -21,8 +21,7 @@ public function __construct( private array $excludePatterns, private array $entityPaths, private string $basePath, - ) { - } + ) {} /** @return array */ public function getProjectRoots(): array diff --git a/src/Config/ConfigParser.php b/src/Config/ConfigParser.php index 9d01f97..5607aec 100644 --- a/src/Config/ConfigParser.php +++ b/src/Config/ConfigParser.php @@ -10,9 +10,7 @@ final class ConfigParser { private const string NAMESPACE_URI = 'https://php.github.io/docbook-cs/config'; - public function __construct(private readonly XmlParser $xmlParser = new XmlParser()) - { - } + public function __construct(private readonly XmlParser $xmlParser = new XmlParser()) {} /** * @throws ConfigParserException if the file cannot be read or contains invalid XML. @@ -115,7 +113,7 @@ private function parseSniffs(\SimpleXMLElement $root): array $entries = []; foreach ($sniffsNode->sniff as $sniffNode) { - $class = (string)($sniffNode['class'] ?? ''); + $class = (string) ($sniffNode['class'] ?? ''); if ($class === '') { throw ConfigParserException::missingAttribute('sniff', 'class'); @@ -123,8 +121,8 @@ private function parseSniffs(\SimpleXMLElement $root): array $properties = []; foreach ($sniffNode->property as $prop) { - $name = (string)($prop['name'] ?? ''); - $value = (string)($prop['value'] ?? ''); + $name = (string) ($prop['name'] ?? ''); + $value = (string) ($prop['value'] ?? ''); if ($name === '') { throw ConfigParserException::missingAttribute('property', 'name'); @@ -149,7 +147,7 @@ private function parsePaths(\SimpleXMLElement $root, string $basePath): array } foreach ($root->paths->path as $pathNode) { - $raw = trim((string)$pathNode); + $raw = trim((string) $pathNode); if ($raw === '') { continue; @@ -171,7 +169,7 @@ private function parseExcludePatterns(\SimpleXMLElement $root): array } foreach ($root->exclude->pattern as $patternNode) { - $raw = trim((string)$patternNode); + $raw = trim((string) $patternNode); if ($raw !== '') { $patterns[] = $raw; @@ -191,7 +189,7 @@ private function parseEntityPaths(\SimpleXMLElement $root, string $basePath): ar } foreach ($root->entities->children() as $node) { - $raw = trim((string)$node); + $raw = trim((string) $node); if ($raw === '') { continue; } diff --git a/src/Diff/DiffBaseResolver.php b/src/Diff/DiffBaseResolver.php index c4dd306..91904cd 100644 --- a/src/Diff/DiffBaseResolver.php +++ b/src/Diff/DiffBaseResolver.php @@ -18,8 +18,7 @@ public function __construct( private GitClient $git, private UpstreamResolver $officialUpstream, - ) { - } + ) {} /** @throws GitException */ public function resolve(string $repoRoot): string diff --git a/src/Diff/DiffChangeset.php b/src/Diff/DiffChangeset.php index fe04d62..a441f89 100644 --- a/src/Diff/DiffChangeset.php +++ b/src/Diff/DiffChangeset.php @@ -7,9 +7,7 @@ final readonly class DiffChangeset { /** @param list $fileChanges */ - public function __construct(public array $fileChanges) - { - } + public function __construct(public array $fileChanges) {} public function changeFor(string $filePath): ?FileChange { diff --git a/src/Diff/FileChange.php b/src/Diff/FileChange.php index 5a98e8a..756dc7c 100644 --- a/src/Diff/FileChange.php +++ b/src/Diff/FileChange.php @@ -14,6 +14,5 @@ public function __construct( public string $filePath, public array $addedLineNumbers, public array $deletionAnchors = [], - ) { - } + ) {} } diff --git a/src/Diff/UpstreamResolver.php b/src/Diff/UpstreamResolver.php index 6705b7c..637ee04 100644 --- a/src/Diff/UpstreamResolver.php +++ b/src/Diff/UpstreamResolver.php @@ -98,7 +98,7 @@ private function refreshAndResolve(string $repoRoot, string $repoName): ?string private function prepareCacheDirectory(): bool { return is_dir($this->cacheDirectory) - || @mkdir($this->cacheDirectory, 0777, recursive: true) + || @mkdir($this->cacheDirectory, 0o777, recursive: true) || is_dir($this->cacheDirectory); } diff --git a/src/Fix/Fix.php b/src/Fix/Fix.php index cbc91c3..42838f6 100644 --- a/src/Fix/Fix.php +++ b/src/Fix/Fix.php @@ -28,6 +28,5 @@ public function __construct( public string $replacement, public string $sniffCode, public ?string $expectedContent = null, - ) { - } + ) {} } diff --git a/src/Fix/FixApplier.php b/src/Fix/FixApplier.php index 048bcdd..39efedd 100644 --- a/src/Fix/FixApplier.php +++ b/src/Fix/FixApplier.php @@ -18,11 +18,11 @@ public function apply(File $file, array $fixes): FixResult } $plans = array_map( - static fn ($fix): FixPlan => $fix instanceof FixPlan ? $fix : new FixPlan($fix), + static fn($fix): FixPlan => $fix instanceof FixPlan ? $fix : new FixPlan($fix), $fixes, ); - usort($plans, static fn (FixPlan $a, FixPlan $b): int => $a->firstOffset() <=> $b->firstOffset()); + usort($plans, static fn(FixPlan $a, FixPlan $b): int => $a->firstOffset() <=> $b->firstOffset()); /** @var list $acceptedFixes */ $acceptedFixes = []; diff --git a/src/Fix/FixResult.php b/src/Fix/FixResult.php index e4d4b00..932436c 100644 --- a/src/Fix/FixResult.php +++ b/src/Fix/FixResult.php @@ -14,6 +14,5 @@ public function __construct( public int $applied = 0, public int $skipped = 0, public array $appliedFixes = [], - ) { - } + ) {} } diff --git a/src/Git/GitClient.php b/src/Git/GitClient.php index 7c7abd6..a00494e 100644 --- a/src/Git/GitClient.php +++ b/src/Git/GitClient.php @@ -12,9 +12,7 @@ { private const string FORCED_BRANCH_REF_SPEC = '+refs/heads/%s:%s'; - public function __construct(private ProcessRunnerInterface $processRunner) - { - } + public function __construct(private ProcessRunnerInterface $processRunner) {} /** @throws GitException */ public function repoRoot(string $workingDirectory): string diff --git a/src/Path/DiffPathLoader.php b/src/Path/DiffPathLoader.php index 406ddc7..da7f792 100644 --- a/src/Path/DiffPathLoader.php +++ b/src/Path/DiffPathLoader.php @@ -16,8 +16,7 @@ public function __construct( private string $basePath, private array $projectRoots, private PathMatcher $matcher, - ) { - } + ) {} public function load(): DiffChangeset { diff --git a/src/Path/EntityResolver.php b/src/Path/EntityResolver.php index abcf311..59d0ac5 100644 --- a/src/Path/EntityResolver.php +++ b/src/Path/EntityResolver.php @@ -26,7 +26,7 @@ public function __construct( array $extensions = ['ent', 'dtd'] ) { $this->extensions = array_values( - array_map(static fn (string $extension): string => ltrim($extension, '.'), $extensions) + array_map(static fn(string $extension): string => ltrim($extension, '.'), $extensions) ); } diff --git a/src/Path/PathMatcher.php b/src/Path/PathMatcher.php index 146896f..f74ab1f 100644 --- a/src/Path/PathMatcher.php +++ b/src/Path/PathMatcher.php @@ -12,8 +12,7 @@ public function __construct( private string $basePath, private array $excludePatterns - ) { - } + ) {} public function isExcluded(string $filePath): bool { diff --git a/src/Process/ProcessResult.php b/src/Process/ProcessResult.php index 8d5f21b..8ba9dd2 100644 --- a/src/Process/ProcessResult.php +++ b/src/Process/ProcessResult.php @@ -10,6 +10,5 @@ public function __construct( public int $exitCode, public string $stdout, public string $stderr, - ) { - } + ) {} } diff --git a/src/Progress/ConsoleProgress.php b/src/Progress/ConsoleProgress.php index 5b6c32b..8039294 100644 --- a/src/Progress/ConsoleProgress.php +++ b/src/Progress/ConsoleProgress.php @@ -73,8 +73,8 @@ private function drawBar(int $current, string $filePath, int $violations = 0): v } $ratio = $current / $this->totalFiles; - $percent = (int)floor($ratio * 100); - $filled = (int)floor($ratio * self::BAR_WIDTH); + $percent = (int) floor($ratio * 100); + $filled = (int) floor($ratio * self::BAR_WIDTH); $empty = self::BAR_WIDTH - $filled; diff --git a/src/Report/FileReport.php b/src/Report/FileReport.php index 635266e..b3df11b 100644 --- a/src/Report/FileReport.php +++ b/src/Report/FileReport.php @@ -32,8 +32,7 @@ final class FileReport public function __construct( public readonly string $filePath, private readonly bool $collectPerformance = false, - ) { - } + ) {} public function markChanged(): void { diff --git a/src/Report/Report.php b/src/Report/Report.php index 4f50d99..4c4a996 100644 --- a/src/Report/Report.php +++ b/src/Report/Report.php @@ -13,9 +13,7 @@ final class Report /** @var array */ public private(set) array $fileReports = []; - public function __construct(private readonly bool $collectPerformance = false) - { - } + public function __construct(private readonly bool $collectPerformance = false) {} /** * @template T diff --git a/src/Report/Reporter/ConsoleReporter.php b/src/Report/Reporter/ConsoleReporter.php index eb0b8b4..d5cb52b 100644 --- a/src/Report/Reporter/ConsoleReporter.php +++ b/src/Report/Reporter/ConsoleReporter.php @@ -142,8 +142,8 @@ private function collectPerformanceRows(Report $report): array // Sort slowest first. uasort( $rows, - static fn(array $left, array $right): int => - (($right['sniffing'] ?? 0.0) + ($right['fixing'] ?? 0.0)) + static fn(array $left, array $right): int + => (($right['sniffing'] ?? 0.0) + ($right['fixing'] ?? 0.0)) <=> (($left['sniffing'] ?? 0.0) + ($left['fixing'] ?? 0.0)), ); diff --git a/src/Runner/EntityPreprocessor.php b/src/Runner/EntityPreprocessor.php index bf83d52..5535731 100644 --- a/src/Runner/EntityPreprocessor.php +++ b/src/Runner/EntityPreprocessor.php @@ -15,8 +15,7 @@ */ public function __construct( private array $entities = [], - ) { - } + ) {} public function process(string $xml): string { diff --git a/src/Runner/RunPlan.php b/src/Runner/RunPlan.php index 6b2096a..553d088 100644 --- a/src/Runner/RunPlan.php +++ b/src/Runner/RunPlan.php @@ -19,6 +19,5 @@ public function __construct( public array $sniffs, public array $targets, public array $entities, - ) { - } + ) {} } diff --git a/src/Runner/RunScope.php b/src/Runner/RunScope.php index 25e30c8..fba50e2 100644 --- a/src/Runner/RunScope.php +++ b/src/Runner/RunScope.php @@ -17,9 +17,7 @@ * * @param list|null $ranges */ - private function __construct(private ?array $ranges) - { - } + private function __construct(private ?array $ranges) {} public static function fromFileAndFileChange(File $file, ?FileChange $fileChange): self { diff --git a/src/Runner/XmlFileProcessor.php b/src/Runner/XmlFileProcessor.php index 477c77c..5cda34b 100644 --- a/src/Runner/XmlFileProcessor.php +++ b/src/Runner/XmlFileProcessor.php @@ -17,8 +17,7 @@ public function __construct( private XmlSniffRunner $xmlSniffRunner, private XmlFixRunner $xmlFixRunner = new XmlFixRunner(), - ) { - } + ) {} /** * @throws FixerException diff --git a/src/Runner/XmlSniffRunner.php b/src/Runner/XmlSniffRunner.php index d8361d9..ea01864 100644 --- a/src/Runner/XmlSniffRunner.php +++ b/src/Runner/XmlSniffRunner.php @@ -21,8 +21,7 @@ public function __construct( private EntityPreprocessor $preprocessor = new EntityPreprocessor(), private XmlParser $xmlParser = new XmlParser(), private ViolationScopeFilter $violationFilter = new ViolationScopeFilter(), - ) { - } + ) {} /** * @return array{ diff --git a/src/Sniff/AttributeOrderSniff.php b/src/Sniff/AttributeOrderSniff.php index a8bec79..9e84582 100644 --- a/src/Sniff/AttributeOrderSniff.php +++ b/src/Sniff/AttributeOrderSniff.php @@ -54,8 +54,8 @@ public function process(\DOMDocument $document, File $file): array // Skip if no relevant attributes at all (fast path) if ( - !str_contains($attrString, 'xml:id') || - !str_contains($attrString, 'xmlns') + !str_contains($attrString, 'xml:id') + || !str_contains($attrString, 'xmlns') ) { continue; } @@ -101,8 +101,8 @@ private function checkAttributes( } if ( - $name === 'xmlns' || - str_starts_with($name, 'xmlns:') + $name === 'xmlns' + || str_starts_with($name, 'xmlns:') ) { $xmlnsPos = min($xmlnsPos, $i); } diff --git a/src/Source/File.php b/src/Source/File.php index 0ead9eb..b123842 100644 --- a/src/Source/File.php +++ b/src/Source/File.php @@ -20,8 +20,7 @@ final class File public function __construct( public readonly string $path, public readonly string $content, - ) { - } + ) {} /** @return \Generator */ public function lines(): \Generator diff --git a/src/Source/Line.php b/src/Source/Line.php index a8ad010..51699ce 100644 --- a/src/Source/Line.php +++ b/src/Source/Line.php @@ -11,8 +11,7 @@ public function __construct( public string $content, public string $lineEnding, public int $beginOffset, - ) { - } + ) {} public function offsetAfterContent(): int { diff --git a/src/Violation/Violation.php b/src/Violation/Violation.php index 4b1ef29..bef873a 100644 --- a/src/Violation/Violation.php +++ b/src/Violation/Violation.php @@ -50,7 +50,7 @@ public static function fromFileReadFailure(string $filePath): self filePath: $filePath, message: 'Could not read file.', affectedRanges: [ - new SourceRange(0, 0, 0) + new SourceRange(0, 0, 0), ], severity: Severity::ERROR, );