From a8ae40f22ee968e1f044411fad7dd6a19b452990 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Fri, 27 Oct 2023 16:39:31 +1300 Subject: [PATCH 1/6] NEW Wrap PHP Code Sniffer with a markdown parser --- .gitignore | 2 + bin/mdphpcs | 76 +++++++++++++++ composer.json | 15 +++ phpcs.default.xml | 9 ++ src/Sniffer.php | 229 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 331 insertions(+) create mode 100644 .gitignore create mode 100755 bin/mdphpcs create mode 100644 composer.json create mode 100644 phpcs.default.xml create mode 100644 src/Sniffer.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94d6d75 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +/composer.lock \ No newline at end of file diff --git a/bin/mdphpcs b/bin/mdphpcs new file mode 100755 index 0000000..7b8d9de --- /dev/null +++ b/bin/mdphpcs @@ -0,0 +1,76 @@ +#!/usr/bin/env php +run($lintLanguage, $usingExplicitStandard); + exit($exitCode); +} catch (DeepExitException $e) { + echo $e->getMessage(); + exit($e->getCode()); +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d35dd8e --- /dev/null +++ b/composer.json @@ -0,0 +1,15 @@ +{ + "name": "silverstripe/markdown-php-codesniffer", + "require": { + "squizlabs/php_codesniffer": "^3.7", + "league/commonmark": "^2.4" + }, + "autoload": { + "psr-4": { + "SilverStripe\\MD_PHP_CodeSniffer\\": "src/" + } + }, + "bin": [ + "bin/mdphpcs" + ] +} diff --git a/phpcs.default.xml b/phpcs.default.xml new file mode 100644 index 0000000..cc64cd1 --- /dev/null +++ b/phpcs.default.xml @@ -0,0 +1,9 @@ + + + PSR12 pared down to what's sensible for code blocks in markdown + + + + + + \ No newline at end of file diff --git a/src/Sniffer.php b/src/Sniffer.php new file mode 100644 index 0000000..8bbb7f5 --- /dev/null +++ b/src/Sniffer.php @@ -0,0 +1,229 @@ +query = new Query(); + $this->query->where(Query::type(FencedCode::class)); + $environment = new Environment(); + $environment->addExtension(new CommonMarkCoreExtension()); + $this->parser = new MarkdownParser($environment); + } + + public function run(string $lintLanguage, bool $usingExplicitStandard = false): int + { + // Prevents errors when unexpected args are passed in, and forces fixing OFF + if (!defined('PHP_CODESNIFFER_CBF')) { + define('PHP_CODESNIFFER_CBF', false); + } + + $sniffer = new Runner(); + $sniffer->checkRequirements(); + $sniffer->config = $this->prepareConfig($usingExplicitStandard, $lintLanguage); + $sniffer->init(); + $sniffer->reporter = new Reporter($sniffer->config); + + // Find all the relevant code blocks for linting + // $files = $this->findMarkdownFiles($sniffer->config, $sniffer->ruleset); + if (PHP_CODESNIFFER_VERBOSITY > 0) { + echo 'Finding markdown files... '; + } + + $files = new FileList($sniffer->config, $sniffer->ruleset); + + if (PHP_CODESNIFFER_VERBOSITY > 0) { + $numFiles = count($files); + echo "DONE ($numFiles files in queue)" . PHP_EOL; + } + + $codeBlocks = $this->findFencedCodeblocks($files, $lintLanguage); + + // Add code blocks to the file list for linting + $todo = []; + foreach ($codeBlocks as $block) { + $dummy = new DummyFile($block['content'], $sniffer->ruleset, $sniffer->config); + $dummy->path = $block['path']; + $todo[$dummy->path] = $dummy; + } + + // Do the actual linting + $numErrors = $this->sniff($sniffer, $todo); + $sniffer->reporter->printReports(); + + if ($numErrors === 0) { + // No errors found. + return 0; + /* we can't fix errors directly yet. + } else if ($sniffer->reporter->totalFixable === 0) { + // Errors found, but none of them can be fixed by PHPCBF. + return 1; + } else { + // Errors found, and some can be fixed by PHPCBF. + return 2; + */ + } + return 1; + } + + private function prepareConfig(bool $usingExplicitStandard, string $lintLanguage): Config + { + // Creating the Config object populates it with all required settings based on the phpcs/phpcbf CLI arguments provided. + $config = new Config(); + + // We don't support STDIN for passing markdown in + if ($config->stdin === true) { + // 3 is the exit code phpcs uses for errors like this + throw new DeepExitException('STDIN isn\'t supported', 3); + } + + // Ensure we can find and lint markdown files + $config->extensions = array_merge($config->extensions, ['md' => $lintLanguage]); + // We're not passing the sniffer any real files, so caching could be unreliable + $config->cache = false; + // We must sniff all "files" sequentially - asyncronous sniffing isn't supported + $config->parallel = 1; + + // If the user hasn't defined an explicit standard, and there's no default standards file to use, + // use our customised PSR12 standard + if (!$usingExplicitStandard && $config->standards === ['PEAR']) { + $config->standards = [__DIR__ . '/../phpcs.default.xml']; + } + + return $config; + } + + /** + * Finds all fenced codeblocks for the relevant language in all the markdown files + */ + private function findFencedCodeblocks(FileList $paths, string $lintLanguage): array + { + if (PHP_CODESNIFFER_VERBOSITY > 0) { + echo 'Finding fenced codeblocks... '; + } + + $blocks = []; + + /** @var string $path */ + foreach ($paths as $path => $v) { + $document = $this->parser->parse(file_get_contents($path)); + $codeBlocks = $this->query->findAll($document); + + $n = 0; + /** @var FencedCode $block */ + foreach ($codeBlocks as $block) { + if (strtoupper($block->getInfo()) !== $lintLanguage) { + continue; + } + // We only want to count relevant code blocks + $n++; + + // $startAt is the line in the md file where the ```php line sits + $startAt = $block->getStartLine(); + + // Pad the content out so we have an accurate line count, and prepend a php code opening tag + $content = str_repeat(PHP_EOL, $startAt - 1); + $content .= 'getLiteral(); + + // Report each block separately (by making the path unique) so it's treated as its own file + // This lets us lint for things like namespaces more easily + $blocks[] = [ + 'content' => $content, + 'path' => dirname($path) . '/' . basename($path, '.md') . "_{$n}" . '.md', + ]; + } + } + + if (PHP_CODESNIFFER_VERBOSITY > 0) { + $numBlocks = count($blocks); + echo "DONE ($numBlocks codeblocks in queue)" . PHP_EOL; + } + + return $blocks; + } + + /** + * Run the codesniffing rules over the identified markdown codeblocks + * + * This is very nearly a direct copy of Runner::run() + */ + private function sniff(Runner $sniffer, array $todo): int + { + // Turn all sniff errors into exceptions. + set_error_handler([$sniffer, 'handleErrors']); + + $lastDir = ''; + $numFiles = count($todo); + + // Process each "file" sequentially - running sniff in parallel isn't supported + // We're not actually running this across real files, but we should give the same output we'd get if we were. + $numProcessed = 0; + foreach ($todo as $path => $file) { + if ($file->ignored === false) { + $currDir = dirname($path); + if ($lastDir !== $currDir) { + if (PHP_CODESNIFFER_VERBOSITY > 0) { + echo 'Changing into directory ' . Common::stripBasepath($currDir, $sniffer->config->basepath) . PHP_EOL; + } + + $lastDir = $currDir; + } + + $sniffer->processFile($file); + } else if (PHP_CODESNIFFER_VERBOSITY > 0) { + echo 'Skipping ' . basename($file->path) . PHP_EOL; + } + + $numProcessed++; + $sniffer->printProgress($file, $numFiles, $numProcessed); + } + + restore_error_handler(); + + if (PHP_CODESNIFFER_VERBOSITY === 0 + && $sniffer->config->interactive === false + && $sniffer->config->showProgress === true + ) { + echo PHP_EOL . PHP_EOL; + } + + $ignoreWarnings = Config::getConfigData('ignore_warnings_on_exit'); + $ignoreErrors = Config::getConfigData('ignore_errors_on_exit'); + + $return = ($sniffer->reporter->totalErrors + $sniffer->reporter->totalWarnings); + if ($ignoreErrors !== null) { + $ignoreErrors = (bool) $ignoreErrors; + if ($ignoreErrors === true) { + $return -= $sniffer->reporter->totalErrors; + } + } + + if ($ignoreWarnings !== null) { + $ignoreWarnings = (bool) $ignoreWarnings; + if ($ignoreWarnings === true) { + $return -= $sniffer->reporter->totalWarnings; + } + } + + return $return; + } +} \ No newline at end of file From c5f5210c65789fb8d4f2ff6514bd102f3fec66a3 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Tue, 31 Oct 2023 14:24:47 +1300 Subject: [PATCH 2/6] NEW Enable fixing linting issues automatically --- bin/mdphpcs | 15 ++++--- src/CodeBlock.php | 29 ++++++++++++++ src/FixerReport.php | 89 +++++++++++++++++++++++++++++++++++++++++ src/Sniffer.php | 97 ++++++++++++++++++++++++++++++++++----------- 4 files changed, 198 insertions(+), 32 deletions(-) create mode 100644 src/CodeBlock.php create mode 100644 src/FixerReport.php diff --git a/bin/mdphpcs b/bin/mdphpcs index 7b8d9de..91f30f3 100755 --- a/bin/mdphpcs +++ b/bin/mdphpcs @@ -1,7 +1,6 @@ #!/usr/bin/env php run($lintLanguage, $usingExplicitStandard); + $exitCode = $sniffer->run($lintLanguage, $fixing, $usingExplicitStandard); exit($exitCode); } catch (DeepExitException $e) { echo $e->getMessage(); diff --git a/src/CodeBlock.php b/src/CodeBlock.php new file mode 100644 index 0000000..84873cb --- /dev/null +++ b/src/CodeBlock.php @@ -0,0 +1,29 @@ +finalContent = $this->content ?? ''; + parent::cleanUp(); + } + + public function getContent(): ?string + { + if ($this->content) { + return $this->content; + } + + return $this->finalContent; + } +} diff --git a/src/FixerReport.php b/src/FixerReport.php new file mode 100644 index 0000000..f838baa --- /dev/null +++ b/src/FixerReport.php @@ -0,0 +1,89 @@ +getFixableCount(); + if ($errors !== 0) { + if (PHP_CODESNIFFER_VERBOSITY > 0) { + ob_end_clean(); + $startTime = microtime(true); + echo "\t=> Fixing file: $errors/$errors violations remaining"; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo PHP_EOL; + } + } + + $fixed = $phpcsFile->fixer->fixFile(); + } + + if ($phpcsFile->config->stdin === true) { + // Replacing STDIN, so output current file to STDOUT + // even if nothing was fixed. Exit here because we + // can't process any more than 1 file in this setup. + $fixedContent = $phpcsFile->fixer->getContents(); + throw new DeepExitException($fixedContent, 1); + } + + if ($errors === 0) { + return false; + } + + if (PHP_CODESNIFFER_VERBOSITY > 0) { + if ($fixed === false) { + echo 'ERROR'; + } else { + echo 'DONE'; + } + + $timeTaken = ((microtime(true) - $startTime) * 1000); + if ($timeTaken < 1000) { + $timeTaken = round($timeTaken); + echo " in {$timeTaken}ms".PHP_EOL; + } else { + $timeTaken = round(($timeTaken / 1000), 2); + echo " in $timeTaken secs".PHP_EOL; + } + } + + // NOTE: This is the only change from the parent method! + // We've ripped out all of the code here which would have written changes to the file. + // Instead, we need to find the old content for a given block and override that with + // the new content. This is done back in the Sniffer class. + + if (PHP_CODESNIFFER_VERBOSITY > 0) { + if ($fixed === true) { + echo "\t=> Fixed content stored in memory".PHP_EOL; + } + ob_start(); + } + + $errorCount = $phpcsFile->getErrorCount(); + $warningCount = $phpcsFile->getWarningCount(); + $fixableCount = $phpcsFile->getFixableCount(); + $fixedCount = ($errors - $fixableCount); + echo $report['filename'] . ">>$errorCount>>$warningCount>>$fixableCount>>$fixedCount" . PHP_EOL; + + return $fixed; + + } +} diff --git a/src/Sniffer.php b/src/Sniffer.php index 8bbb7f5..9eabfb4 100644 --- a/src/Sniffer.php +++ b/src/Sniffer.php @@ -7,9 +7,9 @@ use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode; use League\CommonMark\Node\Query; use League\CommonMark\Parser\MarkdownParser; +use LogicException; use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Exceptions\DeepExitException; -use PHP_CodeSniffer\Files\DummyFile; use PHP_CodeSniffer\Files\FileList; use PHP_CodeSniffer\Reporter; use PHP_CodeSniffer\Runner; @@ -30,16 +30,20 @@ public function __construct() $this->parser = new MarkdownParser($environment); } - public function run(string $lintLanguage, bool $usingExplicitStandard = false): int + public function run(string $lintLanguage, bool $fixing, bool $usingExplicitStandard = false): int { - // Prevents errors when unexpected args are passed in, and forces fixing OFF + // MUST be false when not fixing, and MUST be true when fixing. + // This affects how codesniffer treats various CLI args, changes the output, and defines how some rules are actioned. if (!defined('PHP_CODESNIFFER_CBF')) { - define('PHP_CODESNIFFER_CBF', false); + define('PHP_CODESNIFFER_CBF', $fixing); + } + if (PHP_CODESNIFFER_CBF !== $fixing) { + throw new LogicException('PHP_CODESNIFFER_CBF was defined with an incorrect value'); } $sniffer = new Runner(); $sniffer->checkRequirements(); - $sniffer->config = $this->prepareConfig($usingExplicitStandard, $lintLanguage); + $sniffer->config = $this->prepareConfig($usingExplicitStandard, $lintLanguage, $fixing); $sniffer->init(); $sniffer->reporter = new Reporter($sniffer->config); @@ -61,31 +65,57 @@ public function run(string $lintLanguage, bool $usingExplicitStandard = false): // Add code blocks to the file list for linting $todo = []; foreach ($codeBlocks as $block) { - $dummy = new DummyFile($block['content'], $sniffer->ruleset, $sniffer->config); - $dummy->path = $block['path']; - $todo[$dummy->path] = $dummy; + $dummy = new CodeBlock($block['content'], $sniffer->ruleset, $sniffer->config); + $dummy->num = $block['num']; + $dummy->path = $block['path']; + $dummy->realPath = $block['realpath']; + $todo[] = $dummy; } // Do the actual linting $numErrors = $this->sniff($sniffer, $todo); + + // The dummy files have the fixed content stored - but we still need to write that to the original files. + // There's no good AST to markdown renderer for league/commonmark so we're just doing a bit of an ugly + // search and replace. + if ($fixing) { + /** @var CodeBlock $dummy */ + foreach ($todo as $dummy) { + if ($dummy->getFixedCount() < 1) { + continue; + } + + if (!is_file($dummy->realPath)) { + // 3 is the exit code phpcs uses for errors like this + throw new DeepExitException("Can't find file {$dummy->realPath} to set new content", 3); + } + + /** @var FencedCode $mdBlock */ + $mdBlock = $codeBlocks[$dummy->path]['md']; + $indent = str_repeat(' ', $mdBlock->getOffset()); + $origBlockContent = preg_replace('/^/m', $indent, $mdBlock->getLiteral()); + $newBlockContent = preg_replace('/^/m', $indent, preg_replace('/\s*<\?php\n?/', '', $dummy->getContent())); + $newFileContent = str_replace($origBlockContent, $newBlockContent, file_get_contents($dummy->realPath)); + + file_put_contents($dummy->realPath, $newFileContent); + } + } + $sniffer->reporter->printReports(); if ($numErrors === 0) { // No errors found. return 0; - /* we can't fix errors directly yet. } else if ($sniffer->reporter->totalFixable === 0) { // Errors found, but none of them can be fixed by PHPCBF. return 1; } else { // Errors found, and some can be fixed by PHPCBF. return 2; - */ } - return 1; } - private function prepareConfig(bool $usingExplicitStandard, string $lintLanguage): Config + private function prepareConfig(bool $usingExplicitStandard, string $lintLanguage, bool $fixing): Config { // Creating the Config object populates it with all required settings based on the phpcs/phpcbf CLI arguments provided. $config = new Config(); @@ -109,6 +139,19 @@ private function prepareConfig(bool $usingExplicitStandard, string $lintLanguage $config->standards = [__DIR__ . '/../phpcs.default.xml']; } + if ($fixing) { + // Override some of the command line settings that might break the fixes. + $config->generator = null; + $config->explain = false; + $config->interactive = false; + $config->cache = false; + $config->showSources = false; + $config->recordErrors = false; + $config->reportFile = null; + $config->reports = [FixerReport::class => null]; + $config->dieOnUnknownArg = false; + } + return $config; } @@ -145,10 +188,15 @@ private function findFencedCodeblocks(FileList $paths, string $lintLanguage): ar $content .= 'getLiteral(); // Report each block separately (by making the path unique) so it's treated as its own file - // This lets us lint for things like namespaces more easily - $blocks[] = [ + // This lets us lint for things like namespaces more easily since the namespace in an earlier block + // won't be counted towards a later block in the same file + $key = dirname($path) . '/' . basename($path, '.md') . "_{$n}" . '.md'; + $blocks[$key] = [ 'content' => $content, - 'path' => dirname($path) . '/' . basename($path, '.md') . "_{$n}" . '.md', + 'path' => $key, + 'realpath' => $path, + 'num' => $n, + 'md' => $block, ]; } } @@ -172,14 +220,15 @@ private function sniff(Runner $sniffer, array $todo): int set_error_handler([$sniffer, 'handleErrors']); $lastDir = ''; - $numFiles = count($todo); + $numBlocks = count($todo); - // Process each "file" sequentially - running sniff in parallel isn't supported + // Process each block sequentially - running sniff in parallel isn't supported // We're not actually running this across real files, but we should give the same output we'd get if we were. $numProcessed = 0; - foreach ($todo as $path => $file) { - if ($file->ignored === false) { - $currDir = dirname($path); + /** @var CodeBlock $block */ + foreach ($todo as $block) { + if ($block->ignored === false) { + $currDir = dirname($block->realPath); if ($lastDir !== $currDir) { if (PHP_CODESNIFFER_VERBOSITY > 0) { echo 'Changing into directory ' . Common::stripBasepath($currDir, $sniffer->config->basepath) . PHP_EOL; @@ -188,13 +237,13 @@ private function sniff(Runner $sniffer, array $todo): int $lastDir = $currDir; } - $sniffer->processFile($file); + $sniffer->processFile($block); } else if (PHP_CODESNIFFER_VERBOSITY > 0) { - echo 'Skipping ' . basename($file->path) . PHP_EOL; + echo 'Skipping ' . basename($block->path) . PHP_EOL; } $numProcessed++; - $sniffer->printProgress($file, $numFiles, $numProcessed); + $sniffer->printProgress($block, $numBlocks, $numProcessed); } restore_error_handler(); @@ -226,4 +275,4 @@ private function sniff(Runner $sniffer, array $todo): int return $return; } -} \ No newline at end of file +} From 4534fd6492cd479883c4ba2331217dea756bb865 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Tue, 31 Oct 2023 15:02:43 +1300 Subject: [PATCH 3/6] MNT Standardise package --- .editorconfig | 31 ++++++++++++++++++++ .gitattributes | 6 ++++ .github/workflows/keepalive.yml | 17 +++++++++++ .github/workflows/merge-up.yml | 17 +++++++++++ .gitignore | 2 +- LICENSE | 29 +++++++++++++++++++ README.md | 51 +++++++++++++++++++++++++++++++++ bin/mdphpcs | 2 +- composer.json | 10 +++++++ phpcs.default.xml | 2 +- src/Sniffer.php | 7 ++--- 11 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/workflows/keepalive.yml create mode 100644 .github/workflows/merge-up.yml create mode 100644 LICENSE diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..347aa2f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,31 @@ +# For more information about the properties used in +# this file, please see the EditorConfig documentation: +# http://editorconfig.org/ + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,js,json,css,scss,eslintrc,feature}] +indent_size = 2 +indent_style = space + +[composer.json] +indent_size = 4 + +# Don't perform any clean-up on thirdparty files + +[thirdparty/**] +trim_trailing_whitespace = false +insert_final_newline = false + +[admin/thirdparty/**] +trim_trailing_whitespace = false +insert_final_newline = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7bf7c94 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +/.github export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/phpunit.xml.dist export-ignore +/tests export-ignore +/.editorconfig export-ignore diff --git a/.github/workflows/keepalive.yml b/.github/workflows/keepalive.yml new file mode 100644 index 0000000..ab90e57 --- /dev/null +++ b/.github/workflows/keepalive.yml @@ -0,0 +1,17 @@ +name: Keepalive + +on: + # At 6:30 PM UTC, on day 15 of the month + schedule: + - cron: '30 18 15 * *' + workflow_dispatch: + +jobs: + keepalive: + name: Keepalive + # Only run cron on the silverstripe account + if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule') + runs-on: ubuntu-latest + steps: + - name: Keepalive + uses: silverstripe/gha-keepalive@v1 diff --git a/.github/workflows/merge-up.yml b/.github/workflows/merge-up.yml new file mode 100644 index 0000000..41f3410 --- /dev/null +++ b/.github/workflows/merge-up.yml @@ -0,0 +1,17 @@ +name: Merge-up + +on: + # At 6:30 PM UTC, only on Sunday + schedule: + - cron: '30 18 * * 0' + workflow_dispatch: + +jobs: + merge-up: + name: Merge-up + # Only run cron on the silverstripe account + if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule') + runs-on: ubuntu-latest + steps: + - name: Merge-up + uses: silverstripe/gha-merge-up@v1 diff --git a/.gitignore b/.gitignore index 94d6d75..4fbb073 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ /vendor/ -/composer.lock \ No newline at end of file +/composer.lock diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f86cb0e --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2023, Silverstripe Limited - www.silverstripe.com +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 8b13789..af6c9d8 100644 --- a/README.md +++ b/README.md @@ -1 +1,52 @@ +# Markdown PHP Codesniffer +A wrapper around [`squizlabs/PHP_CodeSniffer`](https://github.com/squizlabs/PHP_CodeSniffer) which lets you lint PHP fenced code blocks in markdown files. + +## Installation + +Unlike `squizlabs/PHP_CodeSniffer`, this isn't intended to be installed globally - you should install it as a dev dependency of your project. + +```bash +composer require --dev silverstripe/markdown-php-codesniffer +``` + +## Usage + +To sniff markdown files, run `mdphpcs` from the vendor bin directory: + +```bash +# sniff a directory +vendor/bin/mdphpcs /path/to/docs + +# sniff a specific file +vendor/bin/mdphpcs /path/to/docs/file.md +``` + +Most of the options available with the `phpcs` and `phpcbf` commands from `squizlabs/PHP_CodeSniffer` are available with `mdphpcs` as well. +See [PHP_CodeSniffer usage](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Usage) for more details. + +### Fixing violations automatically + +Some violations can be fixed automatically, and PHP_CodeSniffer will include information about those in the CLI output. To fix them, simply pass the `--fix` option to `mdphpcs`: + +```bash +vendor/bin/mdphpcs /path/to/docs --fix +``` + +This is the equivalent of using the `phpcbf` command on regular PHP files. + +### Linting other languages + +`squizlabs/PHP_CodeSniffer` supports linting some languages other than PHP. Theoretically that can be done with this tool as well. You'll need to pass the language (as it's written in the markdown language hint) in with the `--linting-language` option. + +```bash +vendor/bin/mdphpcs /path/to/docs --linting-language=JS +``` + +### Linting rules + +If you have a [default configuration file](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Advanced-Usage#using-a-default-configuration-file) or explicitly pass in a standard using the `--standard` option, those rules will be used for linting - but be aware that some rules won't be appropriate for linting code blocks. + +For example, the `PSR12.Files.FileHeader.HeaderPosition` rule will always fail linting, because we need to include empty lines prior to the content of the code block in the content we pass to `squizlabs/PHP_CodeSniffer` so it can correctly report the line of each violation in the original markdown file. + +If you don't specify a standard and have no default configuration file, the default configuration [included in this package](./phpcs.default.xml) will be used. This configuration is based on PSR12, with some exclusions that make it appropriate for use in linting code blocks. diff --git a/bin/mdphpcs b/bin/mdphpcs index 91f30f3..c70ed55 100755 --- a/bin/mdphpcs +++ b/bin/mdphpcs @@ -42,7 +42,7 @@ for ($i = 0; $i < $numArgs; $i++) { $removeArgs[] = $i; $removeArgs[] = $i + 1; } elseif (str_starts_with($arg, '--lint-language=')) { - $lintLanguage = str_replace('--lint-language=', '', $arg); + $lintLanguage = strtoupper(str_replace('--lint-language=', '', $arg)); $removeArgs[] = $i; } diff --git a/composer.json b/composer.json index d35dd8e..f449e52 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,16 @@ { "name": "silverstripe/markdown-php-codesniffer", + "description": "A wrapper around squizlabs/PHP_CodeSniffer which lets you lint PHP fenced code blocks in markdown files", + "keywords": [ + "phpcs", + "standards", + "markdown", + "sniffer", + "codesniffer" + ], + "license": "BSD-3-Clause", "require": { + "php": "^8.0", "squizlabs/php_codesniffer": "^3.7", "league/commonmark": "^2.4" }, diff --git a/phpcs.default.xml b/phpcs.default.xml index cc64cd1..c8a26f2 100644 --- a/phpcs.default.xml +++ b/phpcs.default.xml @@ -6,4 +6,4 @@ - \ No newline at end of file + diff --git a/src/Sniffer.php b/src/Sniffer.php index 9eabfb4..12ea34c 100644 --- a/src/Sniffer.php +++ b/src/Sniffer.php @@ -48,7 +48,6 @@ public function run(string $lintLanguage, bool $fixing, bool $usingExplicitStand $sniffer->reporter = new Reporter($sniffer->config); // Find all the relevant code blocks for linting - // $files = $this->findMarkdownFiles($sniffer->config, $sniffer->ruleset); if (PHP_CODESNIFFER_VERBOSITY > 0) { echo 'Finding markdown files... '; } @@ -165,7 +164,7 @@ private function findFencedCodeblocks(FileList $paths, string $lintLanguage): ar } $blocks = []; - + /** @var string $path */ foreach ($paths as $path => $v) { $document = $this->parser->parse(file_get_contents($path)); @@ -211,7 +210,7 @@ private function findFencedCodeblocks(FileList $paths, string $lintLanguage): ar /** * Run the codesniffing rules over the identified markdown codeblocks - * + * * This is very nearly a direct copy of Runner::run() */ private function sniff(Runner $sniffer, array $todo): int @@ -236,7 +235,7 @@ private function sniff(Runner $sniffer, array $todo): int $lastDir = $currDir; } - + $sniffer->processFile($block); } else if (PHP_CODESNIFFER_VERBOSITY > 0) { echo 'Skipping ' . basename($block->path) . PHP_EOL; From cb5a657650222bc670b861fea08df91ea57b52b7 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Wed, 1 Nov 2023 12:10:55 +1300 Subject: [PATCH 4/6] MNT Add unit tests --- .github/workflows/ci.yml | 11 ++ .github/workflows/dispatch-ci.yml | 16 ++ .github/workflows/merge-up.yml | 4 +- .gitignore | 1 + composer.json | 8 +- phpunit.xml.dist | 12 ++ src/Sniffer.php | 4 + tests/CodeBlockTest.php | 50 +++++++ tests/SnifferFixTest.php | 66 ++++++++ tests/SnifferTest.php | 141 ++++++++++++++++++ tests/bootstrap.php | 28 ++++ .../lint-with-problems.md | 45 ++++++ tests/fixtures/lint-but-no-problems.md | 12 ++ tests/fixtures/lint-with-problems.md | 43 ++++++ tests/fixtures/nothing-to-lint.md | 28 ++++ 15 files changed, 465 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/dispatch-ci.yml create mode 100644 phpunit.xml.dist create mode 100644 tests/CodeBlockTest.php create mode 100644 tests/SnifferFixTest.php create mode 100644 tests/SnifferTest.php create mode 100644 tests/bootstrap.php create mode 100644 tests/expected-after-fixing/lint-with-problems.md create mode 100644 tests/fixtures/lint-but-no-problems.md create mode 100644 tests/fixtures/lint-with-problems.md create mode 100644 tests/fixtures/nothing-to-lint.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bf02210 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,11 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + ci: + name: CI + uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1 diff --git a/.github/workflows/dispatch-ci.yml b/.github/workflows/dispatch-ci.yml new file mode 100644 index 0000000..5632551 --- /dev/null +++ b/.github/workflows/dispatch-ci.yml @@ -0,0 +1,16 @@ +name: Dispatch CI + +on: + # At 6:30 PM UTC, only on Sunday and Monday + schedule: + - cron: '30 18 * * 0,1' + +jobs: + dispatch-ci: + name: Dispatch CI + # Only run cron on the silverstripe account + if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule') + runs-on: ubuntu-latest + steps: + - name: Dispatch CI + uses: silverstripe/gha-dispatch-ci@v1 diff --git a/.github/workflows/merge-up.yml b/.github/workflows/merge-up.yml index 41f3410..d343ab7 100644 --- a/.github/workflows/merge-up.yml +++ b/.github/workflows/merge-up.yml @@ -1,9 +1,9 @@ name: Merge-up on: - # At 6:30 PM UTC, only on Sunday + # At 6:30 PM UTC, only on Thursday schedule: - - cron: '30 18 * * 0' + - cron: '30 18 * * 4' workflow_dispatch: jobs: diff --git a/.gitignore b/.gitignore index 4fbb073..525642d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /vendor/ /composer.lock +.phpunit.result.cache diff --git a/composer.json b/composer.json index f449e52..3da10e0 100644 --- a/composer.json +++ b/composer.json @@ -16,10 +16,14 @@ }, "autoload": { "psr-4": { - "SilverStripe\\MD_PHP_CodeSniffer\\": "src/" + "SilverStripe\\MD_PHP_CodeSniffer\\": "src/", + "SilverStripe\\MD_PHP_CodeSniffer\\Tests\\": "tests/" } }, "bin": [ "bin/mdphpcs" - ] + ], + "require-dev": { + "phpunit/phpunit": "^9.6" + } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..d50172b --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,12 @@ + + + + + tests + tests/SnifferFixTest.php + + + tests/SnifferFixTest.php + + + diff --git a/src/Sniffer.php b/src/Sniffer.php index 12ea34c..2d569cd 100644 --- a/src/Sniffer.php +++ b/src/Sniffer.php @@ -119,6 +119,10 @@ private function prepareConfig(bool $usingExplicitStandard, string $lintLanguage // Creating the Config object populates it with all required settings based on the phpcs/phpcbf CLI arguments provided. $config = new Config(); + if (defined('PHP_CODESNIFFER_IN_TESTS') && PHP_CODESNIFFER_IN_TESTS) { + $config->files = [str_replace('/src', '/tests/fixtures', __DIR__ )]; + } + // We don't support STDIN for passing markdown in if ($config->stdin === true) { // 3 is the exit code phpcs uses for errors like this diff --git a/tests/CodeBlockTest.php b/tests/CodeBlockTest.php new file mode 100644 index 0000000..edb18ca --- /dev/null +++ b/tests/CodeBlockTest.php @@ -0,0 +1,50 @@ +assertSame('This is the content', $block->getContent()); + + $block->setContent('New content now'); + + $this->assertSame('New content now', $block->getContent()); + } + + public function testCleanup() + { + $config = new Config(); + $block = new CodeBlock('This is the content', new Ruleset($config), $config); + $block->cleanUp(); + + $this->assertSame('This is the content', $block->getContent()); + + $reflectionContent = new ReflectionProperty($block, 'content'); + $reflectionContent->setAccessible(true); + $reflectionFinalContent = new ReflectionProperty($block, 'finalContent'); + $reflectionFinalContent->setAccessible(true); + + $this->assertNull($reflectionContent->getValue($block)); + $this->assertSame('This is the content', $reflectionFinalContent->getValue($block)); + } +} diff --git a/tests/SnifferFixTest.php b/tests/SnifferFixTest.php new file mode 100644 index 0000000..211ec4a --- /dev/null +++ b/tests/SnifferFixTest.php @@ -0,0 +1,66 @@ + $v) { + $orig[$path] = file_get_contents($path); + } + + try { + ob_start(); + $exitCode = $sniffer->run('PHP', true, true); + $output = ob_get_clean(); + + // Validate that the files which should change did, and which shouldn't change didn't + foreach ($orig as $path => $content) { + $this->assertFileExists($path); + + if (str_contains($path, 'lint-with-problems')) { + $this->assertFileEquals(str_replace('/fixtures/', '/expected-after-fixing/', $path), $path); + } else { + $this->assertSame($content, file_get_contents($path)); + } + } + + // There are no remaining auto-fixable problems + $this->assertSame(1, $exitCode); + } finally { + // Put the original content back + foreach ($orig as $path => $content) { + file_put_contents($path, $content); + } + } + } + + private static function getFilesList(Sniffer $sniffer): FileList + { + $prepareConfig = new ReflectionMethod($sniffer, 'prepareConfig'); + $prepareConfig->setAccessible(true); + $config = $prepareConfig->invoke($sniffer, false, 'PHP', true); + + return new FileList($config, new Ruleset($config)); + } +} diff --git a/tests/SnifferTest.php b/tests/SnifferTest.php new file mode 100644 index 0000000..addfc2b --- /dev/null +++ b/tests/SnifferTest.php @@ -0,0 +1,141 @@ +setAccessible(true); + $blocks = $findFencedCodeblocks->invoke($sniffer, $files, 'PHP'); + + $blockKey = __DIR__ . $path; + + if ($exists) { + $this->assertArrayHasKey($blockKey, $blocks, 'block must be found'); + + $block = $blocks[$blockKey]; + $this->assertSame($blockKey, $block['path'], 'block path must be correct'); + $this->assertSame(__DIR__ . $realPath, $block['realpath'], 'block realpath must be correct'); + $this->assertSame($num, $block['num'], 'block must be numbered correctly'); + $this->assertSame($content, ltrim($block['content']), 'block content must be correct'); + } else { + $this->assertArrayNotHasKey($blockKey, $blocks, 'block must not be found'); + } + } + + public function provideFindFencedBlocks() + { + return [ + 'nothing to lint 1' => [ + 'path' => '/fixtures/nothing-to-lint.md', + 'exists' => false, + ], + 'nothing to lint 2' => [ + 'path' => '/fixtures/nothing-to-lint_1.md', + 'exists' => false, + ], + 'file paths all include block numbers' => [ + 'path' => '/fixtures/lint-but-no-problems.md', + 'exists' => false, + ], + [ + 'path' => '/fixtures/lint-but-no-problems_1.md', + 'exists' => true, + 'realpath' => '/fixtures/lint-but-no-problems.md', + 'num' => 1, + 'content' => <<<'MD' + [ + 'path' => '/fixtures/lint-but-no-problems_2.md', + 'exists' => false, + ], + // No need to check lint-with-problems_1 and lint-with-problems_2 - they're functionality + // identical to lint-but-no-problems_1 for the purposes of this test. + 'language identifier not case sensitive' => [ + 'path' => '/fixtures/lint-with-problems_3.md', + 'exists' => true, + 'realpath' => '/fixtures/lint-with-problems.md', + 'num' => 3, + 'content' => <<<'MD' + [ + 'path' => '/fixtures/lint-with-problems_4.md', + 'exists' => true, + 'realpath' => '/fixtures/lint-with-problems.md', + 'num' => 4, + 'content' => <<<'MD' + run('PHP', false, true); + $output = ob_get_clean(); + + // There are auto-fixable problems + $this->assertSame(2, $exitCode); + + // Check we didn't find problems where there aren't any + $this->assertStringNotContainsString('nothing-to-lint', $output, 'nothing to lint, so nothing found'); + $this->assertStringNotContainsString('lint-but-no-problems', $output, 'linted but no problems found'); + $this->assertStringNotContainsString('lint-with-problems_2', $output, 'that code block has no linting problems'); + + // Check we did find problems where there are plenty + $this->assertStringContainsString('lint-with-problems_1', $output); + $this->assertStringContainsString('lint-with-problems_3', $output); + $this->assertStringContainsString('lint-with-problems_4', $output); + } + + private static function getFilesList(Sniffer $sniffer): FileList + { + $prepareConfig = new ReflectionMethod($sniffer, 'prepareConfig'); + $prepareConfig->setAccessible(true); + $config = $prepareConfig->invoke($sniffer, false, 'PHP', true); + + return new FileList($config, new Ruleset($config)); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..7713bfa --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,28 @@ + Date: Thu, 2 Nov 2023 11:18:34 +1300 Subject: [PATCH 5/6] MNT Add and apply linting config --- phpcs.xml.dist | 7 +++++++ src/FixerReport.php | 7 +++---- src/Sniffer.php | 31 ++++++++++++++++++++----------- tests/SnifferTest.php | 15 ++++++++++++--- 4 files changed, 42 insertions(+), 18 deletions(-) create mode 100644 phpcs.xml.dist diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..9bf83ab --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,7 @@ + + + + + tests/bootstrap\.php + + diff --git a/src/FixerReport.php b/src/FixerReport.php index f838baa..c43d8f0 100644 --- a/src/FixerReport.php +++ b/src/FixerReport.php @@ -58,10 +58,10 @@ public function generateFileReport($report, File $phpcsFile, $showSources = fals $timeTaken = ((microtime(true) - $startTime) * 1000); if ($timeTaken < 1000) { $timeTaken = round($timeTaken); - echo " in {$timeTaken}ms".PHP_EOL; + echo " in {$timeTaken}ms" . PHP_EOL; } else { $timeTaken = round(($timeTaken / 1000), 2); - echo " in $timeTaken secs".PHP_EOL; + echo " in $timeTaken secs" . PHP_EOL; } } @@ -72,7 +72,7 @@ public function generateFileReport($report, File $phpcsFile, $showSources = fals if (PHP_CODESNIFFER_VERBOSITY > 0) { if ($fixed === true) { - echo "\t=> Fixed content stored in memory".PHP_EOL; + echo "\t=> Fixed content stored in memory" . PHP_EOL; } ob_start(); } @@ -84,6 +84,5 @@ public function generateFileReport($report, File $phpcsFile, $showSources = fals echo $report['filename'] . ">>$errorCount>>$warningCount>>$fixableCount>>$fixedCount" . PHP_EOL; return $fixed; - } } diff --git a/src/Sniffer.php b/src/Sniffer.php index 2d569cd..013c4d2 100644 --- a/src/Sniffer.php +++ b/src/Sniffer.php @@ -33,7 +33,8 @@ public function __construct() public function run(string $lintLanguage, bool $fixing, bool $usingExplicitStandard = false): int { // MUST be false when not fixing, and MUST be true when fixing. - // This affects how codesniffer treats various CLI args, changes the output, and defines how some rules are actioned. + // This affects how codesniffer treats various CLI args, changes the output, and defines how + // some rules are actioned. if (!defined('PHP_CODESNIFFER_CBF')) { define('PHP_CODESNIFFER_CBF', $fixing); } @@ -49,7 +50,7 @@ public function run(string $lintLanguage, bool $fixing, bool $usingExplicitStand // Find all the relevant code blocks for linting if (PHP_CODESNIFFER_VERBOSITY > 0) { - echo 'Finding markdown files... '; + echo 'Finding markdown files... ' . PHP_EOL; } $files = new FileList($sniffer->config, $sniffer->ruleset); @@ -92,10 +93,14 @@ public function run(string $lintLanguage, bool $fixing, bool $usingExplicitStand /** @var FencedCode $mdBlock */ $mdBlock = $codeBlocks[$dummy->path]['md']; $indent = str_repeat(' ', $mdBlock->getOffset()); + // Apply indent to each line of the original block content so we can search/replace $origBlockContent = preg_replace('/^/m', $indent, $mdBlock->getLiteral()); - $newBlockContent = preg_replace('/^/m', $indent, preg_replace('/\s*<\?php\n?/', '', $dummy->getContent())); - $newFileContent = str_replace($origBlockContent, $newBlockContent, file_get_contents($dummy->realPath)); + // Strip out temporary php opening tag and apply indent to new block content + $newBlockContent = preg_replace('/\s*<\?php\n?/', '', $dummy->getContent()); + $newBlockContent = preg_replace('/^/m', $indent, $newBlockContent); + // Search for the original block content and replace it with the new block content + $newFileContent = str_replace($origBlockContent, $newBlockContent, file_get_contents($dummy->realPath)); file_put_contents($dummy->realPath, $newFileContent); } } @@ -105,7 +110,7 @@ public function run(string $lintLanguage, bool $fixing, bool $usingExplicitStand if ($numErrors === 0) { // No errors found. return 0; - } else if ($sniffer->reporter->totalFixable === 0) { + } elseif ($sniffer->reporter->totalFixable === 0) { // Errors found, but none of them can be fixed by PHPCBF. return 1; } else { @@ -116,11 +121,12 @@ public function run(string $lintLanguage, bool $fixing, bool $usingExplicitStand private function prepareConfig(bool $usingExplicitStandard, string $lintLanguage, bool $fixing): Config { - // Creating the Config object populates it with all required settings based on the phpcs/phpcbf CLI arguments provided. + // Creating the Config object populates it with all required settings based on the phpcs/phpcbf + // CLI arguments provided. $config = new Config(); if (defined('PHP_CODESNIFFER_IN_TESTS') && PHP_CODESNIFFER_IN_TESTS) { - $config->files = [str_replace('/src', '/tests/fixtures', __DIR__ )]; + $config->files = [str_replace('/src', '/tests/fixtures', __DIR__)]; } // We don't support STDIN for passing markdown in @@ -164,7 +170,7 @@ private function prepareConfig(bool $usingExplicitStandard, string $lintLanguage private function findFencedCodeblocks(FileList $paths, string $lintLanguage): array { if (PHP_CODESNIFFER_VERBOSITY > 0) { - echo 'Finding fenced codeblocks... '; + echo 'Finding fenced codeblocks... ' . PHP_EOL; } $blocks = []; @@ -234,14 +240,16 @@ private function sniff(Runner $sniffer, array $todo): int $currDir = dirname($block->realPath); if ($lastDir !== $currDir) { if (PHP_CODESNIFFER_VERBOSITY > 0) { - echo 'Changing into directory ' . Common::stripBasepath($currDir, $sniffer->config->basepath) . PHP_EOL; + echo 'Changing into directory ' + . Common::stripBasepath($currDir, $sniffer->config->basepath) + . PHP_EOL; } $lastDir = $currDir; } $sniffer->processFile($block); - } else if (PHP_CODESNIFFER_VERBOSITY > 0) { + } elseif (PHP_CODESNIFFER_VERBOSITY > 0) { echo 'Skipping ' . basename($block->path) . PHP_EOL; } @@ -251,7 +259,8 @@ private function sniff(Runner $sniffer, array $todo): int restore_error_handler(); - if (PHP_CODESNIFFER_VERBOSITY === 0 + if ( + PHP_CODESNIFFER_VERBOSITY === 0 && $sniffer->config->interactive === false && $sniffer->config->showProgress === true ) { diff --git a/tests/SnifferTest.php b/tests/SnifferTest.php index addfc2b..6de735b 100644 --- a/tests/SnifferTest.php +++ b/tests/SnifferTest.php @@ -15,8 +15,13 @@ class SnifferTest extends TestCase * * @dataProvider provideFindFencedBlocks */ - public function testFindFencedCodeBlocks(string $path, bool $exists, string $realPath = '', int $num = 0, string $content = '') - { + public function testFindFencedCodeBlocks( + string $path, + bool $exists, + string $realPath = '', + int $num = 0, + string $content = '' + ) { if (!defined('PHP_CODESNIFFER_CBF')) { define('PHP_CODESNIFFER_CBF', false); } @@ -122,7 +127,11 @@ public function testSniff() // Check we didn't find problems where there aren't any $this->assertStringNotContainsString('nothing-to-lint', $output, 'nothing to lint, so nothing found'); $this->assertStringNotContainsString('lint-but-no-problems', $output, 'linted but no problems found'); - $this->assertStringNotContainsString('lint-with-problems_2', $output, 'that code block has no linting problems'); + $this->assertStringNotContainsString( + 'lint-with-problems_2', + $output, + 'that code block has no linting problems' + ); // Check we did find problems where there are plenty $this->assertStringContainsString('lint-with-problems_1', $output); From dc76ab95dfcbb44be1778edcd38dc9664012cb9a Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Thu, 2 Nov 2023 11:25:47 +1300 Subject: [PATCH 6/6] ENH Tidy-up code --- bin/mdphpcs | 2 +- composer.json | 4 ++-- src/CodeBlock.php | 19 ++++++++++++++++++- src/FixerReport.php | 2 +- src/Sniffer.php | 18 ++++++++++++------ tests/CodeBlockTest.php | 8 ++++---- tests/SnifferFixTest.php | 4 ++-- tests/SnifferTest.php | 4 ++-- 8 files changed, 42 insertions(+), 19 deletions(-) diff --git a/bin/mdphpcs b/bin/mdphpcs index c70ed55..e7af6ae 100755 --- a/bin/mdphpcs +++ b/bin/mdphpcs @@ -2,7 +2,7 @@ path = $path; + $this->realPath = $realPath; + $this->num = $num; + } + public function cleanUp() { $this->finalContent = $this->content ?? ''; diff --git a/src/FixerReport.php b/src/FixerReport.php index c43d8f0..e86d5ea 100644 --- a/src/FixerReport.php +++ b/src/FixerReport.php @@ -1,6 +1,6 @@ ruleset, $sniffer->config); - $dummy->num = $block['num']; - $dummy->path = $block['path']; - $dummy->realPath = $block['realpath']; + $dummy = new CodeBlock( + $sniffer->ruleset, + $sniffer->config, + $block['content'], + $block['path'], + $block['realpath'], + $block['num'] + ); $todo[] = $dummy; } @@ -107,6 +111,7 @@ public function run(string $lintLanguage, bool $fixing, bool $usingExplicitStand $sniffer->reporter->printReports(); + // These return values are directly from Runner::runPHPCS() if ($numErrors === 0) { // No errors found. return 0; @@ -148,6 +153,7 @@ private function prepareConfig(bool $usingExplicitStandard, string $lintLanguage $config->standards = [__DIR__ . '/../phpcs.default.xml']; } + // Most of these overrides are directly from Runner::runPHPCBF() if ($fixing) { // Override some of the command line settings that might break the fixes. $config->generator = null; @@ -228,7 +234,7 @@ private function sniff(Runner $sniffer, array $todo): int // Turn all sniff errors into exceptions. set_error_handler([$sniffer, 'handleErrors']); - $lastDir = ''; + $lastDir = ''; $numBlocks = count($todo); // Process each block sequentially - running sniff in parallel isn't supported diff --git a/tests/CodeBlockTest.php b/tests/CodeBlockTest.php index edb18ca..604b294 100644 --- a/tests/CodeBlockTest.php +++ b/tests/CodeBlockTest.php @@ -1,12 +1,12 @@ assertSame('This is the content', $block->getContent()); @@ -34,7 +34,7 @@ public function testGetContent() public function testCleanup() { $config = new Config(); - $block = new CodeBlock('This is the content', new Ruleset($config), $config); + $block = new CodeBlock(new Ruleset($config), $config, 'This is the content', '', '', 0); $block->cleanUp(); $this->assertSame('This is the content', $block->getContent()); diff --git a/tests/SnifferFixTest.php b/tests/SnifferFixTest.php index 211ec4a..c3429cb 100644 --- a/tests/SnifferFixTest.php +++ b/tests/SnifferFixTest.php @@ -1,12 +1,12 @@