diff --git a/build/kint.phar b/build/kint.phar index 26240a3f8..b261192ff 100644 Binary files a/build/kint.phar and b/build/kint.phar differ diff --git a/psalm-baseline.xml b/psalm-baseline.xml index fdc31772c..da359b18f 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -27,8 +27,8 @@ - + diff --git a/src/Parser/ClassStaticsPlugin.php b/src/Parser/ClassStaticsPlugin.php index 17fa84ff3..8a18a00ae 100644 --- a/src/Parser/ClassStaticsPlugin.php +++ b/src/Parser/ClassStaticsPlugin.php @@ -30,7 +30,6 @@ use Kint\Value\AbstractValue; use Kint\Value\Context\ClassConstContext; use Kint\Value\Context\ClassDeclaredContext; -use Kint\Value\Context\ClassOwnedContext; use Kint\Value\Context\StaticPropertyContext; use Kint\Value\InstanceValue; use Kint\Value\Representation\ContainerRepresentation; @@ -42,7 +41,7 @@ class ClassStaticsPlugin extends AbstractPlugin implements PluginCompleteInterface { - /** @psalm-var array>> */ + /** @psalm-var array>> */ private array $cache = []; public function getTypes(): array @@ -69,162 +68,163 @@ public function parseComplete(&$var, AbstractValue $v, int $trigger): AbstractVa return $v; } - $class = $v->getClassName(); - $parser = $this->getParser(); - $r = new ReflectionClass($class); + $deep = 0 === $this->getParser()->getDepthLimit(); - $statics_full_name = false; - $statics = []; - $props = $r->getProperties(ReflectionProperty::IS_STATIC); - foreach ($props as $prop) { - $statics[$prop->name] = $prop; - } + $r = new ReflectionClass($v->getClassName()); - $parent = $r; - while ($parent = $parent->getParentClass()) { - foreach ($parent->getProperties(ReflectionProperty::IS_STATIC) as $static) { - if (isset($statics[$static->name]) && $statics[$static->name]->getDeclaringClass()->name === $static->getDeclaringClass()->name) { - continue; - } - $statics[] = $static; - } + if ($statics = $this->getStatics($r, $v->getContext()->getDepth() + 1)) { + $v->addRepresentation(new ContainerRepresentation('Static properties', \array_values($statics), 'statics')); } - $statics_parsed = []; - $found_statics = []; + if ($consts = $this->getCachedConstants($r, $deep)) { + $v->addRepresentation(new ContainerRepresentation('Class constants', \array_values($consts), 'constants')); + } - $cdepth = $v->getContext()->getDepth(); + return $v; + } - foreach ($statics as $static) { - $prop = new StaticPropertyContext( - '$'.$static->getName(), - $static->getDeclaringClass()->name, - ClassDeclaredContext::ACCESS_PUBLIC - ); - $prop->depth = $cdepth + 1; - $prop->final = KINT_PHP84 && $static->isFinal(); + /** @psalm-return array */ + private function getStatics(ReflectionClass $r, int $depth): array + { + $cdepth = $depth ?: 1; + $class = $r->getName(); + $parent = $r->getParentClass(); - if ($static->isProtected()) { - $prop->access = ClassDeclaredContext::ACCESS_PROTECTED; - } elseif ($static->isPrivate()) { - $prop->access = ClassDeclaredContext::ACCESS_PRIVATE; - } + $parent_statics = $parent ? $this->getStatics($parent, $depth) : []; + $statics = []; - if ($prop->isAccessible($parser->getCallerClass())) { - $prop->access_path = '\\'.$prop->owner_class.'::'.$prop->name; - } + foreach ($r->getProperties(ReflectionProperty::IS_STATIC) as $pr) { + $canon_name = \strtolower($pr->getDeclaringClass()->name.'::'.$pr->name); - if (isset($found_statics[$prop->name])) { - $statics_full_name = true; + if ($pr->getDeclaringClass()->name === $class) { + $statics[$canon_name] = $this->buildStaticValue($pr, $cdepth); + } elseif (isset($parent_statics[$canon_name])) { + $statics[$canon_name] = $parent_statics[$canon_name]; + unset($parent_statics[$canon_name]); } else { - $found_statics[$prop->name] = true; - - if ($prop->owner_class !== $class && ClassDeclaredContext::ACCESS_PRIVATE === $prop->access) { - $statics_full_name = true; - } + // This should never happen since abstract static properties can't exist + $statics[$canon_name] = $this->buildStaticValue($pr, $cdepth); // @codeCoverageIgnore } + } - if ($statics_full_name) { - $prop->name = $prop->owner_class.'::'.$prop->name; - } + foreach ($parent_statics as $canon_name => $value) { + $statics[$canon_name] = $value; + } - $static->setAccessible(true); + return $statics; + } - /** - * @psalm-suppress TooFewArguments - * Appears to have been fixed in master - */ - if (!$static->isInitialized()) { - $statics_parsed[] = new UninitializedValue($prop); - } else { - $static = $static->getValue(); - $statics_parsed[] = $parser->parse($static, $prop); - } + private function buildStaticValue(ReflectionProperty $pr, int $depth): AbstractValue + { + $context = new StaticPropertyContext( + $pr->name, + $pr->getDeclaringClass()->name, + ClassDeclaredContext::ACCESS_PUBLIC + ); + $context->depth = $depth; + $context->final = KINT_PHP84 && $pr->isFinal(); + + if ($pr->isProtected()) { + $context->access = ClassDeclaredContext::ACCESS_PROTECTED; + } elseif ($pr->isPrivate()) { + $context->access = ClassDeclaredContext::ACCESS_PRIVATE; } - if ($statics_parsed) { - $v->addRepresentation(new ContainerRepresentation('Static properties', $statics_parsed, 'statics')); + $parser = $this->getParser(); + + if ($context->isAccessible($parser->getCallerClass())) { + $context->access_path = '\\'.$context->owner_class.'::$'.$context->name; } - if ($consts = $this->getCachedConstants($r)) { - $v->addRepresentation(new ContainerRepresentation('Class constants', $consts, 'constants')); + $pr->setAccessible(true); + + /** + * @psalm-suppress TooFewArguments + * Appears to have been fixed in master. + */ + if (!$pr->isInitialized()) { + $context->access_path = null; + + return new UninitializedValue($context); } - return $v; + $val = $pr->getValue(); + + $out = $this->getParser()->parse($val, $context); + $context->access_path = null; + + return $out; } - /** @psalm-return list */ - private function getCachedConstants(ReflectionClass $r): array + /** @psalm-return array */ + private function getCachedConstants(ReflectionClass $r, bool $deep): array { $parser = $this->getParser(); - $pdepth = $parser->getDepthLimit(); - $pdepth_enabled = (int) ($pdepth > 0); + $cdepth = $parser->getDepthLimit() ?: 1; + $deepkey = (int) $deep; $class = $r->getName(); // Separate cache for dumping with/without depth limit // This means we can do immediate depth limit on normal dumps - if (!isset($this->cache[$class][$pdepth_enabled])) { + if (!isset($this->cache[$class][$deepkey])) { $consts = []; - $reflectors = []; + $parent_consts = []; + if ($parent = $r->getParentClass()) { + $parent_consts = $this->getCachedConstants($parent, $deep); + } foreach ($r->getConstants() as $name => $val) { $cr = new ReflectionClassConstant($class, $name); // Skip enum constants - if (\is_a($cr->class, UnitEnum::class, true) && $val instanceof UnitEnum && $cr->class === \get_class($val)) { + if ($cr->class === $class && \is_a($class, UnitEnum::class, true)) { continue; } - $reflectors[$cr->name] = [$cr, $val]; - $consts[$cr->name] = null; - } + $canon_name = \strtolower($cr->getDeclaringClass()->name.'::'.$name); - if ($r = $r->getParentClass()) { - $parents = $this->getCachedConstants($r); - - foreach ($parents as $value) { - $c = $value->getContext(); - $cname = $c->getName(); - - if (isset($reflectors[$cname]) && $c instanceof ClassOwnedContext && $reflectors[$cname][0]->getDeclaringClass()->name === $c->owner_class) { - $consts[$cname] = $value; - unset($reflectors[$cname]); - } else { - $value = clone $value; - $c = $value->getContext(); - if ($c instanceof ClassOwnedContext) { - $c->name = $c->owner_class.'::'.$cname; - } - $consts[] = $value; - } - } - } + if ($cr->getDeclaringClass()->name === $class) { + $context = $this->buildConstContext($cr); + $context->depth = $cdepth; - foreach ($reflectors as [$cr, $val]) { - $context = new ClassConstContext( - $cr->name, - $cr->getDeclaringClass()->name, - ClassDeclaredContext::ACCESS_PUBLIC - ); - $context->depth = $pdepth ?: 1; - $context->final = KINT_PHP81 && $cr->isFinal(); - - if ($cr->isProtected()) { - $context->access = ClassDeclaredContext::ACCESS_PROTECTED; - } elseif ($cr->isPrivate()) { - $context->access = ClassDeclaredContext::ACCESS_PRIVATE; + $consts[$canon_name] = $parser->parse($val, $context); + $context->access_path = null; + } elseif (isset($parent_consts[$canon_name])) { + $consts[$canon_name] = $parent_consts[$canon_name]; } else { - // No access path for protected/private. Tough shit the cache is worth it - $context->access_path = '\\'.$context->owner_class.'::'.$context->name; + $context = $this->buildConstContext($cr); + $context->depth = $cdepth; + + $consts[$canon_name] = $parser->parse($val, $context); + $context->access_path = null; } - $consts[$cr->name] = $parser->parse($val, $context); + unset($parent_consts[$canon_name]); } - /** @psalm-var AbstractValue[] $consts */ - $this->cache[$class][$pdepth_enabled] = \array_values($consts); + $this->cache[$class][$deepkey] = $consts + $parent_consts; + } + + return $this->cache[$class][$deepkey]; + } + + private function buildConstContext(ReflectionClassConstant $cr): ClassConstContext + { + $context = new ClassConstContext( + $cr->name, + $cr->getDeclaringClass()->name, + ClassDeclaredContext::ACCESS_PUBLIC + ); + $context->final = KINT_PHP81 && $cr->isFinal(); + + if ($cr->isProtected()) { + $context->access = ClassDeclaredContext::ACCESS_PROTECTED; + } elseif ($cr->isPrivate()) { + $context->access = ClassDeclaredContext::ACCESS_PRIVATE; + } else { + $context->access_path = '\\'.$context->owner_class.'::'.$context->name; } - return $this->cache[$class][$pdepth_enabled]; + return $context; } } diff --git a/src/Value/Context/ClassConstContext.php b/src/Value/Context/ClassConstContext.php index 41777ecaf..27342f2c7 100644 --- a/src/Value/Context/ClassConstContext.php +++ b/src/Value/Context/ClassConstContext.php @@ -31,6 +31,11 @@ class ClassConstContext extends ClassDeclaredContext { public bool $final = false; + public function getName(): string + { + return $this->owner_class.'::'.$this->name; + } + public function getOperator(): string { return '::'; diff --git a/src/Value/Context/StaticPropertyContext.php b/src/Value/Context/StaticPropertyContext.php index 6b89a1075..47b83ddb5 100644 --- a/src/Value/Context/StaticPropertyContext.php +++ b/src/Value/Context/StaticPropertyContext.php @@ -31,6 +31,11 @@ class StaticPropertyContext extends DoubleAccessMemberContext { public bool $final = false; + public function getName(): string + { + return $this->owner_class.'::$'.$this->name; + } + public function getOperator(): string { return '::'; diff --git a/tests/Fixtures/Php74ChildTestClass.php b/tests/Fixtures/Php74ChildTestClass.php index 841b366b1..c3676d44e 100644 --- a/tests/Fixtures/Php74ChildTestClass.php +++ b/tests/Fixtures/Php74ChildTestClass.php @@ -8,6 +8,7 @@ class Php74ChildTestClass extends Php74TestClass public const VALUE_5 = 5; private const VALUE_3 = 'replaced'; private const VALUE_6 = 6; + public const VALUE_ARRAY_2 = ['contents' => '{"test":"value"}']; public static $value_1 = 'replaced'; public static $value_5 = 5; diff --git a/tests/Fixtures/Php74TestClass.php b/tests/Fixtures/Php74TestClass.php index f53f9ce84..10d38651a 100644 --- a/tests/Fixtures/Php74TestClass.php +++ b/tests/Fixtures/Php74TestClass.php @@ -15,6 +15,9 @@ class Php74TestClass protected static int $value_uninit; private static $value_3 = 3; private static $value_4 = 4; + public static $value_a_pub = ['contents' => '{"test":"value"}']; + protected static $value_a_pro = ['contents' => '{"test":"value"}']; + private static $value_a_pri = ['contents' => '{"test":"value"}']; public $a = 1; public string $b = '2'; diff --git a/tests/Fixtures/Php81TestClass.php b/tests/Fixtures/Php81TestClass.php index 04a21ba3c..5bfae5060 100644 --- a/tests/Fixtures/Php81TestClass.php +++ b/tests/Fixtures/Php81TestClass.php @@ -7,6 +7,8 @@ class Php81TestClass { + final public const X = 'Y'; + public readonly string $a; protected readonly string $b; private readonly string $c; diff --git a/tests/Fixtures/Php84TestClass.php b/tests/Fixtures/Php84TestClass.php index e860eb220..8000a3c36 100644 --- a/tests/Fixtures/Php84TestClass.php +++ b/tests/Fixtures/Php84TestClass.php @@ -44,4 +44,6 @@ class Php84TestClass private(set) int $i; protected(set) int $j; protected private(set) int $k; + + final protected static int $l; } diff --git a/tests/Fixtures/TestInterface.php b/tests/Fixtures/TestInterface.php index c3e565ebc..944570480 100644 --- a/tests/Fixtures/TestInterface.php +++ b/tests/Fixtures/TestInterface.php @@ -4,6 +4,8 @@ interface TestInterface { + const VALUE = 'abcd'; + public function normalMethod(); public static function staticMethod(); } diff --git a/tests/Parser/ClassStaticsPluginTest.php b/tests/Parser/ClassStaticsPluginTest.php index f43c70215..2b738b272 100644 --- a/tests/Parser/ClassStaticsPluginTest.php +++ b/tests/Parser/ClassStaticsPluginTest.php @@ -32,9 +32,15 @@ use Kint\Test\Fixtures\Php74ChildTestClass; use Kint\Test\Fixtures\Php74TestClass; use Kint\Test\Fixtures\Php81TestBackedEnum; +use Kint\Test\Fixtures\Php81TestClass; use Kint\Test\Fixtures\Php81TestEnum; +use Kint\Test\Fixtures\Php84ChildTestClass; +use Kint\Test\Fixtures\Php84TestClass; +use Kint\Test\Fixtures\TestClass; +use Kint\Test\Fixtures\TestInterface; use Kint\Test\KintTestCase; use Kint\Value\AbstractValue; +use Kint\Value\ArrayValue; use Kint\Value\Context\BaseContext; use Kint\Value\Context\ClassConstContext; use Kint\Value\Context\StaticPropertyContext; @@ -61,10 +67,12 @@ public function testHooks() /** * @covers \Kint\Parser\ClassStaticsPlugin::parseComplete + * @covers \Kint\Parser\ClassStaticsPlugin::getStatics + * @covers \Kint\Parser\ClassStaticsPlugin::buildStaticValue */ public function testParseStatics() { - $p = new Parser(5); + $p = new Parser(1); $b = new BaseContext('$v'); $b->access_path = '$v'; @@ -77,15 +85,18 @@ public function testParseStatics() $p->addPlugin(new ClassStaticsPlugin($p)); $expected = [ - ['$value_1', 'replaced', '\\'.Php74ChildTestClass::class.'::$value_1'], - ['$value_5', 5, '\\'.Php74ChildTestClass::class.'::$value_5'], - ['$value_3', 'replaced', null], - ['$value_6', 6, null], - ['$value_2', 2, '\\'.Php74TestClass::class.'::$value_2'], - ['$value_uninit', null, null], - [Php74TestClass::class.'::$value_1', 1, '\\'.Php74TestClass::class.'::$value_1'], - [Php74TestClass::class.'::$value_3', 3, null], - [Php74TestClass::class.'::$value_4', 4, null], + [Php74ChildTestClass::class.'::$value_1', 'replaced'], + [Php74ChildTestClass::class.'::$value_5', 5], + [Php74ChildTestClass::class.'::$value_3', 'replaced'], + [Php74ChildTestClass::class.'::$value_6', 6], + [Php74TestClass::class.'::$value_2', 2], + ($index_uninit = 5) => [Php74TestClass::class.'::$value_uninit', null], + ($index_a_pub = 6) => [Php74TestClass::class.'::$value_a_pub', []], + ($index_a_pro = 7) => [Php74TestClass::class.'::$value_a_pro', []], + [Php74TestClass::class.'::$value_1', 1], + [Php74TestClass::class.'::$value_3', 3], + [Php74TestClass::class.'::$value_4', 4], + ($index_a_pri = 11) => [Php74TestClass::class.'::$value_a_pri', []], ]; $o = $p->parse($v, clone $b); @@ -98,19 +109,37 @@ public function testParseStatics() $this->assertInstanceOf(StaticPropertyContext::class, $value->getContext()); $this->assertSame($expect[0], $value->getDisplayName()); - $this->assertSame($expect[2], $value->getContext()->getAccessPath()); + $this->assertFalse($value->getContext()->final); + $this->assertNull($value->getContext()->getAccessPath()); - if ('$value_uninit' === $expect[0]) { + if ($index === $index_uninit) { $this->assertInstanceOf(UninitializedValue::class, $value); + } elseif ([] === $expect[1]) { + $this->assertInstanceOf(ArrayValue::class, $value); + $this->assertEquals(true, $value->flags & AbstractValue::FLAG_DEPTH_LIMIT); + $this->assertCount(0, $value->getContents()); } else { $this->assertSame($expect[1], $value->getValue()); } } - $expected_copy = $expected; - $expected_copy[2][2] = '\\'.Php74ChildTestClass::class.'::$value_3'; - $expected_copy[3][2] = '\\'.Php74ChildTestClass::class.'::$value_6'; - $expected_copy[5][2] = '\\'.Php74TestClass::class.'::$value_uninit'; + $p->setDepthLimit(5); + $o = $p->parse($v, clone $b); + $rep = $o->getRepresentation('statics'); + + $this->assertEquals(false, $rep->getContents()[$index_a_pub]->flags & AbstractValue::FLAG_DEPTH_LIMIT); + $this->assertSame( + '\\'.Php74TestClass::class."::\$value_a_pub['contents']", + $rep->getContents()[$index_a_pub]->getContents()['contents']->getContext()->getAccessPath() + ); + $this->assertEquals(false, $rep->getContents()[$index_a_pro]->flags & AbstractValue::FLAG_DEPTH_LIMIT); + $this->assertNull( + $rep->getContents()[$index_a_pro]->getContents()['contents']->getContext()->getAccessPath() + ); + $this->assertEquals(false, $rep->getContents()[$index_a_pri]->flags & AbstractValue::FLAG_DEPTH_LIMIT); + $this->assertNull( + $rep->getContents()[$index_a_pri]->getContents()['contents']->getContext()->getAccessPath() + ); $p->setCallerClass(Php74ChildTestClass::class); $o = $p->parse($v, clone $b); @@ -118,14 +147,24 @@ public function testParseStatics() $this->assertInstanceOf(ContainerRepresentation::class, $rep); $this->assertCount(\count($expected), $rep->getContents()); - foreach ($expected_copy as $index => $expect) { - $this->assertSame($expect[2], $rep->getContents()[$index]->getContext()->getAccessPath()); - } + foreach ($expected as $index => $expect) { + $value = $rep->getContents()[$index]; - $expected_copy = $expected; - $expected_copy[5][2] = '\\'.Php74TestClass::class.'::$value_uninit'; - $expected_copy[7][2] = '\\'.Php74TestClass::class.'::$value_3'; - $expected_copy[8][2] = '\\'.Php74TestClass::class.'::$value_4'; + $this->assertInstanceOf(StaticPropertyContext::class, $value->getContext()); + $this->assertSame($expect[0], $value->getDisplayName()); + $this->assertNull($value->getContext()->getAccessPath()); + } + $this->assertSame( + '\\'.Php74TestClass::class."::\$value_a_pub['contents']", + $rep->getContents()[$index_a_pub]->getContents()['contents']->getContext()->getAccessPath() + ); + $this->assertSame( + '\\'.Php74TestClass::class."::\$value_a_pro['contents']", + $rep->getContents()[$index_a_pro]->getContents()['contents']->getContext()->getAccessPath() + ); + $this->assertNull( + $rep->getContents()[$index_a_pri]->getContents()['contents']->getContext()->getAccessPath() + ); $p->setCallerClass(Php74TestClass::class); $o = $p->parse($v, clone $b); @@ -133,14 +172,57 @@ public function testParseStatics() $this->assertInstanceOf(ContainerRepresentation::class, $rep); $this->assertCount(\count($expected), $rep->getContents()); - foreach ($expected_copy as $index => $expect) { - $this->assertSame($expect[2], $rep->getContents()[$index]->getContext()->getAccessPath()); + foreach ($expected as $index => $expect) { + $value = $rep->getContents()[$index]; + + $this->assertInstanceOf(StaticPropertyContext::class, $value->getContext()); + $this->assertSame($expect[0], $value->getDisplayName()); + $this->assertNull($value->getContext()->getAccessPath()); + } + $this->assertSame( + '\\'.Php74TestClass::class."::\$value_a_pub['contents']", + $rep->getContents()[$index_a_pub]->getContents()['contents']->getContext()->getAccessPath() + ); + $this->assertSame( + '\\'.Php74TestClass::class."::\$value_a_pro['contents']", + $rep->getContents()[$index_a_pro]->getContents()['contents']->getContext()->getAccessPath() + ); + $this->assertSame( + '\\'.Php74TestClass::class."::\$value_a_pri['contents']", + $rep->getContents()[$index_a_pri]->getContents()['contents']->getContext()->getAccessPath() + ); + } + + /** + * @covers \Kint\Parser\ClassStaticsPlugin::parseComplete + * @covers \Kint\Parser\ClassStaticsPlugin::getStatics + * @covers \Kint\Parser\ClassStaticsPlugin::buildStaticValue + */ + public function testParseFinalStatic() + { + if (!KINT_PHP84) { + $this->markTestSkipped('Not testing final static properties below PHP 8.4'); } + + $p = new Parser(5); + $p->addPlugin(new ClassStaticsPlugin($p)); + + $b = new BaseContext('$v'); + $b->access_path = '$v'; + $v = new Php84ChildTestClass(); + + $o = $p->parse($v, clone $b); + $rep = $o->getRepresentation('statics'); + + $this->assertInstanceOf(ContainerRepresentation::class, $rep); + $this->assertSame(Php84TestClass::class.'::$l', $rep->getContents()[0]->getContext()->getName()); + $this->assertTrue($rep->getContents()[0]->getContext()->final); } /** * @covers \Kint\Parser\ClassStaticsPlugin::parseComplete * @covers \Kint\Parser\ClassStaticsPlugin::getCachedConstants + * @covers \Kint\Parser\ClassStaticsPlugin::buildConstContext */ public function testParseConstants() { @@ -157,15 +239,16 @@ public function testParseConstants() $p->addPlugin(new ClassStaticsPlugin($p)); $expected = [ - ['VALUE_1', 'replaced', '\\'.Php74ChildTestClass::class.'::VALUE_1'], - ['VALUE_5', 5, '\\'.Php74ChildTestClass::class.'::VALUE_5'], - ['VALUE_3', 'replaced', null], - ['VALUE_6', 6, null], - ['VALUE_2', 2, '\\'.Php74TestClass::class.'::VALUE_2'], - ['VALUE_ARRAY', [], null], - [Php74TestClass::class.'::VALUE_1', 1, '\\'.Php74TestClass::class.'::VALUE_1'], - [Php74TestClass::class.'::VALUE_3', 3, null], - [Php74TestClass::class.'::VALUE_4', 4, null], + [Php74ChildTestClass::class.'::VALUE_1', 'replaced'], + [Php74ChildTestClass::class.'::VALUE_5', 5], + [Php74ChildTestClass::class.'::VALUE_3', 'replaced'], + [Php74ChildTestClass::class.'::VALUE_6', 6], + [Php74ChildTestClass::class.'::VALUE_ARRAY_2', []], + [Php74TestClass::class.'::VALUE_2', 2], + [Php74TestClass::class.'::VALUE_ARRAY', []], + [Php74TestClass::class.'::VALUE_1', 1], + [Php74TestClass::class.'::VALUE_3', 3], + [Php74TestClass::class.'::VALUE_4', 4], ]; $o = $p->parse($v, clone $b); @@ -181,45 +264,106 @@ public function testParseConstants() $this->assertInstanceOf(ClassConstContext::class, $value->getContext()); $this->assertSame($expect[0], $value->getDisplayName()); if ([] === $expect[1]) { + $this->assertInstanceOf(ArrayValue::class, $value); $this->assertEquals(true, $value->flags & AbstractValue::FLAG_DEPTH_LIMIT); - $array_key = $index; + $this->assertCount(0, $value->getContents()); + $array_key ??= $index; } else { $this->assertSame($expect[1], $value->getValue()); } - $this->assertSame($expect[2], $value->getContext()->getAccessPath()); + $this->assertNull($value->getContext()->getAccessPath()); } $p->setDepthLimit(0); $o = $p->parse($v, clone $b); - $this->assertEquals(false, $o->getRepresentation('constants')->getContents()[$array_key]->flags & AbstractValue::FLAG_DEPTH_LIMIT); + $array_value = $o->getRepresentation('constants')->getContents()[$array_key]; + $this->assertEquals(false, $array_value->flags & AbstractValue::FLAG_DEPTH_LIMIT); + + $this->assertCount(1, $array_value->getContents()); + $this->assertSame( + '\\'.Php74ChildTestClass::class."::VALUE_ARRAY_2['contents']", + $array_value->getContents()['contents']->getContext()->getAccessPath() + ); } /** * @covers \Kint\Parser\ClassStaticsPlugin::parseComplete * @covers \Kint\Parser\ClassStaticsPlugin::getCachedConstants + * @covers \Kint\Parser\ClassStaticsPlugin::buildConstContext */ public function testParseEnumConstant() { if (!KINT_PHP81) { - $this->markTestSkipped('Not testing ClassStaticsPlugin on enums below PHP 8.1'); + $this->markTestSkipped('Not testing final consts below PHP 8.1'); } $p = new Parser(5); - $p->addPlugin(new ClassStaticsPlugin($p)); $b = new BaseContext('$v'); $b->access_path = '$v'; $v = Php81TestEnum::Hearts; $o = $p->parse($v, clone $b); + $this->assertNull($o->getRepresentation('constants')); - $this->assertNull($o->getRepresentation('statics')); + $p->addPlugin(new ClassStaticsPlugin($p)); + + $o = $p->parse($v, clone $b); + $this->assertNull($o->getRepresentation('constants')); $v = Php81TestBackedEnum::Hearts; $o = $p->parse($v, clone $b); + $this->assertNull($o->getRepresentation('constants')); + } - $this->assertNull($o->getRepresentation('statics')); + /** + * @covers \Kint\Parser\ClassStaticsPlugin::parseComplete + * @covers \Kint\Parser\ClassStaticsPlugin::getCachedConstants + * @covers \Kint\Parser\ClassStaticsPlugin::buildConstContext + */ + public function testParseInterfaceConst() + { + $p = new Parser(5); + $p->addPlugin(new ClassStaticsPlugin($p)); + + $b = new BaseContext('$v'); + $b->access_path = '$v'; + $v = new TestClass(); + + $o = $p->parse($v, clone $b); + + $rep = $o->getRepresentation('constants'); + + $this->assertInstanceOf(ContainerRepresentation::class, $rep); + $this->assertSame(TestInterface::class.'::VALUE', $rep->getContents()[2]->getContext()->getName()); + } + + /** + * @covers \Kint\Parser\ClassStaticsPlugin::parseComplete + * @covers \Kint\Parser\ClassStaticsPlugin::getCachedConstants + * @covers \Kint\Parser\ClassStaticsPlugin::buildConstContext + */ + public function testParseFinalConst() + { + if (!KINT_PHP81) { + $this->markTestSkipped('Not testing ClassStaticsPlugin on enums below PHP 8.1'); + } + + $p = new Parser(5); + $p->addPlugin(new ClassStaticsPlugin($p)); + + $b = new BaseContext('$v'); + $b->access_path = '$v'; + $v = new Php81TestClass(''); + + $o = $p->parse($v, clone $b); + + $rep = $o->getRepresentation('constants'); + + $this->assertInstanceOf(ContainerRepresentation::class, $rep); + $this->assertSame(Php81TestClass::class.'::X', $rep->getContents()[0]->getContext()->getName()); + $this->assertTrue($rep->getContents()[0]->getContext()->final); } /** @@ -234,6 +378,7 @@ public function testParseBadValue() $b = new FixedWidthValue(new BaseContext('$v'), $v); $csp->parseComplete($v, $b, Parser::TRIGGER_SUCCESS); - $this->assertNull($b->getRepresentation('methods')); + $this->assertNull($b->getRepresentation('statics')); + $this->assertNull($b->getRepresentation('constants')); } } diff --git a/tests/Value/Context/ClassConstContextTest.php b/tests/Value/Context/ClassConstContextTest.php index 1ed35c8a3..02b09f9a8 100644 --- a/tests/Value/Context/ClassConstContextTest.php +++ b/tests/Value/Context/ClassConstContextTest.php @@ -77,6 +77,15 @@ public function testGetModifiers(ClassConstContext $c, string $expect) $this->assertSame($expect, $c->getModifiers()); } + /** + * @covers \Kint\Value\Context\ClassConstContext::getName + */ + public function testGetName() + { + $c = new ClassConstContext('base', 'class', ClassDeclaredContext::ACCESS_PUBLIC); + $this->assertSame('class::base', $c->getName()); + } + /** * @covers \Kint\Value\Context\ClassConstContext::getOperator */ diff --git a/tests/Value/Context/StaticPropertyContextTest.php b/tests/Value/Context/StaticPropertyContextTest.php index 2134e096e..4c84163df 100644 --- a/tests/Value/Context/StaticPropertyContextTest.php +++ b/tests/Value/Context/StaticPropertyContextTest.php @@ -77,6 +77,15 @@ public function testGetModifiers(StaticPropertyContext $c, string $expect) $this->assertSame($expect, $c->getModifiers()); } + /** + * @covers \Kint\Value\Context\StaticPropertyContext::getName + */ + public function testGetName() + { + $c = new StaticPropertyContext('base', 'class', ClassDeclaredContext::ACCESS_PUBLIC); + $this->assertSame('class::$base', $c->getName()); + } + /** * @covers \Kint\Value\Context\StaticPropertyContext::getOperator */