├── .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 | 
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 | }
--------------------------------------------------------------------------------