Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new JSONThrowOnError tool #40

Merged
merged 3 commits into from
Dec 23, 2018
Merged
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 .pahout.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
php_version: 7.1.0
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ Arguments:
files List of file names or directory names to be analyzed

Options:
--php-version[=PHP-VERSION] Target PHP version [default: "7.1.8"]
--php-version[=PHP-VERSION] Target PHP version [default: "7.3.0"]
--ignore-tools[=IGNORE-TOOLS] Ignore tool types [default: Nothing to ignore] (multiple values allowed)
--ignore-paths[=IGNORE-PATHS] Ignore files and directories [default: Nothing to ignore] (multiple values allowed)
--vendor[=VENDOR] Check vendor directory [default: false]
Expand Down
28 changes: 28 additions & 0 deletions docs/JSONThrowOnError.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# JSONThrowOnError

PHP 7.3 makes it to easy to handle `json_*` function errors easily.
Previously, these function didn't throw an exception when an error occurred. But now, it throws `JsonException` if passed `JSON_THROW_ON_ERROR` as a option. This is a better way to handle errors.

## Before

```php
json_decode("{");
if (json_last_error() !== JSON_ERROR_NONE) {
echo "An error occurred";
}
```

## After

```php
try {
json_decode("{", false, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $exn) {
echo "An error occurred";
}
```

## Reference

- https://secure.php.net/manual/en/function.json-decode.php
- https://secure.php.net/manual/en/function.json-encode.php
4 changes: 4 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Pahout Hints

## PHP 7.3.0

- [JSONThrowOnError](JSONThrowOnError.md)

## PHP 7.1.0

- [MultipleCatch](MultipleCatch.md)
Expand Down
2 changes: 1 addition & 1 deletion src/Command/Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ protected function configure()
'php-version',
null,
InputOption::VALUE_OPTIONAL,
'Target PHP version <comment>[default: "7.1.8"]</>',
'Target PHP version <comment>[default: "7.3.0"]</>',
null
)
->addOption(
Expand Down
4 changes: 2 additions & 2 deletions src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ class Config
private static $config;

/** @var string Target PHP version. default is latest version */
public $php_version = '7.1.8';
public $php_version = '7.3.0';

/** @var string[] Ignore tool types */
public $ignore_tools = [];

/** @var string[] Ignore files or directories */
public $ignore_paths = [];

/** @var bool Check vendor directory */
/** @var boolean Check vendor directory */
public $vendor = false;

/** @var string The name of formatter */
Expand Down
131 changes: 131 additions & 0 deletions src/Tool/JSONThrowOnError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php declare(strict_types=1);

namespace Pahout\Tool;

use \ast\Node;
use Pahout\Hint;

/**
* Check whether a `json_*` function has `JSON_THROW_ON_ERROR`
*
* PHP 7.3 makes it to easy to handle `json_*` function errors easily.
* Previously, these function didn't throw an exception when an error occurred. But now, it throws `JsonException` if passed `JSON_THROW_ON_ERROR` as a option. This is a better way to handle errors.
*
* ## Before
*
* ```php
* json_decode("{");
* if (json_last_error() !== JSON_ERROR_NONE) {
* echo "An error occurred";
* }
* ```
*
* ## After
*
* ```php
* try {
* json_decode("{", false, 512, JSON_THROW_ON_ERROR);
* } catch (JsonException $exn) {
* echo "An error occurred";
* }
* ```
*/
class JSONThrowOnError implements Base
{
use Howdah;

public const ENTRY_POINT = \ast\AST_CALL;
public const PHP_VERSION = '7.3.0';
public const HINT_TYPE = "JSONThrowOnError";
private const HINT_MESSAGE = 'Encourage to specify JSON_THROW_ON_ERROR option.';
private const HINT_LINK = Hint::DOCUMENT_LINK."/JSONThrowOnError.md";

/**
* Check whether a `json_*` function has `JSON_THROW_ON_ERROR`
*
* @param string $file File name to be analyzed.
* @param Node $node AST node to be analyzed.
* @return Hint[] List of hints obtained from results.
*/
public function run(string $file, Node $node): array
{
if ($this->isFunctionCall($node, 'json_decode')) {
$options = $node->children['args']->children[3] ?? null;
if ($this->shouldCheckOption($options) && !$this->isIncludeJSONThrowOnErrorOption($options)) {
return [new Hint(
self::HINT_TYPE,
self::HINT_MESSAGE,
$file,
$node->lineno,
self::HINT_LINK
)];
}
}
if ($this->isFunctionCall($node, 'json_encode')) {
$options = $node->children['args']->children[1] ?? null;
if ($this->shouldCheckOption($options) && !$this->isIncludeJSONThrowOnErrorOption($options)) {
return [new Hint(
self::HINT_TYPE,
self::HINT_MESSAGE,
$file,
$node->lineno,
self::HINT_LINK
)];
}
}

return [];
}

/**
* Check whether the passed options node should be checked.
*
* This function is used to suppress false positives.
*
* @param mixed $node Options node.
* @return boolean Result.
*/
private function shouldCheckOption($node): Bool
{
if (!$node instanceof Node) {
return true;
}
if ($node->kind === \ast\AST_CONST) {
return true;
}
if ($node->kind === \ast\AST_BINARY_OP && $node->flags === \ast\flags\BINARY_BITWISE_OR) {
return true;
}

return false;
}

/**
* Check whether the passed node has `JSON_THROW_ON_ERROR`
*
* This function is also aware of inclusive or.
*
* @param mixed $node Node or others.
* @return boolean Result.
*/
private function isIncludeJSONThrowOnErrorOption($node): Bool
{
if (!$node instanceof Node) {
return false;
}

if ($node->kind === \ast\AST_CONST) {
$name = $node->children["name"];
if ($name->kind === \ast\AST_NAME && $name->children["name"] === "JSON_THROW_ON_ERROR") {
return true;
}
}

if ($node->kind === \ast\AST_BINARY_OP && $node->flags === \ast\flags\BINARY_BITWISE_OR) {
return $this->isIncludeJSONThrowOnErrorOption($node->children["left"])
|| $this->isIncludeJSONThrowOnErrorOption($node->children["right"]);
}

return false;
}
}
1 change: 1 addition & 0 deletions src/ToolBox.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class ToolBox
'DuplicateCaseCondition',
'LooseReturnCheck',
'UnneededRegularExpression',
'JSONThrowOnError',
];

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/ConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function test_default_config()
]);
$config = Config::getInstance();

$this->assertEquals('7.1.8', $config->php_version);
$this->assertEquals('7.3.0', $config->php_version);
$this->assertEmpty($config->ignore_tools);
$this->assertEquals([
self::FIXTURE_PATH.'/with_vendor/vendor/test.php'
Expand Down
Loading