PHPStan 2.0 (#269)

* Rename to phpstan.dist.neon

* PHPStan 2.0
And update to PHP 8.1+

* Update CI

* Minor analyseAndScan
This commit is contained in:
Alexandre Alapetite 2025-01-12 11:05:16 +01:00 committed by GitHub
parent fecfe37e1c
commit 15d66caafb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 326 additions and 316 deletions

View file

@ -19,6 +19,9 @@ indent_style = tab
indent_size = 4 indent_size = 4
indent_style = tab indent_style = tab
[*.neon]
indent_style = tab
[*.xml] [*.xml]
indent_style = tab indent_style = tab

View file

@ -10,7 +10,7 @@ jobs:
tests: tests:
# https://github.com/actions/virtual-environments # https://github.com/actions/virtual-environments
runs-on: ubuntu-22.04 runs-on: ubuntu-24.04
defaults: defaults:
run: run:
working-directory: ./Extensions working-directory: ./Extensions
@ -54,16 +54,13 @@ jobs:
- name: PHPStan - name: PHPStan
run: composer run-script phpstan run: composer run-script phpstan
- name: PHPStan Next Level
run: composer run-script phpstan-next
# NPM tests # NPM tests
- name: Uses Node.js - name: Uses Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
# https://nodejs.org/en/about/releases/ # https://nodejs.org/en/about/releases/
node-version: '18' node-version: '22'
cache: 'npm' cache: 'npm'
cache-dependency-path: 'Extensions/package-lock.json' cache-dependency-path: 'Extensions/package-lock.json'

View file

@ -17,7 +17,7 @@
"WebSub" "WebSub"
], ],
"require": { "require": {
"php": ">=7.4", "php": ">=8.1",
"ext-ctype": "*", "ext-ctype": "*",
"ext-curl": "*", "ext-curl": "*",
"ext-dom": "*", "ext-dom": "*",
@ -45,28 +45,26 @@
"ext-pdo_pgsql": "*" "ext-pdo_pgsql": "*"
}, },
"require-dev": { "require-dev": {
"php": ">=7.4", "php": ">=8.1",
"ext-phar": "*", "ext-phar": "*",
"ext-tokenizer": "*", "ext-tokenizer": "*",
"ext-xmlwriter": "*", "ext-xmlwriter": "*",
"phpstan/phpstan": "^1.11", "phpstan/phpstan": "^2",
"phpstan/phpstan-strict-rules": "^1.6", "phpstan/phpstan-strict-rules": "^2",
"squizlabs/php_codesniffer": "^3.9" "squizlabs/php_codesniffer": "^3"
}, },
"scripts": { "scripts": {
"php-lint": "find . -type d -name 'vendor' -prune -o -name '*.php' -print0 | xargs -0 -n1 -P4 php -l 1>/dev/null", "php-lint": "find . -type d -name 'vendor' -prune -o -name '*.php' -print0 | xargs -0 -n1 -P4 php -l 1>/dev/null",
"phtml-lint": "find . -type d -name 'vendor' -prune -o -name '*.phtml' -print0 | xargs -0 -n1 -P4 php -l 1>/dev/null", "phtml-lint": "find . -type d -name 'vendor' -prune -o -name '*.phtml' -print0 | xargs -0 -n1 -P4 php -l 1>/dev/null",
"phpcs": "phpcs . -s", "phpcs": "phpcs . -s",
"phpcbf": "phpcbf . -p -s", "phpcbf": "phpcbf . -p -s",
"phpstan": "phpstan analyse --memory-limit 512M .", "phpstan": "phpstan analyse .",
"phpstan-next": "phpstan analyse --memory-limit 512M -c phpstan-next.neon .", "phpstan-third-party": "phpstan analyse -c phpstan-third-party.neon .",
"phpstan-third-party": "phpstan analyse --memory-limit 512M -c phpstan-third-party.neon .",
"test": [ "test": [
"@php-lint", "@php-lint",
"@phtml-lint", "@phtml-lint",
"@phpcs", "@phpcs",
"@phpstan", "@phpstan"
"@phpstan-next"
], ],
"fix": [ "fix": [
"@phpcbf" "@phpcbf"

51
composer.lock generated
View file

@ -4,25 +4,25 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "52101009acffc9684a721cc20ec9e731", "content-hash": "02054632764558c26a4fbecc0e408c09",
"packages": [], "packages": [],
"packages-dev": [ "packages-dev": [
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "1.11.10", "version": "2.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "640410b32995914bde3eed26fa89552f9c2c082f" "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/640410b32995914bde3eed26fa89552f9c2c082f", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7",
"reference": "640410b32995914bde3eed26fa89552f9c2c082f", "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.2|^8.0" "php": "^7.4|^8.0"
}, },
"conflict": { "conflict": {
"phpstan/phpstan-shim": "*" "phpstan/phpstan-shim": "*"
@ -63,32 +63,31 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-08-08T09:02:50+00:00" "time": "2025-01-05T16:43:48+00:00"
}, },
{ {
"name": "phpstan/phpstan-strict-rules", "name": "phpstan/phpstan-strict-rules",
"version": "1.6.0", "version": "2.0.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git", "url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "363f921dd8441777d4fc137deb99beb486c77df1" "reference": "ed6fea0ad4ad9c7e25f3ad2e7c4d420cf1e67fe3"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/363f921dd8441777d4fc137deb99beb486c77df1", "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/ed6fea0ad4ad9c7e25f3ad2e7c4d420cf1e67fe3",
"reference": "363f921dd8441777d4fc137deb99beb486c77df1", "reference": "ed6fea0ad4ad9c7e25f3ad2e7c4d420cf1e67fe3",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.2 || ^8.0", "php": "^7.4 || ^8.0",
"phpstan/phpstan": "^1.11" "phpstan/phpstan": "^2.0.4"
}, },
"require-dev": { "require-dev": {
"nikic/php-parser": "^4.13.0",
"php-parallel-lint/php-parallel-lint": "^1.2", "php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/phpstan-deprecation-rules": "^1.1", "phpstan/phpstan-deprecation-rules": "^2.0",
"phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-phpunit": "^2.0",
"phpunit/phpunit": "^9.5" "phpunit/phpunit": "^9.6"
}, },
"type": "phpstan-extension", "type": "phpstan-extension",
"extra": { "extra": {
@ -110,22 +109,22 @@
"description": "Extra strict and opinionated rules for PHPStan", "description": "Extra strict and opinionated rules for PHPStan",
"support": { "support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.0" "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.1"
}, },
"time": "2024-04-20T06:37:51+00:00" "time": "2024-12-12T20:21:10+00:00"
}, },
{ {
"name": "squizlabs/php_codesniffer", "name": "squizlabs/php_codesniffer",
"version": "3.10.2", "version": "3.11.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
"reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017" "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/86e5f5dd9a840c46810ebe5ff1885581c42a3017", "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/1368f4a58c3c52114b86b1abe8f4098869cb0079",
"reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017", "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -192,7 +191,7 @@
"type": "open_collective" "type": "open_collective"
} }
], ],
"time": "2024-07-21T23:26:44+00:00" "time": "2024-12-11T16:04:26+00:00"
} }
], ],
"aliases": [], "aliases": [],
@ -201,7 +200,7 @@
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"php": ">=7.4", "php": ">=8.1",
"ext-ctype": "*", "ext-ctype": "*",
"ext-curl": "*", "ext-curl": "*",
"ext-dom": "*", "ext-dom": "*",
@ -224,7 +223,7 @@
"ext-zlib": "*" "ext-zlib": "*"
}, },
"platform-dev": { "platform-dev": {
"php": ">=7.4", "php": ">=8.1",
"ext-phar": "*", "ext-phar": "*",
"ext-tokenizer": "*", "ext-tokenizer": "*",
"ext-xmlwriter": "*" "ext-xmlwriter": "*"

View file

@ -29,7 +29,7 @@ try {
} }
foreach ($repositories as $repository) { foreach ($repositories as $repository) {
if (null === $url = ($repository['url'] ?? null)) { if (!is_array($repository) || !is_string($url = ($repository['url'] ?? null))) {
continue; continue;
} }
if (TYPE_GIT === ($repository['type'] ?? null)) { if (TYPE_GIT === ($repository['type'] ?? null)) {
@ -54,7 +54,7 @@ foreach ($gitRepositories as $key => $gitRepository) {
} }
$directory = basename(dirname($metadataFile)); $directory = basename(dirname($metadataFile));
$metadata['url'] = $gitRepository; $metadata['url'] = $gitRepository;
$metadata['version'] = strval($metadata['version']); $metadata['version'] = is_scalar($metadata['version'] ?? null) ? strval($metadata['version']) : '';
$metadata['method'] = TYPE_GIT; $metadata['method'] = TYPE_GIT;
$metadata['directory'] = ($directory === sha1($gitRepository)) ? '.' : $directory; $metadata['directory'] = ($directory === sha1($gitRepository)) ? '.' : $directory;
$extensions[] = $metadata; $extensions[] = $metadata;

View file

@ -1,9 +0,0 @@
includes:
- phpstan.neon
parameters:
level: 9
excludePaths:
analyse:
- xExtension-ImageProxy/configure.phtml
- xExtension-ImageProxy/extension.php

View file

@ -1,5 +1,8 @@
parameters: parameters:
level: 0 phpVersion:
min: 80100 # PHP 8.1
max: 80499 # PHP 8.4
level: 0 # https://phpstan.org/user-guide/rule-levels
fileExtensions: fileExtensions:
- php - php
- phtml - phtml
@ -9,13 +12,13 @@ parameters:
excludePaths: excludePaths:
analyse: analyse:
- ../FreshRSS - ../FreshRSS
- third-party/*/vendor/* - third-party/*/vendor/*?
analyseAndScan: analyseAndScan:
- .git/ - .git/*?
- node_modules/ - node_modules/*?
- symbolic/ - symbolic/*?
- third-party/*/tests/* - third-party/*/tests/*?
- tmp/ - tmp/*?
- vendor/ - vendor/
- xExtension-* - xExtension-*
dynamicConstantNames: dynamicConstantNames:

43
phpstan.dist.neon Normal file
View file

@ -0,0 +1,43 @@
parameters:
phpVersion:
min: 80100 # PHP 8.1
max: 80499 # PHP 8.4
level: 10 # https://phpstan.org/user-guide/rule-levels
fileExtensions:
- php
- phtml
paths:
- ../FreshRSS
- .
excludePaths:
analyse:
- ../FreshRSS
- xExtension-ImageProxy/configure.phtml # TODO pass
- xExtension-ImageProxy/extension.php # TODO pass
analyseAndScan:
- .git/*?
- node_modules/*?
- symbolic/*?
- third-party/*?
- tmp/*?
- vendor/
dynamicConstantNames:
- TYPE_GIT
checkBenevolentUnionTypes: true
checkMissingOverrideMethodAttribute: true
checkTooWideReturnTypesInProtectedAndPublicMethods: true
reportAnyTypeWideningInVarTag: true
treatPhpDocTypesAsCertain: false
strictRules:
disallowedEmpty: false
disallowedLooseComparison: false
disallowedShortTernary: false
exceptions:
check:
missingCheckedExceptionInThrows: true
tooWideThrowType: true
implicitThrows: false
checkedExceptionClasses:
- 'Minz_Exception'
includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon

View file

@ -1,48 +0,0 @@
parameters:
# TODO: Increase rule-level https://phpstan.org/user-guide/rule-levels
level: 1
phpVersion: 80399 # TODO: Remove line when moving composer.json to PHP 8+
fileExtensions:
- php
- phtml
paths:
- ../FreshRSS
- .
excludePaths:
analyse:
- ../FreshRSS
- vendor/
analyseAndScan:
- .git/
- node_modules/
- symbolic/
- third-party/
- tmp/
dynamicConstantNames:
- TYPE_GIT
checkMissingOverrideMethodAttribute: true
reportMaybesInPropertyPhpDocTypes: false
treatPhpDocTypesAsCertain: false
strictRules:
allRules: false
booleansInConditions: true
closureUsesThis: true
disallowedConstructs: false
disallowedLooseComparison: false
matchingInheritedMethodNames: true
noVariableVariables: true
numericOperandsInArithmeticOperators: true
overwriteVariablesWithLoop: true
requireParentConstructorCall: true
strictCalls: true
switchConditionsMatchingType: true
uselessCast: true
exceptions:
check:
missingCheckedExceptionInThrows: false # TODO pass
tooWideThrowType: true
implicitThrows: false
checkedExceptionClasses:
- 'Minz_Exception'
includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon

View file

@ -3,7 +3,10 @@ declare(strict_types=1);
final class FreshExtension_quickCollapse_Controller extends Minz_ActionController { final class FreshExtension_quickCollapse_Controller extends Minz_ActionController {
/** @var QuickCollapse\View */ /**
* @var QuickCollapse\View
* @phpstan-ignore property.phpDocType
*/
protected $view; protected $view;
public function __construct() { public function __construct() {

View file

@ -5,7 +5,10 @@ declare(strict_types=1);
final class FreshExtension_shareByEmail_Controller extends Minz_ActionController { final class FreshExtension_shareByEmail_Controller extends Minz_ActionController {
public ?Minz_Extension $extension; public ?Minz_Extension $extension;
/** @var ShareByEmail\mailers\View */ /**
* @var ShareByEmail\mailers\View
* @phpstan-ignore property.phpDocType
*/
protected $view; protected $view;
public function __construct() { public function __construct() {
@ -17,6 +20,12 @@ final class FreshExtension_shareByEmail_Controller extends Minz_ActionController
$this->extension = Minz_ExtensionManager::findExtension('Share By Email'); $this->extension = Minz_ExtensionManager::findExtension('Share By Email');
} }
/**
* @throws FreshRSS_Context_Exception
* @throws Minz_ConfigurationException
* @throws Minz_ConfigurationNamespaceException
* @throws Minz_PDOConnectionException
*/
public function shareAction(): void { public function shareAction(): void {
if (!FreshRSS_Auth::hasAccess()) { if (!FreshRSS_Auth::hasAccess()) {
Minz_Error::error(403); Minz_Error::error(403);

View file

@ -4,6 +4,9 @@ declare(strict_types=1);
final class ShareByEmailExtension extends Minz_Extension { final class ShareByEmailExtension extends Minz_Extension {
/**
* @throws Minz_ConfigurationException
*/
#[\Override] #[\Override]
public function init(): void { public function init(): void {
$this->registerTranslates(); $this->registerTranslates();

View file

@ -6,13 +6,19 @@ namespace ShareByEmail\mailers;
final class Share extends \Minz_Mailer { final class Share extends \Minz_Mailer {
/** @var View */ /**
* @var View
* @phpstan-ignore property.phpDocType
*/
protected $view; protected $view;
public function __construct() { public function __construct() {
parent::__construct(View::class); parent::__construct(View::class);
} }
/**
* @throws \FreshRSS_Context_Exception
*/
public function send_article(string $to, string $subject, string $content): bool { public function send_article(string $to, string $subject, string $content): bool {
$this->view->_path('share_mailer/article.txt.php'); $this->view->_path('share_mailer/article.txt.php');

View file

@ -55,6 +55,7 @@ final class YouTubeExtension extends Minz_Extension
/** /**
* Initializes the extension configuration, if the user context is available. * Initializes the extension configuration, if the user context is available.
* Do not call that in your extensions init() method, it can't be used there. * Do not call that in your extensions init() method, it can't be used there.
* @throws FreshRSS_Context_Exception
*/ */
public function loadConfigValues(): void public function loadConfigValues(): void
{ {
@ -121,6 +122,7 @@ final class YouTubeExtension extends Minz_Extension
/** /**
* Inserts the YouTube video iframe into the content of an entry, if the entries link points to a YouTube watch URL. * Inserts the YouTube video iframe into the content of an entry, if the entries link points to a YouTube watch URL.
* @throws FreshRSS_Context_Exception
*/ */
public function embedYouTubeVideo(FreshRSS_Entry $entry): FreshRSS_Entry public function embedYouTubeVideo(FreshRSS_Entry $entry): FreshRSS_Entry
{ {
@ -208,21 +210,21 @@ final class YouTubeExtension extends Minz_Extension
// We hide the title so it doesn't appear in the final article, which would be redundant with the RSS article title, // We hide the title so it doesn't appear in the final article, which would be redundant with the RSS article title,
// but we keep it in the content anyway, so RSS clients can extract it if needed. // but we keep it in the content anyway, so RSS clients can extract it if needed.
if ($titles->length > 0) { if ($titles->length > 0 && $titles[0] instanceof DOMNode) {
$content .= '<p class="enclosure-title" hidden>' . $titles[0]->nodeValue . '</p>'; $content .= '<p class="enclosure-title" hidden>' . $titles[0]->nodeValue . '</p>';
} }
// We hide the thumbnail so it doesn't appear in the final article, which would be redundant with the YouTube player preview, // We hide the thumbnail so it doesn't appear in the final article, which would be redundant with the YouTube player preview,
// but we keep it in the content anyway, so RSS clients can extract it to display a preview where it wants (in article listing, // but we keep it in the content anyway, so RSS clients can extract it to display a preview where it wants (in article listing,
// by example, like with Reeder). // by example, like with Reeder).
if ($thumbnails->length > 0) { if ($thumbnails->length > 0 && $thumbnails[0] instanceof DOMNode) {
$content .= '<p hidden><img class="enclosure-thumbnail" src="' . $thumbnails[0]->nodeValue . '" alt=""/></p>'; $content .= '<p hidden><img class="enclosure-thumbnail" src="' . $thumbnails[0]->nodeValue . '" alt=""/></p>';
} }
$content .= $iframe; $content .= $iframe;
if ($descriptions->length > 0) { if ($descriptions->length > 0 && $descriptions[0] instanceof DOMNode) {
$content .= '<p class="enclosure-description">' . nl2br(htmlentities($descriptions[0]->nodeValue)) . '</p>'; $content .= '<p class="enclosure-description">' . nl2br(htmlentities($descriptions[0]->nodeValue ?? '')) . '</p>';
} }
$content .= "</div>\n"; $content .= "</div>\n";
@ -242,6 +244,7 @@ final class YouTubeExtension extends Minz_Extension
* This function is called by FreshRSS when the configuration page is loaded, and when configuration is saved. * This function is called by FreshRSS when the configuration page is loaded, and when configuration is saved.
* - We save configuration in case of a post. * - We save configuration in case of a post.
* - We (re)load configuration in all case, so they are in-sync after a save and before a page load. * - We (re)load configuration in all case, so they are in-sync after a save and before a page load.
* @throws FreshRSS_Context_Exception
*/ */
#[\Override] #[\Override]
public function handleConfigureAction(): void public function handleConfigureAction(): void