Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15398001
D9041.id21502.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
15 KB
Referenced Files
None
Subscribers
None
D9041.id21502.diff
View Options
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
@@ -104,6 +104,7 @@
'ArcanistLintWorkflow' => 'workflow/ArcanistLintWorkflow.php',
'ArcanistLinter' => 'lint/linter/ArcanistLinter.php',
'ArcanistLinterTestCase' => 'lint/linter/__tests__/ArcanistLinterTestCase.php',
+ 'ArcanistLintersWorkflow' => 'workflow/ArcanistLintersWorkflow.php',
'ArcanistListWorkflow' => 'workflow/ArcanistListWorkflow.php',
'ArcanistMarkCommittedWorkflow' => 'workflow/ArcanistMarkCommittedWorkflow.php',
'ArcanistMercurialAPI' => 'repository/api/ArcanistMercurialAPI.php',
@@ -266,6 +267,7 @@
'ArcanistLintSummaryRenderer' => 'ArcanistLintRenderer',
'ArcanistLintWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistLinterTestCase' => 'ArcanistPhutilTestCase',
+ 'ArcanistLintersWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistListWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistMarkCommittedWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistMercurialAPI' => 'ArcanistRepositoryAPI',
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
@@ -381,10 +381,6 @@
}
}
- public function getVersion() {
- return null;
- }
-
protected function buildFutures(array $paths) {
$executable = $this->getExecutableCommand();
diff --git a/src/lint/linter/ArcanistLinter.php b/src/lint/linter/ArcanistLinter.php
--- a/src/lint/linter/ArcanistLinter.php
+++ b/src/lint/linter/ArcanistLinter.php
@@ -3,7 +3,8 @@
/**
* Implements lint rules, like syntax checks for a specific language.
*
- * @group linter
+ * @task info Human Readable Information
+ *
* @stable
*/
abstract class ArcanistLinter {
@@ -25,6 +26,54 @@
private $customSeverityRules = array();
private $config = array();
+
+/* -( Human Readable Information )---------------------------------------- */
+
+
+ /**
+ * Return an optional informative URI where humans can learn more about this
+ * linter.
+ *
+ * For most linters, this should return a link to the project home page. This
+ * is shown on `arc linters`.
+ *
+ * @return string|null Optionally, return an informative URI.
+ * @task info
+ */
+ public function getInfoURI() {
+ return null;
+ }
+
+
+ /**
+ * Return a brief human-readable description of the linter.
+ *
+ * These should be a line or two, and are shown on `arc linters`.
+ *
+ * @return string|null Optionally, return a brief human-readable description.
+ * @task info
+ */
+ public function getInfoDescription() {
+ return null;
+ }
+
+
+ /**
+ * Return a human-readable linter name.
+ *
+ * These are used by `arc linters`, and can let you give a linter a more
+ * presentable name.
+ *
+ * @return string Human-readable linter name.
+ * @task info
+ */
+ public function getInfoName() {
+ return nonempty(
+ $this->getLinterName(),
+ $this->getLinterConfigurationName(),
+ get_class($this));
+ }
+
public function getLinterPriority() {
return 1.0;
}
@@ -268,6 +317,10 @@
abstract public function lintPath($path);
abstract public function getLinterName();
+ public function getVersion() {
+ return null;
+ }
+
public function didRunLinters() {
// This is a hook.
}
diff --git a/src/lint/linter/ArcanistNoLintLinter.php b/src/lint/linter/ArcanistNoLintLinter.php
--- a/src/lint/linter/ArcanistNoLintLinter.php
+++ b/src/lint/linter/ArcanistNoLintLinter.php
@@ -1,13 +1,21 @@
<?php
/**
- * Stops other linters from running on code marked with
- * a nolint annotation.
- *
- * @group linter
+ * Stops other linters from running on code marked with a nolint annotation.
*/
final class ArcanistNoLintLinter extends ArcanistLinter {
+ public function getInfoName() {
+ return pht('Lint Disabler');
+ }
+
+ public function getInfoDescription() {
+ return pht(
+ 'Allows you to disable all lint messages for a file by putting "%s" in '.
+ 'the file body.',
+ '@'.'nolint');
+ }
+
public function getLinterName() {
return 'NOLINT';
}
diff --git a/src/lint/linter/ArcanistPEP8Linter.php b/src/lint/linter/ArcanistPEP8Linter.php
--- a/src/lint/linter/ArcanistPEP8Linter.php
+++ b/src/lint/linter/ArcanistPEP8Linter.php
@@ -2,11 +2,23 @@
/**
* Uses "pep8.py" to enforce PEP8 rules for Python.
- *
- * @group linter
*/
final class ArcanistPEP8Linter extends ArcanistExternalLinter {
+ public function getInfoName() {
+ return 'pep8';
+ }
+
+ public function getInfoURI() {
+ return 'https://pypi.python.org/pypi/pep8';
+ }
+
+ public function getInfoDescription() {
+ return pht(
+ 'pep8 is a tool to check your Python code against some of the '.
+ 'style conventions in PEP 8.');
+ }
+
public function getLinterName() {
return 'PEP8';
}
diff --git a/src/lint/linter/ArcanistPhpcsLinter.php b/src/lint/linter/ArcanistPhpcsLinter.php
--- a/src/lint/linter/ArcanistPhpcsLinter.php
+++ b/src/lint/linter/ArcanistPhpcsLinter.php
@@ -17,6 +17,20 @@
private $reports;
+ public function getInfoName() {
+ return 'PHP_CodeSniffer';
+ }
+
+ public function getInfoURI() {
+ return 'http://pear.php.net/package/PHP_CodeSniffer/';
+ }
+
+ public function getInfoDescription() {
+ return pht(
+ 'PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and '.
+ 'detects violations of a defined set of coding standards.');
+ }
+
public function getLinterName() {
return 'PHPCS';
}
diff --git a/src/lint/linter/ArcanistSpellingLinter.php b/src/lint/linter/ArcanistSpellingLinter.php
--- a/src/lint/linter/ArcanistSpellingLinter.php
+++ b/src/lint/linter/ArcanistSpellingLinter.php
@@ -1,12 +1,10 @@
<?php
/**
- * Enforces basic spelling. Spelling inside code is actually pretty hard to
- * get right without false positives. I take a conservative approach and
+ * Enforces basic spelling. Spelling inside code is actually pretty hard to
+ * get right without false positives. I take a conservative approach and
* just use a blacklisted set of words that are commonly spelled
* incorrectly.
- *
- * @group linter
*/
final class ArcanistSpellingLinter extends ArcanistLinter {
@@ -17,6 +15,14 @@
private $wholeWordRules;
private $severity;
+ public function getInfoName() {
+ return pht('Spellchecker');
+ }
+
+ public function getInfoDescription() {
+ return pht('Detects common misspellings of English words.');
+ }
+
public function __construct($severity = self::LINT_SPELLING_PICKY) {
$this->severity = $severity;
$this->wholeWordRules = ArcanistSpellingDefaultData::getFullWordRules();
diff --git a/src/lint/linter/ArcanistTextLinter.php b/src/lint/linter/ArcanistTextLinter.php
--- a/src/lint/linter/ArcanistTextLinter.php
+++ b/src/lint/linter/ArcanistTextLinter.php
@@ -2,8 +2,6 @@
/**
* Enforces basic text file rules.
- *
- * @group linter
*/
final class ArcanistTextLinter extends ArcanistLinter {
@@ -19,6 +17,16 @@
private $maxLineLength = 80;
+ public function getInfoName() {
+ return pht('Basic Text Linter');
+ }
+
+ public function getInfoDescription() {
+ return pht(
+ 'Enforces basic text rules like line length, character encoding, '.
+ 'and trailing whitespace.');
+ }
+
public function getLinterPriority() {
return 0.5;
}
diff --git a/src/lint/linter/ArcanistXMLLinter.php b/src/lint/linter/ArcanistXMLLinter.php
--- a/src/lint/linter/ArcanistXMLLinter.php
+++ b/src/lint/linter/ArcanistXMLLinter.php
@@ -5,6 +5,15 @@
* errors and potential problems in XML files.
*/
final class ArcanistXMLLinter extends ArcanistLinter {
+
+ public function getInfoName() {
+ return pht('SimpleXML Linter');
+ }
+
+ public function getInfoDescription() {
+ return pht('Uses SimpleXML to detect formatting errors in XML files.');
+ }
+
public function getLinterName() {
return 'XML';
}
diff --git a/src/workflow/ArcanistBaseWorkflow.php b/src/workflow/ArcanistBaseWorkflow.php
--- a/src/workflow/ArcanistBaseWorkflow.php
+++ b/src/workflow/ArcanistBaseWorkflow.php
@@ -1741,4 +1741,54 @@
}
+ /**
+ * Build a new lint engine for the current working copy.
+ *
+ * Optionally, you can pass an explicit engine class name to build an engine
+ * of a particular class. Normally this is used to implement an `--engine`
+ * flag from the CLI.
+ *
+ * @param string Optional explicit engine class name.
+ * @return ArcanistLintEngine Constructed engine.
+ */
+ protected function newLintEngine($engine_class = null) {
+ $working_copy = $this->getWorkingCopy();
+ $config = $this->getConfigurationManager();
+
+ if (!$engine_class) {
+ $engine_class = $config->getConfigFromAnySource('lint.engine');
+ }
+
+ if (!$engine_class) {
+ if (Filesystem::pathExists($working_copy->getProjectPath('.arclint'))) {
+ $engine_class = 'ArcanistConfigurationDrivenLintEngine';
+ }
+ }
+
+ if (!$engine_class) {
+ throw new ArcanistNoEngineException(
+ pht(
+ "No lint engine is configured for this project. ".
+ "Create an '.arclint' file, or configure an advanced engine ".
+ "with 'lint.engine' in '.arcconfig'."));
+ }
+
+ $base_class = 'ArcanistLintEngine';
+ if (!class_exists($engine_class) ||
+ !is_subclass_of($engine_class, $base_class)) {
+ throw new ArcanistUsageException(
+ pht(
+ 'Configured lint engine "%s" is not a subclass of "%s", but must '.
+ 'be.',
+ $engine_class,
+ $base_class));
+ }
+
+ $engine = newv($engine_class, array())
+ ->setWorkingCopy($working_copy)
+ ->setConfigurationManager($config);
+
+ return $engine;
+ }
+
}
diff --git a/src/workflow/ArcanistLintWorkflow.php b/src/workflow/ArcanistLintWorkflow.php
--- a/src/workflow/ArcanistLintWorkflow.php
+++ b/src/workflow/ArcanistLintWorkflow.php
@@ -184,22 +184,7 @@
$working_copy = $this->getWorkingCopy();
$configuration_manager = $this->getConfigurationManager();
- $engine = $this->getArgument('engine');
- if (!$engine) {
- $engine = $configuration_manager->getConfigFromAnySource('lint.engine');
- }
-
- if (!$engine) {
- if (Filesystem::pathExists($working_copy->getProjectPath('.arclint'))) {
- $engine = 'ArcanistConfigurationDrivenLintEngine';
- }
- }
-
- if (!$engine) {
- throw new ArcanistNoEngineException(
- "No lint engine configured for this project. Edit '.arcconfig' to ".
- "specify a lint engine, or create an '.arclint' file.");
- }
+ $engine = $this->newLintEngine($this->getArgument('engine'));
$rev = $this->getArgument('rev');
$paths = $this->getArgument('paths');
@@ -252,17 +237,8 @@
$paths = $this->selectPathsForWorkflow($paths, $rev);
}
- if (!class_exists($engine) ||
- !is_subclass_of($engine, 'ArcanistLintEngine')) {
- throw new ArcanistUsageException(
- "Configured lint engine '{$engine}' is not a subclass of ".
- "'ArcanistLintEngine'.");
- }
-
- $engine = newv($engine, array());
$this->engine = $engine;
- $engine->setWorkingCopy($working_copy);
- $engine->setConfigurationManager($configuration_manager);
+
$engine->setMinimumSeverity(
$this->getArgument('severity', self::DEFAULT_SEVERITY));
diff --git a/src/workflow/ArcanistLintersWorkflow.php b/src/workflow/ArcanistLintersWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/workflow/ArcanistLintersWorkflow.php
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * List available linters.
+ */
+final class ArcanistLintersWorkflow extends ArcanistBaseWorkflow {
+
+ public function getWorkflowName() {
+ return 'linters';
+ }
+
+ public function getCommandSynopses() {
+ return phutil_console_format(<<<EOTEXT
+ **linters** [__options__]
+EOTEXT
+ );
+ }
+
+ public function getCommandHelp() {
+ return phutil_console_format(pht(<<<EOTEXT
+ Supports: cli
+ List the available and configured linters, with information about
+ what they do and which versions are installed.
+EOTEXT
+ ));
+ }
+
+ public function getArguments() {
+ return array();
+ }
+
+ public function run() {
+ $console = PhutilConsole::getConsole();
+
+ $linters = id(new PhutilSymbolLoader())
+ ->setAncestorClass('ArcanistLinter')
+ ->loadObjects();
+
+ try {
+ $built = $this->newLintEngine()->buildLinters();
+ } catch (ArcanistNoEngineException $ex) {
+ $built = $engine->buildLinters();
+ }
+
+ // Note that an engine can emit multiple linters of the same class to run
+ // different rulesets on different groups of files, so these linters do not
+ // necessarily have unique classes or types.
+ $groups = array();
+ foreach ($built as $linter) {
+ $groups[get_class($linter)][] = $linter;
+ }
+
+ $linter_info = array();
+ foreach ($linters as $key => $linter) {
+ $installed = idx($groups, $key, array());
+ $exception = null;
+
+ if ($installed) {
+ $status = 'configured';
+ try {
+ $version = head($installed)->getVersion();
+ } catch (Exception $ex) {
+ $status = 'error';
+ $exception = $ex;
+ }
+ } else {
+ $status = 'available';
+ $version = null;
+ }
+
+ $linter_info[$key] = array(
+ 'short' => $linter->getLinterConfigurationName(),
+ 'class' => get_class($linter),
+ 'status' => $status,
+ 'version' => $version,
+ 'name' => $linter->getInfoName(),
+ 'uri' => $linter->getInfoURI(),
+ 'description' => $linter->getInfoDescription(),
+ 'exception' => $exception,
+ );
+ }
+
+ $linter_info = isort($linter_info, 'short');
+
+ $status_map = $this->getStatusMap();
+ $pad = ' ';
+
+ $color_map = array(
+ 'configured' => 'green',
+ 'available' => 'yellow',
+ 'error' => 'red',
+ );
+
+ foreach ($linter_info as $key => $linter) {
+ $status = $linter['status'];
+ $color = $color_map[$status];
+ $text = $status_map[$status];
+ $print_tail = false;
+
+ $console->writeOut(
+ "<bg:".$color.">** %s **</bg> **%s** (%s)\n",
+ $text,
+ nonempty($linter['short'], '-'),
+ $linter['name']);
+
+ if ($linter['exception']) {
+ $console->writeOut(
+ "\n%s**%s**\n%s\n",
+ $pad,
+ get_class($linter['exception']),
+ phutil_console_wrap(
+ $linter['exception']->getMessage(),
+ strlen($pad)));
+ $print_tail = true;
+ }
+
+ $version = $linter['version'];
+ $uri = $linter['uri'];
+ if ($version || $uri) {
+ $console->writeOut("\n");
+ $print_tail = true;
+ }
+
+ if ($version) {
+ $console->writeOut("%s%s **%s**\n", $pad, pht('Version'), $version);
+ }
+
+ if ($uri) {
+ $console->writeOut("%s__%s__\n", $pad, $linter['uri']);
+ }
+
+ $description = $linter['description'];
+ if ($description) {
+ $console->writeOut(
+ "\n%s\n",
+ phutil_console_wrap($linter['description'], strlen($pad)));
+ $print_tail = true;
+ }
+
+ if ($print_tail) {
+ $console->writeOut("\n");
+ }
+ }
+ }
+
+
+ /**
+ * Get human-readable linter statuses, padded to fixed width.
+ *
+ * @return map<string, string> Human-readable linter status names.
+ */
+ private function getStatusMap() {
+ $text_map = array(
+ 'configured' => pht('CONFIGURED'),
+ 'available' => pht('AVAILABLE'),
+ 'error' => pht('ERROR'),
+ );
+
+ $sizes = array();
+ foreach ($text_map as $key => $string) {
+ $sizes[$key] = phutil_utf8_console_strlen($string);
+ }
+
+ $longest = max($sizes);
+ foreach ($text_map as $key => $string) {
+ if ($sizes[$key] < $longest) {
+ $text_map[$key] .= str_repeat(' ', $longest - $sizes[$key]);
+ }
+ }
+
+ $text_map['padding'] = str_repeat(' ', $longest);
+
+ return $text_map;
+ }
+
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Mar 17, 11:08 PM (4 d, 14 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7578279
Default Alt Text
D9041.id21502.diff (15 KB)
Attached To
Mode
D9041: Add 'arc linters' to list available linters and status
Attached
Detach File
Event Timeline
Log In to Comment