Changeset View
Changeset View
Standalone View
Standalone View
src/workflow/ArcanistLintWorkflow.php
Show First 20 Lines • Show All 123 Lines • ▼ Show 20 Lines | return array( | ||||
'amend-autofixes' => array( | 'amend-autofixes' => array( | ||||
'help' => pht( | 'help' => pht( | ||||
'When linting git repositories, amend HEAD with autofix '. | 'When linting git repositories, amend HEAD with autofix '. | ||||
'patches suggested by lint without prompting.'), | 'patches suggested by lint without prompting.'), | ||||
), | ), | ||||
'everything' => array( | 'everything' => array( | ||||
'help' => pht('Lint all files in the project.'), | 'help' => pht('Lint all files in the project.'), | ||||
'conflicts' => array( | 'conflicts' => array( | ||||
'cache' => pht('%s lints all files', '--everything'), | |||||
'rev' => pht('%s lints all files', '--everything'), | 'rev' => pht('%s lints all files', '--everything'), | ||||
), | ), | ||||
), | ), | ||||
'severity' => array( | 'severity' => array( | ||||
'param' => 'string', | 'param' => 'string', | ||||
'help' => pht( | 'help' => pht( | ||||
"Set minimum message severity. One of: %s. Defaults to '%s'.", | "Set minimum message severity. One of: %s. Defaults to '%s'.", | ||||
sprintf( | sprintf( | ||||
"'%s'", | "'%s'", | ||||
implode( | implode( | ||||
"', '", | "', '", | ||||
array_keys(ArcanistLintSeverity::getLintSeverities()))), | array_keys(ArcanistLintSeverity::getLintSeverities()))), | ||||
self::DEFAULT_SEVERITY), | self::DEFAULT_SEVERITY), | ||||
), | ), | ||||
'cache' => array( | |||||
'param' => 'bool', | |||||
'help' => pht( | |||||
"%d to disable cache, %d to enable. The default value is determined ". | |||||
"by '%s' in configuration, which defaults to off. See notes in '%s'.", | |||||
0, | |||||
1, | |||||
'arc.lint.cache', | |||||
'arc.lint.cache'), | |||||
), | |||||
'*' => 'paths', | '*' => 'paths', | ||||
); | ); | ||||
} | } | ||||
public function requiresAuthentication() { | public function requiresAuthentication() { | ||||
return false; | return false; | ||||
} | } | ||||
public function requiresWorkingCopy() { | public function requiresWorkingCopy() { | ||||
return true; | return true; | ||||
} | } | ||||
public function requiresRepositoryAPI() { | public function requiresRepositoryAPI() { | ||||
return true; | return true; | ||||
} | } | ||||
private function getCacheKey() { | |||||
return implode("\n", array( | |||||
get_class($this->engine), | |||||
$this->getArgument('severity', self::DEFAULT_SEVERITY), | |||||
$this->shouldLintAll, | |||||
)); | |||||
} | |||||
public function run() { | public function run() { | ||||
$console = PhutilConsole::getConsole(); | $console = PhutilConsole::getConsole(); | ||||
$working_copy = $this->getWorkingCopy(); | $working_copy = $this->getWorkingCopy(); | ||||
$configuration_manager = $this->getConfigurationManager(); | $configuration_manager = $this->getConfigurationManager(); | ||||
$engine = $this->newLintEngine($this->getArgument('engine')); | $engine = $this->newLintEngine($this->getArgument('engine')); | ||||
$rev = $this->getArgument('rev'); | $rev = $this->getArgument('rev'); | ||||
$paths = $this->getArgument('paths'); | $paths = $this->getArgument('paths'); | ||||
$use_cache = $this->getArgument('cache', null); | |||||
$everything = $this->getArgument('everything'); | $everything = $this->getArgument('everything'); | ||||
if ($everything && $paths) { | if ($everything && $paths) { | ||||
throw new ArcanistUsageException( | throw new ArcanistUsageException( | ||||
pht( | pht( | ||||
'You can not specify paths with %s. The %s flag lints every file.', | 'You can not specify paths with %s. The %s flag lints every file.', | ||||
'--everything', | '--everything', | ||||
'--everything')); | '--everything')); | ||||
} | } | ||||
if ($use_cache === null) { | |||||
$use_cache = (bool)$configuration_manager->getConfigFromAnySource( | |||||
'arc.lint.cache', | |||||
false); | |||||
} | |||||
if ($rev && $paths) { | if ($rev && $paths) { | ||||
throw new ArcanistUsageException( | throw new ArcanistUsageException( | ||||
pht('Specify either %s or paths.', '--rev')); | pht('Specify either %s or paths.', '--rev')); | ||||
} | } | ||||
// NOTE: When the user specifies paths, we imply --lintall and show all | // NOTE: When the user specifies paths, we imply --lintall and show all | ||||
Show All 13 Lines | if ($everything) { | ||||
$paths = $this->selectPathsForWorkflow($paths, $rev); | $paths = $this->selectPathsForWorkflow($paths, $rev); | ||||
} | } | ||||
$this->engine = $engine; | $this->engine = $engine; | ||||
$engine->setMinimumSeverity( | $engine->setMinimumSeverity( | ||||
$this->getArgument('severity', self::DEFAULT_SEVERITY)); | $this->getArgument('severity', self::DEFAULT_SEVERITY)); | ||||
$file_hashes = array(); | |||||
if ($use_cache) { | |||||
$engine->setRepositoryVersion($this->getRepositoryVersion()); | |||||
$cache = $this->readScratchJSONFile('lint-cache.json'); | |||||
$cache = idx($cache, $this->getCacheKey(), array()); | |||||
$cached = array(); | |||||
foreach ($paths as $path) { | |||||
$abs_path = $engine->getFilePathOnDisk($path); | |||||
if (!Filesystem::pathExists($abs_path)) { | |||||
continue; | |||||
} | |||||
$file_hashes[$abs_path] = md5_file($abs_path); | |||||
if (!isset($cache[$path])) { | |||||
continue; | |||||
} | |||||
$messages = idx($cache[$path], $file_hashes[$abs_path]); | |||||
if ($messages !== null) { | |||||
$cached[$path] = $messages; | |||||
} | |||||
} | |||||
if ($cached) { | |||||
$console->writeErr( | |||||
"%s\n", | |||||
pht( | |||||
"Using lint cache, use '%s' to disable it.", | |||||
'--cache 0')); | |||||
} | |||||
$engine->setCachedResults($cached); | |||||
} | |||||
// Propagate information about which lines changed to the lint engine. | // Propagate information about which lines changed to the lint engine. | ||||
// This is used so that the lint engine can drop warning messages | // This is used so that the lint engine can drop warning messages | ||||
// concerning lines that weren't in the change. | // concerning lines that weren't in the change. | ||||
$engine->setPaths($paths); | $engine->setPaths($paths); | ||||
if (!$this->shouldLintAll) { | if (!$this->shouldLintAll) { | ||||
foreach ($paths as $path) { | foreach ($paths as $path) { | ||||
// Note that getChangedLines() returns null to indicate that a file | // Note that getChangedLines() returns null to indicate that a file | ||||
// is binary or a directory (i.e., changed lines are not relevant). | // is binary or a directory (i.e., changed lines are not relevant). | ||||
▲ Show 20 Lines • Show All 135 Lines • ▼ Show 20 Lines | foreach ($results as $result) { | ||||
phutil_console_format('__%s__', $result->getPath())); | phutil_console_format('__%s__', $result->getPath())); | ||||
if (!phutil_console_confirm($prompt, $default_no = false)) { | if (!phutil_console_confirm($prompt, $default_no = false)) { | ||||
continue; | continue; | ||||
} | } | ||||
} | } | ||||
$patcher->writePatchToDisk(); | $patcher->writePatchToDisk(); | ||||
$wrote_to_disk = true; | $wrote_to_disk = true; | ||||
$file_hashes[$old_file] = md5_file($old_file); | |||||
} | } | ||||
} | } | ||||
$postamble = $renderer->renderPostamble(); | $postamble = $renderer->renderPostamble(); | ||||
if ($tmp) { | if ($tmp) { | ||||
Filesystem::appendFile($tmp, $postamble); | Filesystem::appendFile($tmp, $postamble); | ||||
Filesystem::rename($tmp, $this->getArgument('outfile')); | Filesystem::rename($tmp, $this->getArgument('outfile')); | ||||
} else { | } else { | ||||
▲ Show 20 Lines • Show All 54 Lines • ▼ Show 20 Lines | foreach ($results as $result) { | ||||
$has_warnings = true; | $has_warnings = true; | ||||
} | } | ||||
$unresolved[] = $message; | $unresolved[] = $message; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
$this->unresolvedMessages = $unresolved; | $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; | |||||
} | |||||
$version = $result->getCacheVersion(); | |||||
$cached_path = array(); | |||||
if (isset($stopped[$path])) { | |||||
$cached_path['stopped'] = $stopped[$path]; | |||||
} | |||||
$cached_path['repository_version'] = $this->getRepositoryVersion(); | |||||
foreach ($result->getMessages() as $message) { | |||||
$granularity = $message->getGranularity(); | |||||
if ($granularity == ArcanistLinter::GRANULARITY_GLOBAL) { | |||||
continue; | |||||
} | |||||
if (!$message->isPatchApplied()) { | |||||
$cached_path[] = $message->toDictionary(); | |||||
} | |||||
} | |||||
$hash = idx($file_hashes, $abs_path); | |||||
if (!$hash) { | |||||
$hash = md5_file($abs_path); | |||||
} | |||||
$cached[$path] = array($hash => array($version => $cached_path)); | |||||
} | |||||
$cache[$this->getCacheKey()] = $cached; | |||||
// TODO: Garbage collection. | |||||
$this->writeScratchJSONFile('lint-cache.json', $cache); | |||||
} | |||||
// Take the most severe lint message severity and use that | // Take the most severe lint message severity and use that | ||||
// as the result code. | // as the result code. | ||||
if ($has_errors) { | if ($has_errors) { | ||||
$result_code = self::RESULT_ERRORS; | $result_code = self::RESULT_ERRORS; | ||||
} else if ($has_warnings) { | } else if ($has_warnings) { | ||||
$result_code = self::RESULT_WARNINGS; | $result_code = self::RESULT_WARNINGS; | ||||
} else { | } else { | ||||
$result_code = self::RESULT_OKAY; | $result_code = self::RESULT_OKAY; | ||||
Show All 16 Lines |