Changeset View
Changeset View
Standalone View
Standalone View
src/lint/engine/ArcanistConfigurationDrivenLintEngine.php
<?php | <?php | ||||
final class ArcanistConfigurationDrivenLintEngine extends ArcanistLintEngine { | final class ArcanistConfigurationDrivenLintEngine extends ArcanistLintEngine { | ||||
public function buildLinters() { | public function buildLinters() { | ||||
$working_copy = $this->getWorkingCopy(); | $working_copy = $this->getWorkingCopy(); | ||||
$config_path = $working_copy->getProjectPath('.arclint'); | $config_path = $working_copy->getProjectPath('.arclint'); | ||||
if (!Filesystem::pathExists($config_path)) { | if (!Filesystem::pathExists($config_path)) { | ||||
throw new Exception( | throw new ArcanistUsageException( | ||||
"Unable to find '.arclint' file to configure linters. Create a ". | pht( | ||||
"'.arclint' file in the root directory of the working copy."); | "Unable to find '%s' file to configure linters. Create an ". | ||||
"'%s' file in the root directory of the working copy.", | |||||
'.arclint', | |||||
'.arclint')); | |||||
} | } | ||||
$data = Filesystem::readFile($config_path); | $data = Filesystem::readFile($config_path); | ||||
$config = null; | $config = null; | ||||
try { | try { | ||||
$config = phutil_json_decode($data); | $config = phutil_json_decode($data); | ||||
} catch (PhutilJSONParserException $ex) { | } catch (PhutilJSONParserException $ex) { | ||||
throw new PhutilProxyException( | throw new PhutilProxyException( | ||||
pht( | pht( | ||||
"Expected '.arclint' file to be a valid JSON file, but failed to ". | "Expected '%s' file to be a valid JSON file, but ". | ||||
"decode %s", | "failed to decode '%s'.", | ||||
'.arclint', | |||||
$config_path), | $config_path), | ||||
$ex); | $ex); | ||||
} | } | ||||
$linters = $this->loadAvailableLinters(); | $linters = $this->loadAvailableLinters(); | ||||
try { | try { | ||||
PhutilTypeSpec::checkMap( | PhutilTypeSpec::checkMap( | ||||
$config, | $config, | ||||
array( | array( | ||||
'exclude' => 'optional regex | list<regex>', | 'exclude' => 'optional regex | list<regex>', | ||||
'linters' => 'map<string, map<string, wild>>', | 'linters' => 'map<string, map<string, wild>>', | ||||
)); | )); | ||||
} catch (PhutilTypeCheckException $ex) { | } catch (PhutilTypeCheckException $ex) { | ||||
$message = pht( | throw new PhutilProxyException( | ||||
'Error in parsing ".arclint" file: %s', | pht("Error in parsing '%s' file."), | ||||
epriestley: Missing a parameter. | |||||
Not Done Inline ActionsWell spotted, I though that the linter was meant to pick this kind of stuff up. joshuaspence: Well spotted, I though that the linter was meant to pick this kind of stuff up. | |||||
$ex->getMessage()); | $ex); | ||||
throw new PhutilProxyException($message, $ex); | |||||
} | } | ||||
$global_exclude = (array)idx($config, 'exclude', array()); | $global_exclude = (array)idx($config, 'exclude', array()); | ||||
$built_linters = array(); | $built_linters = array(); | ||||
$all_paths = $this->getPaths(); | $all_paths = $this->getPaths(); | ||||
foreach ($config['linters'] as $name => $spec) { | foreach ($config['linters'] as $name => $spec) { | ||||
$type = idx($spec, 'type'); | $type = idx($spec, 'type'); | ||||
if ($type !== null) { | if ($type !== null) { | ||||
if (empty($linters[$type])) { | if (empty($linters[$type])) { | ||||
$list = implode(', ', array_keys($linters)); | throw new ArcanistUsageException( | ||||
throw new Exception( | pht( | ||||
"Linter '{$name}' specifies invalid type '{$type}'. Available ". | "Linter '%s' specifies invalid type '%s'. ". | ||||
"linters are: {$list}."); | "Available linters are: %s.", | ||||
$name, | |||||
$type, | |||||
implode(', ', array_keys($linters)))); | |||||
} | } | ||||
$linter = clone $linters[$type]; | $linter = clone $linters[$type]; | ||||
$linter->setEngine($this); | $linter->setEngine($this); | ||||
$more = $linter->getLinterConfigurationOptions(); | $more = $linter->getLinterConfigurationOptions(); | ||||
foreach ($more as $key => $option_spec) { | foreach ($more as $key => $option_spec) { | ||||
PhutilTypeSpec::checkMap( | PhutilTypeSpec::checkMap( | ||||
Show All 14 Lines | foreach ($config['linters'] as $name => $spec) { | ||||
PhutilTypeSpec::checkMap( | PhutilTypeSpec::checkMap( | ||||
$spec, | $spec, | ||||
array( | array( | ||||
'type' => 'string', | 'type' => 'string', | ||||
'include' => 'optional regex | list<regex>', | 'include' => 'optional regex | list<regex>', | ||||
'exclude' => 'optional regex | list<regex>', | 'exclude' => 'optional regex | list<regex>', | ||||
) + $more); | ) + $more); | ||||
} catch (PhutilTypeCheckException $ex) { | } catch (PhutilTypeCheckException $ex) { | ||||
$message = pht( | throw new PhutilProxyException( | ||||
'Error in parsing ".arclint" file, for linter "%s": %s', | pht( | ||||
$name, | "Error in parsing '%s' file, for linter '%s'.", | ||||
$ex->getMessage()); | '.arclint', | ||||
throw new PhutilProxyException($message, $ex); | $name), | ||||
$ex); | |||||
} | } | ||||
foreach ($more as $key => $value) { | foreach ($more as $key => $value) { | ||||
if (array_key_exists($key, $spec)) { | if (array_key_exists($key, $spec)) { | ||||
try { | try { | ||||
$linter->setLinterConfigurationValue($key, $spec[$key]); | $linter->setLinterConfigurationValue($key, $spec[$key]); | ||||
} catch (Exception $ex) { | } catch (Exception $ex) { | ||||
$message = pht( | throw new PhutilProxyException( | ||||
'Error in parsing ".arclint" file, in key "%s" for '. | pht( | ||||
'linter "%s": %s', | "Error in parsing '%s' file, in key '%s' for linter '%s'.", | ||||
'.arclint', | |||||
$key, | $key, | ||||
$name, | $name), | ||||
$ex->getMessage()); | $ex); | ||||
throw new PhutilProxyException($message, $ex); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
$include = (array)idx($spec, 'include', array()); | $include = (array)idx($spec, 'include', array()); | ||||
$exclude = (array)idx($spec, 'exclude', array()); | $exclude = (array)idx($spec, 'exclude', array()); | ||||
$console = PhutilConsole::getConsole(); | $console = PhutilConsole::getConsole(); | ||||
$console->writeLog("Examining paths for linter \"%s\".\n", $name); | $console->writeLog( | ||||
"%s\n", | |||||
pht("Examining paths for linter '%s'.", $name)); | |||||
$paths = $this->matchPaths( | $paths = $this->matchPaths( | ||||
$all_paths, | $all_paths, | ||||
$include, | $include, | ||||
$exclude, | $exclude, | ||||
$global_exclude); | $global_exclude); | ||||
$console->writeLog( | $console->writeLog( | ||||
"Found %d matching paths for linter \"%s\".\n", | "%s\n", | ||||
count($paths), | pht("Found %d matching paths for linter '%s'.", count($paths), $name)); | ||||
$name); | |||||
if ($paths) { | if ($paths) { | ||||
$linter->setPaths($paths); | $linter->setPaths($paths); | ||||
$built_linters[] = $linter; | $built_linters[] = $linter; | ||||
} | } | ||||
} | } | ||||
return $built_linters; | return $built_linters; | ||||
Show All 16 Lines | foreach ($linters as $linter) { | ||||
if (empty($map[$name])) { | if (empty($map[$name])) { | ||||
$map[$name] = $linter; | $map[$name] = $linter; | ||||
continue; | continue; | ||||
} | } | ||||
$orig_class = get_class($map[$name]); | $orig_class = get_class($map[$name]); | ||||
$this_class = get_class($linter); | $this_class = get_class($linter); | ||||
throw new Exception( | throw new Exception( | ||||
"Two linters ({$orig_class}, {$this_class}) both have the same ". | pht( | ||||
"configuration name ({$name}). Linters must have unique configuration ". | "Two linters (`%s`, `%s`) both have the same configuration ". | ||||
"names."); | "name ('%s'). Linters must have unique configuration names.", | ||||
$orig_class, | |||||
$this_class, | |||||
$name)); | |||||
} | } | ||||
return $map; | return $map; | ||||
} | } | ||||
private function matchPaths( | private function matchPaths( | ||||
array $paths, | array $paths, | ||||
array $include, | array $include, | ||||
array $exclude, | array $exclude, | ||||
array $global_exclude) { | array $global_exclude) { | ||||
$console = PhutilConsole::getConsole(); | $console = PhutilConsole::getConsole(); | ||||
$match = array(); | $match = array(); | ||||
foreach ($paths as $path) { | foreach ($paths as $path) { | ||||
$console->writeLog("Examining path '%s'...\n", $path); | $console->writeLog("%s\n", pht("Examining path '%s'...", $path)); | ||||
$keep = false; | $keep = false; | ||||
if (!$include) { | if (!$include) { | ||||
$keep = true; | $keep = true; | ||||
$console->writeLog( | $console->writeLog( | ||||
" Including path by default because there is no 'include' rule.\n"); | " %s\n", | ||||
pht('Including path by default because there is no "include" rule.')); | |||||
} else { | } else { | ||||
$console->writeLog(" Testing \"include\" rules.\n"); | $console->writeLog( | ||||
" %s\n", | |||||
pht('Testing "include" rules.')); | |||||
foreach ($include as $rule) { | foreach ($include as $rule) { | ||||
if (preg_match($rule, $path)) { | if (preg_match($rule, $path)) { | ||||
$keep = true; | $keep = true; | ||||
$console->writeLog(" Path matches include rule: %s\n", $rule); | $console->writeLog( | ||||
" %s\n", | |||||
pht('Path matches include rule: %s.', $rule)); | |||||
break; | break; | ||||
} else { | } else { | ||||
$console->writeLog( | $console->writeLog( | ||||
" Path does not match include rule: %s\n", | " %s\n", | ||||
$rule); | pht('Path does not match include rule: %s', $rule)); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if (!$keep) { | if (!$keep) { | ||||
$console->writeLog( | $console->writeLog( | ||||
" Path does not match any include rules, discarding.\n"); | " %s\n", | ||||
pht('Path does not match any include rules, discarding.')); | |||||
continue; | continue; | ||||
} | } | ||||
if ($exclude) { | if ($exclude) { | ||||
$console->writeLog(" Testing \"exclude\" rules.\n"); | $console->writeLog( | ||||
" %s\n", | |||||
pht('Testing "exclude" rules.')); | |||||
foreach ($exclude as $rule) { | foreach ($exclude as $rule) { | ||||
if (preg_match($rule, $path)) { | if (preg_match($rule, $path)) { | ||||
$console->writeLog(" Path matches \"exclude\" rule: %s\n", $rule); | $console->writeLog( | ||||
" %s\n", | |||||
pht('Path matches "exclude" rule: %s.', $rule)); | |||||
continue 2; | continue 2; | ||||
} else { | } else { | ||||
$console->writeLog( | $console->writeLog( | ||||
" Path does not match \"exclude\" rule: %s\n", | " %s\n", | ||||
$rule); | pht('Path does not match "exclude" rule: %s.', $rule)); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if ($global_exclude) { | if ($global_exclude) { | ||||
$console->writeLog(" Testing global \"exclude\" rules.\n"); | $console->writeLog( | ||||
" %s\n", | |||||
pht('Testing global "exclude" rules.')); | |||||
foreach ($global_exclude as $rule) { | foreach ($global_exclude as $rule) { | ||||
if (preg_match($rule, $path)) { | if (preg_match($rule, $path)) { | ||||
$console->writeLog( | $console->writeLog( | ||||
" Path matches global \"exclude\" rule: %s\n", | " %s\n", | ||||
$rule); | pht('Path matches global "exclude" rule: %s.', $rule)); | ||||
continue 2; | continue 2; | ||||
} else { | } else { | ||||
$console->writeLog( | $console->writeLog( | ||||
" Path does not match global \"exclude\" rule: %s\n", | " %s\n", | ||||
$rule); | pht('Path does not match global "exclude" rule: %s.', $rule)); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
$console->writeLog(" Path matches.\n"); | $console->writeLog( | ||||
" %s\n", | |||||
pht('Path matches.')); | |||||
$match[] = $path; | $match[] = $path; | ||||
} | } | ||||
return $match; | return $match; | ||||
} | } | ||||
} | } |
Missing a parameter.