Merge pull request #6944 from pixelfed/chore/add-psalm-static-analysis

chore: add Psalm static analysis (plugin-laravel, baseline, CI)
pull/6946/head
Shlee 4 weeks ago committed by GitHub
commit aa46ca2ba2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -28,3 +28,8 @@ Dockerfile
README.md
.editorconfig
.phpunit.result.cache
# Psalm reports and cache
storage/psalm-reports
psalm.sarif.json
.psalm-cache

@ -0,0 +1,58 @@
name: PHP - Psalm
on:
push:
branches: [staging, dev, unstable]
pull_request:
branches: [staging, dev, unstable]
permissions:
contents: read
# Required for github/codeql-action/upload-sarif to publish Code Scanning alerts.
security-events: write
concurrency:
group: psalm-${{ github.ref }}
cancel-in-progress: true
jobs:
psalm:
name: Psalm type analysis
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.5'
extensions: mbstring, pdo_sqlite, bcmath, intl
coverage: none
- name: Cache Composer dependencies
uses: actions/cache@v6
with:
path: vendor
key: composer-8.5-${{ hashFiles('composer.lock') }}
restore-keys: composer-8.5-
- name: Install Composer dependencies
run: composer install --no-interaction --no-progress --prefer-dist
# --output-format=github emits workflow annotations so findings appear inline
# on the PR's Files changed view. --report writes SARIF for Code Scanning upload.
# Reporting only: `|| true` keeps the step (and job) green even when Psalm finds
# issues, so this workflow surfaces findings without ever blocking a PR.
- name: Run Psalm (type analysis, report only)
run: vendor/bin/psalm --output-format=github --report=psalm.sarif.json || true
# Surface findings as Code Scanning alerts on the PR (Security tab). Runs even
# when the Psalm step is skipped/failed, so the alerts are still published.
- name: Upload SARIF to Code Scanning
if: ${{ !cancelled() }}
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: psalm.sarif.json
category: psalm

5
.gitignore vendored

@ -31,3 +31,8 @@
# Exceptions - these *MUST* be last
!/bootstrap/cache/.gitignore
!/public/vendor/horizon/.gitignore
# Psalm reports and cache
/storage/psalm-reports
/psalm.sarif.json
/.psalm-cache

