Repository: VKCOM/noverify
Pull request #967 (2021-04-05 17:59:35)

linter: added some checks from phpinspectionsea
Simplify
```php
$x[$i] !== null ? $x[$i] : $y;
// to
$x[$i] ?? $y;

array_key_exists($i, $x) ? $x[$i] : $y;
// to
$x[$i] ?? $y;

substr($x, $y, 1);
// to
$x[$y];

array_push($a, $v);
// to
$a[] = $v;
```

#332 


Patch:
diff --git a/src/cmd/embeddedrules/rules.php b/src/cmd/embeddedrules/rules.php
index 711e04f..3e866d8 100644
--- a/src/cmd/embeddedrules/rules.php
+++ b/src/cmd/embeddedrules/rules.php
@@ -35,6 +35,26 @@ function ternarySimplify() {
    * @fix $x ?? $y
    */
   isset($x) ? $x : $y;
+
+  /**
+   * @maybe could rewrite as `$x[$i] ?? $y`
+   * @pure $i
+   */
+  any_indexing: {
+    $x[$i] !== null ? $x[$i] : $y;
+    null !== $x[$i] ? $x[$i] : $y;
+    $x[$i] === null ? $y : $x[$i];
+    null === $x[$i] ? $y : $x[$i];
+  }
+
+  /**
+   * @maybe could rewrite as `$x[$i] ?? $y`
+   * @pure $i
+   */
+  any_array_key_exists: {
+    array_key_exists($i, $x) ? $x[$i] : $y;
+    !array_key_exists($i, $x) ? $y : $x[$i];
+  }
 }
 
 /**
@@ -295,6 +315,16 @@ function callSimplify() {
    * @fix   array_key_exists($key, $a)
    */
   in_array($key, array_keys($a));
