Page MenuHomePhabricator

D11773.diff
No OneTemporary

D11773.diff

diff --git a/resources/grep_linter.php b/resources/grep_linter.php
new file mode 100755
--- /dev/null
+++ b/resources/grep_linter.php
@@ -0,0 +1,79 @@
+#!/usr/bin/env php
+<?php
+
+require_once dirname(dirname(__FILE__)).'/scripts/__init_script__.php';
+
+$args = new PhutilArgumentParser($argv);
+$args->setTagline(pht('a grep linter'));
+$args->setSynopsis(<<<EOHELP
+**grep_linter.php**
+ Checks for the occurence of the specified words.
+EOHELP
+);
+
+$args->parseStandardArguments();
+$args->parse(
+ array(
+ array(
+ 'name' => 'advice',
+ 'param' => 'word',
+ 'help' => pht('TODO'),
+ ),
+ array(
+ 'name' => 'warning',
+ 'param' => 'word',
+ 'help' => pht('TODO'),
+ ),
+ array(
+ 'name' => 'error',
+ 'param' => 'word',
+ 'help' => pht('TODO'),
+ ),
+ array(
+ 'name' => 'path',
+ 'wildcard' => true,
+ ),
+ ));
+
+$paths = $args->getArg('path');
+if (!$paths) {
+ $args->printHelpAndExit();
+}
+
+$advice = $args->getArg('advice');
+$warning = $args->getArg('warning');
+$error = $args->getArg('error');
+
+function get_regex($args, $severity) {
+ if (!$args->getArg($severity)) {
+ return null;
+ }
+
+ return '/('.preg_quote(implode('|', explode(',', $args->getArg($severity)))).')/';
+}
+
+$regexes = array_filter(array(
+ 'advice' => get_regex($args, 'advice'),
+ 'warning' => get_regex($args, 'warning'),
+ 'error' => get_regex($args, 'error'),
+));
+
+foreach ($paths as $path) {
+ $data = Filesystem::readFile($path);
+
+ foreach ($regexes as $severity => $regex) {
+ $matches = null;
+
+ $preg = preg_match_all($regex, $data, $matches, PREG_OFFSET_CAPTURE);
+
+ if (!$preg) {
+ continue;
+ }
+
+ foreach ($matches[0] as $match) {
+ list($string, $offset) = $match;
+
+ echo $severity.':'.$offset."\n";
+ }
+ }
+}
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -155,6 +155,7 @@
'ArcanistRubyLinter' => 'lint/linter/ArcanistRubyLinter.php',
'ArcanistRubyLinterTestCase' => 'lint/linter/__tests__/ArcanistRubyLinterTestCase.php',
'ArcanistScriptAndRegexLinter' => 'lint/linter/ArcanistScriptAndRegexLinter.php',
+ 'ArcanistScriptAndRegexLinterTestCase' => 'lint/linter/__tests__/ArcanistScriptAndRegexLinterTestCase.php',
'ArcanistSetConfigWorkflow' => 'workflow/ArcanistSetConfigWorkflow.php',
'ArcanistSettings' => 'configuration/ArcanistSettings.php',
'ArcanistShellCompleteWorkflow' => 'workflow/ArcanistShellCompleteWorkflow.php',
@@ -332,7 +333,8 @@
'ArcanistRuboCopLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistRubyLinter' => 'ArcanistExternalLinter',
'ArcanistRubyLinterTestCase' => 'ArcanistExternalLinterTestCase',
- 'ArcanistScriptAndRegexLinter' => 'ArcanistLinter',
+ 'ArcanistScriptAndRegexLinter' => 'ArcanistExternalLinter',
+ 'ArcanistScriptAndRegexLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistSetConfigWorkflow' => 'ArcanistWorkflow',
'ArcanistShellCompleteWorkflow' => 'ArcanistWorkflow',
'ArcanistSingleLintEngine' => 'ArcanistLintEngine',
diff --git a/src/lint/linter/ArcanistExternalLinter.php b/src/lint/linter/ArcanistExternalLinter.php
--- a/src/lint/linter/ArcanistExternalLinter.php
+++ b/src/lint/linter/ArcanistExternalLinter.php
@@ -228,7 +228,7 @@
$interpreter,
get_class($this)));
}
- if (!Filesystem::pathExists($binary)) {
+ if ($binary && !Filesystem::pathExists($binary)) {
throw new ArcanistMissingLinterException(
sprintf(
"%s\n%s",
@@ -241,21 +241,19 @@
'TO INSTALL: %s',
$this->getInstallInstructions())));
}
- } else {
- if (!Filesystem::binaryExists($binary)) {
- throw new ArcanistMissingLinterException(
- sprintf(
- "%s\n%s",
- pht(
- 'Unable to locate binary "%s" to run linter %s. You may need '.
- 'to install the binary, or adjust your linter configuration.',
- $binary,
- get_class($this)),
- pht(
- 'TO INSTALL: %s',
- $this->getInstallInstructions())));
+ } else if ($binary && !Filesystem::binaryExists($binary)) {
+ throw new ArcanistMissingLinterException(
+ sprintf(
+ "%s\n%s",
+ pht(
+ 'Unable to locate binary "%s" to run linter %s. You may need '.
+ 'to install the binary, or adjust your linter configuration.',
+ $binary,
+ get_class($this)),
+ pht(
+ 'TO INSTALL: %s',
+ $this->getInstallInstructions())));
}
- }
}
/**
diff --git a/src/lint/linter/ArcanistScriptAndRegexLinter.php b/src/lint/linter/ArcanistScriptAndRegexLinter.php
--- a/src/lint/linter/ArcanistScriptAndRegexLinter.php
+++ b/src/lint/linter/ArcanistScriptAndRegexLinter.php
@@ -2,17 +2,19 @@
/**
* Simple glue linter which runs some script on each path, and then uses a
- * regex to parse lint messages from the script's output. (This linter uses a
- * script and a regex to interpret the results of some real linter, it does
- * not itself lint both scripts and regexes).
+ * regular expression to parse lint messages from the script's output. (This
+ * linter uses a script and a regex to interpret the results of some real
+ * linter, it does not itself lint both scripts and regular expressions).
*
- * Configure this linter by setting these keys in your .arclint section:
+ * Configure this linter by setting these keys in your `.arclint`
+ * configuration:
*
- * - `script-and-regex.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).
- * - `script-and-regex.regex` The regex to process output with. This
- * regex uses named capturing groups (detailed below) to interpret output.
+ * - `script-and-regex.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).
+ * - `script-and-regex.regex` The regular expression to process output with.
+ * This regex uses named capturing groups (detailed below) to interpret
+ * output.
*
* 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
@@ -22,22 +24,22 @@
* linter which can perform custom processing, but may be somewhat simpler to
* configure.
*
- * == Script... ==
+ * == Script ==
*
* 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
+ * 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).
+ * 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.)
+ * 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 lint messages to stdout, which will be parsed with
- * the provided regex.
+ * the provided regular expression.
*
* For example, you might use a configuration like this:
*
@@ -49,7 +51,7 @@
* sh -c '/opt/lint/lint.sh "$0" 2>&1'
*
* The return code of the script must be 0, or an exception will be raised
- * reporting that the linter failed. If you have a script which exits nonzero
+ * reporting that the linter failed. If you have a script which exits non-zero
* under normal circumstances, you can force it to always exit 0 by using a
* configuration like this:
*
@@ -64,12 +66,13 @@
* sh -c '/opt/lint/lint.sh --output /tmp/lint.out "$0" && cat /tmp/lint.out'
*
* There are necessary limits to how gracefully this linter can deal with
- * edge cases, because it is just a script and a regex. If you need to do
- * things that this linter can't handle, you can write a phutil linter and move
- * the logic to handle those cases into PHP. PHP is a better general-purpose
- * programming language than regular expressions are, if only by a small margin.
+ * edge cases, because it is just a script and a regular expression. If you
+ * need to do things that this linter can't handle, you can write a phutil
+ * linter and move the logic to handle those cases into PHP. PHP is a better
+ * general-purpose programming language than regular expressions are, if only
+ * by a small margin.
*
- * == ...and Regex ==
+ * == Regex ==
*
* The regex must be a valid PHP PCRE regex, including delimiters and flags.
*
@@ -86,14 +89,14 @@
* "Syntax Error".
* - `severity` (optional) The word "error", "warning", "autofix", "advice",
* or "disabled", in any combination of upper and lower case. Instead, you
- * may match groups called `error`, `warning`, `advice`, `autofix`, or
+ * may match groups called `error`, `warning`, `advice`, `autofix` or
* `disabled`. These allow you to match output formats like "E123" and
* "W123" to indicate errors and warnings, even though the word "error" is
* not present in the output. If no severity capturing group is present,
- * messages are raised with "error" severity. If multiple severity capturing
- * groups are present, messages are raised with the highest captured
- * severity. Capturing groups like `error` supersede the `severity`
- * capturing group.
+ * messages are raised with "error" severity. If multiple severity
+ * capturing groups are present, messages are raised with the highest
+ * captured severity. Capturing groups like `error` supersede the
+ * `severity` capturing group.
* - `error` (optional) Match some nonempty substring to indicate that this
* message has "error" severity.
* - `warning` (optional) Match some nonempty substring to indicate that this
@@ -102,12 +105,12 @@
* message has "advice" severity.
* - `autofix` (optional) Match some nonempty substring to indicate that this
* message has "autofix" severity.
- * - `disabled` (optional) Match some nonempty substring to indicate that this
- * message has "disabled" severity.
+ * - `disabled` (optional) Match some nonempty substring to indicate that
+ * this message has "disabled" severity.
* - `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 capture this unless the linter can raise messages in files other than
- * the one it is linting.
+ * not specified, defaults to the linted file. It is generally not
+ * necessary to capture 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 captured, this
@@ -124,14 +127,14 @@
* - `ignore` (optional) Match some nonempty substring to ignore the match.
* You can use this if your linter sometimes emits text like "No lint
* errors".
- * - `stop` (optional) Match some nonempty substring to stop processing input.
- * Remaining matches for this file will be discarded, but linting will
- * continue with other linters and other files.
+ * - `stop` (optional) Match some nonempty substring to stop processing
+ * input. Remaining matches for this file will be discarded, but linting
+ * will continue with other linters and other files.
* - `halt` (optional) Match some nonempty substring to halt all linting of
* this file by any linter. Linting will continue with other files.
- * - `throw` (optional) Match some nonempty substring to throw an error, which
- * will stop `arc` completely. You can use this to fail abruptly if you
- * encounter unexpected output. All processing will abort.
+ * - `throw` (optional) Match some nonempty substring to throw an error,
+ * which will stop `arc` completely. You can use this to fail abruptly if
+ * you encounter unexpected output. All processing will abort.
*
* Numbered capturing groups are ignored.
*
@@ -140,11 +143,12 @@
* error:13 Too many goats!
* warning:22 Not enough boats.
*
- * ...you could use this regex to parse it:
+ * ...you could use this regular expression to parse it:
*
* /^(?P<severity>warning|error):(?P<line>\d+) (?P<message>.*)$/m
*
- * The simplest valid regex for line-oriented output is something like this:
+ * The simplest valid regular expression for line-oriented output is something
+ * like this:
*
* /^(?P<message>.*)$/m
*
@@ -153,11 +157,24 @@
* @task parse Parsing Output
* @task config Validating Configuration
*/
-final class ArcanistScriptAndRegexLinter extends ArcanistLinter {
+final class ArcanistScriptAndRegexLinter extends ArcanistExternalLinter {
private $script = null;
private $regex = null;
- private $output = array();
+
+ private $shouldExpectCommandErrors = null;
+ private $shouldLintBinaryFiles = null;
+ private $shouldLintDeletedFiles = null;
+ private $shouldLintDirectories = null;
+ private $shouldLintSymbolicLinks = null;
+
+ public function getLinterName() {
+ return 'S&RX';
+ }
+
+ public function getLinterConfigurationName() {
+ return 'script-and-regex';
+ }
public function getInfoName() {
return pht('Script and Regex');
@@ -170,38 +187,122 @@
'run custom lint scripts.');
}
-/* -( Linting )------------------------------------------------------------ */
+ public function getLinterConfigurationOptions() {
+ $options = array(
+ 'script-and-regex.script' => array(
+ 'type' => 'string',
+ 'help' => pht('Script to execute.'),
+ ),
+ 'script-and-regex.regex' => array(
+ 'type' => 'regex',
+ 'help' => pht('The regex to process output with.'),
+ ),
+ 'script-and-regex.should-expect-command-errors' => array(
+ 'type' => 'optional bool',
+ 'help' => pht('Should expect command errors.'),
+ ),
+ 'script-and-regex.should-lint-binary-files' => array(
+ 'type' => 'optional bool',
+ 'help' => pht('Should binary files be linted.'),
+ ),
+ 'script-and-regex.should-lint-deleted-files' => array(
+ 'type' => 'optional bool',
+ 'help' => pht('Should deleted files be linted.'),
+ ),
+ 'script-and-regex.should-lint-directories' => array(
+ 'type' => 'optional bool',
+ 'help' => pht('Should directories be linted.'),
+ ),
+ 'script-and-regex.should-lint-symbolic-links' => array(
+ 'type' => 'optional bool',
+ 'help' => pht('Should symbolic links be linted.'),
+ ),
+ );
- /**
- * 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;
+ return $options + parent::getLinterConfigurationOptions();
+ }
+
+ public function setLinterConfigurationValue($key, $value) {
+ switch ($key) {
+ case 'script-and-regex.script':
+ $this->script = $value;
+ return;
+ case 'script-and-regex.regex':
+ $this->regex = $value;
+ return;
+
+ case 'script-and-regex.should-expect-command-errors':
+ $this->shouldExpectCommandErrors = $value;
+ return;
+ case 'script-and-regex.should-lint-binary-files':
+ $this->shouldLintBinaryFiles = $value;
+ return;
+ case 'script-and-regex.should-lint-deleted-files':
+ $this->shouldLintDeletedFiles = $value;
+ return;
+ case 'script-and-regex.should-lint-directories':
+ $this->shouldLintDirectories = $value;
+ return;
+ case 'script-and-regex.should-lint-symbolic-links':
+ $this->shouldLintSymbolicLinks = $value;
+ return;
+
+ default:
+ return parent::setLinterConfigurationValue($key, $value);
}
+ }
+
+ public function getDefaultBinary() {
+ return null;
+ }
+
+ public function getInstallInstructions() {
+ // TODO
+ return null;
+ }
- $futures = id(new FutureIterator($futures))
- ->limit(4);
- foreach ($futures as $path => $future) {
- list($stdout) = $future->resolvex();
- $this->output[$path] = $stdout;
+ public function shouldExpectCommandErrors() {
+ if ($this->shouldExpectCommandErrors === null) {
+ return parent::shouldExpectCommandErrors();
}
+
+ return $this->shouldExpectCommandErrors;
}
- /**
- * Run the regex on the output of the script.
- *
- * @task lint
- */
- public function lintPath($path) {
+ protected function shouldLintBinaryFiles() {
+ if ($this->shouldLintBinaryFiles === null) {
+ return parent::shouldLintBinaryFiles();
+ }
+
+ return $this->shouldLintBinaryFiles;
+ }
+
+ protected function shouldLintDeletedFiles() {
+ if ($this->shouldLintDeletedFiles === null) {
+ return parent::shouldLintDeletedFiles();
+ }
+
+ return $this->shouldLintDeletedFiles;
+ }
+
+ protected function shouldLintDirectories() {
+ if ($this->shouldLintDirectories === null) {
+ return parent::shouldLintDirectories();
+ }
+
+ return $this->shouldLintDirectories;
+ }
+
+ protected function shouldLintSymbolicLinks() {
+ if ($this->shouldLintSymbolicLinks === null) {
+ return parent::shouldLintSymbolicLinks();
+ }
+
+ return $this->shouldLintSymbolicLinks;
+ }
+
+ protected function parseLinterOutput($path, $err, $stdout, $stderr) {
$output = idx($this->output, $path);
if (!strlen($output)) {
// No output, but it exited 0, so just move on.
@@ -271,55 +372,6 @@
}
}
-
-/* -( Linter Information )------------------------------------------------- */
-
- /**
- * Return the short name of the linter.
- *
- * @return string Short linter identifier.
- *
- * @task linterinfo
- */
- public function getLinterName() {
- return 'S&RX';
- }
-
- public function getLinterConfigurationName() {
- return 'script-and-regex';
- }
-
- public function getLinterConfigurationOptions() {
- // These fields are optional only to avoid breaking things.
- $options = array(
- 'script-and-regex.script' => array(
- 'type' => 'string',
- 'help' => pht('Script to execute.'),
- ),
- 'script-and-regex.regex' => array(
- 'type' => 'regex',
- 'help' => pht('The regex to process output with.'),
- ),
- );
-
- return $options + parent::getLinterConfigurationOptions();
- }
-
- public function setLinterConfigurationValue($key, $value) {
- switch ($key) {
- case 'script-and-regex.script':
- $this->script = $value;
- return;
- case 'script-and-regex.regex':
- $this->regex = $value;
- return;
- }
-
- return parent::setLinterConfigurationValue($key, $value);
- }
-
-/* -( Parsing Output )----------------------------------------------------- */
-
/**
* Get the line and character of the message from the regex match.
*
@@ -347,7 +399,7 @@
* a nonempty severity name group like 'error', or a group called 'severity'
* with a valid name.
*
- * @param dict Captured groups from regex.
+ * @param dict Captured groups from regex.
* @return const @{class:ArcanistLintSeverity} constant.
*
* @task parse
diff --git a/src/lint/linter/__tests__/ArcanistScriptAndRegexLinterTestCase.php b/src/lint/linter/__tests__/ArcanistScriptAndRegexLinterTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/__tests__/ArcanistScriptAndRegexLinterTestCase.php
@@ -0,0 +1,10 @@
+<?php
+
+final class ArcanistScriptAndRegexLinterTestCase
+ extends ArcanistExternalLinterTestCase {
+
+ public function testLinter() {
+ $this->executeTestsInDirectory(dirname(__FILE__).'/script-and-regex/');
+ }
+
+}
diff --git a/src/lint/linter/__tests__/script-and-regex/goats.lint-test b/src/lint/linter/__tests__/script-and-regex/goats.lint-test
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/__tests__/script-and-regex/goats.lint-test
@@ -0,0 +1,5 @@
+#!/bin/bash
+~~~~~~~~~~
+~~~~~~~~~~
+~~~~~~~~~~
+{"mode": "0755"}

File Metadata

Mime Type
text/plain
Expires
Sun, May 12, 3:30 AM (3 w, 5 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6288113
Default Alt Text
D11773.diff (21 KB)

Event Timeline