diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..203f3c889 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/depsreview.yaml b/.github/workflows/depsreview.yaml new file mode 100644 index 000000000..da99d0c54 --- /dev/null +++ b/.github/workflows/depsreview.yaml @@ -0,0 +1,14 @@ +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout Repository' + uses: actions/checkout@v3 + - name: 'Dependency Review' + uses: actions/dependency-review-action@v3 diff --git a/.github/workflows/grumphp.yml b/.github/workflows/grumphp.yml index 0a5021135..ad49a4613 100644 --- a/.github/workflows/grumphp.yml +++ b/.github/workflows/grumphp.yml @@ -1,13 +1,16 @@ name: GrumPHP on: [push, pull_request] +permissions: + contents: read + jobs: run: runs-on: ${{ matrix.operating-system }} strategy: matrix: operating-system: [ubuntu-latest, macos-latest] #windows-latest currently not working - php-versions: ['7.4', '8.0', '8.1'] + php-versions: ['8.0', '8.1', '8.2'] composer-options: ['', '--prefer-lowest'] composer-versions: ['composer:v2'] fail-fast: false @@ -30,20 +33,22 @@ jobs: id: composercache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.composercache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies + if: matrix.php-versions != '8.2' run: composer update --prefer-dist --no-progress --no-suggest ${{ matrix.composer-options }} -# - name: Install dependencies (Ignore platform) -# if: matrix.php-versions == '8.1' -# run: composer update --prefer-dist --no-progress --no-suggest ${{ matrix.composer-options }} --ignore-platform-req=php + - name: Install dependencies (Ignore platform) + if: matrix.php-versions == '8.2' + run: composer update --prefer-dist --no-progress --no-suggest ${{ matrix.composer-options }} --ignore-platform-req=php+ - name: Set git variables run: | git config --global user.email "you@example.com" git config --global user.name "Your Name" + git config --global protocol.file.allow always - name: Run the tests on Windows if: runner.os == 'Windows' run: php vendor/bin/grumphp run --no-interaction --testsuite=windows diff --git a/appveyor.yml b/appveyor.yml index 06e3e2b8b..6e86b019f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,16 +7,17 @@ platform: #matrix: # allow_failures: -# - php_version: 8.1 +# - php_version: 8.2 environment: matrix: +# TODO : NOT AVAILABLE YET ! +# - dependencies: highest +# php_version: 8.2 - dependencies: highest php_version: 8.1 - dependencies: highest php_version: 8.0 - - dependencies: highest - php_version: 7.4 project_directory: c:\projects\grumphp composer_directory: c:\tools\composer @@ -71,6 +72,7 @@ install: - IF %dependencies%==highest composer update --prefer-dist --no-progress --no-scripts --no-suggest --profile - git config --global user.email "doesntmatter@dispostable.com" - git config --global user.name "GrumPHP" + - git config --global protocol.file.allow always test_script: - ps: cd $Env:project_directory diff --git a/composer.json b/composer.json index 68e3d6a34..20a263809 100644 --- a/composer.json +++ b/composer.json @@ -14,29 +14,29 @@ } ], "require": { - "php": "^7.4 || ^8.0", + "php": "^8.0", "ext-json": "*", "composer-plugin-api": "~2.0", "amphp/amp": "^2.6", "amphp/parallel": "^1.4", "amphp/parallel-functions": "^1.1", - "doctrine/collections": "^1.6.8", + "doctrine/collections": "^1.6.8 || ^2.0", "gitonomy/gitlib": "^1.3", "laravel/serializable-closure": "^1.1", - "monolog/monolog": "^2.0", + "monolog/monolog": "^2.0 || ^3.0", "ondram/ci-detector": "^4.0", "psr/container": "^1.1 || ^2.0", "seld/jsonlint": "~1.8", - "symfony/config": "~5.3 || ~6.0", - "symfony/console": "~5.3 || ~6.0", - "symfony/dependency-injection": "~5.3 || ~6.0", - "symfony/dotenv": "~5.3 || ~6.0", - "symfony/event-dispatcher": "~5.3 || ~6.0", - "symfony/filesystem": "~5.3 || ~6.0", - "symfony/finder": "~5.3 || ~6.0", - "symfony/options-resolver": "~5.3 || ~6.0", - "symfony/process": "~5.3 || ~6.0", - "symfony/yaml": "~5.3 || ~6.0" + "symfony/config": "~5.4 || ~6.0", + "symfony/console": "~5.4 || ~6.0", + "symfony/dependency-injection": "~5.4 || ~6.0", + "symfony/dotenv": "~5.4 || ~6.0", + "symfony/event-dispatcher": "~5.4 || ~6.0", + "symfony/filesystem": "~5.4 || ~6.0", + "symfony/finder": "~5.4 || ~6.0", + "symfony/options-resolver": "~5.4 || ~6.0", + "symfony/process": "~5.4 || ~6.0", + "symfony/yaml": "~5.4 || ~6.0" }, "require-dev": { "amphp/sync": "^v1.4", @@ -69,11 +69,13 @@ "phan/phan": "Lets GrumPHP unleash a static analyzer on your code", "phing/phing": "Lets GrumPHP run your automated PHP tasks.", "php-parallel-lint/php-parallel-lint": "Lets GrumPHP quickly lint your entire code base.", + "phparkitect/phparkitect": "Let GrumPHP keep your codebase coherent and solid, by permitting to add some architectural constraint check to your workflow.", "phpmd/phpmd": "Lets GrumPHP sort out the mess in your code", "phpspec/phpspec": "Lets GrumPHP spec your code.", "phpstan/phpstan": "Lets GrumPHP discover bugs in your code without running it.", "phpunit/phpunit": "Lets GrumPHP run your unit tests.", "povils/phpmnd": "Lets GrumPHP help you detect magic numbers in PHP code.", + "rector/rector ": "Lets GrumPHP instantly upgrade and automatically refactor your PHP code.", "roave/security-advisories": "Lets GrumPHP be sure that there are no known security issues.", "sebastian/phpcpd": "Lets GrumPHP find duplicated code.", "squizlabs/php_codesniffer": "Lets GrumPHP sniff on your code.", diff --git a/doc/tasks.md b/doc/tasks.md index df569e962..072d09332 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -51,7 +51,8 @@ grumphp: phpunitbridge: ~ phpversion: ~ progpilot: ~ - psalm: ~ + psalm: ~ + rector: ~ robo: ~ securitychecker_enlightn: ~ securitychecker_local: ~ @@ -100,6 +101,7 @@ Every task has its own default configuration. It is possible to overwrite the pa - [Phan](tasks/phan.md) - [Phing](tasks/phing.md) - [Php7cc](tasks/php7cc.md) +- [PhpArkitect](tasks/phparkitect.md) - [PhpCpd](tasks/phpcpd.md) - [Phpcs](tasks/phpcs.md) - [PHP-CS-Fixer](tasks/phpcsfixer.md) @@ -114,6 +116,7 @@ Every task has its own default configuration. It is possible to overwrite the pa - [PhpVersion](tasks/phpversion.md) - [Progpilot](tasks/progpilot.md) - [Psalm](tasks/psalm.md) +- [Rector](tasks/rector.md) - [Robo](tasks/robo.md) - [Security Checker](tasks/securitychecker.md) - [Enlightn](tasks/securitychecker/enlightn.md) diff --git a/doc/tasks/deptrac.md b/doc/tasks/deptrac.md index 7a886a622..968412f98 100644 --- a/doc/tasks/deptrac.md +++ b/doc/tasks/deptrac.md @@ -12,11 +12,18 @@ namespace and has following configurable parameters: grumphp: tasks: deptrac: + cache_file: ~ depfile: ~ formatter: ~ output: ~ ``` +**cache_file** + +*Default: null* + +Set location where cache file will be stored. Example: `/var/www/src/.deptrac.cache` + **depfile** *Default: null* diff --git a/doc/tasks/paratest.md b/doc/tasks/paratest.md index bb4ecf903..d722bc7f3 100644 --- a/doc/tasks/paratest.md +++ b/doc/tasks/paratest.md @@ -24,7 +24,6 @@ grumphp: phpunit: null configuration: null runner: null - debugger: null coverage-clover: null coverage-html: null coverage-php: null diff --git a/doc/tasks/phparkitect.md b/doc/tasks/phparkitect.md new file mode 100644 index 000000000..4689cad2e --- /dev/null +++ b/doc/tasks/phparkitect.md @@ -0,0 +1,43 @@ +# PHPArkitect + +PHPArkitect helps you to keep your PHP codebase coherent and solid, by permitting to add some architectural constraint check to your workflow. +It lives under the `phparkitect` namespace and has following configurable parameters: + +PhpArkitect doesn't support checking only the changed files. +It will always run on the directory specified in your config file. + +## Composer +```bash +composer require --dev phparkitect/phparkitect +``` + +## Config +```yaml +# grumphp.yml +grumphp: + tasks: + phparkitect: + config: ~ + target_php_version: ~ + stop_on_failure: ~ +``` + +**config** + +*Default: null* + +With this parameter you can specify the path your project's configuration file. +By default PHPArkitect will search all rules in phparkitect.php located in the root of your project. + +**target_php_version** + +*Default: null* + +With this parameter, you can specify which PHP version should use the parser. +This can be useful to debug problems and to understand if there are problems with a different PHP version. + +**stop_on_failure** + +*Default: false* + +With this option the process will end immediately after the first violation. diff --git a/doc/tasks/phpcs.md b/doc/tasks/phpcs.md index dd39b8280..cdc471fe5 100644 --- a/doc/tasks/phpcs.md +++ b/doc/tasks/phpcs.md @@ -30,7 +30,7 @@ grumphp: sniffs: [] triggered_by: [php] exclude: [] - + show_sniffs_error_path: true ``` **standard** @@ -130,6 +130,12 @@ This is a list of extensions to be sniffed. This list is also passed to phpcs us A list of rules that should not be checked. Leave this option blank to run all configured rules for the selected standard. +**show_sniffs_error_path** + +*Default: true* + +Displays the sniff that triggered the error, allowing you to more easily find the specific rules with their namespaces. + ## Framework presets ### Symfony 2 diff --git a/doc/tasks/rector.md b/doc/tasks/rector.md new file mode 100644 index 000000000..ff780697c --- /dev/null +++ b/doc/tasks/rector.md @@ -0,0 +1,57 @@ +# Rector + +Rector is a tool to instantly upgrade and automatically refactor your PHP 5.3+ code. +It lives under the `rector` namespace and has following configurable parameters: + +## Composer +```bash +composer require --dev rector/rector +``` + +## Config +```yaml +# grumphp.yml +grumphp: + tasks: + rector: + config: null + triggered_by: ['php'] + ignore_patterns: [] + clear_cache: true + no_diffs: false +``` + +**config** + +*Default: null* + +With this parameter you can specify the path your project's configuration file. When 'null' rector will run with the default file: rector.php + +**triggered_by** + +*Default: [php]* + +This is a list of extensions to be sniffed. + + +**ignore_patterns** + +*Default: []* + +This is a list of patterns that will be ignored by Rector. With this option you can skip files like +tests. Leave this option blank to run Rector for every php file/directory specified in your +configuration. + + +**clear_cache** + +*Default: true* + +With this parameter you can run Rector without using the cache. + +**no_diffs** + +*Default: false* + +With this parameter you can run Rector without showing file diffs. + diff --git a/doc/tasks/securitychecker.md b/doc/tasks/securitychecker.md index 0d49eeed2..b6458094e 100644 --- a/doc/tasks/securitychecker.md +++ b/doc/tasks/securitychecker.md @@ -6,4 +6,5 @@ You can use one of following tasks as a replacement: - [securitychecker_enlightn](securitychecker/enlightn.md) - [securitychecker_local](securitychecker/local.md) +- [securitychecker_roave](securitychecker/roave.md) - [securitychecker_symfony](securitychecker/symfony.md) diff --git a/resources/config/tasks.yml b/resources/config/tasks.yml index 654daa249..d459ce1a8 100644 --- a/resources/config/tasks.yml +++ b/resources/config/tasks.yml @@ -211,6 +211,13 @@ services: tags: - {name: grumphp.task, task: php7cc} + GrumPHP\Task\PhpArkitect: + arguments: + - '@process_builder' + - '@formatter.raw_process' + tags: + - {name: grumphp.task, task: phparkitect} + GrumPHP\Task\PhpCpd: arguments: - '@process_builder' @@ -308,6 +315,13 @@ services: tags: - {name: grumphp.task, task: psalm} + GrumPHP\Task\Rector: + arguments: + - '@process_builder' + - '@formatter.raw_process' + tags: + - { name: grumphp.task, task: rector } + GrumPHP\Task\Robo: arguments: - '@process_builder' diff --git a/resources/config/util.yml b/resources/config/util.yml index 4f1fbff91..f95d87fe7 100644 --- a/resources/config/util.yml +++ b/resources/config/util.yml @@ -21,3 +21,4 @@ services: '7.4': '2022-11-28 23:59:59' '8.0': '2023-11-26 23:59:59' '8.1': '2024-11-25 23:59:59' + '8.2': '2025-12-08 23:59:59' diff --git a/src/Configuration/Configuration.php b/src/Configuration/Configuration.php index c17084450..2f96fdaa7 100644 --- a/src/Configuration/Configuration.php +++ b/src/Configuration/Configuration.php @@ -63,7 +63,10 @@ public function getConfigTreeBuilder(): TreeBuilder $rootNode->children()->arrayNode('extensions')->scalarPrototype(); // ascii - $ascii = $rootNode->children()->arrayNode('ascii')->addDefaultsIfNotSet(); + $ascii = $rootNode->children()->arrayNode('ascii')->addDefaultsIfNotSet()->treatNullLike([ + 'failed' => null, + 'succeeded' => null, + ]); $ascii->children()->variableNode('failed')->defaultValue('grumphp-grumpy.txt'); $ascii->children()->variableNode('succeeded')->defaultValue('grumphp-happy.txt'); diff --git a/src/Console/ApplicationConfigurator.php b/src/Console/ApplicationConfigurator.php index c129bbbfb..963d191ad 100644 --- a/src/Console/ApplicationConfigurator.php +++ b/src/Console/ApplicationConfigurator.php @@ -10,7 +10,7 @@ class ApplicationConfigurator { const APP_NAME = 'GrumPHP'; - const APP_VERSION = '1.12.0'; + const APP_VERSION = '1.15.0'; public function configure(Application $application): void { diff --git a/src/Parser/Php/Visitor/AbstractVisitor.php b/src/Parser/Php/Visitor/AbstractVisitor.php index 186295b31..4627477dc 100644 --- a/src/Parser/Php/Visitor/AbstractVisitor.php +++ b/src/Parser/Php/Visitor/AbstractVisitor.php @@ -27,7 +27,7 @@ public function setContext(ParserContext $context): void protected function addError(string $message, int $line = -1, string $type = ParseError::TYPE_ERROR): void { $errors = $this->context->getErrors(); - $fileName = $this->context->getFile()->getPath(); + $fileName = $this->context->getFile()->getPathname(); $errors->add(new PhpParserError($type, $message, $fileName, $line)); } } diff --git a/src/Task/Deptrac.php b/src/Task/Deptrac.php index 5c18a29d1..a58bbb57b 100644 --- a/src/Task/Deptrac.php +++ b/src/Task/Deptrac.php @@ -20,11 +20,13 @@ public static function getConfigurableOptions(): OptionsResolver { $resolver = new OptionsResolver(); $resolver->setDefaults([ + 'cache_file' => null, 'depfile' => null, 'formatter' => null, 'output' => null, ]); + $resolver->addAllowedTypes('cache_file', ['null', 'string']); $resolver->addAllowedTypes('depfile', ['null', 'string']); $resolver->addAllowedTypes('formatter', ['null', 'string']); $resolver->addAllowedTypes('output', ['null', 'string']); @@ -50,6 +52,7 @@ public function run(ContextInterface $context): TaskResultInterface $arguments->add('analyse'); $arguments->addOptionalArgument('--formatter=%s', $config['formatter']); $arguments->addOptionalArgument('--output=%s', $config['output']); + $arguments->addOptionalArgument('--cache-file=%s', $config['cache_file']); $arguments->addOptionalArgument('--config-file=%s', $config['depfile']); $process = $this->processBuilder->buildProcess($arguments); diff --git a/src/Task/ESLint.php b/src/Task/ESLint.php index df1361783..0a37c12cc 100644 --- a/src/Task/ESLint.php +++ b/src/Task/ESLint.php @@ -11,6 +11,7 @@ use GrumPHP\Task\Context\ContextInterface; use GrumPHP\Task\Context\GitPreCommitContext; use GrumPHP\Task\Context\RunContext; +use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Process\Process; @@ -23,8 +24,8 @@ public static function getConfigurableOptions(): OptionsResolver // Task config options 'bin' => null, 'triggered_by' => ['js', 'jsx', 'ts', 'tsx', 'vue'], - 'whitelist_patterns' => null, - + 'whitelist_patterns' => [], + // ESLint native config options 'config' => null, 'ignore_path' => null, @@ -39,7 +40,7 @@ public static function getConfigurableOptions(): OptionsResolver $resolver->addAllowedTypes('bin', ['null', 'string']); $resolver->addAllowedTypes('whitelist_patterns', ['null', 'array']); $resolver->addAllowedTypes('triggered_by', ['array']); - + // ESLint native config options $resolver->addAllowedTypes('config', ['null', 'string']); $resolver->addAllowedTypes('ignore_path', ['null', 'string']); @@ -49,6 +50,19 @@ public static function getConfigurableOptions(): OptionsResolver $resolver->addAllowedTypes('no_eslintrc', ['bool']); $resolver->addAllowedTypes('quiet', ['bool']); + $resolver->setDeprecated( + 'whitelist_patterns', + 'phpro/grumphp', + '1.14', + function (Options $options, $value): string { + if (null === $value) { + return 'Parsing "null" to option "whitelist_patterns" is deprecated, pass an array instead.'; + } + + return ''; + } + ); + return $resolver; } diff --git a/src/Task/PhpArkitect.php b/src/Task/PhpArkitect.php new file mode 100644 index 000000000..ae5df016f --- /dev/null +++ b/src/Task/PhpArkitect.php @@ -0,0 +1,66 @@ +setDefaults([ + 'config' => null, + "target_php_version" => null, + 'stop_on_failure' => false, + ]); + + $resolver->addAllowedTypes('config', ['null', 'string']); + $resolver->addAllowedTypes('target_php_version', ['null', 'string']); + $resolver->addAllowedTypes('stop_on_failure', ['boolean']); + + return $resolver; + } + + /** + * {@inheritdoc} + */ + public function canRunInContext(ContextInterface $context): bool + { + return $context instanceof GitPreCommitContext || $context instanceof RunContext; + } + + /** + * {@inheritdoc} + */ + public function run(ContextInterface $context): TaskResultInterface + { + $config = $this->getConfig()->getOptions(); + + $arguments = $this->processBuilder->createArgumentsForCommand('phparkitect'); + $arguments->add('check'); + $arguments->addOptionalArgument('--config=%s', $config['config']); + $arguments->addOptionalArgument('--target-php-version=%s', $config['target_php_version']); + $arguments->addOptionalArgument('--stop-on-failure', $config['stop_on_failure']); + $arguments->add('--no-interaction'); + + $process = $this->processBuilder->buildProcess($arguments); + $process->run(); + + if (!$process->isSuccessful()) { + return TaskResult::createFailed($this, $context, $this->formatter->format($process)); + } + + return TaskResult::createPassed($this, $context); + } +} diff --git a/src/Task/Phpcs.php b/src/Task/Phpcs.php index 885a277ab..fa1abc6f2 100644 --- a/src/Task/Phpcs.php +++ b/src/Task/Phpcs.php @@ -42,6 +42,7 @@ public static function getConfigurableOptions(): OptionsResolver 'report' => 'full', 'report_width' => null, 'exclude' => [], + 'show_sniffs_error_path' => true ]); $resolver->addAllowedTypes('standard', ['array', 'null', 'string']); @@ -57,6 +58,7 @@ public static function getConfigurableOptions(): OptionsResolver $resolver->addAllowedTypes('report', ['null', 'string']); $resolver->addAllowedTypes('report_width', ['null', 'int']); $resolver->addAllowedTypes('exclude', ['array']); + $resolver->addAllowedTypes('show_sniffs_error_path', ['bool']); return $resolver; } @@ -153,6 +155,7 @@ private function addArgumentsFromConfig( $arguments->addOptionalCommaSeparatedArgument('--sniffs=%s', $config['sniffs']); $arguments->addOptionalCommaSeparatedArgument('--ignore=%s', $config['ignore_patterns']); $arguments->addOptionalCommaSeparatedArgument('--exclude=%s', $config['exclude']); + $arguments->addOptionalArgument('-s', $config['show_sniffs_error_path']); return $arguments; } diff --git a/src/Task/Rector.php b/src/Task/Rector.php new file mode 100644 index 000000000..7a50bd532 --- /dev/null +++ b/src/Task/Rector.php @@ -0,0 +1,86 @@ +setDefaults([ + 'config' => null, + 'triggered_by' => ['php'], + 'ignore_patterns' => [], + 'clear_cache' => true, + 'no_diffs' => false, + ]); + + $resolver->addAllowedTypes('config', ['null', 'string']); + $resolver->addAllowedTypes('triggered_by', ['array']); + $resolver->addAllowedTypes('ignore_patterns', ['array']); + $resolver->addAllowedTypes('clear_cache', ['bool']); + $resolver->addAllowedTypes('no_diffs', ['bool']); + + return $resolver; + } + + public function canRunInContext(ContextInterface $context): bool + { + return $context instanceof RunContext || $context instanceof GitPreCommitContext; + } + + public function run(ContextInterface $context): TaskResultInterface + { + $config = $this->getConfig()->getOptions(); + + $files = $context + ->getFiles() + ->notPaths($config['ignore_patterns']) + ->extensions($config['triggered_by']); + + if (0 === \count($files)) { + return TaskResult::createSkipped($this, $context); + } + + $arguments = $this->processBuilder->createArgumentsForCommand('rector'); + $arguments->add('process'); + $arguments->add('--dry-run'); + $arguments->add('--ansi'); + $arguments->add('--no-progress-bar'); + + $arguments->addOptionalArgument('--config=%s', $config['config']); + $arguments->addOptionalArgument('--clear-cache', $config['clear_cache']); + $arguments->addOptionalArgument('--no-diffs', $config['no_diffs']); + + if ($context instanceof GitPreCommitContext) { + $arguments->addFiles($files); + } + + $process = $this->processBuilder->buildProcess($arguments); + $process->run(); + + if (!$process->isSuccessful()) { + return FixableProcessResultProvider::provide( + TaskResult::createFailed($this, $context, $this->formatter->format($process)), + function () use ($arguments): Process { + $arguments->removeElement('--dry-run'); + + return $this->processBuilder->buildProcess($arguments); + } + ); + } + + return TaskResult::createPassed($this, $context); + } +} diff --git a/test/Unit/Configuration/ConfigurationTest.php b/test/Unit/Configuration/ConfigurationTest.php new file mode 100644 index 000000000..bb3934cd1 --- /dev/null +++ b/test/Unit/Configuration/ConfigurationTest.php @@ -0,0 +1,76 @@ +parse($config); + + self::assertArrayContains($expected, $actual); + } + + public function provideConfigs() + { + yield 'ascii_not_set' => [ + [], + ['ascii' => [ + 'failed' => 'grumphp-grumpy.txt', + 'succeeded' => 'grumphp-happy.txt', + ]] + ]; + yield 'ascii_nulled_out_items' => [ + ['ascii' => [ + 'failed' => null, + 'succeeded' => null, + ]], + ['ascii' => [ + 'failed' => null, + 'succeeded' => null, + ]] + ]; + yield 'ascii_custom' => [ + ['ascii' => [ + 'failed' => 'custom-failure.txt', + 'succeeded' => 'custom-happy.txt', + ]], + ['ascii' => [ + 'failed' => 'custom-failure.txt', + 'succeeded' => 'custom-happy.txt', + ]] + ]; + yield 'ascii_nulled_out' => [ + ['ascii' => null], + ['ascii' => [ + 'failed' => null, + 'succeeded' => null, + ]] + ]; + } + + private function parse(array $config): array + { + $configuration = new Configuration(); + $processor = new Processor(); + + return $processor->process($configuration->getConfigTreeBuilder()->buildTree(), ['grumphp' => $config]); + } + + private function assertArrayContains(array $needle, array $haystack) + { + self::assertSame( + array_intersect_key($haystack, $needle), + $needle + ); + } +} diff --git a/test/Unit/Task/DeptracTest.php b/test/Unit/Task/DeptracTest.php index 2420e6bce..d17d9c625 100644 --- a/test/Unit/Task/DeptracTest.php +++ b/test/Unit/Task/DeptracTest.php @@ -25,6 +25,7 @@ public function provideConfigurableOptions(): iterable yield 'defaults' => [ [], [ + 'cache_file' => null, 'depfile' => null, 'formatter' => null, 'output' => null, @@ -98,6 +99,17 @@ public function provideExternalTaskRuns(): iterable 'analyse', ] ]; + yield 'cache-file' => [ + [ + 'cache_file' => 'example.cache', + ], + $this->mockContext(RunContext::class, ['hello.php', 'hello2.php']), + 'deptrac', + [ + 'analyse', + '--cache-file=example.cache', + ] + ]; yield 'formatter-graphviz' => [ [ 'formatter' => 'graphviz-display', diff --git a/test/Unit/Task/ESLintTest.php b/test/Unit/Task/ESLintTest.php index 43531ed8b..d2dc775dc 100644 --- a/test/Unit/Task/ESLintTest.php +++ b/test/Unit/Task/ESLintTest.php @@ -29,7 +29,7 @@ public function provideConfigurableOptions(): iterable // Task config options 'bin' => null, 'triggered_by' => ['js', 'jsx', 'ts', 'tsx', 'vue'], - 'whitelist_patterns' => null, + 'whitelist_patterns' => [], // ESLint native config options 'config' => null, @@ -207,4 +207,11 @@ public function provideExternalTaskRuns(): iterable ] ]; } + + /** + * @test + */ + public function it_triggers_deprecation_on_null() { + self::assertTrue(ESLint::getConfigurableOptions()->isDeprecated('whitelist_patterns')); + } } diff --git a/test/Unit/Task/PhpArkitectTest.php b/test/Unit/Task/PhpArkitectTest.php new file mode 100644 index 000000000..93df2002a --- /dev/null +++ b/test/Unit/Task/PhpArkitectTest.php @@ -0,0 +1,130 @@ +processBuilder->reveal(), + $this->formatter->reveal() + ); + } + + public function provideConfigurableOptions(): iterable + { + yield 'defaults' => [ + [], + [ + 'config' => null, + 'target_php_version' => null, + 'stop_on_failure' => false, + ], + ]; + } + + public function provideRunContexts(): iterable + { + yield 'run-context' => [ + true, + $this->mockContext(RunContext::class) + ]; + + yield 'pre-commit-context' => [ + true, + $this->mockContext(GitPreCommitContext::class) + ]; + + yield 'other' => [ + false, + $this->mockContext() + ]; + } + + public function provideFailsOnStuff(): iterable + { + yield 'exitCode1' => [ + [], + $this->mockContext(RunContext::class, ['hello.php']), + function () { + $this->mockProcessBuilder('phparkitect', $process = $this->mockProcess(1)); + $this->formatter->format($process)->willReturn('nope'); + }, + 'nope' + ]; + } + + public function providePassesOnStuff(): iterable + { + yield 'exitCode0' => [ + [], + $this->mockContext(RunContext::class, ['hello.php']), + function () { + $this->mockProcessBuilder('phparkitect', $this->mockProcess(0)); + } + ]; + } + + public function provideSkipsOnStuff(): iterable + { + return []; + } + + public function provideExternalTaskRuns(): iterable + { + yield 'defaults' => [ + [], + $this->mockContext(RunContext::class, ['hello.php', 'hello2.php']), + 'phparkitect', + [ + 'check', + '--no-interaction' + ] + ]; + yield 'config' => [ + [ + 'config' => 'phparkitect.php' + ], + $this->mockContext(RunContext::class, ['hello.php', 'hello2.php']), + 'phparkitect', + [ + 'check', + '--config=phparkitect.php', + '--no-interaction' + ] + ]; + yield 'target_php_version' => [ + [ + 'target_php_version' => '8.1' + ], + $this->mockContext(RunContext::class, ['hello.php', 'hello2.php']), + 'phparkitect', + [ + 'check', + '--target-php-version=8.1', + '--no-interaction' + ] + ]; + yield 'stop_on_failure' => [ + [ + 'stop_on_failure' => TRUE + ], + $this->mockContext(RunContext::class, ['hello.php', 'hello2.php']), + 'phparkitect', + [ + 'check', + '--stop-on-failure', + '--no-interaction' + ] + ]; + } +} diff --git a/test/Unit/Task/PhpcsTest.php b/test/Unit/Task/PhpcsTest.php index 34e89f81d..45c36cb1f 100644 --- a/test/Unit/Task/PhpcsTest.php +++ b/test/Unit/Task/PhpcsTest.php @@ -49,6 +49,7 @@ public function provideConfigurableOptions(): iterable 'report' => 'full', 'report_width' => null, 'exclude' => [], + 'show_sniffs_error_path' => true ] ]; } @@ -160,6 +161,7 @@ public function provideExternalTaskRuns(): iterable [ '--extensions=php', '--report=full', + '-s', '--report-json', $this->expectFileList('hello.php'.PHP_EOL.'hello2.php'), ] @@ -174,6 +176,7 @@ public function provideExternalTaskRuns(): iterable '--standard=PSR1,PSR2', '--extensions=php', '--report=full', + '-s', '--report-json', $this->expectFileList('hello.php'.PHP_EOL.'hello2.php'), ] @@ -187,6 +190,7 @@ public function provideExternalTaskRuns(): iterable [ '--extensions=php,phtml', '--report=full', + '-s', '--report-json', $this->expectFileList('hello.php'.PHP_EOL.'hello2.php'), ] @@ -201,6 +205,7 @@ public function provideExternalTaskRuns(): iterable '--extensions=php', '--tab-width=4', '--report=full', + '-s', '--report-json', $this->expectFileList('hello.php'.PHP_EOL.'hello2.php'), ] @@ -215,6 +220,7 @@ public function provideExternalTaskRuns(): iterable '--extensions=php', '--encoding=UTF-8', '--report=full', + '-s', '--report-json', $this->expectFileList('hello.php'.PHP_EOL.'hello2.php'), ] @@ -228,6 +234,7 @@ public function provideExternalTaskRuns(): iterable [ '--extensions=php', '--report=small', + '-s', '--report-json', $this->expectFileList('hello.php'.PHP_EOL.'hello2.php'), ] @@ -242,6 +249,7 @@ public function provideExternalTaskRuns(): iterable '--extensions=php', '--report=full', '--report-width=20', + '-s', '--report-json', $this->expectFileList('hello.php'.PHP_EOL.'hello2.php'), ] @@ -256,6 +264,7 @@ public function provideExternalTaskRuns(): iterable '--extensions=php', '--report=full', '--severity=5', + '-s', '--report-json', $this->expectFileList('hello.php'.PHP_EOL.'hello2.php'), ] @@ -270,6 +279,7 @@ public function provideExternalTaskRuns(): iterable '--extensions=php', '--report=full', '--error-severity=5', + '-s', '--report-json', $this->expectFileList('hello.php'.PHP_EOL.'hello2.php'), ] @@ -284,6 +294,7 @@ public function provideExternalTaskRuns(): iterable '--extensions=php', '--report=full', '--warning-severity=5', + '-s', '--report-json', $this->expectFileList('hello.php'.PHP_EOL.'hello2.php'), ] @@ -298,6 +309,7 @@ public function provideExternalTaskRuns(): iterable '--extensions=php', '--report=full', '--sniffs=sniff1,sniff2', + '-s', '--report-json', $this->expectFileList('hello.php'.PHP_EOL.'hello2.php'), ] @@ -312,6 +324,7 @@ public function provideExternalTaskRuns(): iterable '--extensions=php', '--report=full', '--ignore=ignore1,ignore2', + '-s', '--report-json', $this->expectFileList('hello.php'.PHP_EOL.'hello2.php'), ] @@ -326,6 +339,20 @@ public function provideExternalTaskRuns(): iterable '--extensions=php', '--report=full', '--exclude=exclude1,exclude2', + '-s', + '--report-json', + $this->expectFileList('hello.php'.PHP_EOL.'hello2.php'), + ] + ]; + yield 's' => [ + [ + 'show_sniffs_error_path' => false + ], + $this->mockContext(RunContext::class, ['hello.php', 'hello2.php']), + 'phpcs', + [ + '--extensions=php', + '--report=full', '--report-json', $this->expectFileList('hello.php'.PHP_EOL.'hello2.php'), ] diff --git a/test/Unit/Task/RectorTest.php b/test/Unit/Task/RectorTest.php new file mode 100644 index 000000000..33e4ebc85 --- /dev/null +++ b/test/Unit/Task/RectorTest.php @@ -0,0 +1,171 @@ +formatter = $this->prophesize(RawProcessFormatter::class); + + return new Rector( + $this->processBuilder->reveal(), + $this->formatter->reveal() + ); + } + + public function provideConfigurableOptions(): iterable + { + yield 'defaults' => [ + [], + [ + 'config' => null, + 'triggered_by' => ['php'], + 'ignore_patterns' => [], + 'clear_cache' => true, + 'no_diffs' => false, + ] + ]; + } + + public function provideRunContexts(): iterable + { + yield 'run-context' => [ + true, + $this->mockContext(RunContext::class) + ]; + + yield 'pre-commit-context' => [ + true, + $this->mockContext(GitPreCommitContext::class) + ]; + + yield 'other' => [ + false, + $this->mockContext() + ]; + } + + public function provideFailsOnStuff(): iterable + { + yield 'exitCode1' => [ + [], + $this->mockContext(RunContext::class, ['hello.php']), + function () { + $this->mockProcessBuilder('rector', $process = $this->mockProcess(1)); + + $this->formatter->format($process)->willReturn($message = 'message'); + }, + 'message', + FixableTaskResult::class + ]; + } + + public function providePassesOnStuff(): iterable + { + yield 'exitCode0' => [ + [], + $this->mockContext(RunContext::class, ['hello.php']), + function () { + $this->mockProcessBuilder('rector', $this->mockProcess(0)); + } + ]; + } + + public function provideSkipsOnStuff(): iterable + { + yield 'no-files' => [ + [], + $this->mockContext(RunContext::class), + function () {} + ]; + yield 'no-files-after-triggered-by' => [ + [], + $this->mockContext(RunContext::class, ['notaphpfile.txt']), + function () {} + ]; + yield 'no-files-after-ignore-patterns' => [ + [ + 'ignore_patterns' => ['test/'], + ], + $this->mockContext(RunContext::class, ['test/file.php']), + function () {} + ]; + } + + public function provideExternalTaskRuns(): iterable + { + yield 'defaults' => [ + [], + $this->mockContext(RunContext::class, ['hello.php', 'hello2.php']), + 'rector', + [ + 'process', + '--dry-run', + '--ansi', + '--no-progress-bar', + '--clear-cache', + ] + ]; + yield 'config' => [ + [ + 'config' => 'rector-config.php', + ], + $this->mockContext(RunContext::class, ['hello.php', 'hello2.php']), + 'rector', + [ + 'process', + '--dry-run', + '--ansi', + '--no-progress-bar', + '--config=rector-config.php', + '--clear-cache', + ] + ]; + yield 'no-clear-cache' => [ + [ + 'clear_cache' => false, + ], + $this->mockContext(RunContext::class, ['hello.php', 'hello2.php']), + 'rector', + [ + 'process', + '--dry-run', + '--ansi', + '--no-progress-bar', + ] + ]; + yield 'no-diffs' => [ + [ + 'no_diffs' => true, + ], + $this->mockContext(RunContext::class, ['hello.php', 'hello2.php']), + 'rector', + [ + 'process', + '--dry-run', + '--ansi', + '--no-progress-bar', + '--clear-cache', + '--no-diffs' + ] + ]; + } +}