Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F9584094
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
31 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/lint/engine/ArcanistLintEngine.php b/src/lint/engine/ArcanistLintEngine.php
index fd5816eb..358ba281 100644
--- a/src/lint/engine/ArcanistLintEngine.php
+++ b/src/lint/engine/ArcanistLintEngine.php
@@ -1,416 +1,430 @@
<?php
/**
* Manages lint execution. When you run 'arc lint' or 'arc diff', Arcanist
* checks your .arcconfig to see if you have specified a lint engine in the
* key "lint.engine". The engine must extend this class. For example:
*
* lang=js
* {
* // ...
* "lint.engine" : "ExampleLintEngine",
* // ...
* }
*
* The lint engine is given a list of paths (generally, the paths that you
* modified in your change) and determines which linters to run on them. The
* linters themselves are responsible for actually analyzing file text and
* finding warnings and errors. For example, if the modified paths include some
* JS files and some Python files, you might want to run JSLint on the JS files
* and PyLint on the Python files.
*
* You can also run multiple linters on a single file. For instance, you might
* run one linter on all text files to make sure they don't have trailing
* whitespace, or enforce tab vs space rules, or make sure there are enough
* curse words in them.
*
* Because lint engines are pretty custom to the rules of a project, you will
* generally need to build your own. Fortunately, it's pretty easy (and you
* can use the prebuilt //linters//, you just need to write a little glue code
* to tell Arcanist which linters to run). For a simple example of how to build
* a lint engine, see @{class:ExampleLintEngine}.
*
* You can test an engine like this:
*
* arc lint --engine ExampleLintEngine --lintall some_file.py
*
* ...which will show you all the lint issues raised in the file.
*
* See @{article@phabricator:Arcanist User Guide: Customizing Lint, Unit Tests
* and Workflows} for more information about configuring lint engines.
*
* @group lint
* @stable
*/
abstract class ArcanistLintEngine {
protected $workingCopy;
protected $paths = array();
protected $fileData = array();
protected $charToLine = array();
protected $lineToFirstChar = array();
private $cachedResults;
private $cacheVersion;
private $results = array();
+ private $stopped = array();
private $minimumSeverity = ArcanistLintSeverity::SEVERITY_DISABLED;
private $changedLines = array();
private $commitHookMode = false;
private $hookAPI;
private $enableAsyncLint = false;
private $postponedLinters = array();
public function __construct() {
}
public function setWorkingCopy(ArcanistWorkingCopyIdentity $working_copy) {
$this->workingCopy = $working_copy;
return $this;
}
public function getWorkingCopy() {
return $this->workingCopy;
}
public function setPaths($paths) {
$this->paths = $paths;
return $this;
}
public function getPaths() {
return $this->paths;
}
public function setPathChangedLines($path, $changed) {
if ($changed === null) {
$this->changedLines[$path] = null;
} else {
$this->changedLines[$path] = array_fill_keys($changed, true);
}
return $this;
}
public function getPathChangedLines($path) {
return idx($this->changedLines, $path);
}
public function setFileData($data) {
$this->fileData = $data + $this->fileData;
return $this;
}
public function setCommitHookMode($mode) {
$this->commitHookMode = $mode;
return $this;
}
public function setHookAPI(ArcanistHookAPI $hook_api) {
$this->hookAPI = $hook_api;
return $this;
}
public function getHookAPI() {
return $this->hookAPI;
}
public function setEnableAsyncLint($enable_async_lint) {
$this->enableAsyncLint = $enable_async_lint;
return $this;
}
public function getEnableAsyncLint() {
return $this->enableAsyncLint;
}
public function loadData($path) {
if (!isset($this->fileData[$path])) {
if ($this->getCommitHookMode()) {
$this->fileData[$path] = $this->getHookAPI()
->getCurrentFileData($path);
} else {
$disk_path = $this->getFilePathOnDisk($path);
$this->fileData[$path] = Filesystem::readFile($disk_path);
}
}
return $this->fileData[$path];
}
public function pathExists($path) {
if ($this->getCommitHookMode()) {
$file_data = $this->loadData($path);
return ($file_data !== null);
} else {
$disk_path = $this->getFilePathOnDisk($path);
return Filesystem::pathExists($disk_path);
}
}
public function getFilePathOnDisk($path) {
return Filesystem::resolvePath(
$path,
$this->getWorkingCopy()->getProjectRoot());
}
public function setMinimumSeverity($severity) {
$this->minimumSeverity = $severity;
return $this;
}
public function getCommitHookMode() {
return $this->commitHookMode;
}
public function run() {
$linters = $this->buildLinters();
if (!$linters) {
throw new ArcanistNoEffectException("No linters to run.");
}
$have_paths = false;
foreach ($linters as $linter) {
if ($linter->getPaths()) {
$have_paths = true;
break;
}
}
if (!$have_paths) {
throw new ArcanistNoEffectException("No paths are lintable.");
}
$versions = array($this->getCacheVersion());
foreach ($linters as $linter) {
$versions[] = get_class($linter).':'.$linter->getCacheVersion();
}
$this->cacheVersion = crc32(implode("\n", $versions));
- $stopped = array();
+ $this->stopped = array();
$exceptions = array();
foreach ($linters as $linter_name => $linter) {
+ if (!is_string($linter_name)) {
+ $linter_name = get_class($linter);
+ }
try {
$linter->setEngine($this);
if (!$linter->canRun()) {
continue;
}
$paths = $linter->getPaths();
$cache_granularity = $linter->getCacheGranularity();
foreach ($paths as $key => $path) {
// Make sure each path has a result generated, even if it is empty
// (i.e., the file has no lint messages).
$result = $this->getResultForPath($path);
- if (isset($stopped[$path])) {
+ if (isset($this->stopped[$path])) {
unset($paths[$key]);
}
if (isset($this->cachedResults[$path][$this->cacheVersion])) {
if ($cache_granularity == ArcanistLinter::GRANULARITY_FILE) {
unset($paths[$key]);
+
+ $cached_stopped = idx(
+ $this->cachedResults[$path][$this->cacheVersion],
+ 'stopped');
+ if ($cached_stopped == $linter_name) {
+ $this->stopped[$path] = $linter_name;
+ }
}
}
}
$paths = array_values($paths);
if ($paths) {
$linter->willLintPaths($paths);
foreach ($paths as $path) {
$linter->willLintPath($path);
$linter->lintPath($path);
if ($linter->didStopAllLinters()) {
- $stopped[$path] = true;
+ $this->stopped[$path] = $linter_name;
}
}
}
} catch (Exception $ex) {
- if (!is_string($linter_name)) {
- $linter_name = get_class($linter);
- }
$exceptions[$linter_name] = $ex;
}
}
$this->didRunLinters($linters);
foreach ($linters as $linter) {
$minimum = $this->minimumSeverity;
foreach ($linter->getLintMessages() as $message) {
if (!ArcanistLintSeverity::isAtLeastAsSevere($message, $minimum)) {
continue;
}
if (!$this->isRelevantMessage($message)) {
continue;
}
if ($cache_granularity != ArcanistLinter::GRANULARITY_FILE) {
$message->setUncacheable(true);
}
$result = $this->getResultForPath($message->getPath());
$result->addMessage($message);
}
}
if ($this->cachedResults) {
foreach ($this->cachedResults as $path => $messages) {
- foreach (idx($messages, $this->cacheVersion, array()) as $message) {
+ $messages = idx($messages, $this->cacheVersion, array());
+ unset($messages['stopped']);
+ foreach ($messages as $message) {
$this->getResultForPath($path)->addMessage(
ArcanistLintMessage::newFromDictionary($message));
}
}
}
foreach ($this->results as $path => $result) {
$disk_path = $this->getFilePathOnDisk($path);
$result->setFilePathOnDisk($disk_path);
if (isset($this->fileData[$path])) {
$result->setData($this->fileData[$path]);
} else if ($disk_path && Filesystem::pathExists($disk_path)) {
// TODO: this may cause us to, e.g., load a large binary when we only
// raised an error about its filename. We could refine this by looking
// through the lint messages and doing this load only if any of them
// have original/replacement text or something like that.
try {
$this->fileData[$path] = Filesystem::readFile($disk_path);
$result->setData($this->fileData[$path]);
} catch (FilesystemException $ex) {
// Ignore this, it's noncritical that we access this data and it
// might be unreadable or a directory or whatever else for plenty
// of legitimate reasons.
}
}
}
if ($exceptions) {
throw new PhutilAggregateException('Some linters failed:', $exceptions);
}
return $this->results;
}
/**
- * @param dict<string path, dict<int version, list<dict message>>>
+ * @param dict<string path, dict<string version, list<dict message>>>
* @return this
*/
public function setCachedResults(array $results) {
$this->cachedResults = $results;
return $this;
}
public function getResults() {
return $this->results;
}
+ public function getStoppedPaths() {
+ return $this->stopped;
+ }
+
abstract protected function buildLinters();
protected function didRunLinters(array $linters) {
assert_instances_of($linters, 'ArcanistLinter');
foreach ($linters as $linter) {
$linter->didRunLinters();
}
}
private function isRelevantMessage(ArcanistLintMessage $message) {
// When a user runs "arc lint", we default to raising only warnings on
// lines they have changed (errors are still raised anywhere in the
// file). The list of $changed lines may be null, to indicate that the
// path is a directory or a binary file so we should not exclude
// warnings.
if (!$this->changedLines || $message->isError()) {
return true;
}
$locations = $message->getOtherLocations();
$locations[] = $message->toDictionary();
foreach ($locations as $location) {
$path = idx($location, 'path', $message->getPath());
if (!array_key_exists($path, $this->changedLines)) {
continue;
}
$changed = $this->getPathChangedLines($path);
if ($changed === null || !$location['line']) {
return true;
}
$last_line = $location['line'];
if (isset($location['original'])) {
$last_line += substr_count($location['original'], "\n");
}
for ($l = $location['line']; $l <= $last_line; $l++) {
if (!empty($changed[$l])) {
return true;
}
}
}
return false;
}
protected function getResultForPath($path) {
if (empty($this->results[$path])) {
$result = new ArcanistLintResult();
$result->setPath($path);
$result->setCacheVersion($this->cacheVersion);
$this->results[$path] = $result;
}
return $this->results[$path];
}
public function getLineAndCharFromOffset($path, $offset) {
if (!isset($this->charToLine[$path])) {
$char_to_line = array();
$line_to_first_char = array();
$lines = explode("\n", $this->loadData($path));
$line_number = 0;
$line_start = 0;
foreach ($lines as $line) {
$len = strlen($line) + 1; // Account for "\n".
$line_to_first_char[] = $line_start;
$line_start += $len;
for ($ii = 0; $ii < $len; $ii++) {
$char_to_line[] = $line_number;
}
$line_number++;
}
$this->charToLine[$path] = $char_to_line;
$this->lineToFirstChar[$path] = $line_to_first_char;
}
$line = $this->charToLine[$path][$offset];
$char = $offset - $this->lineToFirstChar[$path][$line];
return array($line, $char);
}
public function getPostponedLinters() {
return $this->postponedLinters;
}
public function setPostponedLinters(array $linters) {
$this->postponedLinters = $linters;
return $this;
}
protected function getCacheVersion() {
- return 0;
+ return 1;
}
protected function getPEP8WithTextOptions() {
// E101 is subset of TXT2 (Tab Literal).
// E501 is same as TXT3 (Line Too Long).
// W291 is same as TXT6 (Trailing Whitespace).
// W292 is same as TXT4 (File Does Not End in Newline).
// W293 is same as TXT6 (Trailing Whitespace).
return '--ignore=E101,E501,W291,W292,W293';
}
}
diff --git a/src/workflow/ArcanistLintWorkflow.php b/src/workflow/ArcanistLintWorkflow.php
index 22b29831..faf32b61 100644
--- a/src/workflow/ArcanistLintWorkflow.php
+++ b/src/workflow/ArcanistLintWorkflow.php
@@ -1,563 +1,567 @@
<?php
/**
* Runs lint rules on changes.
*
* @group workflow
*/
class ArcanistLintWorkflow extends ArcanistBaseWorkflow {
const RESULT_OKAY = 0;
const RESULT_WARNINGS = 1;
const RESULT_ERRORS = 2;
const RESULT_SKIP = 3;
const RESULT_POSTPONED = 4;
const DEFAULT_SEVERITY = ArcanistLintSeverity::SEVERITY_ADVICE;
private $unresolvedMessages;
private $shouldLintAll;
private $shouldAmendChanges = false;
private $shouldAmendWithoutPrompt = false;
private $shouldAmendAutofixesWithoutPrompt = false;
private $engine;
private $postponedLinters;
public function getWorkflowName() {
return 'lint';
}
public function setShouldAmendChanges($should_amend) {
$this->shouldAmendChanges = $should_amend;
return $this;
}
public function setShouldAmendWithoutPrompt($should_amend) {
$this->shouldAmendWithoutPrompt = $should_amend;
return $this;
}
public function setShouldAmendAutofixesWithoutPrompt($should_amend) {
$this->shouldAmendAutofixesWithoutPrompt = $should_amend;
return $this;
}
public function getCommandSynopses() {
return phutil_console_format(<<<EOTEXT
**lint** [__options__] [__paths__]
**lint** [__options__] --rev [__rev__]
EOTEXT
);
}
public function getCommandHelp() {
return phutil_console_format(<<<EOTEXT
Supports: git, svn, hg
Run static analysis on changes to check for mistakes. If no files
are specified, lint will be run on all files which have been modified.
EOTEXT
);
}
public function getArguments() {
return array(
'lintall' => array(
'help' =>
"Show all lint warnings, not just those on changed lines."
),
'rev' => array(
'param' => 'revision',
'help' => "Lint changes since a specific revision.",
'supports' => array(
'git',
'hg',
),
'nosupport' => array(
'svn' => "Lint does not currently support --rev in SVN.",
),
),
'output' => array(
'param' => 'format',
'help' =>
"With 'summary', show lint warnings in a more compact format. ".
"With 'json', show lint warnings in machine-readable JSON format. ".
"With 'compiler', show lint warnings in suitable for your editor."
),
'only-new' => array(
'param' => 'bool',
'supports' => array('git', 'hg'), // TODO: svn
'help' => 'Display only messages not present in the original code.',
),
'engine' => array(
'param' => 'classname',
'help' =>
"Override configured lint engine for this project."
),
'apply-patches' => array(
'help' =>
'Apply patches suggested by lint to the working copy without '.
'prompting.',
'conflicts' => array(
'never-apply-patches' => true,
),
),
'never-apply-patches' => array(
'help' => 'Never apply patches suggested by lint.',
'conflicts' => array(
'apply-patches' => true,
),
),
'amend-all' => array(
'help' =>
'When linting git repositories, amend HEAD with all patches '.
'suggested by lint without prompting.',
),
'amend-autofixes' => array(
'help' =>
'When linting git repositories, amend HEAD with autofix '.
'patches suggested by lint without prompting.',
),
'severity' => array(
'param' => 'string',
'help' =>
"Set minimum message severity. One of: '".
implode(
"', '",
array_keys(ArcanistLintSeverity::getLintSeverities())).
"'. Defaults to '".self::DEFAULT_SEVERITY."'.",
),
'cache' => array(
'param' => 'bool',
'help' => "0 to disable cache, 1 to enable (default).",
),
'*' => 'paths',
);
}
public function requiresAuthentication() {
return (bool)$this->getArgument('only-new');
}
public function requiresWorkingCopy() {
return true;
}
public function requiresRepositoryAPI() {
return true;
}
private function getCacheKey() {
return implode("\n", array(
get_class($this->engine),
$this->getArgument('severity', self::DEFAULT_SEVERITY),
$this->shouldLintAll,
));
}
public function run() {
$working_copy = $this->getWorkingCopy();
$engine = $this->getArgument('engine');
if (!$engine) {
$engine = $working_copy->getConfigFromAnySource('lint.engine');
if (!$engine) {
throw new ArcanistNoEngineException(
"No lint engine configured for this project. Edit .arcconfig to ".
"specify a lint engine.");
}
}
$rev = $this->getArgument('rev');
$paths = $this->getArgument('paths');
$use_cache = $this->getArgument('cache', true);
if ($rev && $paths) {
throw new ArcanistUsageException("Specify either --rev or paths.");
}
$this->shouldLintAll = $this->getArgument('lintall');
if ($paths) {
// NOTE: When the user specifies paths, we imply --lintall and show all
// warnings for the paths in question. This is easier to deal with for
// us and less confusing for users.
$this->shouldLintAll = true;
}
$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->setMinimumSeverity(
$this->getArgument('severity', self::DEFAULT_SEVERITY));
if ($use_cache) {
$cache = $this->readScratchJSONFile('lint-cache.json');
$cache = idx($cache, $this->getCacheKey(), array());
$cache = array_intersect_key($cache, array_flip($paths));
$cached = array();
foreach ($cache as $path => $messages) {
$abs_path = $engine->getFilePathOnDisk($path);
if (!Filesystem::pathExists($abs_path)) {
continue;
}
$messages = idx($messages, md5_file($abs_path));
if ($messages !== null) {
$cached[$path] = $messages;
}
}
$engine->setCachedResults($cached);
}
// Propagate information about which lines changed to the lint engine.
// This is used so that the lint engine can drop warning messages
// concerning lines that weren't in the change.
$engine->setPaths($paths);
if (!$this->shouldLintAll) {
foreach ($paths as $path) {
// Note that getChangedLines() returns null to indicate that a file
// is binary or a directory (i.e., changed lines are not relevant).
$engine->setPathChangedLines(
$path,
$this->getChangedLines($path, 'new'));
}
}
// Enable possible async linting only for 'arc diff' not 'arc lint'
if ($this->getParentWorkflow()) {
$engine->setEnableAsyncLint(true);
} else {
$engine->setEnableAsyncLint(false);
}
if ($this->getArgument('only-new')) {
$conduit = $this->getConduit();
$api = $this->getRepositoryAPI();
if ($rev) {
$api->setBaseCommit($rev);
}
$svn_root = id(new PhutilURI($api->getSourceControlPath()))->getPath();
$all_paths = array();
foreach ($paths as $path) {
$path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
$full_paths = array($path);
$change = $this->getChange($path);
$type = $change->getType();
if (ArcanistDiffChangeType::isOldLocationChangeType($type)) {
$full_paths = $change->getAwayPaths();
} else if (ArcanistDiffChangeType::isNewLocationChangeType($type)) {
continue;
} else if (ArcanistDiffChangeType::isDeleteChangeType($type)) {
continue;
}
foreach ($full_paths as $full_path) {
$all_paths[$svn_root.'/'.$full_path] = $path;
}
}
$lint_future = $conduit->callMethod('diffusion.getlintmessages', array(
'arcanistProject' => $this->getWorkingCopy()->getProjectID(),
'branch' => '', // TODO: Tracking branch.
'commit' => $api->getBaseCommit(),
'files' => array_keys($all_paths),
));
}
$failed = null;
try {
$engine->run();
} catch (Exception $ex) {
$failed = $ex;
}
$results = $engine->getResults();
if ($this->getArgument('only-new')) {
$total = 0;
foreach ($results as $result) {
$total += count($result->getMessages());
}
// Don't wait for response with default value of --only-new.
$timeout = null;
if ($this->getArgument('only-new') === null || !$total) {
$timeout = 0;
}
$raw_messages = $this->resolveCall($lint_future, $timeout);
if ($raw_messages && $total) {
$old_messages = array();
$line_maps = array();
foreach ($raw_messages as $message) {
$path = $all_paths[$message['path']];
$line = $message['line'];
$code = $message['code'];
if (!isset($line_maps[$path])) {
$line_maps[$path] = $this->getChange($path)->buildLineMap();
}
$new_lines = idx($line_maps[$path], $line);
if (!$new_lines) { // Unmodified lines after last hunk.
$last_old = ($line_maps[$path] ? last_key($line_maps[$path]) : 0);
$news = array_filter($line_maps[$path]);
$last_new = ($news ? last(end($news)) : 0);
$new_lines = array($line + $last_new - $last_old);
}
$error = array($code => array(true));
foreach ($new_lines as $new) {
if (isset($old_messages[$path][$new])) {
$old_messages[$path][$new][$code][] = true;
break;
}
$old_messages[$path][$new] = &$error;
}
unset($error);
}
foreach ($results as $result) {
foreach ($result->getMessages() as $message) {
$path = str_replace(DIRECTORY_SEPARATOR, '/', $message->getPath());
$line = $message->getLine();
$code = $message->getCode();
if (!empty($old_messages[$path][$line][$code])) {
$message->setObsolete(true);
array_pop($old_messages[$path][$line][$code]);
}
}
$result->sortAndFilterMessages();
}
}
}
// It'd be nice to just return a single result from the run method above
// which contains both the lint messages and the postponed linters.
// However, to maintain compatibility with existing lint subclasses, use
// a separate method call to grab the postponed linters.
$this->postponedLinters = $engine->getPostponedLinters();
if ($this->getArgument('never-apply-patches')) {
$apply_patches = false;
} else {
$apply_patches = true;
}
if ($this->getArgument('apply-patches')) {
$prompt_patches = false;
} else {
$prompt_patches = true;
}
if ($this->getArgument('amend-all')) {
$this->shouldAmendChanges = true;
$this->shouldAmendWithoutPrompt = true;
}
if ($this->getArgument('amend-autofixes')) {
$prompt_autofix_patches = false;
$this->shouldAmendChanges = true;
$this->shouldAmendAutofixesWithoutPrompt = true;
} else {
$prompt_autofix_patches = true;
}
$wrote_to_disk = false;
switch ($this->getArgument('output')) {
case 'json':
$renderer = new ArcanistLintJSONRenderer();
$prompt_patches = false;
$apply_patches = $this->getArgument('apply-patches');
break;
case 'summary':
$renderer = new ArcanistLintSummaryRenderer();
break;
case 'compiler':
$renderer = new ArcanistLintLikeCompilerRenderer();
$prompt_patches = false;
$apply_patches = $this->getArgument('apply-patches');
break;
default:
$renderer = new ArcanistLintConsoleRenderer();
$renderer->setShowAutofixPatches($prompt_autofix_patches);
break;
}
$all_autofix = true;
$console = PhutilConsole::getConsole();
foreach ($results as $result) {
$result_all_autofix = $result->isAllAutofix();
if (!$result->getMessages() && !$result_all_autofix) {
continue;
}
if (!$result_all_autofix) {
$all_autofix = false;
}
$lint_result = $renderer->renderLintResult($result);
if ($lint_result) {
$console->writeOut('%s', $lint_result);
}
if ($apply_patches && $result->isPatchable()) {
$patcher = ArcanistLintPatcher::newFromArcanistLintResult($result);
if ($prompt_patches &&
!($result_all_autofix && !$prompt_autofix_patches)) {
$old_file = $result->getFilePathOnDisk();
if (!Filesystem::pathExists($old_file)) {
$old_file = '/dev/null';
}
$new_file = new TempFile();
$new = $patcher->getModifiedFileContent();
Filesystem::writeFile($new_file, $new);
// TODO: Improve the behavior here, make it more like
// difference_render().
list(, $stdout, $stderr) =
exec_manual("diff -u %s %s", $old_file, $new_file);
$console->writeOut('%s', $stdout);
$console->writeErr('%s', $stderr);
$prompt = phutil_console_format(
"Apply this patch to __%s__?",
$result->getPath());
if (!$console->confirm($prompt, $default_no = false)) {
continue;
}
}
$patcher->writePatchToDisk();
$wrote_to_disk = true;
}
}
$repository_api = $this->getRepositoryAPI();
if ($wrote_to_disk &&
($repository_api instanceof ArcanistGitAPI) &&
$this->shouldAmendChanges) {
if ($this->shouldAmendWithoutPrompt ||
($this->shouldAmendAutofixesWithoutPrompt && $all_autofix)) {
$console->writeOut(
"<bg:yellow>** LINT NOTICE **</bg> Automatically amending HEAD ".
"with lint patches.\n");
$amend = true;
} else {
$amend = $console->confirm("Amend HEAD with lint patches?");
}
if ($amend) {
execx(
'(cd %s; git commit -a --amend -C HEAD)',
$repository_api->getPath());
} else {
throw new ArcanistUsageException(
"Sort out the lint changes that were applied to the working ".
"copy and relint.");
}
}
if ($this->getArgument('output') == 'json') {
// NOTE: Required by save_lint.php in Phabricator.
return 0;
}
if ($failed) {
throw $failed;
}
$unresolved = array();
$has_warnings = false;
$has_errors = false;
foreach ($results as $result) {
foreach ($result->getMessages() as $message) {
if (!$message->isPatchApplied()) {
if ($message->isError()) {
$has_errors = true;
} else if ($message->isWarning()) {
$has_warnings = true;
}
$unresolved[] = $message;
}
}
}
$this->unresolvedMessages = $unresolved;
$cache = $this->readScratchJSONFile('lint-cache.json');
$cached = idx($cache, $this->getCacheKey(), array());
if ($cached || $use_cache) {
+ $stopped = $engine->getStoppedPaths();
foreach ($results as $result) {
$path = $result->getPath();
if (!$use_cache) {
unset($cached[$path]);
continue;
}
$abs_path = $engine->getFilePathOnDisk($path);
if (!Filesystem::pathExists($abs_path)) {
continue;
}
$hash = md5_file($abs_path);
$version = $result->getCacheVersion();
$cached[$path] = array($hash => array($version => array()));
+ if (isset($stopped[$path])) {
+ $cached[$path][$hash][$version]['stopped'] = $stopped[$path];
+ }
foreach ($result->getMessages() as $message) {
if ($message->isUncacheable()) {
continue;
}
if (!$message->isPatchApplied()) {
$cached[$path][$hash][$version][] = $message->toDictionary();
}
}
}
$cache[$this->getCacheKey()] = $cached;
// TODO: Garbage collection.
$this->writeScratchJSONFile('lint-cache.json', $cache);
}
// Take the most severe lint message severity and use that
// as the result code.
if ($has_errors) {
$result_code = self::RESULT_ERRORS;
} else if ($has_warnings) {
$result_code = self::RESULT_WARNINGS;
} else if (!empty($this->postponedLinters)) {
$result_code = self::RESULT_POSTPONED;
} else {
$result_code = self::RESULT_OKAY;
}
if (!$this->getParentWorkflow()) {
if ($result_code == self::RESULT_OKAY) {
$console->writeOut('%s', $renderer->renderOkayResult());
}
}
return $result_code;
}
public function getUnresolvedMessages() {
return $this->unresolvedMessages;
}
public function getPostponedLinters() {
return $this->postponedLinters;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Oct 11, 11:43 (1 h, 23 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
984309
Default Alt Text
(31 KB)
Attached To
Mode
R118 Arcanist - fork
Attached
Detach File
Event Timeline
Log In to Comment