+
+  /**
+   * @maybe Could simplify to $x[$y]
+   */
+  substr($x, $y, 1);
+
+  /**
+   * @maybe Could simplify to $a[] = $v
+   */
+  array_push($a, $v);
 }
 
 /**


Test patch:
diff --git a/src/tests/golden/testdata/embeddedrules/callSimplify.php b/src/tests/golden/testdata/embeddedrules/callSimplify.php
index 3497240..841cf5c 100644
--- a/src/tests/golden/testdata/embeddedrules/callSimplify.php
+++ b/src/tests/golden/testdata/embeddedrules/callSimplify.php
@@ -6,3 +6,20 @@ function in_array_over_array_keys(array $array) {
     $_ = in_array('abc', array_keys($array), true);  // don't touch, has 3rd arg
     $_ = in_array('abc', array_keys($array), false); // don't touch, has 3rd arg
 }
+
+function some_substr(string $str, int $index) {
+    $_ = substr($str, $index, 1);
+    $_ = substr("hello", $index, 1);
+    $_ = substr("hello", 2, 1);
+
+    // ok, length != 1
+    $_ = substr($str, $index, 10);
+}
+
+function some_array_push(array $array, int $val) {
+    array_push($array, $val);
+    array_push($array, 10);
+
+    // ok, two element
+    array_push($array, 100, 1200);
+}
diff --git a/src/tests/golden/testdata/embeddedrules/golden.txt b/src/tests/golden/testdata/embeddedrules/golden.txt
index f8dcaa4..e9d8950 100644
--- a/src/tests/golden/testdata/embeddedrules/golden.txt
+++ b/src/tests/golden/testdata/embeddedrules/golden.txt
@@ -67,6 +67,21 @@ WARNING bitwiseOps: Used | bitwise operator over bool operands, perhaps || is in
 MAYBE   callSimplify: Could simplify to array_key_exists('abc', $array) at testdata/embeddedrules/callSimplify.php:4
     $_ = in_array('abc', array_keys($array)); // bad
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   callSimplify: Could simplify to $str[$index] at testdata/embeddedrules/callSimplify.php:11
+    $_ = substr($str, $index, 1);
+         ^^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   callSimplify: Could simplify to "hello"[$index] at testdata/embeddedrules/callSimplify.php:12
+    $_ = substr("hello", $index, 1);
+         ^^^^^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   callSimplify: Could simplify to "hello"[2] at testdata/embeddedrules/callSimplify.php:13
+    $_ = substr("hello", 2, 1);
+         ^^^^^^^^^^^^^^^^^^^^^
+MAYBE   callSimplify: Could simplify to $array[] = $val at testdata/embeddedrules/callSimplify.php:20
+    array_push($array, $val);
+    ^^^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   callSimplify: Could simplify to $array[] = 10 at testdata/embeddedrules/callSimplify.php:21
+    array_push($array, 10);
+    ^^^^^^^^^^^^^^^^^^^^^^
 WARNING indexingSyntax: a{i} indexing is deprecated since PHP 7.4, use a[i] instead at testdata/embeddedrules/indexingSyntax.php:14
     $_ = $a{0};
          ^^^^^
@@ -274,6 +289,24 @@ MAYBE   ternarySimplify: could rewrite as `$x ?: $y` at testdata/embeddedrules/t
 MAYBE   ternarySimplify: could rewrite as `$x[1] ?? $y` at testdata/embeddedrules/ternarySimplify.php:15
     sink(isset($x[1]) ? $x[1] : $y);
          ^^^^^^^^^^^^^^^^^^^^^^^^^
-MAYBE   ternarySimplify: could rewrite as `(bool)($flags & SOME_MASK)` at testdata/embeddedrules/ternarySimplify.php:27
+MAYBE   ternarySimplify: could rewrite as `$x_arr[10] ?? $y` at testdata/embeddedrules/ternarySimplify.php:22
+    sink($x_arr[10] !== null ? $x_arr[10] : $y);
+         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   ternarySimplify: could rewrite as `$x_arr[10] ?? $y` at testdata/embeddedrules/ternarySimplify.php:23
+    sink(null !== $x_arr[10] ? $x_arr[10] : $y);
+         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   ternarySimplify: could rewrite as `$x_arr[10] ?? $y` at testdata/embeddedrules/ternarySimplify.php:24
+    sink($x_arr[10] === null ? $y : $x_arr[10]);
+         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   ternarySimplify: could rewrite as `$x_arr[10] ?? $y` at testdata/embeddedrules/ternarySimplify.php:25
+    sink(null === $x_arr[10] ? $y : $x_arr[10]);
+         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   ternarySimplify: could rewrite as `$x_arr[10] ?? null` at testdata/embeddedrules/ternarySimplify.php:27
+    sink(array_key_exists(10, $x_arr) ? $x_arr[10] : null);
+         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   ternarySimplify: could rewrite as `$x_arr[10] ?? null` at testdata/embeddedrules/ternarySimplify.php:28
+    sink(! array_key_exists(10, $x_arr) ? null : $x_arr[10]);
+         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   ternarySimplify: could rewrite as `(bool)($flags & SOME_MASK)` at testdata/embeddedrules/ternarySimplify.php:46
     sink(($flags & SOME_MASK) ? true : false);
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/src/tests/golden/testdata/embeddedrules/ternarySimplify.php b/src/tests/golden/testdata/embeddedrules/ternarySimplify.php
index fb50bd5..fd4648b 100644
--- a/src/tests/golden/testdata/embeddedrules/ternarySimplify.php
+++ b/src/tests/golden/testdata/embeddedrules/ternarySimplify.php
@@ -16,6 +16,25 @@ function ternarySimplify(int $x, $y) {
     sink($x[1] ?? $y);
 
     sink(random() ? random() : $y);
+
+    $x_arr = [];
+
+    sink($x_arr[10] !== null ? $x_arr[10] : $y);
+    sink(null !== $x_arr[10] ? $x_arr[10] : $y);
+    sink($x_arr[10] === null ? $y : $x_arr[10]);
+    sink(null === $x_arr[10] ? $y : $x_arr[10]);
+
+    sink(array_key_exists(10, $x_arr) ? $x_arr[10] : null);
+    sink(! array_key_exists(10, $x_arr) ? null : $x_arr[10]);
+
+    // ok, index not pure
+    sink($x_arr[rand()] !== null ? $x_arr[rand()] : $y);
+    sink(null !== $x_arr[rand()] ? $x_arr[rand()] : $y);
+    sink($x_arr[rand()] === null ? $y : $x_arr[rand()]);
+    sink(null === $x_arr[rand()] ? $y : $x_arr[rand()]);
+
+    sink(array_key_exists(rand(), $x_arr) ? $x_arr[rand()] : null);
+    sink(! array_key_exists(rand(), $x_arr) ? null : $x_arr[rand()]);
 }
 
 define('SOME_MASK', 0x0f);
diff --git a/src/tests/golden/testdata/flysystem/golden.txt b/src/tests/golden/testdata/flysystem/golden.txt
index b3270c8..478f3e0 100644
--- a/src/tests/golden/testdata/flysystem/golden.txt
+++ b/src/tests/golden/testdata/flysystem/golden.txt
@@ -1,6 +1,9 @@
 MAYBE   regexpSimplify: May re-write '/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/' as '/^\d{2,4}-\d{2}-\d{2}/' at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:536
         return preg_match('/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/', $item) ? 'windows' : 'unix';
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   callSimplify: Could simplify to $permissions[0] at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:548
+        return substr($permissions, 0, 1) === 'd' ? 'dir' : 'file';
+               ^^^^^^^^^^^^^^^^^^^^^^^^^^
 MAYBE   regexpSimplify: May re-write '/^total [0-9]*$/' as '/^total \d*$/' at testdata/flysystem/src/Adapter/Ftp.php:407
         if (preg_match('/^total [0-9]*$/', $listing[0])) {
                        ^^^^^^^^^^^^^^^^^^
diff --git a/src/tests/golden/testdata/mustache/golden.txt b/src/tests/golden/testdata/mustache/golden.txt
index 8dabce9..4f9fd95 100644
--- a/src/tests/golden/testdata/mustache/golden.txt
+++ b/src/tests/golden/testdata/mustache/golden.txt
@@ -22,6 +22,15 @@ MAYBE   ternarySimplify: could rewrite as `$node[Mustache_Tokenizer::FILTERS] ??
 WARNING unused: Variable keystr is unused (use $_ to ignore this inspection) at testdata/mustache/src/Mustache/Compiler.php:289
         $keystr = var_export($key, true);
         ^^^^^^^
+MAYBE   callSimplify: Could simplify to $id[0] at testdata/mustache/src/Mustache/Compiler.php:646
+            if (substr($id, 0, 1) === '.') {
+                ^^^^^^^^^^^^^^^^^
+MAYBE   callSimplify: Could simplify to $this->stack[] = $value at testdata/mustache/src/Mustache/Context.php:39
+        array_push($this->stack, $value);
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   callSimplify: Could simplify to $this->blockStack[] = $value at testdata/mustache/src/Mustache/Context.php:49
+        array_push($this->blockStack, $value);
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 MAYBE   ternarySimplify: could rewrite as `$options['cache_file_mode'] ?? null` at testdata/mustache/src/Mustache/Engine.php:156
                 $mode  = isset($options['cache_file_mode']) ? $options['cache_file_mode'] : null;
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/src/tests/golden/testdata/parsedown/golden.txt b/src/tests/golden/testdata/parsedown/golden.txt
index cc95b7a..59007ad 100644
--- a/src/tests/golden/testdata/parsedown/golden.txt
+++ b/src/tests/golden/testdata/parsedown/golden.txt
@@ -52,6 +52,9 @@ MAYBE   regexpSimplify: May re-write '/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[
 MAYBE   regexpSimplify: May re-write '/^<\/\w[\w-]*+[ ]*+>/s' as '/^<\/\w[\w-]*+ *+>/s' at testdata/parsedown/parsedown.php:1470
         if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches))
                                                        ^^^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   callSimplify: Could simplify to $Excerpt['text'][1] at testdata/parsedown/parsedown.php:1497
+        if (substr($Excerpt['text'], 1, 1) !== ' ' and strpos($Excerpt['text'], ';') !== false
+            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 MAYBE   trailingComma: last element in a multi-line array must have a trailing comma at testdata/parsedown/parsedown.php:1522
                     'handler' => array(
                     ^
diff --git a/src/tests/golden/testdata/qrcode/golden.txt b/src/tests/golden/testdata/qrcode/golden.txt
index ac22f8c..cc4cdf3 100644
--- a/src/tests/golden/testdata/qrcode/golden.txt
+++ b/src/tests/golden/testdata/qrcode/golden.txt
@@ -40,12 +40,27 @@ WARNING caseBreak: Add break or '// fallthrough' to the end of the case at testd
 WARNING caseBreak: Add break or '// fallthrough' to the end of the case at testdata/qrcode/qrcode.php:344
       case 1:  /* 10 - 26 */
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   callSimplify: Could simplify to $group[0] at testdata/qrcode/qrcode.php:361
+        $c1     = strpos($alphabet, substr($group, 0, 1));
+                                    ^^^^^^^^^^^^^^^^^^^^
+MAYBE   callSimplify: Could simplify to $group[1] at testdata/qrcode/qrcode.php:362
+        $c2     = strpos($alphabet, substr($group, 1, 1));
+                                    ^^^^^^^^^^^^^^^^^^^^
 WARNING caseBreak: Add break or '// fallthrough' to the end of the case at testdata/qrcode/qrcode.php:393
       case 1:  /* 10 - 26 */
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   callSimplify: Could simplify to $data[$i] at testdata/qrcode/qrcode.php:413
+      $ch     = ord(substr($data, $i, 1));
+                    ^^^^^^^^^^^^^^^^^^^^
 WARNING caseBreak: Add break or '// fallthrough' to the end of the case at testdata/qrcode/qrcode.php:430
       case 2:  /* 27 - 40 */
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 WARNING caseBreak: Add break or '// fallthrough' to the end of the case at testdata/qrcode/qrcode.php:433
       case 1:  /* 10 - 26 */
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   callSimplify: Could simplify to $group[0] at testdata/qrcode/qrcode.php:448
+      $c1    = ord(substr($group, 0, 1));
+                   ^^^^^^^^^^^^^^^^^^^^
+MAYBE   callSimplify: Could simplify to $group[1] at testdata/qrcode/qrcode.php:449
+      $c2    = ord(substr($group, 1, 1));
+                   ^^^^^^^^^^^^^^^^^^^^
diff --git a/src/tests/golden/testdata/quickfix/ternarySimplify.php b/src/tests/golden/testdata/quickfix/ternarySimplify.php
index 14b9842..81fdc3e 100644
--- a/src/tests/golden/testdata/quickfix/ternarySimplify.php
+++ b/src/tests/golden/testdata/quickfix/ternarySimplify.php
@@ -1,15 +1,19 @@
 <?php
 
 $a = 5;
