Skip to content

Commit 5340f45

Browse files
ArshidArshid
authored andcommitted
[php 8.3] Add json_validate rule
1 parent 6df1782 commit 5340f45

1 file changed

Lines changed: 88 additions & 15 deletions

File tree

rules/Php83/Rector/BooleanAnd/JsonValidateRector.php

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,18 @@
1111
use PhpParser\Node\Expr\BinaryOp\NotIdentical;
1212
use PhpParser\Node\Expr\ConstFetch;
1313
use PhpParser\Node\Expr\FuncCall;
14+
use PhpParser\Node\Identifier;
1415
use PhpParser\Node\Name;
16+
use PHPStan\Analyser\Scope;
17+
use PHPStan\Reflection\Native\NativeFunctionReflection;
18+
use Rector\NodeAnalyzer\ArgsAnalyzer;
1519
use Rector\NodeManipulator\BinaryOpManipulator;
20+
use Rector\NodeTypeResolver\Node\AttributeKey;
21+
use Rector\NodeTypeResolver\PHPStan\ParametersAcceptorSelectorVariantsWrapper;
1622
use Rector\Php71\ValueObject\TwoNodeMatch;
1723
use Rector\PhpParser\Node\Value\ValueResolver;
1824
use Rector\Rector\AbstractRector;
25+
use Rector\Reflection\ReflectionResolver;
1926
use Rector\ValueObject\PhpVersionFeature;
2027
use Rector\ValueObject\PolyfillPackage;
2128
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
@@ -34,6 +41,8 @@ final class JsonValidateRector extends AbstractRector implements MinPhpVersionIn
3441

3542
public function __construct(
3643
private readonly BinaryOpManipulator $binaryOpManipulator,
44+
private readonly ReflectionResolver $reflectionResolver,
45+
private readonly ArgsAnalyzer $argsAnalyzer,
3746
private ValueResolver $valueResolver,
3847
) {
3948
}
@@ -87,13 +96,21 @@ public function refactor(Node $node): ?Node
8796
return null;
8897
}
8998

99+
$scope = $node->getAttribute(AttributeKey::SCOPE);
100+
if (! $scope instanceof Scope) {
101+
return null;
102+
}
103+
90104
$args = $funcCall->getArgs();
105+
$positions = $this->argsAnalyzer->hasNamedArg($args)
106+
? $this->resolveNamedPositions($args)
107+
: $this->resolveOriginalPositions($funcCall, $scope);
91108

92-
if (count($args) < 1 ){
109+
if ($positions === []) {
93110
return null;
94111
}
95112

96-
if (! $this->validateArgs($funcCall)) {
113+
if (! $this->validateArgs($args, $positions)) {
97114
return null;
98115
}
99116
$funcCall->name = new Name('json_validate');
@@ -147,25 +164,81 @@ public function matchJsonValidateArg(BooleanAnd $booleanAnd): ?FuncCall
147164
return $funcCall;
148165
}
149166

150-
protected function validateArgs(FuncCall $funcCall): bool
167+
/**
168+
* @param Arg[] $args
169+
* @param int[]|string[] $positions
170+
*/
171+
protected function validateArgs(array $args, array $positions): bool
172+
{
173+
foreach ($positions as $position) {
174+
$arg = $args[$position] ?? '';
175+
if ($arg instanceof Arg && $arg->name instanceof Identifier && $arg->name->toString() === 'flags') {
176+
$flags = $this->valueResolver->getValue($arg);
177+
if ($flags !== JSON_INVALID_UTF8_IGNORE) {
178+
return false;
179+
}
180+
}
181+
if ($arg instanceof Arg && $arg->name instanceof Identifier && $arg->name->toString() === 'depth') {
182+
$depth = $this->valueResolver->getValue($arg);
183+
if ($depth <= 0) {
184+
return false;
185+
}
186+
if ($depth > self::JSON_MAX_DEPTH) {
187+
return false;
188+
}
189+
}
190+
}
191+
192+
return true;
193+
}
194+
195+
/**
196+
* @param Arg[] $args
197+
* @return int[]|string[]
198+
*/
199+
private function resolveNamedPositions(array $args): array
151200
{
152-
$depth = $funcCall->getArg('depth', 2);
153-
$flags = $funcCall->getArg('flags', 3);
201+
$positions = [];
154202

155-
if ($flags instanceof Arg) {
156-
$flagsValue = $this->valueResolver->getValue($flags);
157-
if ($flagsValue !== JSON_INVALID_UTF8_IGNORE) {
158-
return false;
203+
foreach ($args as $position => $arg) {
204+
if (! $arg->name instanceof Identifier) {
205+
continue;
159206
}
207+
208+
if (! $this->isNames($arg->name, self::ARG_NAMES)) {
209+
continue;
210+
}
211+
212+
$positions[] = $position;
160213
}
161214

162-
if ($depth instanceof Arg) {
163-
$depthValue = $this->valueResolver->getValue($depth);
164-
if ($depthValue <= 0 || $depthValue > self::JSON_MAX_DEPTH) {
165-
return false;
215+
return $positions;
216+
}
217+
218+
/**
219+
* @return int[]|string[]
220+
*/
221+
private function resolveOriginalPositions(FuncCall $funcCall, Scope $scope): array
222+
{
223+
$functionReflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($funcCall);
224+
if (! $functionReflection instanceof NativeFunctionReflection) {
225+
return [];
226+
}
227+
228+
$parametersAcceptor = ParametersAcceptorSelectorVariantsWrapper::select(
229+
$functionReflection,
230+
$funcCall,
231+
$scope
232+
);
233+
234+
$positions = [];
235+
236+
foreach ($parametersAcceptor->getParameters() as $position => $parameterReflection) {
237+
if (in_array($parameterReflection->getName(), self::ARG_NAMES, true)) {
238+
$positions[] = $position;
166239
}
167240
}
168241

169-
return true;
242+
return $positions;
170243
}
171-
}
244+
}

0 commit comments

Comments
 (0)