@ -54,7 +54,8 @@
"laravel/telescope": "^5.5",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.8",
"pestphp/pest": "^4.0"
"pestphp/pest": "^4.0",
"psalm/plugin-laravel": "^3"
},
"autoload": {
"classmap": [

2019
composer.lock generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,411 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="6.5.0@38fc8444edf0cebc9205296ee6e30e906ade783b">
<file src="app/Console/Commands/FixBugs/FixUsernames.php">
<NoValue>
<code><![CDATA[$u]]></code>
</NoValue>
</file>
<file src="app/Console/Commands/Status/StatusUser.php">
<ParadoxicalCondition>
<code><![CDATA[in_array($key, $longText, true) && is_string($value) && strlen($value) > 60]]></code>
</ParadoxicalCondition>
</file>
<file src="app/Http/Controllers/AccountController.php">
<NoValue>
<code><![CDATA[return abort(403);]]></code>
<code><![CDATA[return abort(403);]]></code>
<code><![CDATA[return abort(403);]]></code>
<code><![CDATA[return abort(403);]]></code>
<code><![CDATA[return abort(406);]]></code>
<code><![CDATA[return abort(406);]]></code>
<code><![CDATA[return abort(406);]]></code>
<code><![CDATA[return abort(406);]]></code>
</NoValue>
</file>
<file src="app/Http/Controllers/Admin/AdminReportController.php">
<NoValue>
<code><![CDATA[return abort(403);]]></code>
</NoValue>
</file>
<file src="app/Http/Controllers/AdminCuratedRegisterController.php">
<NoValue>
<code><![CDATA[$ur]]></code>
</NoValue>
</file>
<file src="app/Http/Controllers/Api/AdminApiController.php">
<NoValue>
<code><![CDATA[return abort(403);]]></code>
</NoValue>
</file>
<file src="app/Http/Controllers/Api/ApiV1Dot1Controller.php">
<UndefinedDocblockClass>
<code><![CDATA[StatusTransformer]]></code>
</UndefinedDocblockClass>
</file>
<file src="app/Http/Controllers/Api/V1/DomainBlockController.php">
<NoValue>
<code><![CDATA[return abort(500, 'Invalid domain or already blocked by server admins');]]></code>
</NoValue>
</file>
<file src="app/Http/Controllers/AppRegisterController.php">
<InaccessibleProperty>
<code><![CDATA[$token->token]]></code>
</InaccessibleProperty>
</file>
<file src="app/Http/Controllers/Import/Instagram.php">
<NoValue>
<code><![CDATA[return abort(500);]]></code>
</NoValue>
</file>
<file src="app/Http/Controllers/PersonalAccessTokenController.php">
<InaccessibleProperty>
<code><![CDATA[$result->token]]></code>
<code><![CDATA[$result->token]]></code>
</InaccessibleProperty>
</file>
<file src="app/Http/Controllers/ProfileController.php">
<NoValue>
<code><![CDATA[return abort(404);]]></code>
</NoValue>
</file>
<file src="app/Http/Controllers/ReportController.php">
<NoValue>
<code><![CDATA[return abort(400, 'Invalid report type');]]></code>
<code><![CDATA[return abort(400, 'Invalid report type');]]></code>
</NoValue>
</file>
<file src="app/Http/Middleware/Api/Admin.php">
<NoValue>
<code><![CDATA[return abort(403, 'You must be an administrator to do that');]]></code>
</NoValue>
</file>
<file src="app/Models/AdminShadowFilter.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/AutospamCustomTokens.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/ConfigCache.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/Conversation.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/CuratedRegister.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/CuratedRegisterActivity.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/CuratedRegisterTemplate.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/CustomEmoji.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/DefaultDomainBlock.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/Group.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupActivityGraph.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupBlock.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupCategory.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupComment.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupEvent.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupHashtag.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupInteraction.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupInvitation.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupLike.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupLimit.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupMedia.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupMember.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupPost.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupPostHashtag.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupReport.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupRole.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/GroupStore.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/HashtagRelated.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/ImportPost.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/InstanceActor.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/LiveStream.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/ModeratedProfile.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/ParentalControls.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/Poll.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/PollVote.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/Portfolio.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/Profile.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/ProfileAlias.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/ProfileMigration.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/RemoteAuth.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/RemoteAuthInstance.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/RemoteReport.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/Status.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/StatusArchived.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/StatusEdit.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/StatusView.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/User.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/UserAppSettings.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/UserDomainBlock.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/UserEmailForgot.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/UserOidcMapping.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/UserPronoun.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Models/UserRoles.php">
<MissingTemplateParam>
<code><![CDATA[HasFactory]]></code>
</MissingTemplateParam>
</file>
<file src="app/Services/StoryIndexService.php">
<NoValue>
<code><![CDATA[$results]]></code>
</NoValue>
</file>
<file src="app/Util/Lexer/Autolink.php">
<UndefinedDocblockClass>
<code><![CDATA[autolinkActiveUsersOnly]]></code>
<code><![CDATA[protected $autolinkActiveUsersOnly = false;]]></code>
</UndefinedDocblockClass>
</file>
<file src="app/Util/Lexer/Regex.php">
<UnsupportedPropertyReferenceUsage>
<code><![CDATA[$re = &self::$patterns]]></code>
</UnsupportedPropertyReferenceUsage>
</file>
<file src="config/websockets.php">
<UndefinedClass>
<code><![CDATA[Authorize]]></code>
<code><![CDATA[ConfigAppManager]]></code>
<code><![CDATA[DatabaseStore]]></code>
<code><![CDATA[FetchChannel]]></code>
<code><![CDATA[FetchChannels]]></code>
<code><![CDATA[FetchUsers]]></code>
<code><![CDATA[FulfilledPromise]]></code>
<code><![CDATA[HealthHandler]]></code>
<code><![CDATA[LocalChannelManager]]></code>
<code><![CDATA[MemoryCollector]]></code>
<code><![CDATA[RedisChannelManager]]></code>
<code><![CDATA[RedisCollector]]></code>
<code><![CDATA[TriggerEvent]]></code>
<code><![CDATA[WebSocketHandler]]></code>
</UndefinedClass>
</file>
<file src="database/factories/ActivityFactory.php">
<MissingTemplateParam>
<code><![CDATA[ActivityFactory]]></code>
</MissingTemplateParam>
</file>
<file src="database/factories/FollowerFactory.php">
<MissingTemplateParam>
<code><![CDATA[FollowerFactory]]></code>
</MissingTemplateParam>
</file>
<file src="database/factories/ProfileFactory.php">
<MissingTemplateParam>
<code><![CDATA[ProfileFactory]]></code>
</MissingTemplateParam>
</file>
<file src="database/factories/StatusFactory.php">
<MissingTemplateParam>
<code><![CDATA[StatusFactory]]></code>
</MissingTemplateParam>
</file>
<file src="database/factories/UserFactory.php">
<MissingTemplateParam>
<code><![CDATA[UserFactory]]></code>
</MissingTemplateParam>
</file>
<file src="public/index.php">
<MissingFile>
<code><![CDATA[require __DIR__.'/../storage/framework/maintenance.php']]></code>
</MissingFile>
</file>
</files>

@ -0,0 +1,53 @@
<?xml version="1.0"?>
<psalm
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
errorLevel="8"
findUnusedCode="false"
ensureOverrideAttribute="false"
errorBaseline="psalm-baseline.xml"
>
<projectFiles>
<directory name="app"/>
<directory name="bootstrap"/>
<directory name="config"/>
<directory name="database"/>
<directory name="routes"/>
<!-- composer require psalm/plugin-phpunit psalm/plugin-mockery for the full tests support -->
<!-- <directory name="tests"/>-->
<file name="public/index.php"/>
<file name="artisan"/>
<ignoreFiles allowMissingFiles="true">
<directory name="bootstrap/cache"/>
<directory name="storage"/>
<directory name="vendor"/>
</ignoreFiles>
</projectFiles>
<plugins>
<!-- All Psalm Laravel options: https://github.com/psalm/psalm-plugin-laravel/blob/master/docs/config.md -->
<pluginClass class="Psalm\LaravelPlugin\Plugin">
<resolveDynamicWhereClauses value="true" />
<findMissingTranslations value="false" />
<findMissingViews value="false" />
</pluginClass>
</plugins>
<issueHandlers>
<ImplicitToStringCast errorLevel="info"/>
<MissingClosureReturnType errorLevel="info"/>
<MissingImmutableAnnotation errorLevel="info"/>
<MissingOverrideAttribute errorLevel="info"/>
<RedundantCast errorLevel="info"/>
<RedundantCondition errorLevel="info"/>
<UnnecessaryVarAnnotation errorLevel="suppress"/>
</issueHandlers>
<forbiddenFunctions>
<function name="var_dump" />
<function name="dd" />
</forbiddenFunctions>
</psalm>
Loading…
Cancel
Save