diff --git a/.gitattributes b/.gitattributes index b8693428..6d4ec2e7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,6 +15,7 @@ phpcs.xml.dist export-ignore phpdoc.dist.xml export-ignore phpdoc.xml.dist export-ignore +phpstan.neon.dist export-ignore phpunit.xml.dist export-ignore phpunit10.xml.dist export-ignore /.github/ export-ignore diff --git a/.github/workflows/basics.yml b/.github/workflows/basics.yml index 9e58809f..266447bf 100644 --- a/.github/workflows/basics.yml +++ b/.github/workflows/basics.yml @@ -82,6 +82,33 @@ jobs: if: ${{ always() && steps.phpcs.outcome == 'failure' }} run: cs2pr ./phpcs-report.xml + phpstan: + name: "PHPStan" + runs-on: "ubuntu-latest" + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 'latest' + coverage: none + tools: phpstan + + # Install dependencies and handle caching in one go. + # Dependencies need to be installed to make sure the PHPCS and PHPUnit classes are recognized. + # @link https://github.com/marketplace/actions/install-composer-dependencies + - name: Install Composer dependencies + uses: "ramsey/composer-install@v2" + with: + # Bust the cache at least once a month - output format: YYYY-MM. + custom-cache-suffix: $(date -u "+%Y-%m") + + - name: Run PHPStan + run: phpstan analyse + markdownlint: name: 'Lint Markdown' runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index c88cda6f..20a89389 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ vendor/ /.phpunit.result.cache /docs/phpdoc/ !/docs/phpdoc/.nojekyll +phpstan.neon diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 00000000..027a3c88 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,95 @@ +parameters: + #phpVersion: 50400 # Needs to be 70100 or higher... sigh... + level: 6 + paths: + - phpcsutils-autoload.php + - .github/GHPages + - PHPCSUtils + - Tests + excludePaths: + # This file needs to be excluded as the availability of the traits depends on which PHPUnit Polyfills version is loaded/installed. + - Tests/PolyfilledTestCase.php + bootstrapFiles: + - Tests/bootstrap.php + treatPhpDocTypesAsCertain: false + + ignoreErrors: + # Level 0 + # Ignoring this as availability depends on which PHPUnit Polyfills version is loaded/installed. This is 100% okay. + - + message: '`^Call to an undefined method \S+UtilityMethodTestCase::setExpectedException\(\)\.$`' + path: PHPCSUtils/TestUtils/UtilityMethodTestCase.php + count: 1 + + # Level 1 + # These are on purpose for testing the magic method. This is 100% okay. + - + message: '`^Call to an undefined static method PHPCSUtils\\BackCompat\\BCTokens::notATokenArray\(\)\.$`' + path: Tests/BackCompat/BCTokens/UnchangedTokenArraysTest.php + count: 1 + - + message: '`^Call to an undefined static method PHPCSUtils\\Tokens\\Collections::notATokenArray\(\)\.$`' + path: Tests/Tokens/Collections/PropertyBasedTokenArraysTest.php + count: 1 + + # Level 2 + # Ignoring as this refers to a non-mocked method on the original class. This is 100% okay. + - + message: '`^Call to an undefined method PHPUnit\\Framework\\MockObject\\MockObject::process\(\)\.$`' + path: Tests/AbstractSniffs/AbstractArrayDeclaration/AbstractArrayDeclarationSniffTest.php + # Ignoring as availability depends on which PHPUnit version is loaded/installed. This is 100% okay. + - + message: '`^Call to an undefined method PHPUnit\\Framework\\MockObject\\MockBuilder<[^>]+>::setMethods\(\)\.$`' + path: Tests/AbstractSniffs/AbstractArrayDeclaration/AbstractArrayDeclarationSniffTest.php + count: 1 + + # Level 3 + # Ignoring as `null` is the initial value for config settings in PHPCS which this test is resetting to. + # The PHPCS docs just don't reflect that. This is 100% okay. + - + message: '`^Property PHP_CodeSniffer\\Config::\$\S+ \([^\)]+\) does not accept null\.$`' + path: Tests/BackCompat/Helper/GetCommandLineDataTest.php + + # Level 4 + # This is by design. + - + message: '`^Static method PHPCSUtils\\Tokens\\Collections::triggerDeprecation\(\) is unused\.$`' + path: PHPCSUtils/Tokens/Collections.php + count: 1 + + # This depends on the PHP version on which PHPStan is being run, so not valid. + - + message: "`^Comparison operation \"\\>\\=\" between '[0-9\\. -]+' and 10 is always true\\.$`" + path: Tests/Utils/Orthography/FirstCharTest.php + count: 1 + - + message: '`^Else branch is unreachable because previous condition is always true\.$`' + path: Tests/Utils/Orthography/FirstCharTest.php + count: 1 + + # Level 5 + # This is by design to test handling of incorrect input. + - + message: '`^Parameter #[0-9]+ \$\S+ of static method PHPCSUtils\\(?!Tests)[A-Za-z]+\\[A-Za-z]+::[A-Za-z]+\(\) expects [^,]+, \S+ given\.$`' + paths: + - Tests/BackCompat/BCFile/GetTokensAsStringTest.php + - Tests/Exceptions/TestTargetNotFound/TestTargetNotFoundTest.php + - Tests/Fixers/SpacesFixer/SpacesFixerExceptionsTest.php + - Tests/Utils/GetTokensAsString/GetTokensAsStringTest.php + + # Ignoring as this is fine. + - + message: '`^Parameter #1 \$exception of method PHPUnit\\Framework\\TestCase::expectException\(\) expects class-string, string given\.$`' + path: Tests/TestUtils/UtilityMethodTestCase/SkipJSCSSTestsOnPHPCS4Test.php + count: 1 + + # Level 6 + # Test data providers. + - + message: '`^Method PHPCSUtils\\Tests\\[^: ]+Test(Case)?::data\S+\(\) return type has no value type specified in iterable type array\.$`' + path: Tests/* + + # Test methods. + - + message: '`^Method PHPCSUtils\\Tests\\[^: ]+Test(Case)?::\S+\(\) has parameter \$\S* with no value type specified in iterable type array\.$`' + path: Tests/*