mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
psalm-internal - prevent invalid extention of class internal to other namespace
This commit is contained in:
parent
f1d8b1e6e7
commit
4415ef0dbd
@ -133,7 +133,7 @@ class DocComment
|
||||
'ignore-nullable-return', 'override-property-visibility',
|
||||
'override-method-visibility', 'seal-properties', 'seal-methods',
|
||||
'generator-return', 'ignore-falsable-return', 'variadic',
|
||||
'ignore-variable-method', 'ignore-variable-property',
|
||||
'ignore-variable-method', 'ignore-variable-property', 'internal',
|
||||
]
|
||||
)) {
|
||||
throw new DocblockParseException('Unrecognised annotation @psalm-' . $special_key);
|
||||
|
@ -211,6 +211,22 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$parent_class_storage->psalmInternal &&
|
||||
strpos($fq_class_name, trim($parent_class_storage->psalmInternal, '\\') . '\\') !== 0
|
||||
) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InternalClass(
|
||||
$parent_fq_class_name . ' is internal to ' . $parent_class_storage->psalmInternal,
|
||||
$code_location
|
||||
|
||||
),
|
||||
array_merge($storage->suppressed_issues, $this->getSuppressedIssues())
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
if ($codebase->store_node_types && $fq_class_name) {
|
||||
$codebase->analyzer->addNodeReference(
|
||||
$this->getFilePath(),
|
||||
|
@ -42,6 +42,7 @@ class CommentAnalyzer
|
||||
$came_from_line_number = null,
|
||||
array $type_aliases = null
|
||||
) {
|
||||
|
||||
$var_id = null;
|
||||
|
||||
$var_type_tokens = null;
|
||||
@ -663,6 +664,10 @@ class CommentAnalyzer
|
||||
$info->internal = true;
|
||||
}
|
||||
|
||||
if (isset($comments['specials']['psalm-internal'])) {
|
||||
$info->psalmInternal = reset($comments['specials']['psalm-internal']);
|
||||
}
|
||||
|
||||
if (isset($comments['specials']['psalm-seal-properties'])) {
|
||||
$info->sealed_properties = true;
|
||||
}
|
||||
|
@ -20,6 +20,13 @@ class ClassLikeDocblockComment
|
||||
*/
|
||||
public $internal = false;
|
||||
|
||||
/**
|
||||
* If set, the class is internal to the given namespace.
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
public $psalmInternal = null;
|
||||
|
||||
/**
|
||||
* @var array<int, array{string, ?string, ?string, bool}>
|
||||
*/
|
||||
|
@ -972,6 +972,7 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
|
||||
$storage->deprecated = $docblock_info->deprecated;
|
||||
$storage->internal = $docblock_info->internal;
|
||||
$storage->psalmInternal = $docblock_info->psalmInternal;
|
||||
|
||||
$storage->sealed_properties = $docblock_info->sealed_properties;
|
||||
$storage->sealed_methods = $docblock_info->sealed_methods;
|
||||
|
@ -84,6 +84,11 @@ class ClassLikeStorage
|
||||
*/
|
||||
public $internal = false;
|
||||
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
public $psalmInternal = null;
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
|
306
tests/PsalmInternalAnnotationTest.php
Normal file
306
tests/PsalmInternalAnnotationTest.php
Normal file
@ -0,0 +1,306 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\Tests;
|
||||
|
||||
class PsalmInternalAnnotationTest extends TestCase
|
||||
{
|
||||
use Traits\InvalidCodeAnalysisTestTrait;
|
||||
use Traits\ValidCodeAnalysisTestTrait;
|
||||
|
||||
/**
|
||||
* @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[]}>
|
||||
*/
|
||||
public function providerValidCodeParse()
|
||||
{
|
||||
// commented out entries are not yet implemented
|
||||
return [
|
||||
// 'internalMethodWithCall' => [
|
||||
// '<?php
|
||||
// namespace A {
|
||||
// class Foo {
|
||||
// /**
|
||||
// * @psalm-internal
|
||||
// */
|
||||
// public static function barBar(): void {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// namespace A\B {
|
||||
// class Bat {
|
||||
// public function batBat() : void {
|
||||
// \A\Foo::barBar();
|
||||
// }
|
||||
// }
|
||||
// }',
|
||||
// ],
|
||||
// 'internalClassWithStaticCall' => [
|
||||
// '<?php
|
||||
// namespace A {
|
||||
// /**
|
||||
// * @psalm-internal
|
||||
// */
|
||||
// class Foo {
|
||||
// public static function barBar(): void {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// namespace A\B {
|
||||
// class Bat {
|
||||
// public function batBat() : void {
|
||||
// \A\Foo::barBar();
|
||||
// }
|
||||
// }
|
||||
// }',
|
||||
// ],
|
||||
// 'internalClassExtendingNamespaceWithStaticCall' => [
|
||||
// '<?php
|
||||
// namespace A {
|
||||
// /**
|
||||
// * @psalm-internal
|
||||
// */
|
||||
// class Foo extends \B\Foo {
|
||||
// public function __construct() {
|
||||
// parent::__construct();
|
||||
// }
|
||||
// public static function barBar(): void {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// namespace B {
|
||||
// class Foo {
|
||||
// public function __construct() {
|
||||
// static::barBar();
|
||||
// }
|
||||
//
|
||||
// public static function barBar(): void {
|
||||
// }
|
||||
// }
|
||||
// }',
|
||||
// ],
|
||||
// 'internalClassWithNew' => [
|
||||
// '<?php
|
||||
// namespace A {
|
||||
// /**
|
||||
// * @psalm-internal
|
||||
// */
|
||||
// class Foo { }
|
||||
// }
|
||||
//
|
||||
// namespace A\B {
|
||||
// class Bat {
|
||||
// public function batBat() : void {
|
||||
// $a = new \A\Foo();
|
||||
// }
|
||||
// }
|
||||
// }',
|
||||
// ],
|
||||
'internalClassWithExtends' => [
|
||||
'<?php
|
||||
namespace A\B {
|
||||
/**
|
||||
* @psalm-internal A\B
|
||||
*/
|
||||
class Foo { }
|
||||
}
|
||||
|
||||
namespace A\B\C {
|
||||
class Bar extends \A\B\Foo {}
|
||||
}',
|
||||
],
|
||||
// 'internalPropertyGet' => [
|
||||
// '<?php
|
||||
// namespace A {
|
||||
// class Foo {
|
||||
// /**
|
||||
// * @psalm-internal
|
||||
// * @var ?int
|
||||
// */
|
||||
// public $foo;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// namespace A\B {
|
||||
// class Bat {
|
||||
// public function batBat() : void {
|
||||
// echo (new \A\Foo)->foo;
|
||||
// }
|
||||
// }
|
||||
// }',
|
||||
// ],
|
||||
// 'internalPropertySet' => [
|
||||
// '<?php
|
||||
// namespace A {
|
||||
// class Foo {
|
||||
// /**
|
||||
// * @psalm-internal
|
||||
// * @var ?int
|
||||
// */
|
||||
// public $foo;
|
||||
// }
|
||||
// }
|
||||
// namespace A\B {
|
||||
// class Bat {
|
||||
// public function batBat() : void {
|
||||
// $a = new \A\Foo;
|
||||
// $a->foo = 5;
|
||||
// }
|
||||
// }
|
||||
// }',
|
||||
// ],
|
||||
// 'internalMethodInTraitWithCall' => [
|
||||
// '<?php
|
||||
// namespace A {
|
||||
// /**
|
||||
// * @psalm-internal
|
||||
// */
|
||||
// trait T {
|
||||
// public static function barBar(): void {
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// class Foo {
|
||||
// use T;
|
||||
//
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// namespace B {
|
||||
// class Bat {
|
||||
// public function batBat() : void {
|
||||
// \A\Foo::barBar();
|
||||
// }
|
||||
// }
|
||||
// }',
|
||||
// ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
|
||||
*/
|
||||
public function providerInvalidCodeParse()
|
||||
{
|
||||
// commented out entries are not yet implemented
|
||||
return [
|
||||
// 'internalMethodWithCall' => [
|
||||
// '<?php
|
||||
// namespace A {
|
||||
// class Foo {
|
||||
// /**
|
||||
// * @psalm-internal
|
||||
// */
|
||||
// public static function barBar(): void {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// namespace B {
|
||||
// class Bat {
|
||||
// public function batBat() {
|
||||
// \A\Foo::barBar();
|
||||
// }
|
||||
// }
|
||||
// }',
|
||||
// 'error_message' => 'InternalMethod',
|
||||
// ],
|
||||
// 'internalClassWithStaticCall' => [
|
||||
// '<?php
|
||||
// namespace A {
|
||||
// /**
|
||||
// * @psalm-internal
|
||||
// */
|
||||
// class Foo {
|
||||
// public static function barBar(): void {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// namespace B {
|
||||
// class Bat {
|
||||
// public function batBat() {
|
||||
// \A\Foo::barBar();
|
||||
// }
|
||||
// }
|
||||
// }',
|
||||
// 'error_message' => 'InternalClass',
|
||||
// ],
|
||||
// 'internalClassWithNew' => [
|
||||
// '<?php
|
||||
// namespace A {
|
||||
// /**
|
||||
// * @psalm-internal
|
||||
// */
|
||||
// class Foo { }
|
||||
// }
|
||||
//
|
||||
// namespace B {
|
||||
// class Bat {
|
||||
// public function batBat() {
|
||||
// $a = new \A\Foo();
|
||||
// }
|
||||
// }
|
||||
// }',
|
||||
// 'error_message' => 'InternalClass',
|
||||
// ],
|
||||
'internalClassWithExtends' => [
|
||||
'<?php
|
||||
namespace A\B {
|
||||
/**
|
||||
* @psalm-internal A\B
|
||||
*/
|
||||
class Foo { }
|
||||
}
|
||||
|
||||
namespace A\C {
|
||||
class Bar extends \A\B\Foo {}
|
||||
}',
|
||||
'error_message' => 'A\B\Foo is internal to A\B',
|
||||
],
|
||||
// 'internalPropertyGet' => [
|
||||
// '<?php
|
||||
// namespace A {
|
||||
// class Foo {
|
||||
// /**
|
||||
// * @psalm-internal
|
||||
// * @var ?int
|
||||
// */
|
||||
// public $foo;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// namespace B {
|
||||
// class Bat {
|
||||
// public function batBat() : void {
|
||||
// echo (new \A\Foo)->foo;
|
||||
// }
|
||||
// }
|
||||
// }',
|
||||
// 'error_message' => 'InternalProperty',
|
||||
// ],
|
||||
// 'internalPropertySet' => [
|
||||
// '<?php
|
||||
// namespace A {
|
||||
// class Foo {
|
||||
// /**
|
||||
// * @psalm-internal
|
||||
// * @var ?int
|
||||
// */
|
||||
// public $foo;
|
||||
// }
|
||||
// }
|
||||
// namespace B {
|
||||
// class Bat {
|
||||
// public function batBat() : void {
|
||||
// $a = new \A\Foo;
|
||||
// $a->foo = 5;
|
||||
// }
|
||||
// }
|
||||
// }',
|
||||
// 'error_message' => 'InternalProperty',
|
||||
// ],
|
||||
];
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user