diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index b167f425..f2797324 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -38,7 +38,7 @@ jobs: - locked steps: - name: 📦 Check out the codebase - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4 - name: 🛠️ Setup PHP uses: shivammathur/setup-php@2.30.4 @@ -58,7 +58,7 @@ jobs: uses: wayofdev/gh-actions/actions/composer/get-cache-directory@v3.1.0 - name: ♻️ Restore cached dependencies installed with composer - uses: actions/cache@v4.0.2 + uses: actions/cache@v4 with: path: ${{ env.COMPOSER_CACHE_DIR }} key: php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('composer.lock') }} @@ -89,7 +89,7 @@ jobs: - locked steps: - name: 📦 Check out the codebase - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4 - name: 🛠️ Setup PHP uses: shivammathur/setup-php@2.30.4 @@ -109,7 +109,7 @@ jobs: uses: wayofdev/gh-actions/actions/composer/get-cache-directory@v3.1.0 - name: ♻️ Restore cached dependencies installed with composer - uses: actions/cache@v4.0.2 + uses: actions/cache@v4 with: path: ${{ env.COMPOSER_CACHE_DIR }} key: php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('composer.lock') }} @@ -122,3 +122,54 @@ jobs: - name: 🔍 Run static analysis using phpstan/phpstan run: composer stan:ci + + rector: + timeout-minutes: 4 + runs-on: ${{ matrix.os }} + concurrency: + cancel-in-progress: true + group: rector-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + strategy: + fail-fast: true + matrix: + os: + - ubuntu-latest + php-version: + - '8.2' + dependencies: + - locked + steps: + - name: 📦 Check out the codebase + uses: actions/checkout@v4 + + - name: 🛠️ Setup PHP + uses: shivammathur/setup-php@2.30.4 + with: + php-version: ${{ matrix.php-version }} + extensions: none, ctype, curl, dom, json, mbstring, phar, simplexml, tokenizer, xml, xmlwriter, sockets, opcache, pcntl, posix + ini-values: error_reporting=E_ALL + coverage: none + + - name: 🛠️ Setup problem matchers + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - name: 🤖 Validate composer.json and composer.lock + run: composer validate --ansi --strict + + - name: 🔍 Get composer cache directory + uses: wayofdev/gh-actions/actions/composer/get-cache-directory@v3.1.0 + + - name: ♻️ Restore cached dependencies installed with composer + uses: actions/cache@v4 + with: + path: ${{ env.COMPOSER_CACHE_DIR }} + key: php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('composer.lock') }} + restore-keys: php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}- + + - name: 📥 Install "${{ matrix.dependencies }}" dependencies + uses: wayofdev/gh-actions/actions/composer/install@v3.1.0 + with: + dependencies: ${{ matrix.dependencies }} + + - name: 🔍 Run static analysis using rector/rector + run: composer refactor:ci diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 56f76207..90758b8c 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -14,7 +14,7 @@ ->exclude([ __DIR__ . '/src/Test/Proto', ]) - ->addFiles([__FILE__]) + ->addFiles([__FILE__, __DIR__ . '/rector.php']) ->getConfig(); $config->setCacheFile(__DIR__ . '/.build/php-cs-fixer/php-cs-fixer.cache'); diff --git a/composer.json b/composer.json index ef78a3d0..1ac01480 100644 --- a/composer.json +++ b/composer.json @@ -67,6 +67,7 @@ "phpstan/phpstan-phpunit": "^1.3", "phpstan/phpstan-strict-rules": "^1.5", "phpunit/phpunit": "^10.5", + "rector/rector": "^1.1", "roxblnfk/unpoly": "^1.8.1", "vimeo/psalm": "^5.11", "wayofdev/cs-fixer-config": "^1.4" diff --git a/composer.lock b/composer.lock index b64091b2..bcc2ec45 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c63c5b3a7fb9968c9a33be396d75e976", + "content-hash": "b6eac7c3997133078d6b6ff600095717", "packages": [ { "name": "clue/stream-filter", @@ -4644,6 +4644,65 @@ ], "time": "2023-06-16T10:52:11+00:00" }, + { + "name": "rector/rector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "c930cdb21294f10955ddfc31b720971e8333943d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/c930cdb21294f10955ddfc31b720971e8333943d", + "reference": "c930cdb21294f10955ddfc31b720971e8333943d", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.11" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/1.1.1" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2024-06-21T07:51:17+00:00" + }, { "name": "roxblnfk/unpoly", "version": "1.8.1.1", @@ -6530,5 +6589,5 @@ "platform-overrides": { "php": "8.1.27" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/rector.php b/rector.php new file mode 100644 index 00000000..6c86bd9f --- /dev/null +++ b/rector.php @@ -0,0 +1,49 @@ +withPaths([ + // let's add more directories step by step + // __DIR__ . '/src', + // __DIR__ . '/tests', + // __DIR__ . '/bin', + __DIR__ . '/src/Client', + ]) + ->withPHPStanConfigs([ + __DIR__ . '/phpstan-baseline.neon', + ]) + ->withImportNames(importNames: true, importDocBlockNames: true, importShortClasses: false, removeUnusedImports: true) + ->withPhpVersion(PhpVersion::PHP_82) + ->withPhpSets(php81: true) + ->withPreparedSets( + deadCode: false, + codeQuality: true, + codingStyle: true, + typeDeclarations: true, + privatization: true, + naming: false, + instanceOf: true, + earlyReturn: true, + strictBooleans: true, + carbon: true, + rectorPreset: true, + )->withSkip([ + InlineArrayReturnAssignRector::class, + PostIncDecToPreIncDecRector::class, + InlineIfToExplicitIfRector::class, + LogicalToBooleanRector::class, + BinaryOpNullableToInstanceofRector::class, + FlipTypeControlToUseExclusiveTypeRector::class, + DisallowedEmptyRuleFixerRector::class, + ]); diff --git a/src/Client/Caster/ProtobufCaster.php b/src/Client/Caster/ProtobufCaster.php index 05dd8f4b..0912931d 100644 --- a/src/Client/Caster/ProtobufCaster.php +++ b/src/Client/Caster/ProtobufCaster.php @@ -4,6 +4,9 @@ namespace Buggregator\Trap\Client\Caster; +use Google\Protobuf\Internal\FieldDescriptor; +use Google\Protobuf\Internal\EnumDescriptor; +use Google\Protobuf\Internal\EnumValueDescriptorProto; use Google\Protobuf\Descriptor as PublicDescriptor; use Google\Protobuf\Internal\Descriptor as InternalDescriptor; use Google\Protobuf\Internal\DescriptorPool; @@ -118,7 +121,7 @@ private static function extractViaInternal(Message $message, InternalDescriptor $values = []; for ($i = 0; $i < $pub->getFieldCount(); $i++) { - /** @var \Google\Protobuf\Internal\FieldDescriptor $fd */ + /** @var FieldDescriptor $fd */ $fd = $descriptor->getFieldByIndex($i); $value = $message->{$fd->getGetter()}(); @@ -150,9 +153,9 @@ private static function extractViaInternal(Message $message, InternalDescriptor // Wrap ENUM if ($fd->getType() === GPBType::ENUM) { - /** @var \Google\Protobuf\Internal\EnumDescriptor $ed */ + /** @var EnumDescriptor $ed */ $ed = $fd->getEnumType(); - /** @var \Google\Protobuf\Internal\EnumValueDescriptorProto $v */ + /** @var EnumValueDescriptorProto $v */ $v = $ed->getValueByNumber($value); $values[$fd->getName()] = new EnumValue( diff --git a/src/Client/Caster/Trace.php b/src/Client/Caster/Trace.php index 9be5c575..1571e11f 100644 --- a/src/Client/Caster/Trace.php +++ b/src/Client/Caster/Trace.php @@ -10,7 +10,7 @@ * @see tr() * @internal */ -final class Trace +final class Trace implements \Stringable { /** * @param int<0, max> $number The tick number. diff --git a/src/Client/Caster/TraceCaster.php b/src/Client/Caster/TraceCaster.php index 377da934..871bdc27 100644 --- a/src/Client/Caster/TraceCaster.php +++ b/src/Client/Caster/TraceCaster.php @@ -55,6 +55,6 @@ private static function renderMethod(array $line): string $line['type'] ??= "::"; - return "{$line['class']}{$line['type']}{$line['function']}()"; + return \sprintf('%s%s%s()', $line['class'], $line['type'], $line['function']); } } diff --git a/src/Client/Caster/TraceFile.php b/src/Client/Caster/TraceFile.php index 0447750c..b06f6279 100644 --- a/src/Client/Caster/TraceFile.php +++ b/src/Client/Caster/TraceFile.php @@ -7,7 +7,7 @@ /** * @internal */ -final class TraceFile +final class TraceFile implements \Stringable { /** * @param array{ diff --git a/src/Client/TrapHandle.php b/src/Client/TrapHandle.php index f4ba6edf..126e7d52 100644 --- a/src/Client/TrapHandle.php +++ b/src/Client/TrapHandle.php @@ -23,7 +23,7 @@ final class TrapHandle private int $depth = 0; - private StaticState $staticState; + private readonly StaticState $staticState; private function __construct( private array $values, @@ -139,7 +139,7 @@ public function once(): self */ public function return(int|string $key = 0): mixed { - if (\count($this->values) === 0) { + if ($this->values === []) { throw new \InvalidArgumentException('No values to return.'); } @@ -176,8 +176,6 @@ public function return(int|string $key = 0): mixed * ```php * trap()->context(['foo bar', => 42, 'baz' => 69]); * ``` - * - * @param mixed ...$values */ public function context(mixed ...$values): self { diff --git a/src/Client/TrapHandle/ContextProvider/Source.php b/src/Client/TrapHandle/ContextProvider/Source.php index 100d4d6e..9ca0f349 100644 --- a/src/Client/TrapHandle/ContextProvider/Source.php +++ b/src/Client/TrapHandle/ContextProvider/Source.php @@ -27,24 +27,10 @@ */ final class Source implements ContextProviderInterface { - private int $limit; - - private ?string $charset; - - private ?string $projectDir; - - private ?FileLinkFormatter $fileLinkFormatter; - /** * @psalm-suppress UndefinedClass */ - public function __construct(string $charset = null, string $projectDir = null, FileLinkFormatter $fileLinkFormatter = null, int $limit = 9) - { - $this->charset = $charset; - $this->projectDir = $projectDir; - $this->fileLinkFormatter = $fileLinkFormatter; - $this->limit = $limit; - } + public function __construct(private readonly ?string $charset = null, private ?string $projectDir = null, private readonly ?FileLinkFormatter $fileLinkFormatter = null, private readonly int $limit = 9) {} public function getContext(): ?array { @@ -80,7 +66,7 @@ public function getContext(): ?array $file = \method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null; if ($src) { - $src = \explode("\n", $src); + $src = \explode("\n", (string) $src); $fileExcerpt = []; for ($i = \max($line - 3, 1), $max = \min($line + 3, \count($src)); $i <= $max; ++$i) { @@ -90,9 +76,11 @@ public function getContext(): ?array $fileExcerpt = '
    ' . \implode("\n", $fileExcerpt) . '
