diff --git a/.github/workflows/grumphp.yml b/.github/workflows/grumphp.yml index 03409b3ab..ba57508bd 100644 --- a/.github/workflows/grumphp.yml +++ b/.github/workflows/grumphp.yml @@ -7,9 +7,9 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest, macos-latest] #windows-latest currently not working - php-versions: ['7.3', '7.4', '8.0'] + php-versions: ['8.0', '8.1'] composer-options: ['', '--prefer-lowest'] - composer-versions: ['composer:v1', 'composer:v2'] + composer-versions: ['composer:v2'] fail-fast: false name: PHP ${{ matrix.php-versions }} @ ${{ matrix.operating-system }} with ${{ matrix.composer-versions }} ${{ matrix.composer-options }} steps: @@ -36,7 +36,11 @@ jobs: key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies + if: matrix.php-versions != '8.1' 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: Set git variables run: | git config --global user.email "you@example.com" diff --git a/appveyor.yml b/appveyor.yml index 6728996d5..b055e3b27 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,16 +5,14 @@ shallow_clone: false platform: - x64 -matrix: - allow_failures: - - php_version: 8.0 +#matrix: +# allow_failures: +# - php_version: 8.1 environment: matrix: - dependencies: highest - php_version: 7.3 - - dependencies: highest - php_version: 7.4.3 # OpenSSL issue will be resolved in 7.4.6 + php_version: 8.1 - dependencies: highest php_version: 8.0 @@ -33,8 +31,6 @@ cache: init: - ps: $Env:php_directory = $Env:php_root_directory + '\' + $Env:php_version - # TODO: This is a workaround for https://github.com/chocolatey/choco/issues/1843. Once this is fixed we - # should go back to latest version in appveyor saving ourselves test time - ps: choco install chocolatey -y --version 0.10.13 --allow-downgrade - ps: $Env:exact_php_version = (((choco search php --exact --all --limit-output | Select-String -pattern $Env:php_version) -replace '[php|]', '') | %{ New-Object System.Version $_ } | Sort-Object | Select-Object -Last 1).ToString() - ps: $Env:PATH = $Env:php_directory + ';' + $Env:composer_directory + ';' + $Env:PATH @@ -68,8 +64,8 @@ install: # Install dependencies - ps: cd $Env:project_directory - - IF %dependencies%==lowest composer update --prefer-dist --prefer-lowest --prefer-stable --no-progress --no-scripts --no-suggest --profile - - IF %dependencies%==highest composer update --prefer-dist --no-progress --no-scripts --no-suggest --profile + - IF %dependencies%==lowest composer update --prefer-dist --prefer-lowest --prefer-stable --no-progress --no-scripts --no-suggest --profile --ignore-platform-req=php + - IF %dependencies%==highest composer update --prefer-dist --no-progress --no-scripts --no-suggest --profile --ignore-platform-req=php - git config --global user.email "doesntmatter@dispostable.com" - git config --global user.name "GrumPHP" diff --git a/composer.json b/composer.json index 87e1271b4..7f2c9d573 100644 --- a/composer.json +++ b/composer.json @@ -14,38 +14,38 @@ } ], "require": { - "php": "^7.3 || ^8.0", + "php": "^8.0", "ext-json": "*", - "composer-plugin-api": "~1.0 || ~2.0", - "amphp/amp": "^2.4", + "composer-plugin-api": "~2.0", + "amphp/amp": "^2.6", "amphp/parallel": "^1.4", "amphp/parallel-functions": "^1.0", - "doctrine/collections": "^1.6.7", - "gitonomy/gitlib": "^1.0.3", - "monolog/monolog": "~1.16 || ^2.0", - "ondram/ci-detector": "^3.5 || ^4.0", - "opis/closure": "^3.5", - "psr/container": "^1.0", - "seld/jsonlint": "~1.1", - "symfony/config": "~4.4 || ~5.0", - "symfony/console": "~4.4 || ~5.0", - "symfony/dependency-injection": "~4.4 || ~5.0", - "symfony/dotenv": "~4.4 || ~5.0", - "symfony/event-dispatcher": "~4.4 || ~5.0", - "symfony/filesystem": "~4.4 || ~5.0", - "symfony/finder": "~4.4 || ~5.0", - "symfony/options-resolver": "~4.4 || ~5.0", - "symfony/process": "~4.4 || ~5.0", - "symfony/yaml": "~4.4 || ~5.0" + "doctrine/collections": "^1.6.8", + "gitonomy/gitlib": "^1.3", + "monolog/monolog": "^2.0", + "ondram/ci-detector": "^4.0", + "opis/closure": "^3.6", + "psr/container": "^1.1", + "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" }, "require-dev": { "brianium/paratest": "^6.3", - "composer/composer": "^1.10.22 || ^2.0.13", - "nikic/php-parser": "~4.0", + "composer/composer": "^2.1.8", + "nikic/php-parser": "~4.13", "php-parallel-lint/php-parallel-lint": "^1.3", "phpspec/phpspec": "^7.1", "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^9.5.10" }, "suggest": { "atoum/atoum": "Lets GrumPHP run your unit tests.", diff --git a/phive.xml b/phive.xml index 905d55981..da5f8b82e 100644 --- a/phive.xml +++ b/phive.xml @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/resources/config/util.yml b/resources/config/util.yml index 4479ec804..4f1fbff91 100644 --- a/resources/config/util.yml +++ b/resources/config/util.yml @@ -20,3 +20,4 @@ services: '7.3': '2021-12-06 23:59:59' '7.4': '2022-11-28 23:59:59' '8.0': '2023-11-26 23:59:59' + '8.1': '2024-11-25 23:59:59' diff --git a/spec/Parser/Php/Context/ParserContextSpec.php b/spec/Parser/Php/Context/ParserContextSpec.php index cb0ce4468..20ca41186 100644 --- a/spec/Parser/Php/Context/ParserContextSpec.php +++ b/spec/Parser/Php/Context/ParserContextSpec.php @@ -11,6 +11,7 @@ class ParserContextSpec extends ObjectBehavior { function let(SplFileInfo $file, ParseErrorsCollection $errors) { + $file->beConstructedWith(['somefile']); $this->beConstructedWith($file, $errors); } diff --git a/spec/Parser/Php/PhpParserSpec.php b/spec/Parser/Php/PhpParserSpec.php index e2c5c4599..32171678a 100644 --- a/spec/Parser/Php/PhpParserSpec.php +++ b/spec/Parser/Php/PhpParserSpec.php @@ -72,6 +72,7 @@ function it_parses_a_file(NodeTraverser $traverser, Filesystem $filesystem) function it_catches_parse_exceptions(Parser $parser, Filesystem $filesystem, SplFileInfo $file) { + $file->beConstructedWith(['somefile']); $file->getRealPath()->willReturn('a real path'); $filesystem->readFromFileInfo($file)->willReturn('file content'); diff --git a/src/Collection/FilesCollection.php b/src/Collection/FilesCollection.php index 5135353c3..4ee01eed0 100644 --- a/src/Collection/FilesCollection.php +++ b/src/Collection/FilesCollection.php @@ -14,9 +14,9 @@ use Traversable; /** - * @extends ArrayCollection + * @extends ArrayCollection */ -class FilesCollection extends ArrayCollection implements \Serializable +class FilesCollection extends ArrayCollection { /** * Adds a rule that files must match. @@ -47,7 +47,7 @@ public function names(array $patterns): self { $filter = new Iterator\FilenameFilterIterator($this->getIterator(), $patterns, []); - return new self(iterator_to_array($filter)); + return new self([...$filter]); } /** @@ -63,7 +63,7 @@ public function notName(string $pattern): self { $filter = new Iterator\FilenameFilterIterator($this->getIterator(), [], [$pattern]); - return new self(iterator_to_array($filter)); + return new self([...$filter]); } /** @@ -85,7 +85,7 @@ public function paths(array $patterns): self { $filter = new Iterator\PathFilterIterator($this->getIterator(), $patterns, []); - return new self(iterator_to_array($filter)); + return new self([...$filter]); } /** @@ -111,7 +111,7 @@ public function notPaths(array $pattern): self { $filter = new Iterator\PathFilterIterator($this->getIterator(), [], $pattern); - return new self(iterator_to_array($filter)); + return new self([...$filter]); } public function extensions(array $extensions): self @@ -139,7 +139,7 @@ public function size(string $size): self $comparator = new Comparator\NumberComparator($size); $filter = new Iterator\SizeRangeFilterIterator($this->getIterator(), [$comparator]); - return new self(iterator_to_array($filter)); + return new self([...$filter]); } /** @@ -161,7 +161,7 @@ public function date(string $date): self $comparator = new Comparator\DateComparator($date); $filter = new Iterator\DateRangeFilterIterator($this->getIterator(), [$comparator]); - return new self(iterator_to_array($filter)); + return new self([...$filter]); } /** @@ -182,14 +182,14 @@ public function filter(Closure $p): self { $filter = new Iterator\CustomFilterIterator($this->getIterator(), [$p]); - return new self(iterator_to_array($filter)); + return new self([...$filter]); } public function filterByFileList(Traversable $fileList): FilesCollection { $allowedFiles = array_map(function (SplFileInfo $file) { return $file->getPathname(); - }, iterator_to_array($fileList)); + }, [...$fileList]); return $this->filter(function (SplFileInfo $file) use ($allowedFiles) { return \in_array($file->getPathname(), $allowedFiles, true); @@ -216,26 +216,26 @@ public function ignoreSymlinks(): FilesCollection }); } - /* + /** * SplFileInfo cannot be serialized. Therefor, we help PHP a bit. * This stuff is used for running tasks in parallel. */ - public function serialize(): string + public function __serialize(): array { - return serialize($this->map(function (SplFileInfo $fileInfo): string { + return $this->map(function (SplFileInfo $fileInfo): string { return $fileInfo instanceof SymfonySplFileInfo ? $fileInfo->getRelativePathname() : $fileInfo->getPathname(); - })->toArray()); + })->toArray(); } - /* + /** * SplFileInfo cannot be serialized. Therefor, we help PHP a bit. * This stuff is used for running tasks in parallel. */ - public function unserialize($serialized): void + public function __unserialize(array $data): void { - $files = unserialize($serialized, ['allowed_classes' => false]); + $files = $data; $this->clear(); foreach ($files as $file) { $this->add(new SymfonySplFileInfo($file, dirname($file), $file)); @@ -245,7 +245,7 @@ public function unserialize($serialized): void /** * Help Psalm out a bit: * - * @return \ArrayIterator + * @return \ArrayIterator */ public function getIterator(): \ArrayIterator { diff --git a/src/Composer/GrumPHPPlugin.php b/src/Composer/GrumPHPPlugin.php index 5c90e016d..bfbeb8d1a 100644 --- a/src/Composer/GrumPHPPlugin.php +++ b/src/Composer/GrumPHPPlugin.php @@ -222,7 +222,10 @@ private function runGrumPhpCommand(string $command): void // Windows requires double double quotes // https://bugs.php.net/bug.php?id=49139 $windowsIsInsane = function (string $command): string { - return $this->runsOnWindows() ? '"'.$command.'"' : $command; + return $command; + // Looks like this is not needed anymore since PHP8 - even though the bug is still open. + // Leaving this here for reference if the bug pops up again for some poeple! + // return $this->runsOnWindows() ? '"'.$command.'"' : $command; }; // Run command diff --git a/src/Configuration/GrumPHPExtension.php b/src/Configuration/GrumPHPExtension.php index f8ff3a5c4..209948da3 100644 --- a/src/Configuration/GrumPHPExtension.php +++ b/src/Configuration/GrumPHPExtension.php @@ -27,7 +27,7 @@ public function getConfiguration(array $config, ContainerBuilder $container): Co return new Configuration(); } - public function getAlias() + public function getAlias(): string { return 'grumphp'; } diff --git a/src/Console/Command/Git/InitCommand.php b/src/Console/Command/Git/InitCommand.php index 604d0759a..1aadf49bd 100644 --- a/src/Console/Command/Git/InitCommand.php +++ b/src/Console/Command/Git/InitCommand.php @@ -102,10 +102,10 @@ public function execute(InputInterface $input, OutputInterface $output): int foreach (self::$hooks as $hook) { $gitHook = $this->filesystem->buildPath($gitHooksPath, $hook); $hookTemplate = $this->filesystem->guessFile( - array_filter([ + [ $customHooksPath, $resourceHooksPath, - ]), + ], [$hook] ); diff --git a/src/IO/ConsoleIO.php b/src/IO/ConsoleIO.php index 859b9a8dc..6d5268789 100644 --- a/src/IO/ConsoleIO.php +++ b/src/IO/ConsoleIO.php @@ -14,7 +14,7 @@ use Symfony\Component\Console\Style\StyleInterface; use Symfony\Component\Console\Style\SymfonyStyle; -class ConsoleIO implements IOInterface, \Serializable +class ConsoleIO implements IOInterface { private $input; private $output; @@ -143,16 +143,16 @@ static function (string $message) use ($color) : string { * Serializing this IO will result in an unwritable resource stream. * Therefor we serialize the data end build up a new stream instead. */ - public function serialize() + public function __serialize(): array { - return serialize([ + return [ 'input' => [ 'arguments' => $this->input->getArguments(), ], 'output' => [ 'verbosity' => $this->output->getVerbosity(), ], - ]); + ]; } /** @@ -160,10 +160,8 @@ public function serialize() * Note: When you run in parallel mode, the stream will be non-blocking. * All tasks can write at the same time, which is not optimal. */ - public function unserialize($serialized) + public function __unserialize(array $data): void { - $data = unserialize($serialized, ['allowed_classes' => false]); - $this->input = new ArrayInput( (array) ($data['input']['arguments'] ?? []) ); diff --git a/src/Locator/GuessedPathsLocator.php b/src/Locator/GuessedPathsLocator.php index 973e61845..093223be8 100644 --- a/src/Locator/GuessedPathsLocator.php +++ b/src/Locator/GuessedPathsLocator.php @@ -38,7 +38,7 @@ public function __construct( public function locate(?string $cliConfigFile): GuessedPaths { - $workingDir = getcwd(); + $workingDir = (string) getcwd(); $cliConfigFile = $this->makeOptionalPathAbsolute($workingDir, $cliConfigFile); $cliConfigPath = $cliConfigFile ? dirname($cliConfigFile) : null; $projectDirEnv = $this->makeOptionalPathAbsolute($workingDir, (string) ($_SERVER['GRUMPHP_PROJECT_DIR'] ?? '')); @@ -55,13 +55,13 @@ public function locate(?string $cliConfigFile): GuessedPaths ); $composerFilePathname = $this->filesystem->guessFile( - array_filter([ + [ $this->makeOptionalPathAbsolute($workingDir, (string) ($_SERVER['GRUMPHP_COMPOSER_DIR'] ?? '')), $cliConfigPath, $projectDirEnv, $workingDir, $gitWorkingDir - ]), + ], [ 'composer.json' ] @@ -74,13 +74,13 @@ public function locate(?string $cliConfigFile): GuessedPaths : [] ); - $binDir = $this->filesystem->guessPath(array_filter([ + $binDir = $this->filesystem->guessPath([ $this->makeOptionalPathAbsolute($workingDir, (string) ($_SERVER['GRUMPHP_BIN_DIR'] ?? '')), $this->makeOptionalPathAbsolute( $composerFilePath, $this->ensureOptionalArgumentWithValidSlashes($composerFile->getBinDir()) ) - ])); + ]); $composerConfigDefaultPath = $this->makeOptionalPathAbsolute( $composerFilePath, @@ -97,14 +97,14 @@ public function locate(?string $cliConfigFile): GuessedPaths ]); $defaultConfigFile = $this->filesystem->guessFile( - array_filter([ + [ $cliConfigFile, $cliConfigPath, $composerConfigDefaultPath, $projectDir, $workingDir, $gitWorkingDir, - ]), + ], [ 'grumphp.yml', 'grumphp.yaml', diff --git a/src/Task/CloverCoverage.php b/src/Task/CloverCoverage.php index bbd9829b7..9fa9fa3f9 100644 --- a/src/Task/CloverCoverage.php +++ b/src/Task/CloverCoverage.php @@ -84,7 +84,7 @@ public function run(ContextInterface $context): TaskResultInterface { $configuration = $this->getConfig()->getOptions(); $percentage = round(min(100, max(0, (float) $configuration['level'])), 2); - $cloverFile = $configuration['clover_file']; + $cloverFile = (string) $configuration['clover_file']; if (!$this->filesystem->exists($cloverFile)) { return TaskResult::createFailed($this, $context, 'Invalid input file provided'); diff --git a/src/Task/TwigCs.php b/src/Task/TwigCs.php index 51459bac2..e23d5fbe6 100644 --- a/src/Task/TwigCs.php +++ b/src/Task/TwigCs.php @@ -58,7 +58,7 @@ public function run(ContextInterface $context): TaskResultInterface $arguments->addOptionalArgument('--ansi', true); // removes all NULL, FALSE and Empty Strings - $exclude = array_filter($config['exclude'], 'strlen'); + $exclude = array_filter($config['exclude'], static fn (mixed $exclude): bool => $exclude && $exclude !== ''); $arguments->addArgumentArray('--exclude=%s', $exclude); $process = $this->processBuilder->buildProcess($arguments); diff --git a/src/Util/Filesystem.php b/src/Util/Filesystem.php index e86fa838f..ff13c331c 100644 --- a/src/Util/Filesystem.php +++ b/src/Util/Filesystem.php @@ -63,19 +63,30 @@ public function buildPath(string $baseDir, string $path): string return $baseDir.DIRECTORY_SEPARATOR.$path; } + /** + * @param non-empty-list $paths + */ public function guessPath(array $paths): string { + $paths = array_filter($paths); foreach ($paths as $path) { if ($this->exists($path) && is_dir($path)) { return $path; } } - return current($paths); + return (string) current($paths); } + /** + * @param non-empty-list $paths + * @param non-empty-list $fileNames + */ public function guessFile(array $paths, array $fileNames): string { + $paths = array_filter($paths); + $fileNames = array_filter($fileNames); + foreach ($paths as $path) { if (!$this->exists($path)) { continue; @@ -93,8 +104,8 @@ public function guessFile(array $paths, array $fileNames): string } } - $firstPath = current($paths); - $firstName = current($fileNames); + $firstPath = (string) current($paths); + $firstName = (string) current($fileNames); if (preg_match('#'.preg_quote($firstName, '#').'$#', $firstPath)) { return $firstPath; diff --git a/test/E2E/AbstractE2ETestCase.php b/test/E2E/AbstractE2ETestCase.php index c00dff31a..5020ddb1b 100644 --- a/test/E2E/AbstractE2ETestCase.php +++ b/test/E2E/AbstractE2ETestCase.php @@ -407,6 +407,7 @@ protected function runCommand(string $action, Process $process) $process->setTimeout(300); $process->run(); + if (!$process->isSuccessful()) { throw new \RuntimeException( 'Could not '.$action.'! '.$process->getOutput().PHP_EOL.$process->getErrorOutput() @@ -472,8 +473,9 @@ protected function changeGitPermissions() ->in($this->rootDir) ->path('.git'); + /** @var \SplFileInfo $gitDir */ foreach ($gitDirs as $gitDir) { - $this->filesystem->chmod($gitDir, 0777, 0000, true); + $this->filesystem->chmod($gitDir->getPathname(), 0777, 0000, true); } } diff --git a/test/Unit/Locator/GuessedPathsLocatorTest.php b/test/Unit/Locator/GuessedPathsLocatorTest.php index e3b66f01b..69e91ee51 100644 --- a/test/Unit/Locator/GuessedPathsLocatorTest.php +++ b/test/Unit/Locator/GuessedPathsLocatorTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); namespace GrumPHP\Locator { - function getcwd() { + function getcwd(): string { return $GLOBALS['__current_workspace'] ?? \getcwd(); } } diff --git a/tools/composer-normalize b/tools/composer-normalize index dfa36f728..0accdc477 100755 Binary files a/tools/composer-normalize and b/tools/composer-normalize differ diff --git a/tools/phpcbf b/tools/phpcbf index dcf50adc3..b66f5834f 100755 Binary files a/tools/phpcbf and b/tools/phpcbf differ diff --git a/tools/phpcs b/tools/phpcs index f49d1379e..5ed709315 100755 Binary files a/tools/phpcs and b/tools/phpcs differ diff --git a/tools/psalm b/tools/psalm index 35b6dd494..0452e69cc 100755 Binary files a/tools/psalm and b/tools/psalm differ