Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15398656
D14535.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
10 KB
Referenced Files
None
Subscribers
None
D14535.id.diff
View Options
Index: src/__phutil_library_map__.php
===================================================================
--- src/__phutil_library_map__.php
+++ src/__phutil_library_map__.php
@@ -98,6 +98,7 @@
'ArcanistEventType' => 'events/constant/ArcanistEventType.php',
'ArcanistExitExpressionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistExitExpressionXHPASTLinterRule.php',
'ArcanistExportWorkflow' => 'workflow/ArcanistExportWorkflow.php',
+ 'ArcanistExternalJsonLinter' => 'lint/linter/ArcanistExternalJsonLinter.php',
'ArcanistExternalLinter' => 'lint/linter/ArcanistExternalLinter.php',
'ArcanistExternalLinterTestCase' => 'lint/linter/__tests__/ArcanistExternalLinterTestCase.php',
'ArcanistExtractUseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistExtractUseXHPASTLinterRule.php',
@@ -393,6 +394,7 @@
'ArcanistEventType' => 'PhutilEventType',
'ArcanistExitExpressionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistExportWorkflow' => 'ArcanistWorkflow',
+ 'ArcanistExternalJsonLinter' => 'ArcanistLinter',
'ArcanistExternalLinter' => 'ArcanistFutureLinter',
'ArcanistExternalLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistExtractUseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
Index: src/lint/linter/ArcanistExternalJsonLinter.php
===================================================================
--- /dev/null
+++ src/lint/linter/ArcanistExternalJsonLinter.php
@@ -0,0 +1,272 @@
+<?php
+
+/**
+ * Simple glue linter which runs some script on each path and parses
+ * lint violations emitted by the script in JSON format.
+ *
+ * Configure this linter by setting these keys in your .arclint section:
+ *
+ * - `external-json.script` Script command to run. This can be
+ * the path to a linter script, but may also include flags or use shell
+ * features (see below for examples).
+ *
+ * The script will be invoked from the project root, so you can specify a
+ * relative path like `scripts/lint.sh` or an absolute path like
+ * `/opt/lint/lint.sh`.
+ *
+ * This linter is necessarily more limited in its capabilities than a normal
+ * linter which can perform custom processing, but may be somewhat simpler to
+ * configure.
+ *
+ * == Script and JSON format ==
+ *
+ * The script will be invoked once for each file that is to be linted, with
+ * the file passed as the first argument. The file may begin with a "-"; ensure
+ * your script will not interpret such files as flags (perhaps by ending your
+ * script configuration with "--", if its argument parser supports that).
+ *
+ * Note that when run via `arc diff`, the list of files to be linted includes
+ * deleted files and files that were moved away by the change. The linter should
+ * not assume the path it is given exists, and it is not an error for the
+ * linter to be invoked with paths which are no longer there. (Every affected
+ * path is subject to lint because some linters may raise errors in other files
+ * when a file is removed, or raise an error about its removal.)
+ *
+ * The script should emit a JSON array of lint violations to stdout. A lint
+ * violation may have the following attributes,
+ *
+ * - `message` (required) Text describing the lint message. For example,
+ * "This is a syntax error.".
+ * - `name` (optional) Text summarizing the lint message. For example,
+ * "Syntax Error".
+ * - `severity` (optional) The word "error", "warning", "autofix", "advice",
+ * or "disabled", in any combination of upper and lower case. Instead, you
+ * - `file` (optional) The name of the file to raise the lint message in. If
+ * not specified, defaults to the linted file. It is generally not necessary
+ * to specify this unless the linter can raise messages in files other than
+ * the one it is linting.
+ * - `line` (optional) The line number of the message.
+ * - `char` (optional) The character offset of the message.
+ * - `offset` (optional) The byte offset of the message. If provided, this
+ * supersedes `line` and `char`.
+ * - `original` (optional) The text the message affects.
+ * - `replacement` (optional) The text that the range captured by `original`
+ * should be automatically replaced by to resolve the message.
+ * - `code` (optional) A short error type identifier which can be used
+ * elsewhere to configure handling of specific types of messages. For
+ * example, "EXAMPLE1", "EXAMPLE2", etc., where each code identifies a
+ * class of message like "syntax error", "missing whitespace", etc. This
+ * allows configuration to later change the severity of all whitespace
+ * messages, for example.
+ * - `throw` (optional) If set with a string error message `arc` will throw
+ * the given message. You can use this to fail abruptly if you
+ * encounter unexpected output. All processing will abort.
+ *
+ * For example, the following would encode a warning and an error,
+ *
+ * [ { 'message': 'Too many goats!', 'line': 13, 'severity': 'error' }
+ * , { 'message': 'Not enough boats.', 'line': 22, 'severity: 'warning' }
+ * ]
+ *
+ * @task lint Linting
+ * @task linterinfo Linter Information
+ * @task parse Parsing Output
+ * @task config Validating Configuration
+ */
+final class ArcanistExternalJsonLinter extends ArcanistLinter {
+
+ private $script = null;
+ private $output = array();
+
+ public function getInfoName() {
+ return pht('External JSON');
+ }
+
+ public function getInfoDescription() {
+ return pht(
+ 'Run an external script, then parse its output as a JSON document'.
+ 'describing the lint violations. This is a generic binding that can '.
+ 'be used to run custom lint scripts.');
+ }
+
+ protected function shouldLintBinaryFiles() {
+ return true;
+ }
+
+ protected function shouldLintDeletedFiles() {
+ return true;
+ }
+
+ protected function shouldLintDirectories() {
+ return true;
+ }
+
+ protected function shouldLintSymbolicLinks() {
+ return true;
+ }
+
+
+/* -( Linting )------------------------------------------------------------ */
+
+
+ /**
+ * Run the script on each file to be linted.
+ *
+ * @task lint
+ */
+ public function willLintPaths(array $paths) {
+ $root = $this->getProjectRoot();
+
+ $futures = array();
+ foreach ($paths as $path) {
+ $future = new ExecFuture('%C %s', $this->script, $path);
+ $future->setCWD($root);
+ $futures[$path] = $future;
+ }
+
+ $futures = id(new FutureIterator($futures))
+ ->limit(4);
+ foreach ($futures as $path => $future) {
+ list($stdout) = $future->resolvex();
+ $this->output[$path] = $stdout;
+ }
+ }
+
+ /**
+ * Run the regex on the output of the script.
+ *
+ * @task lint
+ */
+ public function lintPath($path) {
+ $output = idx($this->output, $path);
+ if (!strlen($output)) {
+ // No output, but it exited 0, so just move on.
+ return;
+ }
+
+ $messages = json_decode($output, true);
+
+ foreach ($messages as $message) {
+ if (!empty($message['throw'])) {
+ $throw = $message['throw'];
+ throw new ArcanistUsageException(
+ pht(
+ "%s: linter threw an exception: '%s'\n",
+ __CLASS__,
+ $throw));
+ }
+
+ $line = idx($message, 'line');
+ if ($line) {
+ $line = (int)$line;
+ } else {
+ $line = null;
+ }
+ $char = idx($message, 'char');
+ if ($char) {
+ $char = (int)$char;
+ } else {
+ $char = null;
+ }
+
+ $dict = array(
+ 'path' => idx($message, 'file', $path),
+ 'line' => $line,
+ 'char' => $char,
+ 'code' => idx($message, 'code', $this->getLinterName()),
+ 'severity' => $this->getMessageSeverity($message),
+ 'name' => idx($message, 'name', 'Lint'),
+ 'description' => idx($message, 'message',
+ pht('Undefined Lint Message')),
+ );
+
+ $original = idx($message, 'original');
+ if ($original !== null) {
+ $dict['original'] = $original;
+ }
+
+ $replacement = idx($message, 'replacement');
+ if ($replacement !== null) {
+ $dict['replacement'] = $replacement;
+ }
+
+ $lint = ArcanistLintMessage::newFromDictionary($dict);
+ $this->addLintMessage($lint);
+ }
+ }
+
+
+/* -( Linter Information )------------------------------------------------- */
+
+ /**
+ * Return the short name of the linter.
+ *
+ * @return string Short linter identifier.
+ *
+ * @task linterinfo
+ */
+ public function getLinterName() {
+ return 'ExtJson';
+ }
+
+ public function getLinterConfigurationName() {
+ return 'external-json';
+ }
+
+ public function getLinterConfigurationOptions() {
+ // These fields are optional only to avoid breaking things.
+ $options = array(
+ 'external-json.script' => array(
+ 'type' => 'string',
+ 'help' => pht('Script to execute.'),
+ ),
+ );
+
+ return $options + parent::getLinterConfigurationOptions();
+ }
+
+ public function setLinterConfigurationValue($key, $value) {
+ switch ($key) {
+ case 'external-json.script':
+ $this->script = $value;
+ return;
+ }
+
+ return parent::setLinterConfigurationValue($key, $value);
+ }
+
+/* -( Parsing Output )----------------------------------------------------- */
+
+ /**
+ * Map the regex matching groups to a message severity. We look for either
+ * a nonempty severity name group like 'error', or a group called 'severity'
+ * with a valid name.
+ *
+ * @param dict message object
+ * @return const @{class:ArcanistLintSeverity} constant.
+ *
+ * @task parse
+ */
+ private function getMessageSeverity(array $message) {
+ $map = array(
+ 'error' => ArcanistLintSeverity::SEVERITY_ERROR,
+ 'warning' => ArcanistLintSeverity::SEVERITY_WARNING,
+ 'autofix' => ArcanistLintSeverity::SEVERITY_AUTOFIX,
+ 'advice' => ArcanistLintSeverity::SEVERITY_ADVICE,
+ 'disabled' => ArcanistLintSeverity::SEVERITY_DISABLED,
+ );
+
+ if (idx($message, 'severity')) {
+ $severity_name = strtolower(idx($message, 'severity'));
+ if (!idx($map, $severity_name)) {
+ throw new ArcanistUsageException(
+ pht('%s: Unknown severity %s', __CLASS__, $severity_name));
+ } else {
+ return $map[$severity_name];
+ }
+ } else {
+ return ArcanistLintSeverity::SEVERITY_ERROR;
+ }
+ }
+
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Mar 18, 1:07 AM (6 d, 6 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7705947
Default Alt Text
D14535.id.diff (10 KB)
Attached To
Mode
D14535: Add external-json linter
Attached
Detach File
Event Timeline
Log In to Comment