Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ lint:
--exclude tests/PHPStan/Rules/Variables/data/bug-14349.php \
--exclude tests/PHPStan/Rules/Variables/data/bug-14352.php \
--exclude tests/PHPStan/Rules/Variables/data/bug-14351.php \
--exclude tests/PHPStan/Rules/Variables/data/globals-modification.php \
--exclude tests/PHPStan/Rules/Properties/data/bug-14457.php \
src tests

Expand Down
65 changes: 65 additions & 0 deletions src/Php/PhpVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -543,4 +543,69 @@ public function throwsOnStringCast(): bool
return $this->versionId >= 70400;
}

public function warnsOnContinueTargetingSwitch(): bool
{
return $this->versionId >= 70300;
}

public function restrictsGlobalsModification(): bool
{
return $this->versionId >= 80100;
}

public function deprecatesArrayPointerFunctionsOnObjects(): bool
{
return $this->versionId >= 80100;
}

public function deprecatesStaticTraitMemberAccess(): bool
{
return $this->versionId >= 80100;
}

public function deprecatesMbstringPseudoEncodings(): bool
{
return $this->versionId >= 80200;
}

public function deprecatesGetClassWithoutArguments(): bool
{
return $this->versionId >= 80300;
}

public function deprecatesTriggerErrorWithUserError(): bool
{
return $this->versionId >= 80400;
}

public function deprecatesPowerOfZeroWithNegativeExponent(): bool
{
return $this->versionId >= 80400;
}

public function deprecatesCsvEscapeParameterDefault(): bool
{
return $this->versionId >= 80400;
}

public function deprecatesUnderscoreClassName(): bool
{
return $this->versionId >= 80400;
}

public function deprecatesDebugInfoReturningNull(): bool
{
return $this->versionId >= 80500;
}

public function deprecatesOrdWithoutSingleByte(): bool
{
return $this->versionId >= 80500;
}

public function deprecatesFinfoBufferContext(): bool
{
return $this->versionId >= 80500;
}

}
122 changes: 122 additions & 0 deletions src/Rules/Classes/AccessStaticTraitMemberRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Classes;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\RegisteredRule;
use PHPStan\Node\StaticMethodCallableNode;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Rules\MultipleNodeTypesRule;
use PHPStan\Rules\RuleErrorBuilder;
use function count;
use function sprintf;

/**
* @implements MultipleNodeTypesRule<Expr>
*/
#[RegisteredRule(level: 0)]
final class AccessStaticTraitMemberRule implements MultipleNodeTypesRule
{

public function __construct(
private PhpVersion $phpVersion,
)
{
}

public function getNodeTypes(): array
{
return [Expr\StaticCall::class, StaticMethodCallableNode::class, Expr\StaticPropertyFetch::class];
}

public function getNodeType(): string
{
return Expr::class;
}

public function processNode(Node $node, Scope $scope): array
{
if (!$this->phpVersion->deprecatesStaticTraitMemberAccess()) {
return [];
}

if ($node instanceof Expr\StaticCall) {
if ($node->isFirstClassCallable()) {
return [];
}
$class = $node->class;
$name = $node->name;
} elseif ($node instanceof StaticMethodCallableNode) {
$class = $node->getClass();
$name = $node->getName();
} elseif ($node instanceof Expr\StaticPropertyFetch) {
$class = $node->class;
$name = $node->name;
} else {
return [];
}

if (!$name instanceof Node\Identifier) {
return [];
}

$trait = $this->findTrait($class, $scope);
if ($trait === null) {
return [];
}

if ($node instanceof Expr\StaticPropertyFetch) {
if (!$trait->hasStaticProperty($name->toString())) {
return [];
}

return [
RuleErrorBuilder::message(sprintf(
'Accessing static trait property %s::$%s is deprecated in PHP 8.1, it should only be accessed on a class using the trait.',
$trait->getDisplayName(),
$name->toString(),
))
->identifier('staticProperty.onTrait')
->build(),
];
}

if (!$trait->hasNativeMethod($name->toString())) {
return [];
}

$method = $trait->getNativeMethod($name->toString());
if (!$method->isStatic()) {
return [];
}

return [
RuleErrorBuilder::message(sprintf(
'Calling static trait method %s::%s() is deprecated in PHP 8.1, it should only be called on a class using the trait.',
$trait->getDisplayName(),
$method->getName(),
))
->identifier('staticMethod.onTrait')
->build(),
];
}

private function findTrait(Name|Expr $class, Scope $scope): ?ClassReflection
{
$classType = $class instanceof Name
? $scope->resolveTypeByName($class)
: $scope->getType($class)->getObjectTypeOrClassStringObjectType();

$classReflections = $classType->getObjectClassReflections();
if (count($classReflections) !== 1 || !$classReflections[0]->isTrait()) {
return null;
}

return $classReflections[0];
}

}
59 changes: 59 additions & 0 deletions src/Rules/Classes/UnderscoreClassNameRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Classes;

