diff --git a/bin/docs/generate_issues_list_doc.php b/bin/docs/generate_issues_list_doc.php
index 0b324af3504..e75266af371 100755
--- a/bin/docs/generate_issues_list_doc.php
+++ b/bin/docs/generate_issues_list_doc.php
@@ -3,7 +3,7 @@
declare(strict_types=1);
-$docs_dir = dirname(__DIR__) . DIRECTORY_SEPARATOR . "docs"
+$docs_dir = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . "docs"
. DIRECTORY_SEPARATOR . "running_psalm" . DIRECTORY_SEPARATOR;
$issues_index = "{$docs_dir}issues.md";
$issues_dir = "{$docs_dir}issues";
diff --git a/bin/docs/generate_levels_doc.php b/bin/docs/generate_levels_doc.php
index 10915c9a577..4e01d091c81 100644
--- a/bin/docs/generate_levels_doc.php
+++ b/bin/docs/generate_levels_doc.php
@@ -5,7 +5,7 @@
use Psalm\Config\IssueHandler;
use Psalm\Issue\CodeIssue;
-require_once(dirname(__DIR__) . '/vendor/autoload.php');
+require_once(dirname(__DIR__, 2) . '/vendor/autoload.php');
$issue_types = IssueHandler::getAllIssueTypes();
@@ -60,7 +60,7 @@
$result .= ' - [' . $issue_type . '](issues/' . $issue_type . '.md)' . "\n";
}
-$f = dirname(__DIR__).'/docs/running_psalm/error_levels.md';
+$f = dirname(__DIR__, 2).'/docs/running_psalm/error_levels.md';
$content = file_get_contents($f);
$content = explode('', $content)[0].$result;
diff --git a/bin/docs/max_used_shortcode.php b/bin/docs/max_used_shortcode.php
index 54df5f0899d..f1c5b3b949f 100755
--- a/bin/docs/max_used_shortcode.php
+++ b/bin/docs/max_used_shortcode.php
@@ -5,7 +5,7 @@
use Psalm\Config\IssueHandler;
-require_once(dirname(__DIR__) . '/vendor/autoload.php');
+require_once(dirname(__DIR__, 2) . '/vendor/autoload.php');
$issue_types = IssueHandler::getAllIssueTypes();
diff --git a/bin/stubs/gen_base_callmap.php b/bin/stubs/gen_base_callmap.php
index cac6c652dfc..af1bfe53389 100644
--- a/bin/stubs/gen_base_callmap.php
+++ b/bin/stubs/gen_base_callmap.php
@@ -116,6 +116,6 @@ function paramsToEntries(ReflectionFunctionAbstract $reflectionFunction, string
$payload = ' require __DIR__."/../dictionaries/override/CallMap.php",
+ 84 => require __DIR__."/../../dictionaries/override/CallMap.php",
];
$customDiffs = [];
-foreach (glob(__DIR__."/../dictionaries/override/CallMap_*.php") as $file) {
+foreach (glob(__DIR__."/../../dictionaries/override/CallMap_*.php") as $file) {
if (!preg_match('/_(\d+)_delta\.php/', $file, $matches)) {
continue;
}
@@ -202,11 +202,11 @@
// Merge hand-written full maps into autogenerated full maps, write to files
foreach ($customMaps as $version => $data) {
$data = normalizeCallMap(array_replace($baseMaps[$version] ?? [], $data));
- writeCallMap(__DIR__."/../dictionaries/CallMap_$version.php", $data);
+ writeCallMap(__DIR__."/../../dictionaries/CallMap_$version.php", $data);
}
// Overwrite custom diff maps
-writeCallMap(__DIR__."/../dictionaries/override/CallMap.php", $customMaps[$last]);
+writeCallMap(__DIR__."/../../dictionaries/override/CallMap.php", $customMaps[$last]);
// Regenerate diff maps from custom maps
foreach ($customMaps as $prevVersion => $prevData) {
@@ -233,7 +233,7 @@
}
}
- writeCallMap(__DIR__."/../dictionaries/override/CallMap_{$nextVersion}_delta.php", normalizeCallMap($diff));
+ writeCallMap(__DIR__."/../../dictionaries/override/CallMap_{$nextVersion}_delta.php", normalizeCallMap($diff));
$nextVersion = $prevVersion;
$nextData = $prevData;
diff --git a/bin/stubs/gen_callmap.sh b/bin/stubs/gen_callmap.sh
index e1a0538cb08..c18994dc32a 100755
--- a/bin/stubs/gen_callmap.sh
+++ b/bin/stubs/gen_callmap.sh
@@ -10,8 +10,8 @@ done
wait
for f in $VERSIONS; do
- docker run --rm -it -v $PWD:/app psalm_test_$f php /app/bin/gen_base_callmap.php
+ docker run --rm -it -v $PWD:/app psalm_test_$f php /app/bin/stubs/gen_base_callmap.php
done
-php bin/gen_callmap.php
-php bin/gen_callmap.php
\ No newline at end of file
+php bin/stubs/gen_callmap.php
+php bin/stubs/gen_callmap.php
diff --git a/bin/stubs/update-property-map.php b/bin/stubs/update-property-map.php
index 5d7188458d7..115bc01f912 100755
--- a/bin/stubs/update-property-map.php
+++ b/bin/stubs/update-property-map.php
@@ -33,7 +33,7 @@
$stubbedClasses = [];
foreach (new RecursiveDirectoryIterator(
- __DIR__ . '/../stubs',
+ __DIR__ . '/../../stubs',
FilesystemIterator::CURRENT_AS_PATHNAME|FilesystemIterator::SKIP_DOTS,
) as $file) {
if (is_dir($file)) {
@@ -47,7 +47,7 @@
}
unset($file, $contents, $stmts);
-$docDir = realpath(__DIR__ . '/../build/doc-en');
+$docDir = realpath(__DIR__ . '/../../build/doc-en');
if (false === $docDir) {
echo 'PHP doc not found!' . PHP_EOL;
diff --git a/config.xsd b/config.xsd
index c0a9fd37db2..3d9a2c13b04 100644
--- a/config.xsd
+++ b/config.xsd
@@ -219,6 +219,7 @@
+
diff --git a/docs/running_psalm/error_levels.md b/docs/running_psalm/error_levels.md
index 490696d547f..1dd02d7562b 100644
--- a/docs/running_psalm/error_levels.md
+++ b/docs/running_psalm/error_levels.md
@@ -136,6 +136,7 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even
These issues are treated as errors at level 2 and below.
+ - [ClassMustBeFinal](issues/ClassMustBeFinal.md)
- [DeprecatedClass](issues/DeprecatedClass.md)
- [DeprecatedConstant](issues/DeprecatedConstant.md)
- [DeprecatedFunction](issues/DeprecatedFunction.md)
diff --git a/docs/running_psalm/issues.md b/docs/running_psalm/issues.md
index d438b9a3d43..62614decd43 100644
--- a/docs/running_psalm/issues.md
+++ b/docs/running_psalm/issues.md
@@ -7,6 +7,7 @@
- [AssignmentToVoid](issues/AssignmentToVoid.md)
- [CheckType](issues/CheckType.md)
- [CircularReference](issues/CircularReference.md)
+ - [ClassMustBeFinal](issues/ClassMustBeFinal.md)
- [ComplexFunction](issues/ComplexFunction.md)
- [ComplexMethod](issues/ComplexMethod.md)
- [ConfigIssue](issues/ConfigIssue.md)
diff --git a/docs/running_psalm/issues/ClassMustBeFinal.md b/docs/running_psalm/issues/ClassMustBeFinal.md
new file mode 100644
index 00000000000..b477a37b38b
--- /dev/null
+++ b/docs/running_psalm/issues/ClassMustBeFinal.md
@@ -0,0 +1,48 @@
+# ClassMustBeFinal
+
+Emitted when a non-final, non-abstract class with no child classes is found.
+
+```php
+
-
+
tags['variablesfrom'][0]]]>
@@ -66,6 +66,7 @@
+
diff --git a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php
index b9477098912..80b0862a07a 100644
--- a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php
@@ -22,6 +22,7 @@
use Psalm\Internal\Provider\ProjectCacheProvider;
use Psalm\Internal\Provider\Providers;
use Psalm\Internal\Provider\StatementsProvider;
+use Psalm\Issue\ClassMustBeFinal;
use Psalm\Issue\CodeIssue;
use Psalm\Issue\InvalidFalsableReturnType;
use Psalm\Issue\InvalidNullableReturnType;
@@ -164,6 +165,7 @@ final class ProjectAnalyzer
* @var array>
*/
private const SUPPORTED_ISSUES_TO_FIX = [
+ ClassMustBeFinal::class,
InvalidFalsableReturnType::class,
InvalidNullableReturnType::class,
InvalidReturnType::class,
diff --git a/src/Psalm/Internal/Codebase/ClassLikes.php b/src/Psalm/Internal/Codebase/ClassLikes.php
index 0f6318efc3f..ebe746214dd 100644
--- a/src/Psalm/Internal/Codebase/ClassLikes.php
+++ b/src/Psalm/Internal/Codebase/ClassLikes.php
@@ -24,6 +24,7 @@
use Psalm\Internal\Provider\FileReferenceProvider;
use Psalm\Internal\Provider\StatementsProvider;
use Psalm\Internal\Type\TypeExpander;
+use Psalm\Issue\ClassMustBeFinal;
use Psalm\Issue\PossiblyUnusedMethod;
use Psalm\Issue\PossiblyUnusedParam;
use Psalm\Issue\PossiblyUnusedProperty;
@@ -861,6 +862,34 @@ public function consolidateAnalyzedData(Methods $methods, ?Progress $progress, b
}
$this->checkMethodParamReferences($classlike_storage);
}
+ if (!$classlike_storage->public_api
+ && !$classlike_storage->has_children
+ && !$classlike_storage->abstract
+ && !$classlike_storage->final
+ && !$classlike_storage->is_enum
+ && !$classlike_storage->is_interface
+ ) {
+ IssueBuffer::maybeAdd(
+ new ClassMustBeFinal(
+ 'Class ' . $classlike_storage->name
+ . ' is never extended and is not part of the public API, and thus must be made final.',
+ $classlike_storage->location,
+ $classlike_storage->name,
+ ),
+ $classlike_storage->suppressed_issues,
+ true,
+ );
+
+ if ($codebase->alter_code
+ && $classlike_storage->stmt_location !== null
+ && isset($project_analyzer->getIssuesToFix()['ClassMustBeFinal'])
+ ) {
+ $idx = $classlike_storage->stmt_location->getSelectionBounds()[0];
+ FileManipulationBuffer::add($classlike_storage->stmt_location->file_path, [
+ new FileManipulation($idx, $idx, 'final ', true),
+ ]);
+ }
+ }
$this->findPossibleMethodParamTypes($classlike_storage);
diff --git a/src/Psalm/Internal/Codebase/Populator.php b/src/Psalm/Internal/Codebase/Populator.php
index f00e45fa50e..7f9b74e550d 100644
--- a/src/Psalm/Internal/Codebase/Populator.php
+++ b/src/Psalm/Internal/Codebase/Populator.php
@@ -560,6 +560,8 @@ private function populateDataFromParentClass(
$storage->declaring_pseudo_method_ids[$method_name] = $pseudo_method_id;
};
}
+
+ $parent_storage->has_children = true;
}
private function populateInterfaceData(
diff --git a/src/Psalm/Internal/Provider/ParamsProvider/ArrayFilterParamsProvider.php b/src/Psalm/Internal/Provider/ParamsProvider/ArrayFilterParamsProvider.php
index 4404473fe47..a1f264bd11f 100644
--- a/src/Psalm/Internal/Provider/ParamsProvider/ArrayFilterParamsProvider.php
+++ b/src/Psalm/Internal/Provider/ParamsProvider/ArrayFilterParamsProvider.php
@@ -29,7 +29,7 @@
/**
* @internal
*/
-class ArrayFilterParamsProvider implements FunctionParamsProviderInterface
+final class ArrayFilterParamsProvider implements FunctionParamsProviderInterface
{
/**
* @return array
diff --git a/src/Psalm/Internal/Provider/ParamsProvider/ArrayMultisortParamsProvider.php b/src/Psalm/Internal/Provider/ParamsProvider/ArrayMultisortParamsProvider.php
index a27b79566cd..a7018149e97 100644
--- a/src/Psalm/Internal/Provider/ParamsProvider/ArrayMultisortParamsProvider.php
+++ b/src/Psalm/Internal/Provider/ParamsProvider/ArrayMultisortParamsProvider.php
@@ -32,7 +32,7 @@
/**
* @internal
*/
-class ArrayMultisortParamsProvider implements FunctionParamsProviderInterface
+final class ArrayMultisortParamsProvider implements FunctionParamsProviderInterface
{
/**
* @return array
diff --git a/src/Psalm/Internal/Provider/ParamsProvider/ArrayUArrayParamsProvider.php b/src/Psalm/Internal/Provider/ParamsProvider/ArrayUArrayParamsProvider.php
index a57ca3058b3..ccfe60aef32 100644
--- a/src/Psalm/Internal/Provider/ParamsProvider/ArrayUArrayParamsProvider.php
+++ b/src/Psalm/Internal/Provider/ParamsProvider/ArrayUArrayParamsProvider.php
@@ -19,7 +19,7 @@
/**
* @internal
*/
-class ArrayUArrayParamsProvider implements FunctionParamsProviderInterface
+final class ArrayUArrayParamsProvider implements FunctionParamsProviderInterface
{
/**
diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterInputReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterInputReturnTypeProvider.php
index 511f269f9b8..f4d0857e6dc 100644
--- a/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterInputReturnTypeProvider.php
+++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterInputReturnTypeProvider.php
@@ -34,7 +34,7 @@
/**
* @internal
*/
-class FilterInputReturnTypeProvider implements FunctionReturnTypeProviderInterface
+final class FilterInputReturnTypeProvider implements FunctionReturnTypeProviderInterface
{
/**
* @return array
diff --git a/src/Psalm/Issue/ClassMustBeFinal.php b/src/Psalm/Issue/ClassMustBeFinal.php
new file mode 100644
index 00000000000..99209e11e30
--- /dev/null
+++ b/src/Psalm/Issue/ClassMustBeFinal.php
@@ -0,0 +1,11 @@
+ [
'src/A.php' => <<<'PHP'
do();
@@ -137,7 +137,7 @@ public function do(B $b): void
PHP,
'src/B.php' => <<<'PHP'
[
'src/A.php' => <<<'PHP'
value;
@@ -174,7 +174,7 @@ public function foo(B $b): int
PHP,
'src/B.php' => <<<'PHP'
[
'src/B.php' => <<<'PHP'
<<<'PHP'
foo(1);
@@ -240,7 +240,7 @@ public function foo(): void
/**
* @template K
*/
- class A {
+ final class A {
/**
* @param T $baz
*/
@@ -268,7 +268,7 @@ public function foo($baz): void
'files' => [
'src/A.php' => <<<'PHP'
[
'src/A.php' => <<<'PHP'
' 'foo();
}
@@ -139,11 +139,11 @@ class A { }',
(string) getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'B.php' => ' 'foo();
}
@@ -160,14 +160,14 @@ public function bar() : void {
(string) getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo;
}
@@ -179,14 +179,14 @@ public function foo() : string {
(string) getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo;
}
@@ -203,13 +203,13 @@ public function foo() : string {
(string) getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' 'getB()->getString();
}
@@ -272,7 +272,7 @@ public function existingMethod() : string {
(string) getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' 'getB()->getString();
}
@@ -304,7 +304,7 @@ public function newMethod() : void {}
(string) getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' 'getB()->getString();
}
@@ -337,7 +337,7 @@ public function existingMethod() : string {
(string) getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' ' ' ' ' 'foo;',
],
@@ -548,7 +548,7 @@ abstract class A {
(string) getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'B.php' => 'foo;',
],
@@ -568,13 +568,13 @@ abstract class A {
public function __construct() {}
}
- class C extends A {}
+ final class C extends A {}
new C();',
(string) getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'B.php' => ' ' ' ' ' ' ' 'UnusedClass',
@@ -672,7 +672,7 @@ class A {}',
(string) getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'foo("hello");',
@@ -760,11 +760,11 @@ class B extends A {
(string) getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => 'foo();
}
@@ -776,11 +776,11 @@ public function bar() : void {
(string) getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'foo;
}
@@ -841,12 +841,12 @@ public function bar() : void {
(string) getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' ' ' ' [[84], [84]],
+ 'error_positions' => [[90], [90]],
'ignored_issues' => [],
'test_save' => false,
'check_unused_code' => true,
@@ -1750,7 +1750,7 @@ class B {}',
(string) getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo();',
],
],
- 'error_positions' => [[201], [234]],
+ 'error_positions' => [[207], [240]],
'ignored_issues' => [],
'test_save' => false,
'check_unused_code' => true,
@@ -1787,7 +1787,7 @@ public function bar() : void {}
(string) getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' '>
* @psalm-type TCallMaps=array>>
*/
-class CallMapTest extends TestCase
+final class CallMapTest extends TestCase
{
protected const DICTIONARY_PATH = 'dictionaries';
diff --git a/tests/Internal/CliUtilsTest.php b/tests/Internal/CliUtilsTest.php
index 1f67fa50832..3e8a66d12dd 100644
--- a/tests/Internal/CliUtilsTest.php
+++ b/tests/Internal/CliUtilsTest.php
@@ -11,7 +11,7 @@
use const DIRECTORY_SEPARATOR;
-class CliUtilsTest extends TestCase
+final class CliUtilsTest extends TestCase
{
/**
* @var list
diff --git a/tests/Internal/Codebase/InternalCallMapHandlerTest.php b/tests/Internal/Codebase/InternalCallMapHandlerTest.php
index 459ca64226f..bb380f903f3 100644
--- a/tests/Internal/Codebase/InternalCallMapHandlerTest.php
+++ b/tests/Internal/Codebase/InternalCallMapHandlerTest.php
@@ -50,7 +50,7 @@
use const PHP_VERSION_ID;
/** @group callmap */
-class InternalCallMapHandlerTest extends TestCase
+final class InternalCallMapHandlerTest extends TestCase
{
/**
* Regex patterns for callmap entries that should be skipped.
diff --git a/tests/Internal/Provider/ClassLikeStorageInstanceCacheProvider.php b/tests/Internal/Provider/ClassLikeStorageInstanceCacheProvider.php
index 657ac4a2d93..6420c13c98a 100644
--- a/tests/Internal/Provider/ClassLikeStorageInstanceCacheProvider.php
+++ b/tests/Internal/Provider/ClassLikeStorageInstanceCacheProvider.php
@@ -10,7 +10,7 @@
use function strtolower;
-class ClassLikeStorageInstanceCacheProvider extends ClassLikeStorageCacheProvider
+final class ClassLikeStorageInstanceCacheProvider extends ClassLikeStorageCacheProvider
{
/** @var array */
private array $cache = [];
diff --git a/tests/Internal/Provider/FakeFileReferenceCacheProvider.php b/tests/Internal/Provider/FakeFileReferenceCacheProvider.php
index caa5e92ad73..f3ea07b5977 100644
--- a/tests/Internal/Provider/FakeFileReferenceCacheProvider.php
+++ b/tests/Internal/Provider/FakeFileReferenceCacheProvider.php
@@ -11,7 +11,7 @@
* Used to determine which files reference other files, necessary for using the --diff
* option from the command line.
*/
-class FakeFileReferenceCacheProvider extends FileReferenceCacheProvider
+final class FakeFileReferenceCacheProvider extends FileReferenceCacheProvider
{
private ?array $cached_file_references = null;
diff --git a/tests/Internal/Provider/FakeParserCacheProvider.php b/tests/Internal/Provider/FakeParserCacheProvider.php
index db5f8c6e835..fe76a4bee9d 100644
--- a/tests/Internal/Provider/FakeParserCacheProvider.php
+++ b/tests/Internal/Provider/FakeParserCacheProvider.php
@@ -6,7 +6,7 @@
use Psalm\Internal\Provider\ParserCacheProvider;
-class FakeParserCacheProvider extends ParserCacheProvider
+final class FakeParserCacheProvider extends ParserCacheProvider
{
public function __construct()
{
diff --git a/tests/Internal/Provider/FileStorageInstanceCacheProvider.php b/tests/Internal/Provider/FileStorageInstanceCacheProvider.php
index d32bc4e63f2..2d9dde04d92 100644
--- a/tests/Internal/Provider/FileStorageInstanceCacheProvider.php
+++ b/tests/Internal/Provider/FileStorageInstanceCacheProvider.php
@@ -9,7 +9,7 @@
use function strtolower;
-class FileStorageInstanceCacheProvider extends FileStorageCacheProvider
+final class FileStorageInstanceCacheProvider extends FileStorageCacheProvider
{
/** @var array */
private array $cache = [];
diff --git a/tests/Internal/Provider/ParserInstanceCacheProvider.php b/tests/Internal/Provider/ParserInstanceCacheProvider.php
index c227fec9935..ba2a4769a6e 100644
--- a/tests/Internal/Provider/ParserInstanceCacheProvider.php
+++ b/tests/Internal/Provider/ParserInstanceCacheProvider.php
@@ -9,7 +9,7 @@
use function microtime;
-class ParserInstanceCacheProvider extends ParserCacheProvider
+final class ParserInstanceCacheProvider extends ParserCacheProvider
{
/**
* @var array
diff --git a/tests/Internal/Provider/ProjectCacheProvider.php b/tests/Internal/Provider/ProjectCacheProvider.php
index a8ad1219b4f..e3a0529af72 100644
--- a/tests/Internal/Provider/ProjectCacheProvider.php
+++ b/tests/Internal/Provider/ProjectCacheProvider.php
@@ -6,7 +6,7 @@
use Psalm\Internal\Provider\ProjectCacheProvider as PsalmProjectCacheProvider;
-class ProjectCacheProvider extends PsalmProjectCacheProvider
+final class ProjectCacheProvider extends PsalmProjectCacheProvider
{
private int $last_run = 0;
diff --git a/tests/Internal/Scanner/FileScannerTest.php b/tests/Internal/Scanner/FileScannerTest.php
index 588738fa041..40af1ab936d 100644
--- a/tests/Internal/Scanner/FileScannerTest.php
+++ b/tests/Internal/Scanner/FileScannerTest.php
@@ -15,7 +15,7 @@
use Psalm\Tests\TestCase;
use Psalm\Tests\TestConfig;
-class FileScannerTest extends TestCase
+final class FileScannerTest extends TestCase
{
/**
* @dataProvider providerScan
diff --git a/tests/InternalAnnotationTest.php b/tests/InternalAnnotationTest.php
index c54ea686e48..4f48a15bae9 100644
--- a/tests/InternalAnnotationTest.php
+++ b/tests/InternalAnnotationTest.php
@@ -7,7 +7,7 @@
use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait;
use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;
-class InternalAnnotationTest extends TestCase
+final class InternalAnnotationTest extends TestCase
{
use InvalidCodeAnalysisTestTrait;
use ValidCodeAnalysisTestTrait;
diff --git a/tests/IssueBufferTest.php b/tests/IssueBufferTest.php
index 375e701274c..69273f2e2e8 100644
--- a/tests/IssueBufferTest.php
+++ b/tests/IssueBufferTest.php
@@ -17,7 +17,7 @@
use function ob_get_clean;
use function ob_start;
-class IssueBufferTest extends TestCase
+final class IssueBufferTest extends TestCase
{
public function testFinishDoesNotCorruptInternalState(): void
diff --git a/tests/IssueSuppressionTest.php b/tests/IssueSuppressionTest.php
index 3b703bb4260..9fd22b64c5e 100644
--- a/tests/IssueSuppressionTest.php
+++ b/tests/IssueSuppressionTest.php
@@ -15,7 +15,7 @@
use const DIRECTORY_SEPARATOR;
-class IssueSuppressionTest extends TestCase
+final class IssueSuppressionTest extends TestCase
{
use ValidCodeAnalysisTestTrait;
use InvalidCodeAnalysisTestTrait;
@@ -217,7 +217,7 @@ public function testPossiblyUnusedPropertySuppressedOnClass(): void
$file_path,
'addFile(
$file_path,
'addFile(
$file_path,
'bar();
}
- class Foo {
+ final class Foo {
function bar(): void{
echo "foo";
}
@@ -152,7 +152,7 @@ public function testSeesUnusedClassReferencedByUnevaluatedCode(): void
echo "bar";
}
- class Foo {
+ final class Foo {
function bar(): void{
echo "foo";
}
@@ -174,9 +174,27 @@ function bar(): void{
public function providerValidCodeParse(): array
{
return [
+ 'nonFinalClassWithChildren' => [
+ 'code' => ' [
+ 'code' => ' [
+ 'code' => ' [
'code' => ' [
'code' => 'foo();
}
@@ -254,11 +272,11 @@ private function foo() : void {}
interface I {
public function foo() : void;
}
- class B implements I {
+ final class B implements I {
public function foo() : void {}
}
- class A
+ final class A
{
/**
* @var I
@@ -312,7 +330,7 @@ function foo(int $unusedArg) : void {}
],
'possiblyUnusedParamWithUnderscore' => [
'code' => ' [
'code' => ' [
'code' => ' [
@@ -339,7 +357,7 @@ public function foo() : void {}
trait T {
public function foo() : void {}
}
- class B extends A {
+ final class B extends A {
use T;
}
function takesA(A $a) : void {
@@ -354,11 +372,11 @@ protected function getC() : C {
return new C;
}
}
- class C {
+ final class C {
public function foo() : void {}
}
- class B extends A {
+ final class B extends A {
public function bar() : void {
$c = $this->getC();
@@ -374,7 +392,7 @@ public function bar() : void {
],
'suppressPrivateUnusedMethod' => [
'code' => ' [
'code' => ' [
'code' => ' [
'code' => ' [
'code' => ' [
'code' => ' [
'code' => ' [
'code' => ' [
'code' => ' [
'code' => ' [
'code' => ' [
'code' => ' [
'code' => ' [
'code' => ' [
'code' => 'baz();',
],
'usedMethodReferencedByString' => [
'code' => ' [
'code' => ' [
'code' => '
*/
@@ -898,7 +916,7 @@ public function bar(array $map) : void {
],
'promotedPropertyIsUsed' => [
'code' => ' [
'code' => ' [
'code' => 'a === "goodbye") {}
}
@@ -1084,7 +1102,7 @@ public function foo(A $a): void {
],
'privatePropertyReadInMethod' => [
'code' => ' [
'code' => 'work();
@@ -1160,7 +1178,7 @@ function f(IWorker $worker): void {
],
'methodReturnValueUsedInThrow' => [
'code' => ' [
'code' => ' [
'code' => 'assert($val);
@@ -1272,7 +1290,7 @@ public function validate(): void
],
'usedPropertyAsAssignmentKey' => [
'code' => ' <<<'PHP'
[
@@ -1311,7 +1329,7 @@ protected function c(): void {}
'psalm-api on unused public method' => [
'code' => <<<'PHP'
<<<'PHP'
[
'code' => <<<'PHP'
[
'code' => ' 'UnusedClass',
],
'publicUnusedMethod' => [
'code' => ' [
'code' => ' [
'code' => ' [
'code' => ' */
public array $foo = [];
}
@@ -1443,7 +1461,7 @@ class A {
],
'unusedProperty' => [
'code' => ' [
'code' => ' [
'code' => 'foo();
@@ -1506,7 +1524,7 @@ public function bar() : void {}
],
'unusedRecursivelyUsedStaticMethod' => [
'code' => ' [
'code' => ' [
'code' => ' ' [
'code' => ' [
'code' => ' <<<'PHP'
<<<'PHP'
'UnusedDocblockParam',
],
+ 'nonFinalClass' => [
+ 'code' => ' 'ClassMustBeFinal',
+ ],
];
}
}
diff --git a/tests/UnusedVariableTest.php b/tests/UnusedVariableTest.php
index 03dba56ae84..34a4c261f88 100644
--- a/tests/UnusedVariableTest.php
+++ b/tests/UnusedVariableTest.php
@@ -9,7 +9,7 @@
use const DIRECTORY_SEPARATOR;
-class UnusedVariableTest extends TestCase
+final class UnusedVariableTest extends TestCase
{
use ValidCodeAnalysisTestTrait;
use InvalidCodeAnalysisTestTrait;
diff --git a/tests/ValueOfTest.php b/tests/ValueOfTest.php
index ee9c13b2540..072edec3203 100644
--- a/tests/ValueOfTest.php
+++ b/tests/ValueOfTest.php
@@ -7,7 +7,7 @@
use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait;
use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;
-class ValueOfTest extends TestCase
+final class ValueOfTest extends TestCase
{
use InvalidCodeAnalysisTestTrait;
use ValidCodeAnalysisTestTrait;
diff --git a/tests/VariadicTest.php b/tests/VariadicTest.php
index f8d67d7626c..5f7fb8480a0 100644
--- a/tests/VariadicTest.php
+++ b/tests/VariadicTest.php
@@ -17,7 +17,7 @@
use function dirname;
use function getcwd;
-class VariadicTest extends TestCase
+final class VariadicTest extends TestCase
{
use ValidCodeAnalysisTestTrait;
diff --git a/tests/autoload.php b/tests/autoload.php
index 449bca68efa..92ea18a9aff 100644
--- a/tests/autoload.php
+++ b/tests/autoload.php
@@ -7,3 +7,4 @@
require __DIR__ . '/../vendor/autoload.php';
BypassFinals::enable();
+BypassFinals::denyPaths(['*tests/fixtures/DummyProject*']);
diff --git a/tests/fixtures/DummyProject/Bar.php b/tests/fixtures/DummyProject/Bar.php
index 16245ebd252..7b114fab336 100644
--- a/tests/fixtures/DummyProject/Bar.php
+++ b/tests/fixtures/DummyProject/Bar.php
@@ -1,7 +1,7 @@
* @author Brent R. Matzelle (original founder)
*/
-class PHPMailer
+final class PHPMailer
{
const CHARSET_ISO88591 = 'iso-8859-1';
const CHARSET_UTF8 = 'utf-8';
diff --git a/tests/fixtures/performance/b.test b/tests/fixtures/performance/b.test
index c750465932f..b47ed93ebd3 100644
--- a/tests/fixtures/performance/b.test
+++ b/tests/fixtures/performance/b.test
@@ -28,7 +28,7 @@ namespace PHPMailer\PHPMailer;
* @author Andy Prevost (codeworxtech)
* @author Brent R. Matzelle (original founder)
*/
-class PHPMailer
+final class PHPMailer
{
const CHARSET_ISO88591 = 'iso-8859-1';
const CHARSET_UTF8 = 'utf-8';