-function f() {
+function f(): int {
+    global $a;
     $a++;
     return $a;
 }
 
 function ternarySimplify() {
-    $z = $x ? $x : 5;
-    // f not pure
-    $z = f() ? f() : 5;
+    $x = 0;
+    $y = 0;
 
-    $z = isset($x) ? $x : $y;
+    $_ = $x ? $x : 5;
+    // ok, f not pure
+    $_ = f() ? f() : 5;
+
+    $_ = isset($x) ? $x : $y;
 }
diff --git a/src/tests/golden/testdata/quickfix/ternarySimplify.php.fix.expected b/src/tests/golden/testdata/quickfix/ternarySimplify.php.fix.expected
index 1f54655..ea918f1 100644
--- a/src/tests/golden/testdata/quickfix/ternarySimplify.php.fix.expected
+++ b/src/tests/golden/testdata/quickfix/ternarySimplify.php.fix.expected
@@ -1,15 +1,19 @@
 <?php
 
 $a = 5;
-function f() {
+function f(): int {
+    global $a;
     $a++;
     return $a;
 }
 
 function ternarySimplify() {
-    $z = $x ?: 5;
-    // f not pure
-    $z = f() ? f() : 5;
+    $x = 0;
+    $y = 0;
 
-    $z = $x ?? $y;
+    $_ = $x ?: 5;
+    // ok, f not pure
+    $_ = f() ? f() : 5;
+
+    $_ = $x ?? $y;
 }
diff --git a/src/tests/golden/testdata/twitter-api-php/golden.txt b/src/tests/golden/testdata/twitter-api-php/golden.txt
index 3e75deb..e5242d1 100644
--- a/src/tests/golden/testdata/twitter-api-php/golden.txt
+++ b/src/tests/golden/testdata/twitter-api-php/golden.txt
@@ -1,3 +1,6 @@
+MAYBE   callSimplify: Could simplify to $array['status'][0] at testdata/twitter-api-php/TwitterAPIExchange.php:117
+        if (isset($array['status']) && substr($array['status'], 0, 1) === '@')
+                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 WARNING unused: Variable key is unused (use $_ to ignore this inspection) at testdata/twitter-api-php/TwitterAPIExchange.php:122
         foreach ($array as $key => &$value)
                            ^^^^
diff --git a/src/tests/golden/testdata/underscore/golden.txt b/src/tests/golden/testdata/underscore/golden.txt
index 01a27ba..f07cb02 100644
--- a/src/tests/golden/testdata/underscore/golden.txt
+++ b/src/tests/golden/testdata/underscore/golden.txt
@@ -76,6 +76,9 @@ MAYBE   arraySyntax: Use of old array syntax (use short form instead) at testdat
 MAYBE   arrayAccess: Array access to non-array type \__|mixed at testdata/underscore/underscore.php:480
       if(!is_array($return_arrays[$k])) $return_arrays[$k] = array();
                                         ^^^^^^^^^^^^^^
+MAYBE   ternarySimplify: could rewrite as `$array[$k] ?? null` at testdata/underscore/underscore.php:483
+        $return_arrays[$k][$a] = array_key_exists($k, $array) ? $array[$k] : null;
+                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 MAYBE   arrayAccess: Array access to non-array type \__|empty_array[]|mixed at testdata/underscore/underscore.php:483
         $return_arrays[$k][$a] = array_key_exists($k, $array) ? $array[$k] : null;
         ^^^^^^^^^^^^^^