use PhpParser\Node;
use PhpParser\Node\Stmt;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\RegisteredRule;
use PHPStan\Php\PhpVersion;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use function sprintf;

/**
* @implements Rule<Stmt\ClassLike>
*/
#[RegisteredRule(level: 0)]
final class UnderscoreClassNameRule implements Rule
{

public function __construct(
private PhpVersion $phpVersion,
)
{
}

public function getNodeType(): string
{
return Stmt\ClassLike::class;
}

public function processNode(Node $node, Scope $scope): array
{
if (!$this->phpVersion->deprecatesUnderscoreClassName()) {
return [];
}

if ($node->name === null || $node->name->toString() !== '_') {
return [];
}

if ($node instanceof Stmt\Interface_) {
$kind = 'an interface';
} elseif ($node instanceof Stmt\Trait_) {
$kind = 'a trait';
} elseif ($node instanceof Stmt\Enum_) {
$kind = 'an enum';
} else {
$kind = 'a class';
}

return [
RuleErrorBuilder::message(sprintf('Using "_" as %s name is deprecated in PHP 8.4.', $kind))
->identifier('classLike.underscoreName')
->build(),
];
}

}
89 changes: 89 additions & 0 deletions src/Rules/Functions/ArrayPointerFunctionOnObjectRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Functions;

use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\RegisteredRule;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Type\ArrayType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\VerbosityLevel;
use function count;
use function in_array;
use function sprintf;

/**
* @implements Rule<FuncCall>
*/
#[RegisteredRule(level: 5)]
final class ArrayPointerFunctionOnObjectRule implements Rule
{

private const FUNCTIONS = ['current', 'pos', 'key', 'next', 'prev', 'reset', 'end'];

public function __construct(
private ReflectionProvider $reflectionProvider,
private RuleLevelHelper $ruleLevelHelper,
private PhpVersion $phpVersion,
)
{
}

public function getNodeType(): string
{
return FuncCall::class;
}

public function processNode(Node $node, Scope $scope): array
{
if (!$this->phpVersion->deprecatesArrayPointerFunctionsOnObjects()) {
return [];
}

if (!$node->name instanceof Node\Name) {
return [];
}

$args = $node->getArgs();
if (count($args) !== 1 || $args[0]->unpack) {
return [];
}

if (!$this->reflectionProvider->hasFunction($node->name, $scope)) {
return [];
}

$functionName = $this->reflectionProvider->getFunction($node->name, $scope)->getName();
if (!in_array($functionName, self::FUNCTIONS, true)) {
return [];
}

$type = $scope->getType($args[0]->value);
$arrayType = new ArrayType(new MixedType(), new MixedType());
if (
!TypeCombinator::union($arrayType, new ObjectWithoutClassType())->isSuperTypeOf($type)->yes()
|| $this->ruleLevelHelper->accepts($arrayType, $type, $scope->isDeclareStrictTypes())->result
) {
return [];
}

return [
RuleErrorBuilder::message(sprintf(
'Calling %s() on %s is deprecated in PHP 8.1.',
$functionName,
$type->describe(VerbosityLevel::typeOnly()),
))
->identifier('argument.deprecatedObject')
->build(),
];
}

}
Loading
Loading