'; } } + break; } } + break; } } @@ -107,8 +95,8 @@ public function getContext(): ?array if ($this->projectDir !== null) { $context['project_dir'] = $this->projectDir; - if (\str_starts_with($file, $this->projectDir)) { - $context['file_relative'] = \ltrim(\substr($file, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + if (\str_starts_with((string) $file, $this->projectDir)) { + $context['file_relative'] = \ltrim(\substr((string) $file, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); } } @@ -123,7 +111,7 @@ private function htmlEncode(string $s): string { $html = ''; - $dumper = new HtmlDumper(static function ($line) use (&$html): void { $html .= $line; }, $this->charset); + $dumper = new HtmlDumper(static function (string $line) use (&$html): void { $html .= $line; }, $this->charset); $dumper->setDumpHeader(''); $dumper->setDumpBoundaries('', ''); diff --git a/src/Client/TrapHandle/Dumper.php b/src/Client/TrapHandle/Dumper.php index 10e662f6..69c3431f 100644 --- a/src/Client/TrapHandle/Dumper.php +++ b/src/Client/TrapHandle/Dumper.php @@ -4,6 +4,7 @@ namespace Buggregator\Trap\Client\TrapHandle; +use Buggregator\Trap\Client\TrapHandle\ContextProvider\Source; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; @@ -92,7 +93,7 @@ private static function registerHandler(): \Closure $dumper = new CliDumper(); break; case $format === 'server': - case $format && \parse_url($format, \PHP_URL_SCHEME) === 'tcp': + case $format && \parse_url((string) $format, \PHP_URL_SCHEME) === 'tcp': $host = $format === 'server' ? $_SERVER['VAR_DUMPER_SERVER'] ?? '127.0.0.1:9912' : $format; $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliDumper() : new HtmlDumper(); $dumper = new ServerDumper($host, $dumper, self::getContextProviders()); @@ -132,7 +133,7 @@ private static function getContextProviders(): array return $contextProviders + [ 'cli' => new CliContextProvider(), - 'source' => new ContextProvider\Source(null, null, $fileLinkFormatter), + 'source' => new Source(null, null, $fileLinkFormatter), ]; } }