diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..fd20b19 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: +- package-ecosystem: composer + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: daily diff --git a/.github/workflows/phpcsfixer.yml b/.github/workflows/phpcsfixer.yml new file mode 100644 index 0000000..3b1265e --- /dev/null +++ b/.github/workflows/phpcsfixer.yml @@ -0,0 +1,56 @@ +name: PHPCSFixer + +on: + pull_request: + branches: + - develop + paths: + - '**.php' + - '.github/workflows/phpcsfixer.yml' + push: + branches: + - develop + paths: + - '**.php' + - '.github/workflows/phpcsfixer.yml' + +jobs: + build: + name: Coding Standards + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + extensions: json, tokenizer + coverage: none + env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: | + if [ -f composer.lock ]; then + composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader + else + composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader + fi + + - name: Check code for standards compliance + run: vendor/bin/php-cs-fixer fix --verbose --ansi --dry-run --using-cache=no --diff diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..6bdd00f --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,75 @@ +name: PHPStan + +on: + pull_request: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'phpstan*' + - '.github/workflows/phpstan.yml' + push: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'phpstan*' + - '.github/workflows/phpstan.yml' + +jobs: + build: + name: PHP ${{ matrix.php-versions }} Static Analysis + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + strategy: + fail-fast: false + matrix: + php-versions: ['7.4', '8.0', '8.1'] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + tools: phpstan, phpunit + extensions: intl, json, mbstring, xml + coverage: none + env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Create PHPStan cache directory + run: mkdir -p build/phpstan + + - name: Cache PHPStan results + uses: actions/cache@v3 + with: + path: build/phpstan + key: ${{ runner.os }}-phpstan-${{ github.sha }} + restore-keys: ${{ runner.os }}-phpstan- + + - name: Install dependencies + run: | + if [ -f composer.lock ]; then + composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader + else + composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader + fi + + - name: Run static analysis + run: vendor/bin/phpstan analyze diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml new file mode 100644 index 0000000..5e6ccf6 --- /dev/null +++ b/.github/workflows/psalm.yml @@ -0,0 +1,71 @@ +name: Psalm + +on: + pull_request: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'psalm*' + - '.github/workflows/psalm.yml' + push: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'psalm*' + - '.github/workflows/psalm.yml' + +jobs: + build: + name: Psalm Analysis + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + tools: phpstan, phpunit + extensions: intl, json, mbstring, xml + coverage: none + env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Create Psalm cache directory + run: mkdir -p build/psalm + + - name: Cache Psalm results + uses: actions/cache@v3 + with: + path: build/psalm + key: ${{ runner.os }}-psalm-${{ github.sha }} + restore-keys: ${{ runner.os }}-psalm- + + - name: Install dependencies + run: | + if [ -f composer.lock ]; then + composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader + else + composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader + fi + + - name: Run Psalm analysis + run: vendor/bin/psalm diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml new file mode 100644 index 0000000..36d6d2c --- /dev/null +++ b/.github/workflows/rector.yml @@ -0,0 +1,67 @@ +name: Rector + +on: + pull_request: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'rector.php' + - '.github/workflows/rector.yml' + push: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'rector.php' + - '.github/workflows/rector.yml' + +jobs: + build: + name: PHP ${{ matrix.php-versions }} Rector Analysis + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + strategy: + fail-fast: false + matrix: + php-versions: ['7.4', '8.0', '8.1'] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + tools: phpstan + extensions: intl, json, mbstring, xml + coverage: none + env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: | + if [ -f composer.lock ]; then + composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader + else + composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader + fi + + - name: Analyze for refactoring + run: | + composer global require --dev rector/rector:^0.13.8 + rector process --dry-run --no-progress-bar diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..ac7cd76 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,22 @@ +files() + ->in([ + __DIR__ . '/src/', + ]) + ->exclude('build') + ->append([__FILE__]); + +$overrides = []; + +$options = [ + 'finder' => $finder, + 'cacheFile' => 'build/.php-cs-fixer.cache', +]; + +return Factory::create(new CodeIgniter4(), $overrides, $options)->forProjects(); diff --git a/composer.json b/composer.json index e2b96d7..76f8c53 100644 --- a/composer.json +++ b/composer.json @@ -35,11 +35,29 @@ "roave/security-advisories": "dev-latest", "vimeo/psalm": "^4.22" }, + "require-dev": { + "codeigniter4/framework": "^4.1", + "icanhazstring/composer-unused": "^0.8.2", + "rector/rector": "0.13.5" + }, "minimum-stability": "dev", "prefer-stable": true, "config": { "allow-plugins": { "phpstan/extension-installer": true } + }, + "scripts": { + "analyze": [ + "phpstan analyze", + "psalm", + "rector process --dry-run" + ], + "ci": [ + "Composer\\Config::disableProcessTimeout", + "@analyze", + "@style" + ], + "style": "php-cs-fixer fix --verbose --ansi --using-cache=no" } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..113d258 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,21 @@ +parameters: + tmpDir: build/phpstan + level: 5 + paths: + - src/ + bootstrapFiles: + - vendor/codeigniter4/framework/system/Test/bootstrap.php + excludePaths: + - src/Config/Routes.php + - src/Views/* + ignoreErrors: + universalObjectCratesClasses: + - CodeIgniter\Entity + - CodeIgniter\Entity\Entity + - Faker\Generator + scanDirectories: + - vendor/codeigniter4/framework/system/Helpers + dynamicConstantNames: + - APP_NAMESPACE + - CI_DEBUG + - ENVIRONMENT diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..7c33561 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/psalm_autoload.php b/psalm_autoload.php new file mode 100644 index 0000000..5a25347 --- /dev/null +++ b/psalm_autoload.php @@ -0,0 +1,20 @@ +sets([SetList::DEAD_CODE, LevelSetList::UP_TO_PHP_74, PHPUnitSetList::PHPUNIT_SPECIFIC_METHOD, PHPUnitSetList::PHPUNIT_80]); + $rectorConfig->parallel(); + // The paths to refactor (can also be supplied with CLI arguments) + $rectorConfig->paths([ + __DIR__ . '/src/', + ]); + + // Include Composer's autoload - required for global execution, remove if running locally + $rectorConfig->autoloadPaths([ + __DIR__ . '/vendor/autoload.php', + ]); + + // Do you need to include constants, class aliases, or a custom autoloader? + $rectorConfig->bootstrapFiles([ + realpath(getcwd()) . '/vendor/codeigniter4/framework/system/Test/bootstrap.php', + ]); + + if (is_file(__DIR__ . '/phpstan.neon.dist')) { + $rectorConfig->phpstanConfig(__DIR__ . '/phpstan.neon.dist'); + } + + // Set the target version for refactoring + $rectorConfig->phpVersion(PhpVersion::PHP_74); + + // Auto-import fully qualified class names + $rectorConfig->importNames(); + + // Are there files or rules you need to skip? + $rectorConfig->skip([ + __DIR__ . '/src/Views', + + JsonThrowOnErrorRector::class, + StringifyStrNeedlesRector::class, + + // Note: requires php 8 + RemoveUnusedPromotedPropertyRector::class, + + // Ignore tests that might make calls without a result + RemoveEmptyMethodCallRector::class => [ + __DIR__ . '/tests', + ], + + // Ignore files that should not be namespaced + NormalizeNamespaceByPSR4ComposerAutoloadRector::class => [ + __DIR__ . '/src/Helpers', + ], + + // May load view files directly when detecting classes + StringClassNameToClassConstantRector::class, + + // May be uninitialized on purpose + AddDefaultValueForUndefinedVariableRector::class, + ]); + $rectorConfig->rule(SimplifyUselessVariableRector::class); + $rectorConfig->rule(RemoveAlwaysElseRector::class); + $rectorConfig->rule(CountArrayToEmptyArrayComparisonRector::class); + $rectorConfig->rule(ForToForeachRector::class); + $rectorConfig->rule(ChangeNestedForeachIfsToEarlyContinueRector::class); + $rectorConfig->rule(ChangeIfElseValueAssignToEarlyReturnRector::class); + $rectorConfig->rule(SimplifyStrposLowerRector::class); + $rectorConfig->rule(CombineIfRector::class); + $rectorConfig->rule(SimplifyIfReturnBoolRector::class); + $rectorConfig->rule(InlineIfToExplicitIfRector::class); + $rectorConfig->rule(PreparedValueToEarlyReturnRector::class); + $rectorConfig->rule(ShortenElseIfRector::class); + $rectorConfig->rule(SimplifyIfElseToTernaryRector::class); + $rectorConfig->rule(UnusedForeachValueToArrayKeysRector::class); + $rectorConfig->rule(ChangeArrayPushToArrayAssignRector::class); + $rectorConfig->rule(UnnecessaryTernaryExpressionRector::class); + $rectorConfig->rule(AddPregQuoteDelimiterRector::class); + $rectorConfig->rule(SimplifyRegexPatternRector::class); + $rectorConfig->rule(FuncGetArgsToVariadicParamRector::class); + $rectorConfig->rule(MakeInheritedMethodVisibilitySameAsParentRector::class); + $rectorConfig->rule(SimplifyEmptyArrayCheckRector::class); + $rectorConfig->rule(NormalizeNamespaceByPSR4ComposerAutoloadRector::class); + $rectorConfig + ->ruleWithConfiguration(TypedPropertyRector::class, [ + // Set to false if you use in libraries, or it does create breaking changes. + TypedPropertyRector::INLINE_PUBLIC => true, + ]); +}; diff --git a/src/Template/composer-unused.php b/src/Template/composer-unused.php index f46e293..6ba235a 100644 --- a/src/Template/composer-unused.php +++ b/src/Template/composer-unused.php @@ -7,11 +7,9 @@ use ComposerUnused\ComposerUnused\Configuration\PatternFilter; use Webmozart\Glob\Glob; -return static function (Configuration $config): Configuration { - return $config - // ->addNamedFilter(NamedFilter::fromString('symfony/config')) - // ->addPatternFilter(PatternFilter::fromString('/symfony-.*/')) - ->setAdditionalFilesFor('codeigniter4/framework', [ - ...Glob::glob(__DIR__ . '/vendor/codeigniter4/framework/system/Helpers/*.php'), - ]); -}; +return static fn (Configuration $config): Configuration => $config + // ->addNamedFilter(NamedFilter::fromString('symfony/config')) + // ->addPatternFilter(PatternFilter::fromString('/symfony-.*/')) + ->setAdditionalFilesFor('codeigniter4/framework', [ + ...Glob::glob(__DIR__ . '/vendor/codeigniter4/framework/system/Helpers/*.php'), + ]); diff --git a/src/Template/rector.php b/src/Template/rector.php index 212083a..5d05999 100644 --- a/src/Template/rector.php +++ b/src/Template/rector.php @@ -1,6 +1,5 @@