From ab5c1562c0f6a8736f687a1628f85ec25c13426b Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Mon, 5 May 2014 20:15:35 -0700 Subject: [PATCH] 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 --- src/__phutil_library_map__.php | 4 + src/lint/linter/ArcanistJSONLintLinter.php | 83 +++++++++++++++++++ .../ArcanistJSONLintLinterTestCase.php | 12 +++ .../linter/__tests__/jsonlint/1.lint-test | 59 +++++++++++++ .../linter/__tests__/jsonlint/10.lint-test | 3 + .../linter/__tests__/jsonlint/11.lint-test | 3 + .../linter/__tests__/jsonlint/12.lint-test | 3 + .../linter/__tests__/jsonlint/13.lint-test | 3 + .../linter/__tests__/jsonlint/14.lint-test | 3 + .../linter/__tests__/jsonlint/15.lint-test | 3 + .../linter/__tests__/jsonlint/16.lint-test | 3 + .../linter/__tests__/jsonlint/17.lint-test | 3 + .../linter/__tests__/jsonlint/19.lint-test | 3 + .../linter/__tests__/jsonlint/2.lint-test | 2 + .../linter/__tests__/jsonlint/20.lint-test | 3 + .../linter/__tests__/jsonlint/21.lint-test | 3 + .../linter/__tests__/jsonlint/22.lint-test | 3 + .../linter/__tests__/jsonlint/23.lint-test | 3 + .../linter/__tests__/jsonlint/24.lint-test | 3 + .../linter/__tests__/jsonlint/25.lint-test | 3 + .../linter/__tests__/jsonlint/26.lint-test | 3 + .../linter/__tests__/jsonlint/27.lint-test | 4 + .../linter/__tests__/jsonlint/28.lint-test | 4 + .../linter/__tests__/jsonlint/29.lint-test | 3 + .../linter/__tests__/jsonlint/3.lint-test | 7 ++ .../linter/__tests__/jsonlint/30.lint-test | 3 + .../linter/__tests__/jsonlint/31.lint-test | 3 + .../linter/__tests__/jsonlint/32.lint-test | 3 + .../linter/__tests__/jsonlint/33.lint-test | 3 + .../linter/__tests__/jsonlint/34.lint-test | 3 + .../linter/__tests__/jsonlint/4.lint-test | 3 + .../linter/__tests__/jsonlint/5.lint-test | 3 + .../linter/__tests__/jsonlint/6.lint-test | 3 + .../linter/__tests__/jsonlint/7.lint-test | 3 + .../linter/__tests__/jsonlint/8.lint-test | 3 + .../linter/__tests__/jsonlint/9.lint-test | 3 + 36 files changed, 259 insertions(+) create mode 100644 src/lint/linter/ArcanistJSONLintLinter.php create mode 100644 src/lint/linter/__tests__/ArcanistJSONLintLinterTestCase.php create mode 100644 src/lint/linter/__tests__/jsonlint/1.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/10.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/11.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/12.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/13.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/14.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/15.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/16.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/17.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/19.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/2.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/20.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/21.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/22.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/23.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/24.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/25.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/26.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/27.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/28.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/29.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/3.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/30.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/31.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/32.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/33.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/34.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/4.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/5.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/6.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/7.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/8.lint-test create mode 100644 src/lint/linter/__tests__/jsonlint/9.lint-test diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2c36f3b9..1eab07df 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', diff --git a/src/lint/linter/ArcanistJSONLintLinter.php b/src/lint/linter/ArcanistJSONLintLinter.php new file mode 100644 index 00000000..60276e0c --- /dev/null +++ b/src/lint/linter/ArcanistJSONLintLinter.php @@ -0,0 +1,83 @@ +getExecutableCommand()); + + $matches = array(); + if (preg_match('/^(?P\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( + '/^(?:(?.+): )?' . + 'line (?\d+), col (?\d+), ' . + '(?.*)$/', + $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; + } +} diff --git a/src/lint/linter/__tests__/ArcanistJSONLintLinterTestCase.php b/src/lint/linter/__tests__/ArcanistJSONLintLinterTestCase.php new file mode 100644 index 00000000..6589bd8d --- /dev/null +++ b/src/lint/linter/__tests__/ArcanistJSONLintLinterTestCase.php @@ -0,0 +1,12 @@ +executeTestsInDirectory( + dirname(__FILE__).'/jsonlint/', + new ArcanistJSONLintLinter()); + } + +} diff --git a/src/lint/linter/__tests__/jsonlint/1.lint-test b/src/lint/linter/__tests__/jsonlint/1.lint-test new file mode 100644 index 00000000..2ddd135c --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/1.lint-test @@ -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": "" \u0022 %22 0x22 034 "", + "\/\\\"\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"] +~~~~~~~~~~ diff --git a/src/lint/linter/__tests__/jsonlint/10.lint-test b/src/lint/linter/__tests__/jsonlint/10.lint-test new file mode 100644 index 00000000..c8542e71 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/10.lint-test @@ -0,0 +1,3 @@ +{"Extra value after close": true} "misplaced quoted value" +~~~~~~~~~~ +error:1:33 diff --git a/src/lint/linter/__tests__/jsonlint/11.lint-test b/src/lint/linter/__tests__/jsonlint/11.lint-test new file mode 100644 index 00000000..f6b58925 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/11.lint-test @@ -0,0 +1,3 @@ +{"Illegal expression": 1 + 2} +~~~~~~~~~~ +error:1:24 diff --git a/src/lint/linter/__tests__/jsonlint/12.lint-test b/src/lint/linter/__tests__/jsonlint/12.lint-test new file mode 100644 index 00000000..7a34e2d4 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/12.lint-test @@ -0,0 +1,3 @@ +{"Illegal invocation": alert()} +~~~~~~~~~~ +error:1:22 diff --git a/src/lint/linter/__tests__/jsonlint/13.lint-test b/src/lint/linter/__tests__/jsonlint/13.lint-test new file mode 100644 index 00000000..d04fb848 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/13.lint-test @@ -0,0 +1,3 @@ +{"Numbers cannot have leading zeroes": 013} +~~~~~~~~~~ +error:1:38 diff --git a/src/lint/linter/__tests__/jsonlint/14.lint-test b/src/lint/linter/__tests__/jsonlint/14.lint-test new file mode 100644 index 00000000..37165bb4 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/14.lint-test @@ -0,0 +1,3 @@ +{"Numbers cannot be hex": 0x14} +~~~~~~~~~~ +error:1:25 diff --git a/src/lint/linter/__tests__/jsonlint/15.lint-test b/src/lint/linter/__tests__/jsonlint/15.lint-test new file mode 100644 index 00000000..eeaa5c31 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/15.lint-test @@ -0,0 +1,3 @@ +["Illegal backslash escape: \x15"] +~~~~~~~~~~ +error:1:1 diff --git a/src/lint/linter/__tests__/jsonlint/16.lint-test b/src/lint/linter/__tests__/jsonlint/16.lint-test new file mode 100644 index 00000000..c69cf296 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/16.lint-test @@ -0,0 +1,3 @@ +[\naked] +~~~~~~~~~~ +error:1:1 diff --git a/src/lint/linter/__tests__/jsonlint/17.lint-test b/src/lint/linter/__tests__/jsonlint/17.lint-test new file mode 100644 index 00000000..51157896 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/17.lint-test @@ -0,0 +1,3 @@ +["Illegal backslash escape: \017"] +~~~~~~~~~~ +error:1:1 diff --git a/src/lint/linter/__tests__/jsonlint/19.lint-test b/src/lint/linter/__tests__/jsonlint/19.lint-test new file mode 100644 index 00000000..23387dad --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/19.lint-test @@ -0,0 +1,3 @@ +{"Missing colon" null} +~~~~~~~~~~ +error:1:16 diff --git a/src/lint/linter/__tests__/jsonlint/2.lint-test b/src/lint/linter/__tests__/jsonlint/2.lint-test new file mode 100644 index 00000000..cfdc831f --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/2.lint-test @@ -0,0 +1,2 @@ +[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]] +~~~~~~~~~~ diff --git a/src/lint/linter/__tests__/jsonlint/20.lint-test b/src/lint/linter/__tests__/jsonlint/20.lint-test new file mode 100644 index 00000000..df13ef24 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/20.lint-test @@ -0,0 +1,3 @@ +{"Double colon":: null} +~~~~~~~~~~ +error:1:16 diff --git a/src/lint/linter/__tests__/jsonlint/21.lint-test b/src/lint/linter/__tests__/jsonlint/21.lint-test new file mode 100644 index 00000000..4b1a0e9d --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/21.lint-test @@ -0,0 +1,3 @@ +{"Comma instead of colon", null} +~~~~~~~~~~ +error:1:25 diff --git a/src/lint/linter/__tests__/jsonlint/22.lint-test b/src/lint/linter/__tests__/jsonlint/22.lint-test new file mode 100644 index 00000000..ccbadf97 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/22.lint-test @@ -0,0 +1,3 @@ +["Colon instead of comma": false] +~~~~~~~~~~ +error:1:25 diff --git a/src/lint/linter/__tests__/jsonlint/23.lint-test b/src/lint/linter/__tests__/jsonlint/23.lint-test new file mode 100644 index 00000000..394c5b97 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/23.lint-test @@ -0,0 +1,3 @@ +["Bad value", truth] +~~~~~~~~~~ +error:1:13 diff --git a/src/lint/linter/__tests__/jsonlint/24.lint-test b/src/lint/linter/__tests__/jsonlint/24.lint-test new file mode 100644 index 00000000..46af63e5 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/24.lint-test @@ -0,0 +1,3 @@ +['single quote'] +~~~~~~~~~~ +error:1:1 diff --git a/src/lint/linter/__tests__/jsonlint/25.lint-test b/src/lint/linter/__tests__/jsonlint/25.lint-test new file mode 100644 index 00000000..b2cb8052 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/25.lint-test @@ -0,0 +1,3 @@ +[" tab character in string "] +~~~~~~~~~~ +error:1:1 diff --git a/src/lint/linter/__tests__/jsonlint/26.lint-test b/src/lint/linter/__tests__/jsonlint/26.lint-test new file mode 100644 index 00000000..d0715ddd --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/26.lint-test @@ -0,0 +1,3 @@ +["tab\ character\ in\ string\ "] +~~~~~~~~~~ +error:1:1 diff --git a/src/lint/linter/__tests__/jsonlint/27.lint-test b/src/lint/linter/__tests__/jsonlint/27.lint-test new file mode 100644 index 00000000..c47e4d1c --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/27.lint-test @@ -0,0 +1,4 @@ +["line +break"] +~~~~~~~~~~ +error:1:1 diff --git a/src/lint/linter/__tests__/jsonlint/28.lint-test b/src/lint/linter/__tests__/jsonlint/28.lint-test new file mode 100644 index 00000000..1c523475 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/28.lint-test @@ -0,0 +1,4 @@ +["line\ +break"] +~~~~~~~~~~ +error:1:1 diff --git a/src/lint/linter/__tests__/jsonlint/29.lint-test b/src/lint/linter/__tests__/jsonlint/29.lint-test new file mode 100644 index 00000000..e5024695 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/29.lint-test @@ -0,0 +1,3 @@ +[0e] +~~~~~~~~~~ +error:1:1 diff --git a/src/lint/linter/__tests__/jsonlint/3.lint-test b/src/lint/linter/__tests__/jsonlint/3.lint-test new file mode 100644 index 00000000..3c303e32 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/3.lint-test @@ -0,0 +1,7 @@ +{ + "JSON Test Pattern pass3": { + "The outermost value": "must be an object or array.", + "In this test": "It is an object." + } +} +~~~~~~~~~~ diff --git a/src/lint/linter/__tests__/jsonlint/30.lint-test b/src/lint/linter/__tests__/jsonlint/30.lint-test new file mode 100644 index 00000000..ec82b153 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/30.lint-test @@ -0,0 +1,3 @@ +[0e+] +~~~~~~~~~~ +error:1:1 diff --git a/src/lint/linter/__tests__/jsonlint/31.lint-test b/src/lint/linter/__tests__/jsonlint/31.lint-test new file mode 100644 index 00000000..207067fe --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/31.lint-test @@ -0,0 +1,3 @@ +[0e+-1] +~~~~~~~~~~ +error:1:1 diff --git a/src/lint/linter/__tests__/jsonlint/32.lint-test b/src/lint/linter/__tests__/jsonlint/32.lint-test new file mode 100644 index 00000000..0b246b06 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/32.lint-test @@ -0,0 +1,3 @@ +{"Comma instead if closing brace": true, +~~~~~~~~~~ +error:1:40 diff --git a/src/lint/linter/__tests__/jsonlint/33.lint-test b/src/lint/linter/__tests__/jsonlint/33.lint-test new file mode 100644 index 00000000..9b84b26d --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/33.lint-test @@ -0,0 +1,3 @@ +["mismatch"} +~~~~~~~~~~ +error:1:11 diff --git a/src/lint/linter/__tests__/jsonlint/34.lint-test b/src/lint/linter/__tests__/jsonlint/34.lint-test new file mode 100644 index 00000000..d05858f1 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/34.lint-test @@ -0,0 +1,3 @@ +{"extra brace": 1}} +~~~~~~~~~~ +error:1:18 diff --git a/src/lint/linter/__tests__/jsonlint/4.lint-test b/src/lint/linter/__tests__/jsonlint/4.lint-test new file mode 100644 index 00000000..1c242948 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/4.lint-test @@ -0,0 +1,3 @@ +["extra comma",] +~~~~~~~~~~ +error:1:15 diff --git a/src/lint/linter/__tests__/jsonlint/5.lint-test b/src/lint/linter/__tests__/jsonlint/5.lint-test new file mode 100644 index 00000000..1edbea15 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/5.lint-test @@ -0,0 +1,3 @@ +["double extra comma",,] +~~~~~~~~~~ +error:1:22 diff --git a/src/lint/linter/__tests__/jsonlint/6.lint-test b/src/lint/linter/__tests__/jsonlint/6.lint-test new file mode 100644 index 00000000..fd01a5a8 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/6.lint-test @@ -0,0 +1,3 @@ +[, "<-- missing value"] +~~~~~~~~~~ +error:1:1 diff --git a/src/lint/linter/__tests__/jsonlint/7.lint-test b/src/lint/linter/__tests__/jsonlint/7.lint-test new file mode 100644 index 00000000..eb0bedaf --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/7.lint-test @@ -0,0 +1,3 @@ +["Comma after the close"], +~~~~~~~~~~ +error:1:25 diff --git a/src/lint/linter/__tests__/jsonlint/8.lint-test b/src/lint/linter/__tests__/jsonlint/8.lint-test new file mode 100644 index 00000000..265454d7 --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/8.lint-test @@ -0,0 +1,3 @@ +["Extra close"]] +~~~~~~~~~~ +error:1:15 diff --git a/src/lint/linter/__tests__/jsonlint/9.lint-test b/src/lint/linter/__tests__/jsonlint/9.lint-test new file mode 100644 index 00000000..35919c2d --- /dev/null +++ b/src/lint/linter/__tests__/jsonlint/9.lint-test @@ -0,0 +1,3 @@ +{"Extra comma": true,} +~~~~~~~~~~ +error:1:21