1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-09 00:12:40 +01:00

Modularize "arc lint" renderers

Summary:
Ref T12996. `arc lint` can render in different formats with `--output <format>`.

Previously, these were a big `switch()` statement and a bunch of hard-coded stuff.

Modularize them so that that third parties could add new renderers, and this code can be refactored toward parallelizing the `lint` step.

This has a small behavioral change: we no longer hide "autofix" messages. I'd like to generally simplify the number of amend/autofix flags here, so this edges us toward that slightly.

Test Plan: Ran `arc lint` with all the `--output` flags in states with warnings and no warnings, saw sensible-seeming behavior.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T12996

Differential Revision: https://secure.phabricator.com/D18645
This commit is contained in:
epriestley 2017-09-25 13:31:54 -07:00
parent a053fcc4d5
commit 7af60b07cb
9 changed files with 160 additions and 176 deletions

View file

@ -1,10 +1,9 @@
<?php <?php
/**
* Shows lint messages to the user.
*/
final class ArcanistCheckstyleXMLLintRenderer extends ArcanistLintRenderer { final class ArcanistCheckstyleXMLLintRenderer extends ArcanistLintRenderer {
const RENDERERKEY = 'xml';
private $writer; private $writer;
public function __construct() { public function __construct() {
@ -14,11 +13,11 @@ final class ArcanistCheckstyleXMLLintRenderer extends ArcanistLintRenderer {
$this->writer->setIndentString(' '); $this->writer->setIndentString(' ');
} }
public function renderPreamble() { public function willRenderResults() {
$this->writer->startDocument('1.0', 'UTF-8'); $this->writer->startDocument('1.0', 'UTF-8');
$this->writer->startElement('checkstyle'); $this->writer->startElement('checkstyle');
$this->writer->writeAttribute('version', '4.3'); $this->writer->writeAttribute('version', '4.3');
return $this->writer->flush(); $this->writeOut($this->writer->flush());
} }
public function renderLintResult(ArcanistLintResult $result) { public function renderLintResult(ArcanistLintResult $result) {
@ -39,17 +38,13 @@ final class ArcanistCheckstyleXMLLintRenderer extends ArcanistLintRenderer {
} }
$this->writer->endElement(); $this->writer->endElement();
return $this->writer->flush(); $this->writeOut($this->writer->flush());
} }
public function renderOkayResult() { public function didRenderResults() {
return '';
}
public function renderPostamble() {
$this->writer->endElement(); $this->writer->endElement();
$this->writer->endDocument(); $this->writer->endDocument();
return $this->writer->flush(); $this->writeOut($this->writer->flush());
} }
private function getStringForSeverity($severity) { private function getStringForSeverity($severity) {

View file

@ -1,10 +1,9 @@
<?php <?php
/**
* Shows lint messages to the user.
*/
final class ArcanistCompilerLintRenderer extends ArcanistLintRenderer { final class ArcanistCompilerLintRenderer extends ArcanistLintRenderer {
const RENDERERKEY = 'compiler';
public function renderLintResult(ArcanistLintResult $result) { public function renderLintResult(ArcanistLintResult $result) {
$lines = array(); $lines = array();
$messages = $result->getMessages(); $messages = $result->getMessages();
@ -25,11 +24,7 @@ final class ArcanistCompilerLintRenderer extends ArcanistLintRenderer {
$description); $description);
} }
return implode('', $lines); $this->writeOut(implode('', $lines));
}
public function renderOkayResult() {
return '';
} }
} }

View file

@ -1,17 +1,10 @@
<?php <?php
/**
* Shows lint messages to the user.
*/
final class ArcanistConsoleLintRenderer extends ArcanistLintRenderer { final class ArcanistConsoleLintRenderer extends ArcanistLintRenderer {
private $showAutofixPatches = false; const RENDERERKEY = 'console';
private $testableMode;
public function setShowAutofixPatches($show_autofix_patches) { private $testableMode;
$this->showAutofixPatches = $show_autofix_patches;
return $this;
}
public function setTestableMode($testable_mode) { public function setTestableMode($testable_mode) {
$this->testableMode = $testable_mode; $this->testableMode = $testable_mode;
@ -22,6 +15,38 @@ final class ArcanistConsoleLintRenderer extends ArcanistLintRenderer {
return $this->testableMode; return $this->testableMode;
} }
public function supportsPatching() {
return true;
}
public function renderResultCode($result_code) {
if ($result_code == ArcanistLintWorkflow::RESULT_OKAY) {
$view = new PhutilConsoleInfo(
pht('OKAY'),
pht('No lint messages.'));
$this->writeOut($view->drawConsoleString());
}
}
public function promptForPatch(
ArcanistLintResult $result,
$old_path,
$new_path) {
if ($old_path === null) {
$old_path = '/dev/null';
}
list($err, $stdout) = exec_manual('diff -u %s %s', $old_path, $new_path);
$this->writeOut($stdout);
$prompt = pht(
'Apply this patch to %s?',
tsprintf('__%s__', $result->getPath()));
return phutil_console_confirm($prompt, $default_no = false);
}
public function renderLintResult(ArcanistLintResult $result) { public function renderLintResult(ArcanistLintResult $result) {
$messages = $result->getMessages(); $messages = $result->getMessages();
$path = $result->getPath(); $path = $result->getPath();
@ -31,10 +56,6 @@ final class ArcanistConsoleLintRenderer extends ArcanistLintRenderer {
$text = array(); $text = array();
foreach ($messages as $message) { foreach ($messages as $message) {
if (!$this->showAutofixPatches && $message->isAutofix()) {
continue;
}
if ($message->isError()) { if ($message->isError()) {
$color = 'red'; $color = 'red';
} else { } else {
@ -77,9 +98,7 @@ final class ArcanistConsoleLintRenderer extends ArcanistLintRenderer {
pht( pht(
'Lint for %s:', 'Lint for %s:',
phutil_console_format('__%s__', $path))); phutil_console_format('__%s__', $path)));
return $prefix.implode("\n", $text); $this->writeOut($prefix.implode("\n", $text));
} else {
return null;
} }
} }
@ -278,13 +297,6 @@ final class ArcanistConsoleLintRenderer extends ArcanistLintRenderer {
$data); $data);
} }
public function renderOkayResult() {
return phutil_console_format(
"<bg:green>** %s **</bg> %s\n",
pht('OKAY'),
pht('No lint warnings.'));
}
private function newOffsetMap($data) { private function newOffsetMap($data) {
$lines = phutil_split_lines($data); $lines = phutil_split_lines($data);

View file

@ -1,10 +1,9 @@
<?php <?php
/**
* Shows lint messages to the user.
*/
final class ArcanistJSONLintRenderer extends ArcanistLintRenderer { final class ArcanistJSONLintRenderer extends ArcanistLintRenderer {
const RENDERERKEY = 'json';
const LINES_OF_CONTEXT = 3; const LINES_OF_CONTEXT = 3;
public function renderLintResult(ArcanistLintResult $result) { public function renderLintResult(ArcanistLintResult $result) {
@ -25,11 +24,7 @@ final class ArcanistJSONLintRenderer extends ArcanistLintRenderer {
$output[$path][] = $dictionary; $output[$path][] = $dictionary;
} }
return json_encode($output)."\n"; $this->writeOut(json_encode($output)."\n");
}
public function renderOkayResult() {
return '';
} }
} }

View file

@ -1,19 +1,61 @@
<?php <?php
/**
* Shows lint messages to the user.
*/
abstract class ArcanistLintRenderer extends Phobject { abstract class ArcanistLintRenderer extends Phobject {
public function renderPreamble() { private $output;
return '';
final public function getRendererKey() {
return $this->getPhobjectClassConstant('RENDERERKEY');
}
final public static function getAllRenderers() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getRendererKey')
->execute();
}
final public function setOutputPath($path) {
$this->output = $path;
return $this;
}
/**
* Does this renderer support applying lint patches?
*
* @return bool True if patches should be applied when using this renderer.
*/
public function supportsPatching() {
return false;
}
public function willRenderResults() {
return null;
}
public function didRenderResults() {
return null;
}
public function renderResultCode($result_code) {
return null;
}
public function handleException(Exception $ex) {
throw $ex;
} }
abstract public function renderLintResult(ArcanistLintResult $result); abstract public function renderLintResult(ArcanistLintResult $result);
abstract public function renderOkayResult();
public function renderPostamble() { protected function writeOut($message) {
return ''; if ($this->output) {
Filesystem::appendFile($this->output, $message);
} else {
echo $message;
}
return $this;
} }
} }

View file

@ -2,12 +2,10 @@
final class ArcanistNoneLintRenderer extends ArcanistLintRenderer { final class ArcanistNoneLintRenderer extends ArcanistLintRenderer {
public function renderLintResult(ArcanistLintResult $result) { const RENDERERKEY = 'none';
return '';
}
public function renderOkayResult() { public function renderLintResult(ArcanistLintResult $result) {
return ''; return null;
} }
} }

View file

@ -1,10 +1,9 @@
<?php <?php
/**
* Shows lint messages to the user.
*/
final class ArcanistSummaryLintRenderer extends ArcanistLintRenderer { final class ArcanistSummaryLintRenderer extends ArcanistLintRenderer {
const RENDERERKEY = 'summary';
public function renderLintResult(ArcanistLintResult $result) { public function renderLintResult(ArcanistLintResult $result) {
$messages = $result->getMessages(); $messages = $result->getMessages();
$path = $result->getPath(); $path = $result->getPath();
@ -19,14 +18,16 @@ final class ArcanistSummaryLintRenderer extends ArcanistLintRenderer {
$text[] = "{$path}:{$line}:{$severity}: {$name}\n"; $text[] = "{$path}:{$line}:{$severity}: {$name}\n";
} }
return implode('', $text); $this->writeOut(implode('', $text));
} }
public function renderOkayResult() { public function renderResultCode($result_code) {
return phutil_console_format( if ($result_code == ArcanistLintWorkflow::RESULT_OKAY) {
"<bg:green>** %s **</bg> %s\n", $view = new PhutilConsoleInfo(
pht('OKAY'), pht('OKAY'),
pht('No lint warnings.')); pht('No lint messages.'));
$this->writeOut($view->drawConsoleString());
}
} }
} }

View file

@ -171,7 +171,13 @@ EOTEXT;
try { try {
PhutilConsoleFormatter::disableANSI(true); PhutilConsoleFormatter::disableANSI(true);
$actual = $renderer->renderLintResult($result);
$tmp = new TempFile();
$renderer->setOutputPath($tmp);
$renderer->renderLintResult($result);
$actual = Filesystem::readFile($tmp);
unset($tmp);
PhutilConsoleFormatter::disableANSI(false); PhutilConsoleFormatter::disableANSI(false);
} catch (Exception $ex) { } catch (Exception $ex) {
PhutilConsoleFormatter::disableANSI(false); PhutilConsoleFormatter::disableANSI(false);

View file

@ -74,12 +74,7 @@ EOTEXT
), ),
'output' => array( 'output' => array(
'param' => 'format', 'param' => 'format',
'help' => pht( 'help' => pht('Select an output format.'),
"With 'summary', show lint warnings in a more compact format. ".
"With 'json', show lint warnings in machine-readable JSON format. ".
"With 'none', show no lint warnings. ".
"With 'compiler', show lint warnings in suitable for your editor. ".
"With 'xml', show lint warnings in the Checkstyle XML format."),
), ),
'outfile' => array( 'outfile' => array(
'param' => 'path', 'param' => 'path',
@ -243,11 +238,8 @@ EOTEXT
} }
if ($this->getArgument('amend-autofixes')) { if ($this->getArgument('amend-autofixes')) {
$prompt_autofix_patches = false;
$this->shouldAmendChanges = true; $this->shouldAmendChanges = true;
$this->shouldAmendAutofixesWithoutPrompt = true; $this->shouldAmendAutofixesWithoutPrompt = true;
} else {
$prompt_autofix_patches = true;
} }
$repository_api = $this->getRepositoryAPI(); $repository_api = $this->getRepositoryAPI();
@ -258,110 +250,76 @@ EOTEXT
$wrote_to_disk = false; $wrote_to_disk = false;
switch ($this->getArgument('output')) { $default_renderer = ArcanistConsoleLintRenderer::RENDERERKEY;
case 'json': $renderer_key = $this->getArgument('output', $default_renderer);
$renderer = new ArcanistJSONLintRenderer();
$prompt_patches = false; $renderers = ArcanistLintRenderer::getAllRenderers();
$apply_patches = $this->getArgument('apply-patches'); if (!isset($renderers[$renderer_key])) {
break; throw new Exception(
case 'summary': pht(
$renderer = new ArcanistSummaryLintRenderer(); 'Lint renderer "%s" is unknown. Supported renderers are: %s.',
break; $renderer_key,
case 'none': implode(', ', array_keys($renderers))));
$prompt_patches = false;
$apply_patches = $this->getArgument('apply-patches');
$renderer = new ArcanistNoneLintRenderer();
break;
case 'compiler':
$renderer = new ArcanistCompilerLintRenderer();
$prompt_patches = false;
$apply_patches = $this->getArgument('apply-patches');
break;
case 'xml':
$renderer = new ArcanistCheckstyleXMLLintRenderer();
$prompt_patches = false;
$apply_patches = $this->getArgument('apply-patches');
break;
default:
$renderer = new ArcanistConsoleLintRenderer();
$renderer->setShowAutofixPatches($prompt_autofix_patches);
break;
} }
$renderer = $renderers[$renderer_key];
$all_autofix = true; $all_autofix = true;
$tmp = null;
if ($this->getArgument('outfile') !== null) { $out_path = $this->getArgument('outfile');
$tmp = id(new TempFile()) if ($out_path !== null) {
->setPreserveFile(true); $tmp = new TempFile();
} $renderer->setOutputPath((string)$tmp);
$preamble = $renderer->renderPreamble();
if ($tmp) {
Filesystem::appendFile($tmp, $preamble);
} else { } else {
$console->writeOut('%s', $preamble); $tmp = null;
} }
foreach ($results as $result) { if ($failed) {
$result_all_autofix = $result->isAllAutofix(); $renderer->handleException($failed);
}
if (!$result->getMessages() && !$result_all_autofix) { $renderer->willRenderResults();
$should_patch = ($apply_patches && $renderer->supportsPatching());
foreach ($results as $result) {
if (!$result->getMessages()) {
continue; continue;
} }
$result_all_autofix = $result->isAllAutofix();
if (!$result_all_autofix) { if (!$result_all_autofix) {
$all_autofix = false; $all_autofix = false;
} }
$lint_result = $renderer->renderLintResult($result); $renderer->renderLintResult($result);
if ($lint_result) {
if ($tmp) {
Filesystem::appendFile($tmp, $lint_result);
} else {
$console->writeOut('%s', $lint_result);
}
}
if ($apply_patches && $result->isPatchable()) { if ($should_patch && $result->isPatchable()) {
$patcher = ArcanistLintPatcher::newFromArcanistLintResult($result); $patcher = ArcanistLintPatcher::newFromArcanistLintResult($result);
$old_file = $result->getFilePathOnDisk();
if ($prompt_patches && $apply = true;
!($result_all_autofix && !$prompt_autofix_patches)) { if ($prompt_patches && !$result_all_autofix) {
$old_file = $result->getFilePathOnDisk();
if (!Filesystem::pathExists($old_file)) { if (!Filesystem::pathExists($old_file)) {
$old_file = '/dev/null'; $old_file = null;
} }
$new_file = new TempFile(); $new_file = new TempFile();
$new = $patcher->getModifiedFileContent(); $new = $patcher->getModifiedFileContent();
Filesystem::writeFile($new_file, $new); Filesystem::writeFile($new_file, $new);
// TODO: Improve the behavior here, make it more like $apply = $renderer->promptForPatch($result, $old_file, $new_file);
// difference_render().
list(, $stdout, $stderr) =
exec_manual('diff -u %s %s', $old_file, $new_file);
$console->writeOut('%s', $stdout);
$console->writeErr('%s', $stderr);
$prompt = pht(
'Apply this patch to %s?',
phutil_console_format('__%s__', $result->getPath()));
if (!phutil_console_confirm($prompt, $default_no = false)) {
continue;
}
} }
$patcher->writePatchToDisk(); if ($apply) {
$wrote_to_disk = true; $patcher->writePatchToDisk();
$wrote_to_disk = true;
}
} }
} }
$postamble = $renderer->renderPostamble(); $renderer->didRenderResults();
if ($tmp) { if ($tmp) {
Filesystem::appendFile($tmp, $postamble); Filesystem::rename($tmp, $out_path);
Filesystem::rename($tmp, $this->getArgument('outfile'));
} else {
$console->writeOut('%s', $postamble);
} }
if ($wrote_to_disk && $this->shouldAmendChanges) { if ($wrote_to_disk && $this->shouldAmendChanges) {
@ -391,20 +349,6 @@ EOTEXT
} }
} }
if ($this->getArgument('output') == 'json') {
// NOTE: Required by save_lint.php in Phabricator.
return 0;
}
if ($failed) {
if ($failed instanceof ArcanistNoEffectException) {
if ($renderer instanceof ArcanistNoneLintRenderer) {
return 0;
}
}
throw $failed;
}
$unresolved = array(); $unresolved = array();
$has_warnings = false; $has_warnings = false;
$has_errors = false; $has_errors = false;
@ -433,11 +377,7 @@ EOTEXT
$result_code = self::RESULT_OKAY; $result_code = self::RESULT_OKAY;
} }
if (!$this->getParentWorkflow()) { $renderer->renderResultCode($result_code);
if ($result_code == self::RESULT_OKAY) {
$console->writeOut('%s', $renderer->renderOkayResult());
}
}
return $result_code; return $result_code;
} }