diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 9d1223c..833c61f 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -30,7 +30,7 @@ - + diff --git a/rules.neon b/rules.neon index 439e808..38d510a 100644 --- a/rules.neon +++ b/rules.neon @@ -60,6 +60,10 @@ services: class: ShipMonk\PHPStan\DeadCode\Collector\ClassDefinitionCollector tags: - phpstan.collector + - + class: ShipMonk\PHPStan\DeadCode\Collector\ConstantFetchCollector + tags: + - phpstan.collector - class: ShipMonk\PHPStan\DeadCode\Collector\EntrypointCollector tags: diff --git a/src/Collector/ClassDefinitionCollector.php b/src/Collector/ClassDefinitionCollector.php index d34bef5..19850d3 100644 --- a/src/Collector/ClassDefinitionCollector.php +++ b/src/Collector/ClassDefinitionCollector.php @@ -23,6 +23,7 @@ * @implements Collector, * methods: array}>, * parents: array, * traits: array, aliases?: array}>, @@ -42,6 +43,7 @@ public function getNodeType(): string * @return array{ * kind: string, * name: string, + * constants: array, * methods: array}>, * parents: array, * traits: array, aliases?: array}>, @@ -70,10 +72,21 @@ public function processNode( ]; } + $constants = []; + + foreach ($node->getConstants() as $constant) { + foreach ($constant->consts as $const) { + $constants[$const->name->toString()] = [ + 'line' => $const->getStartLine(), + ]; + } + } + return [ 'kind' => $kind, 'name' => $typeName, 'methods' => $methods, + 'constants' => $constants, 'parents' => $this->getParents($node), 'traits' => $this->getTraits($node), 'interfaces' => $this->getInterfaces($node), diff --git a/src/Collector/ConstantFetchCollector.php b/src/Collector/ConstantFetchCollector.php new file mode 100644 index 0000000..107eb35 --- /dev/null +++ b/src/Collector/ConstantFetchCollector.php @@ -0,0 +1,103 @@ +> + */ +class ConstantFetchCollector implements Collector +{ + + /** + * @var list + */ + private array $accessBuffer = []; + + public function getNodeType(): string + { + return Node::class; + } + + /** + * @return non-empty-list|null + */ + public function processNode( + Node $node, + Scope $scope + ): ?array + { + if ($node instanceof ClassConstFetch) { + $this->registerFetch($node, $scope); + } + + if (!$scope->isInClass() || $node instanceof ClassMethodsNode) { // @phpstan-ignore-line ignore BC promise + $data = $this->accessBuffer; + $this->accessBuffer = []; + + // collect data once per class to save memory & resultCache size + return $data === [] + ? null + : array_map( + static fn (ClassConstantFetch $fetch): string => $fetch->toString(), + $data, + ); + } + + return null; + } + + private function registerFetch(ClassConstFetch $node, Scope $scope): void + { + $ownerType = $node->class instanceof Name + ? $scope->resolveTypeByName($node->class) + : $scope->getType($node->class); + + $constantNames = $node->name instanceof Expr + ? array_map(static fn (ConstantStringType $string): string => $string->getValue(), $scope->getType($node->name)->getConstantStrings()) + : [$node->name->toString()]; + + foreach ($ownerType->getObjectClassReflections() as $classReflection) { + foreach ($constantNames as $constantName) { + if ($classReflection->hasConstant($constantName)) { + $className = $classReflection->getConstant($constantName)->getDeclaringClass()->getName(); + + } else { // call of unknown const (might be present on children) + $className = $classReflection->getName(); // TODO untested yet + } + + $this->accessBuffer[] = new ClassConstantFetch( + $this->getCaller($scope), + new ClassConstantRef($className, $constantName), + ); + } + } + } + + private function getCaller(Scope $scope): ?ClassMethodRef + { + if (!$scope->isInClass()) { + return null; + } + + if (!$scope->getFunction() instanceof MethodReflection) { + return null; + } + + return new ClassMethodRef($scope->getClassReflection()->getName(), $scope->getFunction()->getName()); + } + +} diff --git a/src/Collector/EntrypointCollector.php b/src/Collector/EntrypointCollector.php index 70b94b7..c3d697c 100644 --- a/src/Collector/EntrypointCollector.php +++ b/src/Collector/EntrypointCollector.php @@ -6,8 +6,8 @@ use PHPStan\Analyser\Scope; use PHPStan\Collectors\Collector; use PHPStan\Node\InClassNode; -use ShipMonk\PHPStan\DeadCode\Crate\Call; -use ShipMonk\PHPStan\DeadCode\Crate\Method; +use ShipMonk\PHPStan\DeadCode\Crate\ClassMethodCall; +use ShipMonk\PHPStan\DeadCode\Crate\ClassMethodRef; use ShipMonk\PHPStan\DeadCode\Provider\MethodEntrypointProvider; /** @@ -49,9 +49,9 @@ public function processNode( foreach ($this->entrypointProviders as $entrypointProvider) { foreach ($entrypointProvider->getEntrypoints($node->getClassReflection()) as $entrypointMethod) { - $call = new Call( + $call = new ClassMethodCall( null, - new Method($entrypointMethod->getDeclaringClass()->getName(), $entrypointMethod->getName()), + new ClassMethodRef($entrypointMethod->getDeclaringClass()->getName(), $entrypointMethod->getName()), false, ); $entrypoints[] = $call->toString(); diff --git a/src/Collector/MethodCallCollector.php b/src/Collector/MethodCallCollector.php index d8bda2a..ae8005b 100644 --- a/src/Collector/MethodCallCollector.php +++ b/src/Collector/MethodCallCollector.php @@ -23,8 +23,8 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; -use ShipMonk\PHPStan\DeadCode\Crate\Call; -use ShipMonk\PHPStan\DeadCode\Crate\Method; +use ShipMonk\PHPStan\DeadCode\Crate\ClassMethodCall; +use ShipMonk\PHPStan\DeadCode\Crate\ClassMethodRef; use function array_map; /** @@ -34,7 +34,7 @@ class MethodCallCollector implements Collector { /** - * @var list + * @var list */ private array $callsBuffer = []; @@ -94,7 +94,7 @@ public function processNode( return $data === [] ? null : array_map( - static fn (Call $call): string => $call->toString(), + static fn (ClassMethodCall $call): string => $call->toString(), $data, ); } @@ -131,9 +131,9 @@ private function registerMethodCall( foreach ($methodNames as $methodName) { foreach ($this->getDeclaringTypesWithMethod($scope, $callerType, $methodName, TrinaryLogic::createNo()) as $className) { - $this->callsBuffer[] = new Call( + $this->callsBuffer[] = new ClassMethodCall( $this->getCaller($scope), - new Method($className, $methodName), + new ClassMethodRef($className, $methodName), $possibleDescendantCall, ); } @@ -158,9 +158,9 @@ private function registerStaticCall( foreach ($methodNames as $methodName) { foreach ($this->getDeclaringTypesWithMethod($scope, $callerType, $methodName, TrinaryLogic::createYes()) as $className) { - $this->callsBuffer[] = new Call( + $this->callsBuffer[] = new ClassMethodCall( $this->getCaller($scope), - new Method($className, $methodName), + new ClassMethodRef($className, $methodName), $possibleDescendantCall, ); } @@ -184,9 +184,9 @@ private function registerArrayCallable( $possibleDescendantCall = !$caller->isClassString()->yes(); foreach ($this->getDeclaringTypesWithMethod($scope, $caller, $methodName, TrinaryLogic::createMaybe()) as $className) { - $this->callsBuffer[] = new Call( + $this->callsBuffer[] = new ClassMethodCall( $this->getCaller($scope), - new Method($className, $methodName), + new ClassMethodRef($className, $methodName), $possibleDescendantCall, ); } @@ -197,9 +197,9 @@ private function registerArrayCallable( private function registerAttribute(Attribute $node, Scope $scope): void { - $this->callsBuffer[] = new Call( + $this->callsBuffer[] = new ClassMethodCall( null, - new Method($scope->resolveName($node->name), '__construct'), + new ClassMethodRef($scope->resolveName($node->name), '__construct'), false, ); } @@ -210,9 +210,9 @@ private function registerClone(Clone_ $node, Scope $scope): void $callerType = $scope->getType($node->expr); foreach ($this->getDeclaringTypesWithMethod($scope, $callerType, $methodName, TrinaryLogic::createNo()) as $className) { - $this->callsBuffer[] = new Call( + $this->callsBuffer[] = new ClassMethodCall( $this->getCaller($scope), - new Method($className, $methodName), + new ClassMethodRef($className, $methodName), true, ); } @@ -278,7 +278,7 @@ private function getDeclaringTypesWithMethod( return $result; } - private function getCaller(Scope $scope): ?Method + private function getCaller(Scope $scope): ?ClassMethodRef { if (!$scope->isInClass()) { return null; @@ -288,7 +288,7 @@ private function getCaller(Scope $scope): ?Method return null; } - return new Method($scope->getClassReflection()->getName(), $scope->getFunction()->getName()); + return new ClassMethodRef($scope->getClassReflection()->getName(), $scope->getFunction()->getName()); } } diff --git a/src/Crate/ClassConstantFetch.php b/src/Crate/ClassConstantFetch.php new file mode 100644 index 0000000..9a5e0ba --- /dev/null +++ b/src/Crate/ClassConstantFetch.php @@ -0,0 +1,45 @@ +origin = $origin; + $this->fetch = $fetch; + } + + public function toString(): string + { + return serialize($this); + } + + public static function fromString(string $callKey): self + { + $result = unserialize($callKey, ['allowed_classes' => [self::class, ClassMethodRef::class, ClassConstantRef::class]]); + + if (!$result instanceof self) { + $self = self::class; + throw new LogicException("Invalid string for $self deserialization: $callKey"); + } + + return $result; + } + +} diff --git a/src/Crate/ClassConstantRef.php b/src/Crate/ClassConstantRef.php new file mode 100644 index 0000000..47d8f80 --- /dev/null +++ b/src/Crate/ClassConstantRef.php @@ -0,0 +1,19 @@ +className = $className; + $this->memberName = $memberName; + $this->memberType = $memberType; + } + + public function toString(): string + { + $classRef = $this->className ?? self::UNKNOWN_CLASS; + return $classRef . '::' . $this->memberName; + } + +} diff --git a/src/Crate/Call.php b/src/Crate/ClassMethodCall.php similarity index 84% rename from src/Crate/Call.php rename to src/Crate/ClassMethodCall.php index 0051562..86bb5ba 100644 --- a/src/Crate/Call.php +++ b/src/Crate/ClassMethodCall.php @@ -9,18 +9,18 @@ /** * @immutable */ -class Call +class ClassMethodCall { - public ?Method $caller; + public ?ClassMethodRef $caller; - public Method $callee; + public ClassMethodRef $callee; public bool $possibleDescendantCall; public function __construct( - ?Method $caller, - Method $callee, + ?ClassMethodRef $caller, + ClassMethodRef $callee, bool $possibleDescendantCall ) { @@ -62,8 +62,8 @@ public static function fromString(string $callKey): self } [$calleeClassName, $calleeMethodName] = $calleeSplit; - $callee = new Method( - $calleeClassName === Method::UNKNOWN_CLASS ? null : $calleeClassName, + $callee = new ClassMethodRef( + $calleeClassName === ClassMemberRef::UNKNOWN_CLASS ? null : $calleeClassName, $calleeMethodName, ); @@ -77,7 +77,7 @@ public static function fromString(string $callKey): self } [$callerClassName, $callerMethodName] = $callerSplit; - $caller = new Method($callerClassName, $callerMethodName); + $caller = new ClassMethodRef($callerClassName, $callerMethodName); } return new self( diff --git a/src/Crate/ClassMethodRef.php b/src/Crate/ClassMethodRef.php new file mode 100644 index 0000000..5490937 --- /dev/null +++ b/src/Crate/ClassMethodRef.php @@ -0,0 +1,19 @@ +className = $className; - $this->methodName = $methodName; - } - - public function toString(): string - { - $classRef = $this->className ?? self::UNKNOWN_CLASS; - return $classRef . '::' . $this->methodName; - } - -} diff --git a/src/Rule/DeadMethodRule.php b/src/Rule/DeadMethodRule.php index 3d81b24..7bf83dc 100644 --- a/src/Rule/DeadMethodRule.php +++ b/src/Rule/DeadMethodRule.php @@ -12,11 +12,14 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use ShipMonk\PHPStan\DeadCode\Collector\ClassDefinitionCollector; +use ShipMonk\PHPStan\DeadCode\Collector\ConstantFetchCollector; use ShipMonk\PHPStan\DeadCode\Collector\EntrypointCollector; use ShipMonk\PHPStan\DeadCode\Collector\MethodCallCollector; -use ShipMonk\PHPStan\DeadCode\Crate\Call; +use ShipMonk\PHPStan\DeadCode\Crate\ClassConstantFetch; +use ShipMonk\PHPStan\DeadCode\Crate\ClassMemberRef; +use ShipMonk\PHPStan\DeadCode\Crate\ClassMethodCall; +use ShipMonk\PHPStan\DeadCode\Crate\ClassMethodRef; use ShipMonk\PHPStan\DeadCode\Crate\Kind; -use ShipMonk\PHPStan\DeadCode\Crate\Method; use ShipMonk\PHPStan\DeadCode\Crate\Visibility; use ShipMonk\PHPStan\DeadCode\Hierarchy\ClassHierarchy; use function array_key_exists; @@ -33,7 +36,7 @@ /** * @implements Rule */ -class DeadMethodRule implements Rule, DiagnoseExtension +class DeadMethodRule implements Rule, DiagnoseExtension // TODO rename to DeadCodeRule { public const ERROR_IDENTIFIER = 'shipmonk.deadMethod'; @@ -64,6 +67,7 @@ class DeadMethodRule implements Rule, DiagnoseExtension * kind: string, * name: string, * file: string, + * constants: array, * methods: array}>, * parents: array, * traits: array, aliases?: array}>, @@ -87,14 +91,24 @@ class DeadMethodRule implements Rule, DiagnoseExtension private array $blackMethods = []; /** - * @var array> methodName => Call[] + * @var array constantKey => [file, line] + */ + private array $blackConstants = []; + + /** + * @var array> originMethodKey => constantKey[] + */ + private array $constantFetches = []; + + /** + * @var array> methodName => Call[] */ private array $mixedCalls = []; /** * @var array> caller => callee[] */ - private array $callGraph = []; + private array $callGraph = []; // TODO include also const fetches? public function __construct( ClassHierarchy $classHierarchy, @@ -125,10 +139,14 @@ public function processNode( return []; } - /** @var list $calls */ + /** @var list $calls */ $calls = []; + /** @var list $fetches */ + $fetches = []; + $methodDeclarationData = $node->get(ClassDefinitionCollector::class); $methodCallData = $node->get(MethodCallCollector::class); + $constFetchData = $node->get(ConstantFetchCollector::class); $entrypointData = $node->get(EntrypointCollector::class); /** @var array>> $callData */ @@ -138,10 +156,10 @@ public function processNode( foreach ($callData as $callsPerFile) { foreach ($callsPerFile as $callStrings) { foreach ($callStrings as $callString) { - $call = Call::fromString($callString); + $call = ClassMethodCall::fromString($callString); if ($call->callee->className === null) { - $this->mixedCalls[$call->callee->methodName][] = $call; + $this->mixedCalls[$call->callee->memberName][] = $call; continue; } @@ -150,6 +168,19 @@ public function processNode( } } + foreach ($constFetchData as $file => $data) { + foreach ($data as $constantData) { + foreach ($constantData as $constantKey) { + $fetch = ClassConstantFetch::fromString($constantKey); + $fetches[] = $fetch; + + if ($fetch->origin !== null) { + $this->constantFetches[$fetch->origin->toString()][] = $fetch->fetch->toString(); + } + } + } + } + foreach ($methodDeclarationData as $file => $data) { foreach ($data as $typeData) { $typeName = $typeData['name']; @@ -157,6 +188,7 @@ public function processNode( 'kind' => $typeData['kind'], 'name' => $typeName, 'file' => $file, + 'constants' => $typeData['constants'], 'methods' => $typeData['methods'], 'parents' => $typeData['parents'], 'traits' => $typeData['traits'], @@ -169,6 +201,7 @@ public function processNode( foreach ($this->typeDefinitions as $typeName => $typeDefinition) { $methods = $typeDefinition['methods']; + $constants = $typeDefinition['constants']; $file = $typeDefinition['file']; $ancestorNames = $this->getAncestorNames($typeName); @@ -182,14 +215,19 @@ public function processNode( if (isset($this->mixedCalls[$methodName])) { foreach ($this->mixedCalls[$methodName] as $originalCall) { - $calls[] = new Call( + $calls[] = new ClassMethodCall( $originalCall->caller, - new Method($typeName, $methodName), + new ClassMethodRef($typeName, $methodName), $originalCall->possibleDescendantCall, ); } } } + + foreach ($constants as $constantName => $constantData) { + $definition = $typeName . '::' . $constantName; + $this->blackConstants[$definition] = [$file, $constantData['line']]; + } } $whiteCallees = []; @@ -211,8 +249,15 @@ public function processNode( } } + foreach ($fetches as $fetch) { + if ($fetch->origin === null) { + unset($this->blackConstants[$fetch->fetch->toString()]); + } + // else utilize call-graph traversal via markTransitivesWhite + } + foreach ($whiteCallees as $whiteCalleeKey) { - $this->markTransitiveCallsWhite($whiteCalleeKey); + $this->markTransitivesWhite($whiteCalleeKey); } foreach ($this->blackMethods as $blackMethodKey => $_) { @@ -228,6 +273,10 @@ public function processNode( $errors[] = $this->buildError($deadMethodKey, [], $file, $line); } + foreach ($this->blackConstants as $deadConstantKey => [$file, $line]) { + $errors[] = $this->buildError($deadConstantKey, [], $file, $line); + } + return $errors; } @@ -305,7 +354,7 @@ private function isAnonymousClass(?string $className): bool /** * @return list */ - private function getAlternativeMethodKeys(Method $method, bool $possibleDescendant): array + private function getAlternativeMethodKeys(ClassMemberRef $method, bool $possibleDescendant): array { if ($method->className === null) { throw new LogicException('Those were eliminated above, should never happen'); @@ -322,7 +371,7 @@ private function getAlternativeMethodKeys(Method $method, bool $possibleDescenda if ($possibleDescendant) { foreach ($this->classHierarchy->getClassDescendants($method->className) as $descendantName) { - $result[] = $this->getMethodKey($descendantName, $method->methodName); + $result[] = $this->getMethodKey($descendantName, $method->memberName); } } @@ -343,13 +392,17 @@ private function getAlternativeMethodKeys(Method $method, bool $possibleDescenda /** * @param array $visitedKeys */ - private function markTransitiveCallsWhite(string $callerKey, array $visitedKeys = []): void + private function markTransitivesWhite(string $callerKey, array $visitedKeys = []): void { $visitedKeys = $visitedKeys === [] ? [$callerKey => null] : $visitedKeys; $calleeKeys = $this->callGraph[$callerKey] ?? []; unset($this->blackMethods[$callerKey]); + foreach ($this->constantFetches[$callerKey] ?? [] as $constantKey) { + unset($this->blackConstants[$constantKey]); + } + foreach ($calleeKeys as $calleeKey) { if (array_key_exists($calleeKey, $visitedKeys)) { continue; @@ -359,7 +412,7 @@ private function markTransitiveCallsWhite(string $callerKey, array $visitedKeys continue; } - $this->markTransitiveCallsWhite($calleeKey, array_merge($visitedKeys, [$calleeKey => null])); + $this->markTransitivesWhite($calleeKey, array_merge($visitedKeys, [$calleeKey => null])); } } @@ -527,11 +580,11 @@ private function getMethodKey(string $typeName, string $methodName): string return $typeName . '::' . $methodName; } - private function isConsideredWhite(Call $call): bool + private function isConsideredWhite(ClassMethodCall $call): bool { return $call->caller === null || $this->isAnonymousClass($call->caller->className) - || array_key_exists($call->caller->methodName, self::UNSUPPORTED_MAGIC_METHODS); + || array_key_exists($call->caller->memberName, self::UNSUPPORTED_MAGIC_METHODS); } private function isNeverReportedAsDead(string $methodKey): bool @@ -591,7 +644,7 @@ public function print(Output $output): void } /** - * @param list $calls + * @param list $calls */ private function getExampleCaller(array $calls): ?string { diff --git a/tests/AllServicesInConfigTest.php b/tests/AllServicesInConfigTest.php index 9e58499..03b5371 100644 --- a/tests/AllServicesInConfigTest.php +++ b/tests/AllServicesInConfigTest.php @@ -7,9 +7,12 @@ use PHPStan\Testing\PHPStanTestCase; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; -use ShipMonk\PHPStan\DeadCode\Crate\Call; +use ShipMonk\PHPStan\DeadCode\Crate\ClassConstantFetch; +use ShipMonk\PHPStan\DeadCode\Crate\ClassConstantRef; +use ShipMonk\PHPStan\DeadCode\Crate\ClassMemberRef; +use ShipMonk\PHPStan\DeadCode\Crate\ClassMethodCall; +use ShipMonk\PHPStan\DeadCode\Crate\ClassMethodRef; use ShipMonk\PHPStan\DeadCode\Crate\Kind; -use ShipMonk\PHPStan\DeadCode\Crate\Method; use ShipMonk\PHPStan\DeadCode\Crate\Visibility; use ShipMonk\PHPStan\DeadCode\Provider\MethodEntrypointProvider; use ShipMonk\PHPStan\DeadCode\Provider\SimpleMethodEntrypointProvider; @@ -45,8 +48,11 @@ public function test(): void $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory)); $missingClassNames = []; $excluded = [ - Call::class, - Method::class, + ClassMethodCall::class, + ClassMethodRef::class, + ClassConstantRef::class, + ClassConstantFetch::class, + ClassMemberRef::class, Kind::class, Visibility::class, MethodEntrypointProvider::class, diff --git a/tests/Rule/DeadMethodRuleTest.php b/tests/Rule/DeadMethodRuleTest.php index 31c6bd7..8f74a8f 100644 --- a/tests/Rule/DeadMethodRuleTest.php +++ b/tests/Rule/DeadMethodRuleTest.php @@ -16,6 +16,7 @@ use PHPStan\Symfony\ServiceMapFactory; use ReflectionMethod; use ShipMonk\PHPStan\DeadCode\Collector\ClassDefinitionCollector; +use ShipMonk\PHPStan\DeadCode\Collector\ConstantFetchCollector; use ShipMonk\PHPStan\DeadCode\Collector\EntrypointCollector; use ShipMonk\PHPStan\DeadCode\Collector\MethodCallCollector; use ShipMonk\PHPStan\DeadCode\Formatter\RemoveDeadCodeFormatter; @@ -70,6 +71,7 @@ protected function getCollectors(): array new EntrypointCollector($this->getEntrypointProviders()), new ClassDefinitionCollector(), new MethodCallCollector($this->trackMixedCalls), + new ConstantFetchCollector(), ]; } @@ -232,6 +234,13 @@ public function testGrouping(): void ]); } + public function testConstants(): void + { + $this->emitErrorsInGroups = false; // TODO test even groups + $this->analyseFiles([__DIR__ . '/data/DeadMethodRule/constants/basic.php']); + $this->analyseFiles([__DIR__ . '/data/DeadMethodRule/constants/override.php']); + } + /** * @return array, 1?: int}> */ @@ -240,7 +249,6 @@ public static function provideFiles(): iterable yield 'anonym' => [__DIR__ . '/data/DeadMethodRule/anonym.php']; yield 'enum' => [__DIR__ . '/data/DeadMethodRule/enum.php', 8_01_00]; yield 'callables' => [__DIR__ . '/data/DeadMethodRule/callables.php']; - yield 'code' => [__DIR__ . '/data/DeadMethodRule/basic.php']; yield 'ctor' => [__DIR__ . '/data/DeadMethodRule/ctor.php']; yield 'ctor-interface' => [__DIR__ . '/data/DeadMethodRule/ctor-interface.php']; yield 'ctor-private' => [__DIR__ . '/data/DeadMethodRule/ctor-private.php']; diff --git a/tests/Rule/data/DeadMethodRule/constants/basic.php b/tests/Rule/data/DeadMethodRule/constants/basic.php new file mode 100644 index 0000000..c47435c --- /dev/null +++ b/tests/Rule/data/DeadMethodRule/constants/basic.php @@ -0,0 +1,34 @@ +parentMethod(); + } + +} + +new TestClass(); diff --git a/tests/Rule/data/DeadMethodRule/constants/override.php b/tests/Rule/data/DeadMethodRule/constants/override.php new file mode 100644 index 0000000..45e2245 --- /dev/null +++ b/tests/Rule/data/DeadMethodRule/constants/override.php @@ -0,0 +1,25 @@ +