2014-05-11 13:42:56 -07:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* List available linters.
|
|
|
|
*/
|
2014-07-22 07:49:15 +10:00
|
|
|
final class ArcanistLintersWorkflow extends ArcanistWorkflow {
|
2014-05-11 13:42:56 -07:00
|
|
|
|
|
|
|
public function getWorkflowName() {
|
|
|
|
return 'linters';
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getCommandSynopses() {
|
|
|
|
return phutil_console_format(<<<EOTEXT
|
2015-08-28 03:26:29 -07:00
|
|
|
**linters** [__options__] [__name__]
|
2014-05-11 13:42:56 -07:00
|
|
|
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.
|
2015-08-28 03:26:29 -07:00
|
|
|
|
|
|
|
if __name__ is provided, the linter with that name will be displayed.
|
2014-05-11 13:42:56 -07:00
|
|
|
EOTEXT
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getArguments() {
|
2014-05-11 20:23:07 -07:00
|
|
|
return array(
|
|
|
|
'verbose' => array(
|
|
|
|
'help' => pht('Show detailed information, including options.'),
|
|
|
|
),
|
2015-08-28 03:26:29 -07:00
|
|
|
'search' => array(
|
|
|
|
'param' => 'search',
|
|
|
|
'repeat' => true,
|
|
|
|
'help' => pht(
|
2017-08-04 12:57:34 -07:00
|
|
|
'Search for linters. Search is case-insensitive, and is performed '.
|
2015-08-28 03:26:29 -07:00
|
|
|
'against name and description of each linter.'),
|
|
|
|
),
|
|
|
|
'*' => 'exact',
|
2014-05-11 20:23:07 -07:00
|
|
|
);
|
2014-05-11 13:42:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public function run() {
|
|
|
|
$console = PhutilConsole::getConsole();
|
|
|
|
|
2015-08-11 06:50:47 +10:00
|
|
|
$linters = id(new PhutilClassMapQuery())
|
2014-05-11 13:42:56 -07:00
|
|
|
->setAncestorClass('ArcanistLinter')
|
2015-08-11 06:50:47 +10:00
|
|
|
->execute();
|
2014-05-11 13:42:56 -07:00
|
|
|
|
|
|
|
try {
|
|
|
|
$built = $this->newLintEngine()->buildLinters();
|
|
|
|
} catch (ArcanistNoEngineException $ex) {
|
2014-05-22 10:36:31 -07:00
|
|
|
$built = array();
|
2014-05-11 13:42:56 -07:00
|
|
|
}
|
|
|
|
|
2015-08-28 03:26:29 -07:00
|
|
|
$linter_info = $this->getLintersInfo($linters, $built);
|
2014-05-11 13:42:56 -07:00
|
|
|
|
|
|
|
$status_map = $this->getStatusMap();
|
|
|
|
$pad = ' ';
|
|
|
|
|
|
|
|
$color_map = array(
|
|
|
|
'configured' => 'green',
|
|
|
|
'available' => 'yellow',
|
|
|
|
'error' => 'red',
|
|
|
|
);
|
|
|
|
|
2015-08-28 03:26:29 -07:00
|
|
|
$is_verbose = $this->getArgument('verbose');
|
|
|
|
$exact = $this->getArgument('exact');
|
|
|
|
$search_terms = $this->getArgument('search');
|
|
|
|
|
|
|
|
if ($exact && $search_terms) {
|
|
|
|
throw new ArcanistUsageException(
|
|
|
|
'Specify either search expression or exact name');
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($exact) {
|
|
|
|
$linter_info = $this->findExactNames($linter_info, $exact);
|
2015-12-02 06:25:22 +11:00
|
|
|
|
2015-08-28 03:26:29 -07:00
|
|
|
if (!$linter_info) {
|
|
|
|
$console->writeOut(
|
|
|
|
"%s\n",
|
|
|
|
pht(
|
|
|
|
'No match found. Try `%s %s` to search for a linter.',
|
|
|
|
'arc linters --search',
|
|
|
|
$exact[0]));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
$is_verbose = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($search_terms) {
|
|
|
|
$linter_info = $this->filterByNames($linter_info, $search_terms);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-05-11 13:42:56 -07:00
|
|
|
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,
|
2015-09-08 10:39:26 -07:00
|
|
|
nonempty($linter['name'], '-'),
|
|
|
|
$linter['short']);
|
2014-05-11 13:42:56 -07:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-08-28 03:26:29 -07:00
|
|
|
if ($is_verbose) {
|
|
|
|
$version = $linter['version'];
|
|
|
|
$uri = $linter['uri'];
|
|
|
|
if ($version || $uri) {
|
|
|
|
$console->writeOut("\n");
|
|
|
|
$print_tail = true;
|
|
|
|
}
|
2014-05-11 13:42:56 -07:00
|
|
|
|
2015-08-28 03:26:29 -07:00
|
|
|
if ($version) {
|
|
|
|
$console->writeOut("%s%s **%s**\n", $pad, pht('Version'), $version);
|
|
|
|
}
|
2014-05-11 13:42:56 -07:00
|
|
|
|
2015-08-28 03:26:29 -07:00
|
|
|
if ($uri) {
|
|
|
|
$console->writeOut("%s__%s__\n", $pad, $linter['uri']);
|
|
|
|
}
|
2014-05-11 20:23:07 -07:00
|
|
|
|
2015-08-28 03:26:29 -07:00
|
|
|
$description = $linter['description'];
|
|
|
|
if ($description) {
|
2014-05-11 20:23:07 -07:00
|
|
|
$console->writeOut(
|
2015-08-28 03:26:29 -07:00
|
|
|
"\n%s\n",
|
|
|
|
phutil_console_wrap($linter['description'], strlen($pad)));
|
|
|
|
$print_tail = true;
|
|
|
|
}
|
2014-05-11 20:23:07 -07:00
|
|
|
|
2015-08-28 03:26:29 -07:00
|
|
|
$options = $linter['options'];
|
|
|
|
if ($options) {
|
2014-05-11 20:23:07 -07:00
|
|
|
$console->writeOut(
|
2015-08-28 03:26:29 -07:00
|
|
|
"\n%s**%s**\n\n",
|
|
|
|
$pad,
|
|
|
|
pht('Configuration Options'));
|
|
|
|
|
|
|
|
$last_option = last_key($options);
|
|
|
|
foreach ($options as $option => $option_spec) {
|
|
|
|
$console->writeOut(
|
|
|
|
"%s__%s__ (%s)\n",
|
|
|
|
$pad,
|
|
|
|
$option,
|
|
|
|
$option_spec['type']);
|
|
|
|
|
|
|
|
$console->writeOut(
|
|
|
|
"%s\n",
|
|
|
|
phutil_console_wrap(
|
|
|
|
$option_spec['help'],
|
|
|
|
strlen($pad) + 2));
|
|
|
|
|
|
|
|
if ($option != $last_option) {
|
|
|
|
$console->writeOut("\n");
|
|
|
|
}
|
2014-05-11 20:23:07 -07:00
|
|
|
}
|
2015-08-28 03:26:29 -07:00
|
|
|
$print_tail = true;
|
2014-05-11 20:23:07 -07:00
|
|
|
}
|
|
|
|
|
2015-12-02 06:25:22 +11:00
|
|
|
$additional = $linter['additional'];
|
|
|
|
foreach ($additional as $title => $body) {
|
|
|
|
$console->writeOut(
|
|
|
|
"\n%s**%s**\n\n",
|
|
|
|
$pad,
|
|
|
|
$title);
|
|
|
|
|
|
|
|
// TODO: This should maybe use `tsprintf`.
|
|
|
|
// See some discussion in D14563.
|
|
|
|
echo $body;
|
|
|
|
}
|
|
|
|
|
2015-08-28 03:26:29 -07:00
|
|
|
if ($print_tail) {
|
|
|
|
$console->writeOut("\n");
|
|
|
|
}
|
2014-05-11 13:42:56 -07:00
|
|
|
}
|
|
|
|
}
|
2014-05-11 20:23:07 -07:00
|
|
|
|
2015-08-28 03:26:29 -07:00
|
|
|
if (!$is_verbose) {
|
2014-05-11 20:23:07 -07:00
|
|
|
$console->writeOut(
|
|
|
|
"%s\n",
|
2015-05-13 18:05:15 +10:00
|
|
|
pht('(Run `%s` for more details.)', 'arc linters --verbose'));
|
2014-05-11 20:23:07 -07:00
|
|
|
}
|
2014-05-11 13:42:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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'),
|
2015-05-13 18:05:15 +10:00
|
|
|
'available' => pht('AVAILABLE'),
|
|
|
|
'error' => pht('ERROR'),
|
2014-05-11 13:42:56 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
$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;
|
|
|
|
}
|
|
|
|
|
2015-08-28 03:26:29 -07:00
|
|
|
private function getLintersInfo(array $linters, array $built) {
|
|
|
|
// 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(
|
2015-09-08 10:39:26 -07:00
|
|
|
'name' => $linter->getLinterConfigurationName(),
|
2015-08-28 03:26:29 -07:00
|
|
|
'class' => get_class($linter),
|
|
|
|
'status' => $status,
|
|
|
|
'version' => $version,
|
2015-09-08 10:39:26 -07:00
|
|
|
'short' => $linter->getInfoName(),
|
2015-08-28 03:26:29 -07:00
|
|
|
'uri' => $linter->getInfoURI(),
|
|
|
|
'description' => $linter->getInfoDescription(),
|
|
|
|
'exception' => $exception,
|
|
|
|
'options' => $linter->getLinterConfigurationOptions(),
|
2015-12-02 06:25:22 +11:00
|
|
|
'additional' => $linter->getAdditionalInformation(),
|
2015-08-28 03:26:29 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return isort($linter_info, 'short');
|
|
|
|
}
|
|
|
|
|
|
|
|
private function filterByNames(array $linters, array $search_terms) {
|
|
|
|
$filtered = array();
|
|
|
|
|
|
|
|
foreach ($linters as $key => $linter) {
|
|
|
|
$name = $linter['name'];
|
|
|
|
$short = $linter['short'];
|
|
|
|
$description = $linter['description'];
|
|
|
|
foreach ($search_terms as $term) {
|
|
|
|
if (stripos($name, $term) !== false ||
|
|
|
|
stripos($short, $term) !== false ||
|
|
|
|
stripos($description, $term) !== false) {
|
|
|
|
$filtered[$key] = $linter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $filtered;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function findExactNames(array $linters, array $names) {
|
|
|
|
$filtered = array();
|
|
|
|
|
|
|
|
foreach ($linters as $key => $linter) {
|
|
|
|
$name = $linter['name'];
|
|
|
|
|
|
|
|
foreach ($names as $term) {
|
|
|
|
if (strcasecmp($name, $term) == 0) {
|
|
|
|
$filtered[$key] = $linter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $filtered;
|
|
|
|
}
|
|
|
|
|
2014-05-11 13:42:56 -07:00
|
|
|
}
|