├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── assets └── screenshot1.png ├── composer.json ├── src └── SarifErrorFormatter.php └── tests ├── phpstan.neon └── test.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: jbelien 4 | # patreon: # Replace with a single Patreon username 5 | # open_collective: # Replace with a single Open Collective username 6 | # ko_fi: # Replace with a single Ko-fi username 7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | # liberapay: # Replace with a single Liberapay username 10 | # issuehunt: # Replace with a single IssueHunt username 11 | # otechie: # Replace with a single Otechie username 12 | # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "composer" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "monthly" 12 | - package-ecosystem: "github-actions" # See documentation for possible values 13 | directory: "/" # Location of package manifests 14 | schedule: 15 | interval: "monthly" 16 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | local-phpstant-1x: 14 | name: Local PHPStan 1.x (${{ matrix.php-version }}) 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: read # for checkout to fetch code 18 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 19 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | php-version: [8.1, 8.2, 8.3, 8.4] 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Use PHP ${{ matrix.php-version }} 27 | uses: shivammathur/setup-php@v2 28 | with: 29 | php-version: ${{ matrix.php-version }} 30 | - name: Validate composer.json and composer.lock 31 | run: composer validate --strict 32 | - name: Install dependencies 33 | run: composer update --prefer-lowest --no-progress 34 | - name: Run PHPStan 35 | continue-on-error: true 36 | run: vendor/bin/phpstan analyze --configuration=tests/phpstan.neon --error-format=sarif --xdebug "tests/" > local-results.sarif 37 | - uses: actions/upload-artifact@v4 38 | with: 39 | name: local-v1-${{ matrix.php-version }} 40 | path: local-results.sarif 41 | - name: Upload analysis results to GitHub 42 | uses: github/codeql-action/upload-sarif@v3 43 | with: 44 | sarif_file: local-results.sarif 45 | wait-for-processing: true 46 | local-phpstant-2x: 47 | name: Local PHPStan 2.x (${{ matrix.php-version }}) 48 | runs-on: ubuntu-latest 49 | permissions: 50 | contents: read # for checkout to fetch code 51 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 52 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 53 | strategy: 54 | fail-fast: false 55 | matrix: 56 | php-version: [8.1, 8.2, 8.3, 8.4] 57 | steps: 58 | - uses: actions/checkout@v4 59 | - name: Use PHP ${{ matrix.php-version }} 60 | uses: shivammathur/setup-php@v2 61 | with: 62 | php-version: ${{ matrix.php-version }} 63 | - name: Validate composer.json and composer.lock 64 | run: composer validate --strict 65 | - name: Install dependencies 66 | run: composer update --prefer-dist --no-progress 67 | - name: Run PHPStan 68 | continue-on-error: true 69 | run: vendor/bin/phpstan analyze --configuration=tests/phpstan.neon --error-format=sarif --xdebug "tests/" > local-results.sarif 70 | - uses: actions/upload-artifact@v4 71 | with: 72 | name: local-v2-${{ matrix.php-version }} 73 | path: local-results.sarif 74 | - name: Upload analysis results to GitHub 75 | uses: github/codeql-action/upload-sarif@v3 76 | with: 77 | sarif_file: local-results.sarif 78 | wait-for-processing: true 79 | 80 | package: 81 | name: Package ${{ matrix.version }} (${{ matrix.php-version }}) 82 | runs-on: ubuntu-latest 83 | permissions: 84 | contents: read # for checkout to fetch code 85 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 86 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 87 | strategy: 88 | fail-fast: false 89 | matrix: 90 | php-version: [8.1, 8.2, 8.3, 8.4] 91 | version: ['dev-master', 1.0, 1.1] 92 | steps: 93 | - uses: actions/checkout@v4 94 | - name: Use PHP ${{ matrix.php-version }} 95 | uses: shivammathur/setup-php@v2 96 | with: 97 | php-version: ${{ matrix.php-version }} 98 | - run: rm composer.* 99 | - name: Install PHPStan SARIF Formatter 100 | run: composer require --dev --prefer-dist --no-progress phpstan/phpstan jbelien/phpstan-sarif-formatter:${{ matrix.version }} 101 | - name: Run PHPStan 102 | continue-on-error: true 103 | run: vendor/bin/phpstan analyze --configuration=tests/phpstan.neon --error-format=sarif --xdebug "tests/" > package-results.sarif 104 | - uses: actions/upload-artifact@v4 105 | with: 106 | name: package-${{ matrix.version }}-${{ matrix.php-version }} 107 | path: package-results.sarif 108 | - name: Upload analysis results to GitHub 109 | uses: github/codeql-action/upload-sarif@v3 110 | with: 111 | sarif_file: package-results.sarif 112 | wait-for-processing: true 113 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jonathan Beliën 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SARIF formatter for PHPStan 2 | 3 | 🔖 **Compatible with PHPStan 1.x and 2.x** 4 | 5 | ## PHPStan 6 | 7 | PHPStan focuses on finding errors in your code without actually running it. It catches whole classes of bugs even before you write tests for the code. It moves PHP closer to compiled languages in the sense that the correctness of each line of the code can be checked before you run the actual line. 8 | 9 | 10 | 11 | ## Static Analysis Results Interchange Format (SARIF) 12 | 13 | SARIF, the Static Analysis Results Interchange Format, is a standard, JSON-based format for the output of static analysis tool. 14 | It has been [approved](https://www.oasis-open.org/news/announcements/static-analysis-results-interchange-format-sarif-v2-1-0-is-approved-as-an-oasis-s) as an [OASIS](https://www.oasis-open.org/) standard. 15 | 16 | SARIF is a rich format intended to meet the needs of sophisticated tools, while still being practical for use by simpler tools. 17 | Because it would be impractical to support every feature of every tool, SARIF provides an extensibility mechanism to allow tool authors to store custom data that the SARIF format doesn't directly represent. 18 | 19 | 20 | 21 | ## Usage 22 | 23 | ```cmd 24 | composer require --dev phpstan/phpstan jbelien/phpstan-sarif-formatter 25 | ``` 26 | 27 | Then update your `phpstan.neon` configuration file: 28 | 29 | ```yaml 30 | services: 31 | errorFormatter.sarif: 32 | class: PHPStanSarifErrorFormatter\SarifErrorFormatter 33 | arguments: 34 | relativePathHelper: @simpleRelativePathHelper 35 | currentWorkingDirectory: %currentWorkingDirectory% 36 | pretty: true 37 | ``` 38 | 39 | ## GitHub Code Scanning 40 | 41 | Documentation: 42 | 43 | GitHub Code Scanning features are compatible with SARIF. 44 | The SARIF Formatter for PHPStan allows you to use PHPStan as GitHub Code Scanning tool. 45 | 46 | To use in one of your GitHub Actions workflows, add the following in your job: 47 | 48 | ```yaml 49 | - name: Run PHPStan 50 | continue-on-error: true 51 | run: phpstan analyze --error-format=sarif > phpstan-results.sarif 52 | 53 | - name: Upload analysis results to GitHub 54 | uses: github/codeql-action/upload-sarif@v2 55 | with: 56 | sarif_file: phpstan-results.sarif 57 | wait-for-processing: true 58 | ``` 59 | 60 | It will display PHPStan error messages in your PR (check [this PR](https://github.com/jbelien/phpstan-sarif-formatter/pull/1)) and add alerts in the "Code scanning" report in "Security" tab of your project (see screenshot below). 61 | 62 | ![Screenshot](assets/screenshot1.png) 63 | -------------------------------------------------------------------------------- /assets/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelien/phpstan-sarif-formatter/6a21162f610238f86647065b7c99c9c7af380a96/assets/screenshot1.png -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jbelien/phpstan-sarif-formatter", 3 | "description": "SARIF formatter for PHPStan", 4 | "license": "MIT", 5 | "require": { 6 | "php": "^8.1", 7 | "nette/utils": "^3.0 || ^4.0", 8 | "phpstan/phpstan": "^1.9 || ^2.0" 9 | }, 10 | "autoload": { 11 | "psr-4": { 12 | "PHPStanSarifErrorFormatter\\": "src/" 13 | } 14 | }, 15 | "config": { 16 | "optimize-autoloader": true, 17 | "preferred-install": "dist", 18 | "sort-packages": true 19 | } 20 | } -------------------------------------------------------------------------------- /src/SarifErrorFormatter.php: -------------------------------------------------------------------------------- 1 | [ 34 | 'name' => 'PHPStan', 35 | 'fullName' => 'PHP Static Analysis Tool', 36 | 'informationUri' => 'https://phpstan.org', 37 | 'version' => $phpstanVersion, 38 | 'semanticVersion' => $phpstanVersion, 39 | 'rules' => $rules, 40 | ], 41 | ]; 42 | 43 | $originalUriBaseIds = [ 44 | self::URI_BASE_ID => [ 45 | 'uri' => 'file://' . $this->currentWorkingDirectory . '/', 46 | ], 47 | ]; 48 | 49 | $results = []; 50 | 51 | foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { 52 | $result = [ 53 | 'level' => $fileSpecificError->canBeIgnored() ? 'warning' : 'error', 54 | 'message' => [ 55 | 'text' => $fileSpecificError->getMessage(), 56 | ], 57 | 'locations' => [ 58 | [ 59 | 'physicalLocation' => [ 60 | 'artifactLocation' => [ 61 | 'uri' => $this->relativePathHelper->getRelativePath($fileSpecificError->getFile()), 62 | 'uriBaseId' => self::URI_BASE_ID, 63 | ], 64 | 'region' => [ 65 | 'startLine' => $fileSpecificError->getLine(), 66 | ], 67 | ], 68 | ], 69 | ], 70 | 'properties' => [ 71 | 'ignorable' => $fileSpecificError->canBeIgnored(), 72 | ], 73 | ]; 74 | 75 | if ($fileSpecificError->getIdentifier() !== null) { 76 | $result['ruleId'] = $fileSpecificError->getIdentifier(); 77 | 78 | $ruleID = $fileSpecificError->getIdentifier(); 79 | $rules[$ruleID] = [ 80 | 'id' => $ruleID, 81 | 'shortDescription' => [ 82 | 'text' => $fileSpecificError->getMessage() 83 | ], 84 | ]; 85 | 86 | if ($fileSpecificError->getTip() !== null) { 87 | $rules[$ruleID]['help']['text'] = $fileSpecificError->getTip(); 88 | } 89 | } 90 | 91 | if ($fileSpecificError->getTip() !== null) { 92 | $result['properties']['tip'] = $fileSpecificError->getTip(); 93 | } 94 | 95 | $results[] = $result; 96 | } 97 | 98 | foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) { 99 | $results[] = [ 100 | 'level' => 'error', 101 | 'message' => [ 102 | 'text' => $notFileSpecificError, 103 | ], 104 | ]; 105 | } 106 | 107 | foreach ($analysisResult->getWarnings() as $warning) { 108 | $results[] = [ 109 | 'level' => 'warning', 110 | 'message' => [ 111 | 'text' => $warning, 112 | ], 113 | ]; 114 | } 115 | 116 | $tool['driver']['rules'] = array_values($rules); 117 | 118 | $sarif = [ 119 | '$schema' => 'https://json.schemastore.org/sarif-2.1.0.json', 120 | 'version' => '2.1.0', 121 | 'runs' => [ 122 | [ 123 | 'tool' => $tool, 124 | 'originalUriBaseIds' => $originalUriBaseIds, 125 | 'results' => $results, 126 | ], 127 | ], 128 | ]; 129 | 130 | $json = Json::encode($sarif, $this->pretty ? Json::PRETTY : 0); 131 | 132 | $output->writeRaw($json); 133 | 134 | return $analysisResult->hasErrors() ? 1 : 0; 135 | } 136 | } -------------------------------------------------------------------------------- /tests/phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | fileExtensions: 3 | - php 4 | level: 8 5 | services: 6 | errorFormatter.sarif: 7 | class: PHPStanSarifErrorFormatter\SarifErrorFormatter 8 | arguments: 9 | relativePathHelper: @simpleRelativePathHelper 10 | currentWorkingDirectory: %currentWorkingDirectory% 11 | pretty: true -------------------------------------------------------------------------------- /tests/test.php: -------------------------------------------------------------------------------- 1 | string = intval($string); 11 | $this->int = (string) $int; 12 | } 13 | } --------------------------------------------------------------------------------