From 57fe652ffa19bcc7015937a86528a0c418ca9a5e Mon Sep 17 00:00:00 2001 From: Emilien Escalle Date: Wed, 19 Feb 2025 15:53:02 +0100 Subject: [PATCH] ci: add rector Signed-off-by: Emilien Escalle --- .github/workflows/__shared-ci.yml | 21 +- Makefile | 12 +- README.md | 30 +-- composer.json | 7 +- rector.php | 35 ++++ src/CssLint/Cli.php | 138 +++++++------ src/CssLint/CliArgs.php | 47 +++-- src/CssLint/Linter.php | 325 +++++++++++++++--------------- src/CssLint/Properties.php | 98 +++++---- tests/TestSuite/CliTest.php | 16 +- tests/TestSuite/LinterTest.php | 50 ++--- tools/composer.json | 25 +-- 12 files changed, 425 insertions(+), 379 deletions(-) create mode 100644 rector.php diff --git a/.github/workflows/__shared-ci.yml b/.github/workflows/__shared-ci.yml index db5a95e..1cc1704 100644 --- a/.github/workflows/__shared-ci.yml +++ b/.github/workflows/__shared-ci.yml @@ -24,30 +24,30 @@ jobs: php-version: ${{ matrix.php-versions }} extensions: none,iconv,dom,curl,mbstring,tokenizer,xml,xmlwriter,simplexml,ctype coverage: pcov - + - name: ♻️ Get composer cache directory id: composer-cache shell: bash run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" - + - name: ♻️ Cache composer dependencies uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + key: ${{ runner.os }}-composer-${{ matrix.php-versions }}-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- - + - name: ⚙️ Install dependencies shell: bash run: | composer install --no-progress --prefer-dist --optimize-autoloader composer --working-dir=tools install --no-progress --prefer-dist --optimize-autoloader - + - name: ♻️ Tools cache uses: actions/cache@v4 with: path: tools/cache - key: ${{ runner.os }}-tools-${{ github.sha }} + key: ${{ runner.os }}-tools-${{ matrix.php-versions }}-${{ github.sha }} restore-keys: | ${{ runner.os }}-tools- @@ -55,9 +55,14 @@ jobs: if: matrix.stable run: composer php-cs-fixer -- --format=checkstyle | tools/vendor/bin/cs2pr + - name: 🔬 Rector + id: rector + if: matrix.stable + run: composer rector + - name: 🔬 Static analysis if: matrix.stable - run: composer stan -- --error-format=checkstyle | tools/vendor/bin/cs2pr + run: composer phpstan -- --error-format=github - name: ♻️ Tests cache uses: actions/cache@v4 @@ -66,7 +71,7 @@ jobs: key: ${{ runner.os }}-tests-${{ github.sha }} restore-keys: | ${{ runner.os }}-tests- - + - name: 🧪 Test run: composer test:ci diff --git a/Makefile b/Makefile index 218018b..a5655a5 100644 --- a/Makefile +++ b/Makefile @@ -34,10 +34,16 @@ lint: ## Execute lint for given PHP version @$(call run-php,composer php-cs-fixer $(filter-out $@,$(MAKECMDGOALS))) lint-fix: ## Execute lint fixing for given PHP version - @$(call run-php,composer php-cs-fixer:fix $(filter-out $@,$(MAKECMDGOALS))) + @$(call run-php,composer php-cs-fixer:fix $(filter-out $@,$(MAKECMDGOALS))) -stan: ## Execute PHPStan for given PHP version - @$(call run-php,composer stan $(filter-out $@,$(MAKECMDGOALS))) +rector: ## Execute rector for given PHP version + @$(call run-php,composer rector $(filter-out $@,$(MAKECMDGOALS))) + +rector-fix: ## Execute rector fixing for given PHP version + @$(call run-php,composer rector:fix $(filter-out $@,$(MAKECMDGOALS))) + +phpstan: ## Execute PHPStan for given PHP version + @$(call run-php,composer phpstan $(filter-out $@,$(MAKECMDGOALS))) ci: ## Execute CI scripts for given PHP version @$(call run-php,composer ci $(filter-out $@,$(MAKECMDGOALS))) diff --git a/README.md b/README.md index 81100b4..c8fecb6 100644 --- a/README.md +++ b/README.md @@ -44,32 +44,4 @@ 2. [Usage](https://neilime.github.io/php-css-lint/usage) 3. [Code Coverage](https://codecov.io/gh/neilime/php-css-lint) 4. [PHP Doc](https://neilime.github.io/php-css-lint/phpdoc) - -# Development - -## Setup - -`PHP_VERSION` is the version of php to use during the development. Example: `8.2` - -```sh -make build-php PHP_VERSION -make install PHP_VERSION -``` - -## Running tests - -```sh -make test PHP_VERSION -``` - -## Fix code linting - -```sh -make lint-fix PHP_VERSION -``` - -## Running CI scripts - -```sh -make ci PHP_VERSION -``` +5. [Development](https://neilime.github.io/php-css-lint/development) diff --git a/composer.json b/composer.json index ce0efd9..7384c7b 100644 --- a/composer.json +++ b/composer.json @@ -49,10 +49,13 @@ "test:ci": "@test -d pcov.enabled=1 -d max_execution_time=0 --coverage-text --coverage-clover ./build/logs/clover.xml --coverage-html ./build/coverage/", "php-cs-fixer": "@php-cs-fixer:fix --dry-run", "php-cs-fixer:fix": "tools/vendor/bin/php-cs-fixer fix --show-progress=dots --diff --config=.php-cs-fixer.dist.php", - "stan": "tools/vendor/bin/phpstan analyse --level 5 src", + "rector": "@rector:fix --dry-run", + "rector:fix": "tools/vendor/bin/rector process src", + "phpstan": "tools/vendor/bin/phpstan analyse --level max src", "ci": [ "@php-cs-fixer", - "@stan", + "@rector", + "@phpstan", "@test:ci" ] }, diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..154ffb4 --- /dev/null +++ b/rector.php @@ -0,0 +1,35 @@ +withPaths([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->withPhpSets() + ->withAttributesSets( + all: true + ) + ->withPreparedSets( + deadCode: true, + codeQuality: true, + codingStyle: true, + typeDeclarations: true, + privatization: true, + naming: true, + instanceOf: true, + earlyReturn: true, + strictBooleans: true, + carbon: true, + rectorPreset: true, + phpunitCodeQuality: true, + phpunit: true, + ) + ->withCache( + cacheClass: FileCacheStorage::class, + cacheDirectory: __DIR__ . '/tools/cache/rector' + );; diff --git a/src/CssLint/Cli.php b/src/CssLint/Cli.php index f967137..2374c4b 100644 --- a/src/CssLint/Cli.php +++ b/src/CssLint/Cli.php @@ -1,77 +1,92 @@ parseArguments($aArguments); - if (!$oCliArgs->filePathOrCssString) { + $cliArgs = $this->parseArguments($arguments); + if ($cliArgs->filePathOrCssString === null || $cliArgs->filePathOrCssString === '' || $cliArgs->filePathOrCssString === '0') { $this->printUsage(); - return self::$RETURN_CODE_SUCCESS; + return self::RETURN_CODE_SUCCESS; } - $oProperties = new \CssLint\Properties(); - if ($oCliArgs->options) { - $aOptions = json_decode($oCliArgs->options, true); + $properties = new \CssLint\Properties(); + if ($cliArgs->options !== null && $cliArgs->options !== '' && $cliArgs->options !== '0') { + $options = json_decode($cliArgs->options, true); - if (json_last_error()) { - $sErrorMessage = json_last_error_msg(); - $this->printError('Unable to parse option argument: ' . $sErrorMessage); - return self::$RETURN_CODE_ERROR; + if (json_last_error() !== 0) { + $errorMessage = json_last_error_msg(); + $this->printError('Unable to parse option argument: ' . $errorMessage); + return self::RETURN_CODE_ERROR; } - if (!$aOptions) { + if (!$options) { $this->printError('Unable to parse empty option argument'); - return self::$RETURN_CODE_ERROR; + return self::RETURN_CODE_ERROR; } - $oProperties->setOptions($aOptions); + + if (!is_array($options)) { + $this->printError('Unable to parse option argument: must be a json object'); + return self::RETURN_CODE_ERROR; + } + + $properties->setOptions($options); } - $oCssLinter = new \CssLint\Linter($oProperties); + $cssLinter = new \CssLint\Linter($properties); - $sFilePathOrCssString = $oCliArgs->filePathOrCssString; - if (!file_exists($sFilePathOrCssString)) { - return $this->lintString($oCssLinter, $sFilePathOrCssString); + $filePathOrCssString = $cliArgs->filePathOrCssString; + if (!file_exists($filePathOrCssString)) { + return $this->lintString($cssLinter, $filePathOrCssString); } - $sFilePath = $sFilePathOrCssString; - if (!is_readable($sFilePath)) { - $this->printError('File "' . $sFilePath . '" is not readable'); - return self::$RETURN_CODE_ERROR; + $filePath = $filePathOrCssString; + if (!is_readable($filePath)) { + $this->printError('File "' . $filePath . '" is not readable'); + return self::RETURN_CODE_ERROR; } - return $this->lintFile($oCssLinter, $sFilePath); + return $this->lintFile($cssLinter, $filePath); } /** * Retrieve the parsed Cli arguments from given arguments array + * @param string[] $arguments arguments to be parsed (@see $_SERVER['argv']) * @return \CssLint\CliArgs an instance of Cli arguments object containing parsed arguments */ - private function parseArguments(array $aArguments): \CssLint\CliArgs + private function parseArguments(array $arguments): \CssLint\CliArgs { - return new \CssLint\CliArgs($aArguments); + return new \CssLint\CliArgs($arguments); } /** * Display usage of the cli */ - private function printUsage() + private function printUsage(): void { $this->printLine('Usage:' . PHP_EOL . '------' . PHP_EOL . PHP_EOL . - ' ' . self::$SCRIPT_NAME . ' [--options=\'{ }\'] css_file_or_string_to_lint' . PHP_EOL . + ' ' . self::SCRIPT_NAME . " [--options='{ }'] css_file_or_string_to_lint" . PHP_EOL . PHP_EOL . 'Arguments:' . PHP_EOL . '----------' . PHP_EOL . @@ -95,83 +110,84 @@ private function printUsage() '---------' . PHP_EOL . PHP_EOL . ' Lint a CSS file:' . PHP_EOL . - ' ' . self::$SCRIPT_NAME . ' ./path/to/css_file_path_to_lint.css' . PHP_EOL . PHP_EOL . + ' ' . self::SCRIPT_NAME . ' ./path/to/css_file_path_to_lint.css' . PHP_EOL . PHP_EOL . ' Lint a CSS string:' . PHP_EOL . - ' ' . self::$SCRIPT_NAME . ' ".test { color: red; }"' . PHP_EOL . PHP_EOL . + ' ' . self::SCRIPT_NAME . ' ".test { color: red; }"' . PHP_EOL . PHP_EOL . ' Lint with only tabulation as indentation:' . PHP_EOL . - ' ' . self::$SCRIPT_NAME . + ' ' . self::SCRIPT_NAME . ' --options=\'{ "allowedIndentationChars": ["\t"] }\' ".test { color: red; }"' . PHP_EOL . PHP_EOL . PHP_EOL); } /** * Performs lint on a given file path - * @param \CssLint\Linter $oCssLinter the instance of the linter - * @param string $sFilePath the path of the file to be linted + * @param \CssLint\Linter $cssLinter the instance of the linter + * @param string $filePath the path of the file to be linted * @return int the return code related to the execution of the linter */ - private function lintFile(\CssLint\Linter $oCssLinter, string $sFilePath): int + private function lintFile(\CssLint\Linter $cssLinter, string $filePath): int { - $this->printLine('# Lint CSS file "' . $sFilePath . '"...'); + $this->printLine('# Lint CSS file "' . $filePath . '"...'); - if ($oCssLinter->lintFile($sFilePath)) { - $this->printLine("\033[32m => CSS file \"" . $sFilePath . "\" is valid\033[0m" . PHP_EOL); - return self::$RETURN_CODE_SUCCESS; + if ($cssLinter->lintFile($filePath)) { + $this->printLine("\033[32m => CSS file \"" . $filePath . "\" is valid\033[0m" . PHP_EOL); + return self::RETURN_CODE_SUCCESS; } - $this->printLine("\033[31m => CSS file \"" . $sFilePath . "\" is not valid:\033[0m" . PHP_EOL); - $this->displayLinterErrors($oCssLinter->getErrors()); - return self::$RETURN_CODE_ERROR; + $this->printLine("\033[31m => CSS file \"" . $filePath . "\" is not valid:\033[0m" . PHP_EOL); + $this->displayLinterErrors($cssLinter->getErrors()); + return self::RETURN_CODE_ERROR; } /** * Performs lint on a given string - * @param \CssLint\Linter $oCssLinter the instance of the linter - * @param string $sString the CSS string to be linted + * @param \CssLint\Linter $cssLinter the instance of the linter + * @param string $stringValue the CSS string to be linted * @return int the return code related to the execution of the linter */ - private function lintString(\CssLint\Linter $oCssLinter, string $sString): int + private function lintString(\CssLint\Linter $cssLinter, string $stringValue): int { $this->printLine('# Lint CSS string...'); - if ($oCssLinter->lintString($sString)) { + if ($cssLinter->lintString($stringValue)) { $this->printLine("\033[32m => CSS string is valid\033[0m" . PHP_EOL); - return self::$RETURN_CODE_SUCCESS; + return self::RETURN_CODE_SUCCESS; } $this->printLine("\033[31m => CSS string is not valid:\033[0m" . PHP_EOL); - $this->displayLinterErrors($oCssLinter->getErrors()); - return self::$RETURN_CODE_ERROR; + $this->displayLinterErrors($cssLinter->getErrors()); + return self::RETURN_CODE_ERROR; } /** * Display an error message - * @param string $sError the message to be displayed + * @param string $error the message to be displayed */ - private function printError(string $sError) + private function printError(string $error): void { - $this->printLine("\033[31m/!\ Error: " . $sError . "\033[0m" . PHP_EOL); + $this->printLine("\033[31m/!\ Error: " . $error . "\033[0m" . PHP_EOL); } /** * Display the errors returned by the linter - * @param array $aErrors the generated errors to be displayed + * @param Errors $errors the generated errors to be displayed */ - private function displayLinterErrors(array $aErrors) + private function displayLinterErrors(array $errors): void { - foreach ($aErrors as $sError) { - $this->printLine("\033[31m - " . $sError . "\033[0m"); + foreach ($errors as $error) { + $this->printLine("\033[31m - " . $error . "\033[0m"); } + $this->printLine(""); } /** * Display the given message in a new line - * @param string $sMessage the message to be displayed + * @param string $message the message to be displayed */ - private function printLine(string $sMessage) + private function printLine(string $message): void { - echo $sMessage . PHP_EOL; + echo $message . PHP_EOL; } } diff --git a/src/CssLint/CliArgs.php b/src/CssLint/CliArgs.php index fcd5884..6eba7ed 100644 --- a/src/CssLint/CliArgs.php +++ b/src/CssLint/CliArgs.php @@ -1,58 +1,67 @@ + */ class CliArgs { public ?string $filePathOrCssString = null; + public ?string $options = null; /** * Constructor - * @param array $aArguments arguments to be parsed (@see $_SERVER['argv']) + * @param Arguments $arguments arguments to be parsed (@see $_SERVER['argv']) * Accepts "-o", "--options" '{}' * Accepts a string as last argument, a file path or a string containing CSS */ - public function __construct(array $aArguments) + public function __construct(array $arguments) { - if (empty($aArguments) || count($aArguments) === 1) { + if ($arguments === [] || count($arguments) === 1) { return; } - array_shift($aArguments); + array_shift($arguments); - $this->filePathOrCssString = array_pop($aArguments); + $this->filePathOrCssString = array_pop($arguments); - if ($aArguments) { - $aParsedArguments = $this->extractArguments($aArguments); + if ($arguments !== []) { + $parsedArguments = $this->parseArguments($arguments); - if (!empty($aParsedArguments['options'])) { - $this->options = $aParsedArguments['options']; + if (!empty($parsedArguments['options'])) { + $this->options = $parsedArguments['options']; } } } /** - * @param array $aArguments array of arguments to be parsed (@see $_SERVER['argv']) - * @return array an associative array of key=>value arguments + * @param Arguments $arguments array of arguments to be parsed (@see $_SERVER['argv']) + * @return ParsedArguments an associative array of key=>value arguments */ - private function extractArguments(array $aArguments): array + private function parseArguments(array $arguments): array { $aParsedArguments = []; - foreach ($aArguments as $sArgument) { + foreach ($arguments as $argument) { // --foo --bar=baz - if (substr($sArgument, 0, 2) == '--') { - $sEqualPosition = strpos($sArgument, '='); + if (str_starts_with((string) $argument, '--')) { + $equalPosition = strpos((string) $argument, '='); // --bar=baz - if ($sEqualPosition !== false) { - $sKey = substr($sArgument, 2, $sEqualPosition - 2); - $sValue = substr($sArgument, $sEqualPosition + 1); - $aParsedArguments[$sKey] = $sValue; + if ($equalPosition !== false) { + $key = substr((string) $argument, 2, $equalPosition - 2); + $value = substr((string) $argument, $equalPosition + 1); + $aParsedArguments[$key] = $value; } } } + return $aParsedArguments; } } diff --git a/src/CssLint/Linter.php b/src/CssLint/Linter.php index adeb323..3189db1 100644 --- a/src/CssLint/Linter.php +++ b/src/CssLint/Linter.php @@ -1,13 +1,25 @@ + * @phpstan-type ContextEntry string|null + * @phpstan-type Context ContextEntry|ContextEntry[] + */ class Linter { public const CONTEXT_SELECTOR = 'selector'; + public const CONTEXT_SELECTOR_CONTENT = 'selector content'; + public const CONTEXT_NESTED_SELECTOR_CONTENT = 'nested selector content'; + public const CONTEXT_PROPERTY_NAME = 'property name'; + public const CONTEXT_PROPERTY_CONTENT = 'property content'; /** @@ -18,7 +30,7 @@ class Linter /** * Errors occurred during the lint process - * @var array|null + * @var Errors */ protected $errors = []; @@ -70,32 +82,32 @@ class Linter */ public function __construct(\CssLint\Properties $oProperties = null) { - if ($oProperties) { + if ($oProperties instanceof \CssLint\Properties) { $this->setCssLintProperties($oProperties); } } /** * Performs lint on a given string - * @param string $sString * @return boolean : true if the string is a valid css string, false else */ - public function lintString(string $sString): bool + public function lintString(string $stringValue): bool { $this->initLint(); $iIterator = 0; - while (isset($sString[$iIterator])) { - if ($this->lintChar($sString[$iIterator]) === false) { + while (isset($stringValue[$iIterator])) { + if ($this->lintChar($stringValue[$iIterator]) === false) { return false; } - $iIterator++; + + ++$iIterator; } if (!$this->assertContext(null)) { $this->addError('Unterminated "' . $this->context . '"'); } - return !$this->getErrors(); + return in_array($this->getErrors(), [null, []], true); } /** @@ -128,8 +140,8 @@ public function lintFile(string $sFilePath): bool $this->initLint(); - while (($sChar = fgetc($rFileHandle)) !== false) { - if ($this->lintChar($sChar) === false) { + while (($charValue = fgetc($rFileHandle)) !== false) { + if ($this->lintChar($charValue) === false) { fclose($rFileHandle); return false; } @@ -138,20 +150,20 @@ public function lintFile(string $sFilePath): bool if (!feof($rFileHandle)) { throw new \RuntimeException('An error occurred while reading file "' . $sFilePath . '"'); } + fclose($rFileHandle); if (!$this->assertContext(null)) { $this->addError('Unterminated "' . $this->context . '"'); } - return !$this->getErrors(); + return in_array($this->getErrors(), [null, []], true); } /** * Initialize linter, reset all process properties - * @return \CssLint\Linter */ - protected function initLint() + protected function initLint(): static { $this ->resetPreviousChar() @@ -165,74 +177,78 @@ protected function initLint() /** * Performs lint on a given char - * @param string $sChar * @return boolean : true if the process should continue, else false */ - protected function lintChar(string $sChar): ?bool + protected function lintChar(string $charValue): ?bool { $this->incrementCharNumber(); - if ($this->isEndOfLine($sChar)) { - $this->setPreviousChar($sChar); - if ($sChar === "\n") { + if ($this->isEndOfLine($charValue)) { + $this->setPreviousChar($charValue); + if ($charValue === "\n") { $this->incrementLineNumber()->resetCharNumber(); } + return true; } - if (is_bool($bLintCommentChar = $this->lintCommentChar($sChar))) { - $this->setPreviousChar($sChar); + if (is_bool($bLintCommentChar = $this->lintCommentChar($charValue))) { + $this->setPreviousChar($charValue); return $bLintCommentChar; } - if (is_bool($bLintSelectorChar = $this->lintSelectorChar($sChar))) { - $this->setPreviousChar($sChar); + if (is_bool($bLintSelectorChar = $this->lintSelectorChar($charValue))) { + $this->setPreviousChar($charValue); return $bLintSelectorChar; } - if (is_bool($bLintSelectorContentChar = $this->lintSelectorContentChar($sChar))) { - $this->setPreviousChar($sChar); + if (is_bool($bLintSelectorContentChar = $this->lintSelectorContentChar($charValue))) { + $this->setPreviousChar($charValue); return $bLintSelectorContentChar; } - if (is_bool($bLintPropertyNameChar = $this->lintPropertyNameChar($sChar))) { - $this->setPreviousChar($sChar); + if (is_bool($bLintPropertyNameChar = $this->lintPropertyNameChar($charValue))) { + $this->setPreviousChar($charValue); return $bLintPropertyNameChar; } - if (is_bool($bLintPropertyContentChar = $this->lintPropertyContentChar($sChar))) { - $this->setPreviousChar($sChar); + if (is_bool($bLintPropertyContentChar = $this->lintPropertyContentChar($charValue))) { + $this->setPreviousChar($charValue); return $bLintPropertyContentChar; } - if (is_bool($bLintNestedSelectorChar = $this->lintNestedSelectorChar($sChar))) { - $this->setPreviousChar($sChar); + if (is_bool($bLintNestedSelectorChar = $this->lintNestedSelectorChar($charValue))) { + $this->setPreviousChar($charValue); return $bLintNestedSelectorChar; } - $this->addError('Unexpected char ' . json_encode($sChar)); - $this->setPreviousChar($sChar); + $this->addError('Unexpected char ' . json_encode($charValue)); + $this->setPreviousChar($charValue); return false; } /** * Performs lint for a given char, check comment part - * @param string $sChar * @return boolean|null : true if the process should continue, else false, null if this char is not about comment */ - protected function lintCommentChar(string $sChar): ?bool + protected function lintCommentChar(string $charValue): ?bool { // Manage comment context if ($this->isComment()) { - if ($sChar === '/' && $this->assertPreviousChar('*')) { + if ($charValue === '/' && $this->assertPreviousChar('*')) { $this->setComment(false); } - $this->setPreviousChar($sChar); + + $this->setPreviousChar($charValue); return true; } + // First char for a comment - if ($sChar === '/') { + if ($charValue === '/') { return true; - } elseif ($sChar === '*' && $this->assertPreviousChar('/')) { + } + + // First char for a comment + if ($charValue === '*' && $this->assertPreviousChar('/')) { // End of comment $this->setComment(true); return true; @@ -243,33 +259,35 @@ protected function lintCommentChar(string $sChar): ?bool /** * Performs lint for a given char, check selector part - * @param string $sChar * @return boolean|null : true if the process should continue, else false, null if this char is not about selector */ - protected function lintSelectorChar(string $sChar): ?bool + protected function lintSelectorChar(string $charValue): ?bool { // Selector must start by #.a-zA-Z if ($this->assertContext(null)) { - if ($this->getCssLintProperties()->isAllowedIndentationChar($sChar)) { + if ($this->getCssLintProperties()->isAllowedIndentationChar($charValue)) { return true; } - if (preg_match('/[@#.a-zA-Z\[\*-:]+/', $sChar)) { + if (preg_match('/[@#.a-zA-Z\[\*-:]+/', $charValue)) { $this->setContext(self::CONTEXT_SELECTOR); - $this->addContextContent($sChar); + $this->addContextContent($charValue); return true; } + return null; } + // Selector must contains if ($this->assertContext(self::CONTEXT_SELECTOR)) { // A space is valid - if ($sChar === ' ') { - $this->addContextContent($sChar); + if ($charValue === ' ') { + $this->addContextContent($charValue); return true; } + // Start of selector content - if ($sChar === '{') { + if ($charValue === '{') { // Check if selector if valid $sSelector = trim($this->getContextContent()); @@ -285,52 +303,58 @@ protected function lintSelectorChar(string $sChar): ?bool } else { $this->setContext(self::CONTEXT_SELECTOR_CONTENT); } - $this->addContextContent($sChar); + + $this->addContextContent($charValue); return true; } // There cannot have two following commas - if ($sChar === ',') { + if ($charValue === ',') { $sSelector = $this->getContextContent(); - if (!$sSelector || !preg_match('/, *$/', $sSelector)) { - $this->addContextContent($sChar); + if ($sSelector === '' || $sSelector === '0' || in_array(preg_match('/, *$/', $sSelector), [0, false], true)) { + $this->addContextContent($charValue); return true; } + $this->addError(sprintf( 'Selector token %s cannot be preceded by "%s"', - json_encode($sChar), + json_encode($charValue), $sSelector )); return false; } // Wildcard and hash - if (in_array($sChar, ['*', '#'], true)) { + if (in_array($charValue, ['*', '#'], true)) { $sSelector = $this->getContextContent(); - if (!$sSelector || preg_match('/[a-zA-Z>,\'"] *$/', $sSelector)) { - $this->addContextContent($sChar); + if ($sSelector === '' || $sSelector === '0' || preg_match('/[a-zA-Z>,\'"] *$/', $sSelector)) { + $this->addContextContent($charValue); return true; } - $this->addError('Selector token "' . $sChar . '" cannot be preceded by "' . $sSelector . '"'); + + $this->addError('Selector token "' . $charValue . '" cannot be preceded by "' . $sSelector . '"'); return true; } + // Dot - if ($sChar === '.') { + if ($charValue === '.') { $sSelector = $this->getContextContent(); - if (!$sSelector || preg_match('/(, |[a-zA-Z]).*$/', $sSelector)) { - $this->addContextContent($sChar); + if ($sSelector === '' || $sSelector === '0' || preg_match('/(, |[a-zA-Z]).*$/', $sSelector)) { + $this->addContextContent($charValue); return true; } - $this->addError('Selector token "' . $sChar . '" cannot be preceded by "' . $sSelector . '"'); + + $this->addError('Selector token "' . $charValue . '" cannot be preceded by "' . $sSelector . '"'); return true; } - if (preg_match('/^[#*.0-9a-zA-Z,:()\[\]="\'-^~_%]+/', $sChar)) { - $this->addContextContent($sChar); + + if (preg_match('/^[#*.0-9a-zA-Z,:()\[\]="\'-^~_%]+/', $charValue)) { + $this->addContextContent($charValue); return true; } - $this->addError('Unexpected selector token "' . $sChar . '"'); + $this->addError('Unexpected selector token "' . $charValue . '"'); return true; } @@ -339,35 +363,31 @@ protected function lintSelectorChar(string $sChar): ?bool /** * Performs lint for a given char, check selector content part - * @param string $sChar * @return bool|null : true if the process should continue, else false, null if this char is not a selector content */ - protected function lintSelectorContentChar(string $sChar): ?bool + protected function lintSelectorContentChar(string $charValue): ?bool { if (!$this->assertContext(self::CONTEXT_SELECTOR_CONTENT)) { return null; } - $sContextContent = $this->getContextContent(); + $contextContent = $this->getContextContent(); if ( - (!$sContextContent || $sContextContent === '{') && - $this->getCssLintProperties()->isAllowedIndentationChar($sChar) + ($contextContent === '' || $contextContent === '0' || $contextContent === '{') && + $this->getCssLintProperties()->isAllowedIndentationChar($charValue) ) { return true; } - if ($sChar === '}') { - if ($this->isNestedSelector()) { - $this->resetContext(); - } else { - $this->resetContext(); - } + if ($charValue === '}') { + $this->resetContext(); + return true; } - if (preg_match('/[-a-zA-Z]+/', $sChar)) { + if (preg_match('/[-a-zA-Z]+/', $charValue)) { $this->setContext(self::CONTEXT_PROPERTY_NAME); - $this->addContextContent($sChar); + $this->addContextContent($charValue); return true; } @@ -376,83 +396,85 @@ protected function lintSelectorContentChar(string $sChar): ?bool /** * Performs lint for a given char, check property name part - * @param string $sChar * @return bool|null : true if the process should continue, else false, null if this char is not a property name */ - protected function lintPropertyNameChar(string $sChar): ?bool + protected function lintPropertyNameChar(string $charValue): ?bool { if (!$this->assertContext(self::CONTEXT_PROPERTY_NAME)) { return null; } - if ($sChar === ':') { - $sPropertyName = trim($this->getContextContent()); + if ($charValue === ':') { + $propertyName = trim($this->getContextContent()); // Ignore CSS variables (names starting with --) - if (substr($sPropertyName, 0, 2) === '--') { + if (str_starts_with($propertyName, '--')) { $this->setContext(self::CONTEXT_PROPERTY_CONTENT); return true; } // Check if property name exists - if (!$this->getCssLintProperties()->propertyExists($sPropertyName)) { - $this->addError('Unknown CSS property "' . $sPropertyName . '"'); + if (!$this->getCssLintProperties()->propertyExists($propertyName)) { + $this->addError('Unknown CSS property "' . $propertyName . '"'); } + $this->setContext(self::CONTEXT_PROPERTY_CONTENT); return true; } - $this->addContextContent($sChar); + $this->addContextContent($charValue); - if ($sChar === ' ') { + if ($charValue === ' ') { return true; } - if (!preg_match('/[-a-zA-Z0-9]+/', $sChar)) { - $this->addError('Unexpected property name token "' . $sChar . '"'); + if (in_array(preg_match('/[-a-zA-Z0-9]+/', $charValue), [0, false], true)) { + $this->addError('Unexpected property name token "' . $charValue . '"'); } + return true; } /** * Performs lint for a given char, check property content part - * @param string $sChar * @return bool|null : true if the process should continue, else false, null if this char is not a property content */ - protected function lintPropertyContentChar(string $sChar): ?bool + protected function lintPropertyContentChar(string $charValue): ?bool { if (!$this->assertContext(self::CONTEXT_PROPERTY_CONTENT)) { return null; } - $this->addContextContent($sChar); + $this->addContextContent($charValue); // End of the property content - if ($sChar === ';') { + if ($charValue === ';') { // Check if the ";" is not quoted - $sContextContent = $this->getContextContent(); - if (!(substr_count($sContextContent, '"') & 1) && !(substr_count($sContextContent, '\'') & 1)) { + $contextContent = $this->getContextContent(); + if ((substr_count($contextContent, '"') & 1) === 0 && (substr_count($contextContent, "'") & 1) === 0) { $this->setContext(self::CONTEXT_SELECTOR_CONTENT); } - if (trim($sContextContent)) { + + if (trim($contextContent) !== '' && trim($contextContent) !== '0') { return true; } + $this->addError('Property cannot be empty'); return true; } + // No property content validation return true; } /** * Performs lint for a given char, check nested selector part - * @param string $sChar * @return bool|null : true if the process should continue, else false, null if this char is not a nested selector */ - protected function lintNestedSelectorChar(string $sChar): ?bool + protected function lintNestedSelectorChar(string $charValue): ?bool { // End of nested selector - if ($this->isNestedSelector() && $this->assertContext(null) && $sChar === '}') { + if ($this->isNestedSelector() && $this->assertContext(null) && $charValue === '}') { $this->setNestedSelector(false); return true; } @@ -462,17 +484,15 @@ protected function lintNestedSelectorChar(string $sChar): ?bool /** * Check if a given char is an end of line token - * @param string $sChar * @return boolean : true if the char is an end of line token, else false */ - protected function isEndOfLine(string $sChar): bool + protected function isEndOfLine(string $charValue): bool { - return $sChar === "\r" || $sChar === "\n"; + return $charValue === "\r" || $charValue === "\n"; } /** * Return the current char number - * @return int */ protected function getCharNumber(): int { @@ -481,19 +501,16 @@ protected function getCharNumber(): int /** * Assert that previous char is the same as given - * @param string $sChar - * @return boolean */ - protected function assertPreviousChar(string $sChar): bool + protected function assertPreviousChar(string $charValue): bool { - return $this->previousChar === $sChar; + return $this->previousChar === $charValue; } /** * Reset previous char property - * @return \CssLint\Linter */ - protected function resetPreviousChar(): Linter + protected function resetPreviousChar(): self { $this->previousChar = null; return $this; @@ -501,18 +518,15 @@ protected function resetPreviousChar(): Linter /** * Set new previous char - * @param string $sChar - * @return \CssLint\Linter */ - protected function setPreviousChar(string $sChar): Linter + protected function setPreviousChar(string $charValue): self { - $this->previousChar = $sChar; + $this->previousChar = $charValue; return $this; } /** * Return the current line number - * @return int */ protected function getLineNumber(): int { @@ -521,19 +535,17 @@ protected function getLineNumber(): int /** * Add 1 to the current line number - * @return \CssLint\Linter */ - protected function incrementLineNumber(): Linter + protected function incrementLineNumber(): self { - $this->lineNumber++; + ++$this->lineNumber; return $this; } /** * Reset current line number property - * @return \CssLint\Linter */ - protected function resetLineNumber(): Linter + protected function resetLineNumber(): self { $this->lineNumber = 0; return $this; @@ -541,9 +553,8 @@ protected function resetLineNumber(): Linter /** * Reset current char number property - * @return \CssLint\Linter */ - protected function resetCharNumber(): Linter + protected function resetCharNumber(): self { $this->charNumber = 0; return $this; @@ -551,55 +562,52 @@ protected function resetCharNumber(): Linter /** * Add 1 to the current char number - * @return \CssLint\Linter */ - protected function incrementCharNumber(): Linter + protected function incrementCharNumber(): self { - $this->charNumber++; + ++$this->charNumber; return $this; } /** * Assert that current context is the same as given - * @param string|array|null $sContext - * @return boolean + * @param Context $context */ - protected function assertContext($sContext): bool + protected function assertContext($context): bool { - if (is_array($sContext)) { - foreach ($sContext as $sTmpContext) { - if ($this->assertContext($sTmpContext)) { + if (is_array($context)) { + foreach ($context as $tmpContext) { + if ($this->assertContext($tmpContext)) { return true; } } + return false; } - return $this->context === $sContext; + + return $this->context === $context; } /** * Reset context property - * @return \CssLint\Linter */ - protected function resetContext(): Linter + protected function resetContext(): self { return $this->setContext(null); } /** * Set new context - * @param string|null $sContext - * @return \CssLint\Linter + * @param string|null $context */ - protected function setContext($sContext): Linter + protected function setContext($context): self { - $this->context = $sContext; + $this->context = $context; return $this->resetContextContent(); } /** * Return context content - * @return string */ protected function getContextContent(): string { @@ -608,9 +616,8 @@ protected function getContextContent(): string /** * Reset context content property - * @return \CssLint\Linter */ - protected function resetContextContent(): Linter + protected function resetContextContent(): self { $this->contextContent = ''; return $this; @@ -618,48 +625,42 @@ protected function resetContextContent(): Linter /** * Append new value to context content - * @param string $sContextContent - * @return \CssLint\Linter */ - protected function addContextContent($sContextContent): Linter + protected function addContextContent(string $contextContent): self { - $this->contextContent .= $sContextContent; + $this->contextContent .= $contextContent; return $this; } /** * Add a new error message to the errors property, it adds extra infos to the given error message - * @param string $sError - * @return \CssLint\Linter */ - protected function addError($sError): Linter + protected function addError(string $error): self { - $this->errors[] = $sError . ' (line: ' . $this->getLineNumber() . ', char: ' . $this->getCharNumber() . ')'; + $this->errors[] = $error . ' (line: ' . $this->getLineNumber() . ', char: ' . $this->getCharNumber() . ')'; return $this; } /** * Return the errors occurred during the lint process - * @return array + * @return Errors */ - public function getErrors(): ?array + public function getErrors(): array { return $this->errors; } /** * Reset the errors property - * @return \CssLint\Linter */ protected function resetErrors(): Linter { - $this->errors = null; + $this->errors = []; return $this; } /** * Tells if the linter is parsing a nested selector - * @return boolean */ protected function isNestedSelector(): bool { @@ -668,16 +669,14 @@ protected function isNestedSelector(): bool /** * Set the nested selector flag - * @param boolean $bNestedSelector */ - protected function setNestedSelector(bool $bNestedSelector): void + protected function setNestedSelector(bool $nestedSelector): void { - $this->nestedSelector = $bNestedSelector; + $this->nestedSelector = $nestedSelector; } /** * Tells if the linter is parsing a comment - * @return boolean */ protected function isComment(): bool { @@ -686,30 +685,30 @@ protected function isComment(): bool /** * Set the comment flag - * @param boolean $bComment */ - protected function setComment(bool $bComment): void + protected function setComment(bool $comment): void { - $this->comment = $bComment; + $this->comment = $comment; } /** * Return an instance of the "\CssLint\Properties" helper, initialize a new one if not define already - * @return \CssLint\Properties */ public function getCssLintProperties(): \CssLint\Properties { - if (!$this->cssLintProperties) { - $this->setCssLintProperties(new \CssLint\Properties()); + if ($this->cssLintProperties) { + return $this->cssLintProperties; } - return $this->cssLintProperties; + + return $this->cssLintProperties = new \CssLint\Properties(); } /** * Set an instance of the "\CssLint\Properties" helper */ - public function setCssLintProperties(\CssLint\Properties $oCssLintProperties): void + public function setCssLintProperties(\CssLint\Properties $cssLintProperties): self { - $this->cssLintProperties = $oCssLintProperties; + $this->cssLintProperties = $cssLintProperties; + return $this; } } diff --git a/src/CssLint/Properties.php b/src/CssLint/Properties.php index 2dc21a1..09435e4 100644 --- a/src/CssLint/Properties.php +++ b/src/CssLint/Properties.php @@ -1,12 +1,28 @@ + * @phpstan-type Constructors array + * @phpstan-type Standards array + * @phpstan-type NonStandards array + * + */ class Properties { /** * List of existing constructor prefix - * @var array + * @var Constructors */ protected $constructors = [ 'ms' => true, @@ -18,7 +34,7 @@ class Properties /** * List of existing css properties * https://www.w3.org/Style/CSS/all-properties.en.html - * @var array + * @var Standards */ protected $standards = [ 'align-content' => true, @@ -451,7 +467,7 @@ class Properties /** * List of non standards properties - * @var array + * @var NonStandards */ protected $nonStandards = [ 'font-smoothing' => true, @@ -464,68 +480,70 @@ class Properties /** * List of allowed indentation chars - * @var array + * @var AllowedIndentationChars */ protected $allowedIndentationChars = [' ']; /** - * Override default properties + * @param PropertiesOptions $options Override default properties * "allowedIndentationChars" => [" "] or ["\t"]: will override current property * "constructors": ["property" => bool]: will merge with current property * "standards": ["property" => bool]: will merge with current property * "nonStandards": ["property" => bool]: will merge with current property */ - public function setOptions(array $aOptions = []) + public function setOptions(array $options = []): void { - if (isset($aOptions['allowedIndentationChars'])) { - $this->setAllowedIndentationChars($aOptions['allowedIndentationChars']); + if (isset($options['allowedIndentationChars'])) { + $this->setAllowedIndentationChars($options['allowedIndentationChars']); } - if (isset($aOptions['constructors'])) { - $this->mergeConstructors($aOptions['constructors']); + if (isset($options['constructors'])) { + $this->mergeConstructors($options['constructors']); } - if (isset($aOptions['standards'])) { - $this->mergeStandards($aOptions['standards']); + if (isset($options['standards'])) { + $this->mergeStandards($options['standards']); } - if (isset($aOptions['nonStandards'])) { - $this->mergeNonStandards($aOptions['nonStandards']); + if (isset($options['nonStandards'])) { + $this->mergeNonStandards($options['nonStandards']); } } /** * Checks that the given CSS property is an existing one - * @param string $sProperty the property to check + * @param string $property the property to check * @return boolean true if the property exists, else returns false */ - public function propertyExists(string $sProperty): bool + public function propertyExists(string $property): bool { - if (!empty($this->standards[$sProperty])) { + if (!empty($this->standards[$property])) { return true; } - $aAllowedConstrutors = array_keys(array_filter($this->constructors)); - $sPropertyWithoutConstructor = preg_replace( - '/^(-(' . join('|', $aAllowedConstrutors) . ')-)/', + $allowedConstrutors = array_keys(array_filter($this->constructors)); + $propertyWithoutConstructor = preg_replace( + '/^(-(' . implode('|', $allowedConstrutors) . ')-)/', '', - $sProperty + $property ); - if ($sPropertyWithoutConstructor !== $sProperty) { - if (!empty($this->standards[$sPropertyWithoutConstructor])) { + if ($propertyWithoutConstructor !== $property) { + if (!empty($this->standards[$propertyWithoutConstructor])) { return true; } - if (!empty($this->nonStandards[$sPropertyWithoutConstructor])) { + + if (!empty($this->nonStandards[$propertyWithoutConstructor])) { return true; } } + return false; } /** * Retrieve indentation chars allowed by the linter - * @return array a list of allowed indentation chars + * @return AllowedIndentationChars a list of allowed indentation chars */ public function getAllowedIndentationChars(): array { @@ -534,47 +552,47 @@ public function getAllowedIndentationChars(): array /** * Define the indentation chars allowed by the linter - * @param array $aAllowedIndentationChars a list of allowed indentation chars + * @param AllowedIndentationChars $allowedIndentationChars a list of allowed indentation chars */ - public function setAllowedIndentationChars(array $aAllowedIndentationChars) + public function setAllowedIndentationChars(array $allowedIndentationChars): void { - $this->allowedIndentationChars = $aAllowedIndentationChars; + $this->allowedIndentationChars = $allowedIndentationChars; } /** * Check if the given char is allowed as an indentation char - * @param string $sChar the character to be checked + * @param string $charValue the character to be checked * @return bool according to whether the character is allowed or not */ - public function isAllowedIndentationChar(string $sChar): bool + public function isAllowedIndentationChar(string $charValue): bool { - return in_array($sChar, $this->allowedIndentationChars, true); + return in_array($charValue, $this->allowedIndentationChars, true); } /** * Merge the given constructors properties with the current ones - * @param array $aConstructors the constructors properties to be merged + * @param Constructors $constructors the constructors properties to be merged */ - public function mergeConstructors(array $aConstructors) + public function mergeConstructors(array $constructors): void { - $this->constructors = array_merge($this->constructors, $aConstructors); + $this->constructors = array_merge($this->constructors, $constructors); } /** * Merge the given standards properties with the current ones - * @param array $aStandards the standards properties to be merged + * @param Standards $standards the standards properties to be merged */ - public function mergeStandards(array $aStandards) + public function mergeStandards(array $standards): void { - $this->standards = array_merge($this->standards, $aStandards); + $this->standards = array_merge($this->standards, $standards); } /** * Merge the given non standards properties with the current ones - * @param array $aNonStandards non the standards properties to be merged + * @param NonStandards $nonStandards non the standards properties to be merged */ - public function mergeNonStandards(array $aNonStandards) + public function mergeNonStandards(array $nonStandards): void { - $this->nonStandards = array_merge($this->nonStandards, $aNonStandards); + $this->nonStandards = array_merge($this->nonStandards, $nonStandards); } } diff --git a/tests/TestSuite/CliTest.php b/tests/TestSuite/CliTest.php index 27a5d5d..181fbfd 100644 --- a/tests/TestSuite/CliTest.php +++ b/tests/TestSuite/CliTest.php @@ -54,28 +54,28 @@ public function testRunWithNotValidStringShouldReturnErrorCode() public function testRunWithValidFileShouldReturnSuccessCode() { - $sFileToLint = realpath(__DIR__ . '/../_files/valid.css'); + $fileToLint = realpath(__DIR__ . '/../_files/valid.css'); $this->expectOutputString( - "# Lint CSS file \"$sFileToLint\"..." . PHP_EOL . - "\033[32m => CSS file \"$sFileToLint\" is valid\033[0m" . PHP_EOL . + "# Lint CSS file \"$fileToLint\"..." . PHP_EOL . + "\033[32m => CSS file \"$fileToLint\" is valid\033[0m" . PHP_EOL . PHP_EOL ); - $this->assertEquals(0, $this->cli->run(['php-css-lint', $sFileToLint])); + $this->assertEquals(0, $this->cli->run(['php-css-lint', $fileToLint])); } public function testRunWithNotValidFileShouldReturnErrorCode() { - $sFileToLint = realpath(__DIR__ . '/../_files/not_valid.css'); + $fileToLint = realpath(__DIR__ . '/../_files/not_valid.css'); $this->expectOutputString( - "# Lint CSS file \"$sFileToLint\"..." . PHP_EOL . - "\033[31m => CSS file \"$sFileToLint\" is not valid:\033[0m" . PHP_EOL . + "# Lint CSS file \"$fileToLint\"..." . PHP_EOL . + "\033[31m => CSS file \"$fileToLint\" is not valid:\033[0m" . PHP_EOL . PHP_EOL . "\033[31m - Unknown CSS property \"bordr-top-style\" (line: 8, char: 20)\033[0m" . PHP_EOL . "\033[31m - Unterminated \"selector content\" (line: 17, char: 0)\033[0m" . PHP_EOL . PHP_EOL ); - $this->assertEquals(1, $this->cli->run(['php-css-lint', $sFileToLint])); + $this->assertEquals(1, $this->cli->run(['php-css-lint', $fileToLint])); } public function testRunWithOptionsMustBeUsedByTheLinter() diff --git a/tests/TestSuite/LinterTest.php b/tests/TestSuite/LinterTest.php index 8beb74a..4391c44 100644 --- a/tests/TestSuite/LinterTest.php +++ b/tests/TestSuite/LinterTest.php @@ -13,11 +13,6 @@ class LinterTest extends \PHPUnit\Framework\TestCase */ protected $linter; - /** - * @var string - */ - protected $phpVersion; - /** * @var vfsStreamDirectory */ @@ -26,21 +21,15 @@ class LinterTest extends \PHPUnit\Framework\TestCase protected function setUp(): void { $this->linter = new \CssLint\Linter(); - $sPhpVersion = phpversion(); - if (version_compare($sPhpVersion, '8.0.0', '>=')) { - $this->phpVersion = '8'; - } else { - $this->phpVersion = '7'; - } $this->root = vfsStream::setup('testDir'); } public function testConstructWithCustomCssLintProperties() { - $oProperties = new \CssLint\Properties(); - $oLinter = new \CssLint\Linter($oProperties); - $this->assertSame($oProperties, $oLinter->getCssLintProperties()); + $properties = new \CssLint\Properties(); + $linter = new \CssLint\Linter($properties); + $this->assertSame($properties, $linter->getCssLintProperties()); } public function testLintValidString() @@ -81,9 +70,14 @@ public function testLintNotValidString() public function testLintValidStringContainingTabs() { $this->linter->getCssLintProperties()->setAllowedIndentationChars(["\t"]); - $this->assertTrue($this->linter->lintString("\t\t" . '.button.dropdown::after { + $this->assertTrue( + $this->linter->lintString( + "\t\t" . '.button.dropdown::after { ' . "\t\t" . 'display: block; -' . "\t\t" . '}'), print_r($this->linter->getErrors(), true)); +' . "\t\t" . '}' + ), + print_r($this->linter->getErrors(), true) + ); $this->linter->getCssLintProperties()->setAllowedIndentationChars([' ']); } @@ -134,30 +128,18 @@ public function testLintStringWithWrongSelectorUnexpectedToken() public function testLintStringWithWrongTypeParam() { $this->expectException(\TypeError::class); - if ($this->phpVersion == '8') { - $this->expectExceptionMessage( - 'CssLint\Linter::lintString(): Argument #1 ($sString) must be of type string, array given' - ); - } else { - $this->expectExceptionMessage( - 'Argument 1 passed to CssLint\Linter::lintString() must be of the type string, array given' - ); - } + $this->expectExceptionMessage( + 'CssLint\Linter::lintString(): Argument #1 ($stringValue) must be of type string, array given' + ); $this->linter->lintString(['wrong']); } public function testLintFileWithWrongTypeParam() { $this->expectException(\TypeError::class); - if ($this->phpVersion == '8') { - $this->expectExceptionMessage( - 'CssLint\Linter::lintFile(): Argument #1 ($sFilePath) must be of type string, array given' - ); - } else { - $this->expectExceptionMessage( - 'Argument 1 passed to CssLint\Linter::lintFile() must be of the type string, array given' - ); - } + $this->expectExceptionMessage( + 'CssLint\Linter::lintFile(): Argument #1 ($sFilePath) must be of type string, array given' + ); $this->linter->lintFile(['wrong']); } diff --git a/tools/composer.json b/tools/composer.json index a68b0d2..a75c1c0 100644 --- a/tools/composer.json +++ b/tools/composer.json @@ -1,14 +1,15 @@ { - "require": { - "friendsofphp/php-cs-fixer": "^3.68", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^1.12", - "phpstan/phpstan-phpunit": "^1.4", - "staabm/annotate-pull-request-from-checkstyle": "^1.8" - }, - "config": { - "allow-plugins": { - "phpstan/extension-installer": true - } + "require": { + "friendsofphp/php-cs-fixer": "^3.68", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^1.12", + "phpstan/phpstan-phpunit": "^1.4", + "staabm/annotate-pull-request-from-checkstyle": "^1.8", + "rector/rector": "^1.2" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true } - } \ No newline at end of file + } +}