Skip to content

Commit eafa741

Browse files
Lenny4samsonasik
andauthored
add JSON_THROW_ON_ERROR even if there is already a flag (#7867)
* add JSON_THROW_ON_ERROR even if there is already a flag * rename $constFetchs to $constFetches * fix phpstan * improve lisibility getFlags * improve lisibility getArgWithFlags * handle case the same flag is written multiple times * please move $newArg = $this->getArgWithFlags($flags) to before if, and use ! instanceof check instead of ! is_null * Apply suggestion from @samsonasik Co-authored-by: Abdul Malik Ikhsan <samsonasik@gmail.com> * Apply suggestion from @samsonasik Co-authored-by: Abdul Malik Ikhsan <samsonasik@gmail.com> * typo --------- Co-authored-by: Abdul Malik Ikhsan <samsonasik@gmail.com>
1 parent 4997962 commit eafa741

File tree

2 files changed

+78
-14
lines changed

2 files changed

+78
-14
lines changed

rules-tests/Php73/Rector/FuncCall/JsonThrowOnErrorRector/Fixture/fixture.php.inc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ function jsonThrowOnError()
1010
json_decode($json, true, 215);
1111

1212
json_decode($json, true, 122, JSON_THROW_ON_ERROR);
13+
14+
json_decode($json, true, 122, JSON_UNESCAPED_UNICODE);
1315
}
1416

1517
?>
@@ -26,6 +28,8 @@ function jsonThrowOnError()
2628
json_decode($json, true, 215, JSON_THROW_ON_ERROR);
2729

2830
json_decode($json, true, 122, JSON_THROW_ON_ERROR);
31+
32+
json_decode($json, true, 122, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
2933
}
3034

3135
?>

rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php

Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use PhpParser\Node;
88
use PhpParser\Node\Arg;
9+
use PhpParser\Node\Expr;
910
use PhpParser\Node\Expr\ConstFetch;
1011
use PhpParser\Node\Expr\FuncCall;
1112
use PhpParser\Node\Identifier;
@@ -26,6 +27,7 @@
2627
final class JsonThrowOnErrorRector extends AbstractRector implements MinPhpVersionInterface
2728
{
2829
private bool $hasChanged = false;
30+
private const array FLAGS = ['JSON_THROW_ON_ERROR'];
2931

3032
public function __construct(
3133
private readonly ValueResolver $valueResolver,
@@ -61,9 +63,6 @@ public function getNodeTypes(): array
6163
return NodeGroup::STMTS_AWARE;
6264
}
6365

64-
/**
65-
* @param StmtsAware $node
66-
*/
6766
public function refactor(Node $node): ?Node
6867
{
6968
// if found, skip it :)
@@ -133,22 +132,29 @@ private function shouldSkipFuncCall(FuncCall $funcCall): bool
133132
return $this->isFirstValueStringOrArray($funcCall);
134133
}
135134

136-
private function processJsonEncode(FuncCall $funcCall): ?FuncCall
135+
private function processJsonEncode(FuncCall $funcCall): FuncCall
137136
{
137+
$flags = [];
138138
if (isset($funcCall->args[1])) {
139-
return null;
139+
/** @var Arg $arg */
140+
$arg = $funcCall->args[1];
141+
$flags = $this->getFlags($arg);
142+
}
143+
$newArg = $this->getArgWithFlags($flags);
144+
if ($newArg instanceof Arg) {
145+
$this->hasChanged = true;
146+
$funcCall->args[1] = $newArg;
140147
}
141-
142-
$this->hasChanged = true;
143-
144-
$funcCall->args[1] = new Arg($this->createConstFetch('JSON_THROW_ON_ERROR'));
145148
return $funcCall;
146149
}
147150

148-
private function processJsonDecode(FuncCall $funcCall): ?FuncCall
151+
private function processJsonDecode(FuncCall $funcCall): FuncCall
149152
{
153+
$flags = [];
150154
if (isset($funcCall->args[3])) {
151-
return null;
155+
/** @var Arg $arg */
156+
$arg = $funcCall->args[3];
157+
$flags = $this->getFlags($arg);
152158
}
153159

154160
// set default to inter-args
@@ -160,9 +166,11 @@ private function processJsonDecode(FuncCall $funcCall): ?FuncCall
160166
$funcCall->args[2] = new Arg(new Int_(512));
161167
}
162168

163-
$this->hasChanged = true;
164-
$funcCall->args[3] = new Arg($this->createConstFetch('JSON_THROW_ON_ERROR'));
165-
169+
$newArg = $this->getArgWithFlags($flags);
170+
if ($newArg instanceof Arg) {
171+
$this->hasChanged = true;
172+
$funcCall->args[3] = $newArg;
173+
}
166174
return $funcCall;
167175
}
168176

@@ -186,4 +194,56 @@ private function isFirstValueStringOrArray(FuncCall $funcCall): bool
186194

187195
return is_array($value);
188196
}
197+
198+
/**
199+
* @param string[] $flags
200+
* @return string[]
201+
*/
202+
private function getFlags(Expr|Arg $arg, array $flags = []): array
203+
{
204+
// Unwrap Arg
205+
if ($arg instanceof Arg) {
206+
$arg = $arg->value;
207+
}
208+
209+
// Single flag: SOME_CONST
210+
if ($arg instanceof ConstFetch) {
211+
$flags[] = $arg->name->getFirst();
212+
return $flags;
213+
}
214+
215+
// Multiple flags: FLAG_A | FLAG_B | FLAG_C
216+
if ($arg instanceof Node\Expr\BinaryOp\BitwiseOr) {
217+
$flags = $this->getFlags($arg->left, $flags);
218+
$flags = $this->getFlags($arg->right, $flags);
219+
}
220+
return array_values(array_unique($flags)); // array_unique in case the same flag is written multiple times
221+
}
222+
223+
/**
224+
* @param string[] $flags
225+
*/
226+
private function getArgWithFlags(array $flags): ?Arg
227+
{
228+
$originalCount = count($flags);
229+
$flags = array_values(array_unique(array_merge($flags, self::FLAGS)));
230+
if ($originalCount === count($flags)) {
231+
return null;
232+
}
233+
// Single flag
234+
if (count($flags) === 1) {
235+
return new Arg($this->createConstFetch($flags[0]));
236+
}
237+
// Build FLAG_A | FLAG_B | FLAG_C
238+
$expr = $this->createConstFetch(array_shift($flags));
239+
240+
foreach ($flags as $flag) {
241+
$expr = new Node\Expr\BinaryOp\BitwiseOr(
242+
$expr,
243+
$this->createConstFetch($flag)
244+
);
245+
}
246+
247+
return new Arg($expr);
248+
}
189249
}

0 commit comments

Comments
 (0)