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';