1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-12-23 05:50:54 +01:00

Add a JSON linter.

Summary: Provide bindings for [[https://github.com/zaach/jsonlint | JSONLint]], which is a useful tool for linting and validating JSON. Theoretically, this could be done with pure PHP, however it would not be trivial (`json_decode`, for example, does not provide any context as to JSON validation errors).

Test Plan: Wrote and executed unit tests.

Reviewers: epriestley, #blessed_reviewers

Reviewed By: epriestley, #blessed_reviewers

Subscribers: epriestley, Korvin

Differential Revision: https://secure.phabricator.com/D8988
This commit is contained in:
Joshua Spence 2014-05-05 20:15:35 -07:00 committed by epriestley
parent 85000be96f
commit ab5c1562c0
36 changed files with 259 additions and 0 deletions

View file

@ -84,6 +84,8 @@ phutil_register_library_map(array(
'ArcanistInstallCertificateWorkflow' => 'workflow/ArcanistInstallCertificateWorkflow.php',
'ArcanistJSHintLinter' => 'lint/linter/ArcanistJSHintLinter.php',
'ArcanistJSHintLinterTestCase' => 'lint/linter/__tests__/ArcanistJSHintLinterTestCase.php',
'ArcanistJSONLintLinter' => 'lint/linter/ArcanistJSONLintLinter.php',
'ArcanistJSONLintLinterTestCase' => 'lint/linter/__tests__/ArcanistJSONLintLinterTestCase.php',
'ArcanistLandWorkflow' => 'workflow/ArcanistLandWorkflow.php',
'ArcanistLiberateWorkflow' => 'workflow/ArcanistLiberateWorkflow.php',
'ArcanistLintConsoleRenderer' => 'lint/renderer/ArcanistLintConsoleRenderer.php',
@ -245,6 +247,8 @@ phutil_register_library_map(array(
'ArcanistInstallCertificateWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistJSHintLinter' => 'ArcanistExternalLinter',
'ArcanistJSHintLinterTestCase' => 'ArcanistArcanistLinterTestCase',
'ArcanistJSONLintLinter' => 'ArcanistExternalLinter',
'ArcanistJSONLintLinterTestCase' => 'ArcanistArcanistLinterTestCase',
'ArcanistLandWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistLiberateWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistLintConsoleRenderer' => 'ArcanistLintRenderer',

View file

@ -0,0 +1,83 @@
<?php
/**
* A linter for JSON files.
*/
final class ArcanistJSONLintLinter extends ArcanistExternalLinter {
public function getLinterName() {
return 'JSON';
}
public function getLinterConfigurationName() {
return 'jsonlint';
}
public function getDefaultBinary() {
return 'jsonlint';
}
public function getVersion() {
// NOTE: `jsonlint --version` returns a non-zero exit status.
list($err, $stdout) = exec_manual(
'%C --version',
$this->getExecutableCommand());
$matches = array();
if (preg_match('/^(?P<version>\d+\.\d+\.\d+)$/', $stdout, $matches)) {
$version = $matches['version'];
} else {
return false;
}
}
public function getInstallInstructions() {
return pht('Install jsonlint using `npm install -g jsonlint`.');
}
public function shouldExpectCommandErrors() {
return true;
}
public function supportsReadDataFromStdin() {
return true;
}
protected function getMandatoryFlags() {
return array(
'--compact',
);
}
protected function parseLinterOutput($path, $err, $stdout, $stderr) {
$lines = phutil_split_lines($stderr, false);
$messages = array();
foreach ($lines as $line) {
$matches = null;
$match = preg_match(
'/^(?:(?<path>.+): )?' .
'line (?<line>\d+), col (?<column>\d+), ' .
'(?<description>.*)$/',
$line,
$matches);
if ($match) {
$message = new ArcanistLintMessage();
$message->setPath($path);
$message->setLine($matches['line']);
$message->setChar($matches['column']);
$message->setCode($this->getLinterName());
$message->setDescription(ucfirst($matches['description']));
$message->setSeverity(ArcanistLintSeverity::SEVERITY_ERROR);
$messages[] = $message;
}
}
if ($err && !$messages) {
return false;
}
return $messages;
}
}

View file

@ -0,0 +1,12 @@
<?php
final class ArcanistJSONLintLinterTestCase
extends ArcanistArcanistLinterTestCase {
public function testJSONLintLinter() {
$this->executeTestsInDirectory(
dirname(__FILE__).'/jsonlint/',
new ArcanistJSONLintLinter());
}
}

View file

@ -0,0 +1,59 @@
[
"JSON Test Pattern pass1",
{"object with 1 member":["array with 1 element"]},
{},
[],
-42,
true,
false,
null,
{
"integer": 1234567890,
"real": -9876.543210,
"e": 0.123456789e-12,
"E": 1.234567890E+34,
"": 23456789012E66,
"zero": 0,
"one": 1,
"space": " ",
"quote": "\"",
"backslash": "\\",
"controls": "\b\f\n\r\t",
"slash": "/ & \/",
"alpha": "abcdefghijklmnopqrstuvwyz",
"ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
"digit": "0123456789",
"0123456789": "digit",
"special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
"hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
"true": true,
"false": false,
"null": null,
"array":[ ],
"object":{ },
"address": "50 St. James Street",
"url": "http://www.JSON.org/",
"comment": "// /* <!-- --",
"# -- --> */": " ",
" s p a c e d " :[1,2 , 3
,
4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7],
"jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",
"quotes": "&#34; \u0022 %22 0x22 034 &#x22;",
"\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
: "A key can be any string"
},
0.5 ,98.6
,
99.44
,
1066,
1e1,
0.1e1,
1e-1,
1e00,2e+00,2e-00
,"rosebud"]
~~~~~~~~~~

View file

@ -0,0 +1,3 @@
{"Extra value after close": true} "misplaced quoted value"
~~~~~~~~~~
error:1:33

View file

@ -0,0 +1,3 @@
{"Illegal expression": 1 + 2}
~~~~~~~~~~
error:1:24

View file

@ -0,0 +1,3 @@
{"Illegal invocation": alert()}
~~~~~~~~~~
error:1:22

View file

@ -0,0 +1,3 @@
{"Numbers cannot have leading zeroes": 013}
~~~~~~~~~~
error:1:38

View file

@ -0,0 +1,3 @@
{"Numbers cannot be hex": 0x14}
~~~~~~~~~~
error:1:25

View file

@ -0,0 +1,3 @@
["Illegal backslash escape: \x15"]
~~~~~~~~~~
error:1:1

View file

@ -0,0 +1,3 @@
[\naked]
~~~~~~~~~~
error:1:1

View file

@ -0,0 +1,3 @@
["Illegal backslash escape: \017"]
~~~~~~~~~~
error:1:1

View file

@ -0,0 +1,3 @@
{"Missing colon" null}
~~~~~~~~~~
error:1:16

View file

@ -0,0 +1,2 @@
[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]]
~~~~~~~~~~

View file

@ -0,0 +1,3 @@
{"Double colon":: null}
~~~~~~~~~~
error:1:16

View file

@ -0,0 +1,3 @@
{"Comma instead of colon", null}
~~~~~~~~~~
error:1:25

View file

@ -0,0 +1,3 @@
["Colon instead of comma": false]
~~~~~~~~~~
error:1:25

View file

@ -0,0 +1,3 @@
["Bad value", truth]
~~~~~~~~~~
error:1:13

View file

@ -0,0 +1,3 @@
['single quote']
~~~~~~~~~~
error:1:1

View file

@ -0,0 +1,3 @@
[" tab character in string "]
~~~~~~~~~~
error:1:1

View file

@ -0,0 +1,3 @@
["tab\ character\ in\ string\ "]
~~~~~~~~~~
error:1:1

View file

@ -0,0 +1,4 @@
["line
break"]
~~~~~~~~~~
error:1:1

View file

@ -0,0 +1,4 @@
["line\
break"]
~~~~~~~~~~
error:1:1

View file

@ -0,0 +1,3 @@
[0e]
~~~~~~~~~~
error:1:1

View file

@ -0,0 +1,7 @@
{
"JSON Test Pattern pass3": {
"The outermost value": "must be an object or array.",
"In this test": "It is an object."
}
}
~~~~~~~~~~

View file

@ -0,0 +1,3 @@
[0e+]
~~~~~~~~~~
error:1:1

View file

@ -0,0 +1,3 @@
[0e+-1]
~~~~~~~~~~
error:1:1

View file

@ -0,0 +1,3 @@
{"Comma instead if closing brace": true,
~~~~~~~~~~
error:1:40

View file

@ -0,0 +1,3 @@
["mismatch"}
~~~~~~~~~~
error:1:11

View file

@ -0,0 +1,3 @@
{"extra brace": 1}}
~~~~~~~~~~
error:1:18

View file

@ -0,0 +1,3 @@
["extra comma",]
~~~~~~~~~~
error:1:15

View file

@ -0,0 +1,3 @@
["double extra comma",,]
~~~~~~~~~~
error:1:22

View file

@ -0,0 +1,3 @@
[, "<-- missing value"]
~~~~~~~~~~
error:1:1

View file

@ -0,0 +1,3 @@
["Comma after the close"],
~~~~~~~~~~
error:1:25

View file

@ -0,0 +1,3 @@
["Extra close"]]
~~~~~~~~~~
error:1:15

View file

@ -0,0 +1,3 @@
{"Extra comma": true,}
~~~~~~~~~~
error:1:21