1
0
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:
Barney Laurance 2019-05-09 23:33:27 +01:00 committed by Matthew Brown
parent f1d8b1e6e7
commit 4415ef0dbd
7 changed files with 341 additions and 1 deletions

View File

@ -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);

View File

@ -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(),

View File

@ -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;
}

View File

@ -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}>
*/

View File

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

View File

@ -84,6 +84,11 @@ class ClassLikeStorage
*/
public $internal = false;
/**
* @var null|string
*/
public $psalmInternal = null;
/**
* @var array<string, bool>
*/

View 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',
// ],
];
}
}