Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F9584104
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
18 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/lint/linter/ArcanistConduitLinter.php b/src/lint/linter/ArcanistConduitLinter.php
index 2618ea99..5a7a67a2 100644
--- a/src/lint/linter/ArcanistConduitLinter.php
+++ b/src/lint/linter/ArcanistConduitLinter.php
@@ -1,98 +1,102 @@
<?php
/**
* Implements linting via Conduit RPC call.
* Slow by definition, but allows sophisticated linting that relies on
* stuff like big indexes of a codebase.
* Recommended usage is to gate these to the advice lint level.
*
* The conduit endpoint should implement a method named the same as
* the value of ArcanistConduitLinter::CONDUIT_METHOD.
*
* It takes an array with a key 'file_contents' which is an array mapping
* file paths to their complete contents.
*
* It should return an array mapping those same paths to arrays describing the
* lint for each path.
*
* The lint for a path is described as a list of structured dictionaries.
*
* The dictionary structure is effectively defined by
* ArcanistLintMessage::newFromDictionary.
*
* Effective keys are:
* 'path' => must match passed in path.
* 'line'
* 'char'
* 'code'
* 'severity' => Must match a constant in ArcanistLintSeverity.
* 'name'
* 'description'
* 'original' & 'replacement' => optional patch information
* 'locations' => other locations of the same error (in the same format)
*
* This class is intended for customization via instantiation, not via
* subclassing.
*/
final class ArcanistConduitLinter extends ArcanistLinter {
const CONDUIT_METHOD = 'lint.getalllint';
private $conduitURI;
private $linterName;
private $lintByPath; // array(/pa/th/ => <lint>), valid after willLintPaths().
public function __construct($conduit_uri = null, $linter_name = null) {
// TODO: Facebook uses this (probably?) but we need to be able to
// construct it without arguments for ".arclint".
$this->conduitURI = $conduit_uri;
$this->linterName = $linter_name;
}
public function willLintPaths(array $paths) {
// Load all file path data into $this->data.
array_map(array($this, 'getData'), $paths);
$conduit = new ConduitClient($this->conduitURI);
$this->lintByPath = $conduit->callMethodSynchronous(
self::CONDUIT_METHOD,
array(
'file_contents' => $this->data,
));
}
public function lintPath($path) {
$lint_for_path = idx($this->lintByPath, $path);
if (!$lint_for_path) {
return;
}
foreach ($lint_for_path as $lint) {
$this->addLintMessage(ArcanistLintMessage::newFromDictionary($lint));
}
}
public function getLinterName() {
return $this->linterName;
}
public function getLintSeverityMap() {
// The rationale here is that this class will only be used for custom
// linting in installations. No two server endpoints will be the same across
// different instantiations. Therefore, the server can handle all severity
// customization directly.
throw new ArcanistUsageException(
'ArcanistConduitLinter does not support client-side severity '.
'customization.'
);
}
public function getLintNameMap() {
// See getLintSeverityMap for rationale.
throw new ArcanistUsageException(
'ArcanistConduitLinter does not support a name map.'
);
}
+
+ protected function canCustomizeLintSeverities() {
+ return false;
+ }
}
diff --git a/src/lint/linter/ArcanistGeneratedLinter.php b/src/lint/linter/ArcanistGeneratedLinter.php
index 7cf90450..29c1e809 100644
--- a/src/lint/linter/ArcanistGeneratedLinter.php
+++ b/src/lint/linter/ArcanistGeneratedLinter.php
@@ -1,38 +1,42 @@
<?php
/**
* Stops other linters from running on generated code.
*/
final class ArcanistGeneratedLinter extends ArcanistLinter {
public function getInfoName() {
return pht('Generated Code');
}
public function getInfoDescription() {
return pht(
'Disables lint for files that are marked as "%s", indicating that they '.
'contain generated code.',
'@'.'generated');
}
public function getLinterName() {
return 'GEN';
}
public function getLinterPriority() {
return 0.25;
}
public function getLinterConfigurationName() {
return 'generated';
}
+ protected function canCustomizeLintSeverities() {
+ return false;
+ }
+
public function lintPath($path) {
$data = $this->getData($path);
if (preg_match('/@'.'generated/', $data)) {
$this->stopAllLinters();
}
}
}
diff --git a/src/lint/linter/ArcanistLinter.php b/src/lint/linter/ArcanistLinter.php
index e8847512..ae40eb90 100644
--- a/src/lint/linter/ArcanistLinter.php
+++ b/src/lint/linter/ArcanistLinter.php
@@ -1,486 +1,503 @@
<?php
/**
* Implements lint rules, like syntax checks for a specific language.
*
* @task info Human Readable Information
*
* @stable
*/
abstract class ArcanistLinter {
const GRANULARITY_FILE = 1;
const GRANULARITY_DIRECTORY = 2;
const GRANULARITY_REPOSITORY = 3;
const GRANULARITY_GLOBAL = 4;
protected $paths = array();
protected $data = array();
protected $engine;
protected $activePath;
protected $messages = array();
protected $stopAllLinters = false;
private $customSeverityMap = array();
private $customSeverityRules = array();
private $config = array();
/* -( Human Readable Information )---------------------------------------- */
/**
* Return an optional informative URI where humans can learn more about this
* linter.
*
* For most linters, this should return a link to the project home page. This
* is shown on `arc linters`.
*
* @return string|null Optionally, return an informative URI.
* @task info
*/
public function getInfoURI() {
return null;
}
/**
* Return a brief human-readable description of the linter.
*
* These should be a line or two, and are shown on `arc linters`.
*
* @return string|null Optionally, return a brief human-readable description.
* @task info
*/
public function getInfoDescription() {
return null;
}
/**
* Return a human-readable linter name.
*
* These are used by `arc linters`, and can let you give a linter a more
* presentable name.
*
* @return string Human-readable linter name.
* @task info
*/
public function getInfoName() {
return nonempty(
$this->getLinterName(),
$this->getLinterConfigurationName(),
get_class($this));
}
public function getLinterPriority() {
return 1.0;
}
public function setCustomSeverityMap(array $map) {
$this->customSeverityMap = $map;
return $this;
}
public function setCustomSeverityRules(array $rules) {
$this->customSeverityRules = $rules;
return $this;
}
public function setConfig(array $config) {
$this->config = $config;
return $this;
}
protected function getConfig($key, $default = null) {
return idx($this->config, $key, $default);
}
public function getActivePath() {
return $this->activePath;
}
public function getOtherLocation($offset, $path = null) {
if ($path === null) {
$path = $this->getActivePath();
}
list($line, $char) = $this->getEngine()->getLineAndCharFromOffset(
$path,
$offset);
return array(
'path' => $path,
'line' => $line + 1,
'char' => $char,
);
}
public function stopAllLinters() {
$this->stopAllLinters = true;
return $this;
}
public function didStopAllLinters() {
return $this->stopAllLinters;
}
public function addPath($path) {
$this->paths[$path] = $path;
return $this;
}
public function setPaths(array $paths) {
$this->paths = $paths;
return $this;
}
/**
* Filter out paths which this linter doesn't act on (for example, because
* they are binaries and the linter doesn't apply to binaries).
*/
private function filterPaths($paths) {
$engine = $this->getEngine();
$keep = array();
foreach ($paths as $path) {
if (!$this->shouldLintDeletedFiles() && !$engine->pathExists($path)) {
continue;
}
if (!$this->shouldLintDirectories() && $engine->isDirectory($path)) {
continue;
}
if (!$this->shouldLintBinaryFiles() && $engine->isBinaryFile($path)) {
continue;
}
$keep[] = $path;
}
return $keep;
}
public function getPaths() {
return $this->filterPaths(array_values($this->paths));
}
public function addData($path, $data) {
$this->data[$path] = $data;
return $this;
}
protected function getData($path) {
if (!array_key_exists($path, $this->data)) {
$this->data[$path] = $this->getEngine()->loadData($path);
}
return $this->data[$path];
}
public function setEngine(ArcanistLintEngine $engine) {
$this->engine = $engine;
return $this;
}
protected function getEngine() {
return $this->engine;
}
public function getCacheVersion() {
return 0;
}
public function getLintMessageFullCode($short_code) {
return $this->getLinterName().$short_code;
}
public function getLintMessageSeverity($code) {
$map = $this->customSeverityMap;
if (isset($map[$code])) {
return $map[$code];
}
$map = $this->getLintSeverityMap();
if (isset($map[$code])) {
return $map[$code];
}
foreach ($this->customSeverityRules as $rule => $severity) {
if (preg_match($rule, $code)) {
return $severity;
}
}
return $this->getDefaultMessageSeverity($code);
}
protected function getDefaultMessageSeverity($code) {
return ArcanistLintSeverity::SEVERITY_ERROR;
}
public function isMessageEnabled($code) {
return ($this->getLintMessageSeverity($code) !==
ArcanistLintSeverity::SEVERITY_DISABLED);
}
public function getLintMessageName($code) {
$map = $this->getLintNameMap();
if (isset($map[$code])) {
return $map[$code];
}
return "Unknown lint message!";
}
protected function addLintMessage(ArcanistLintMessage $message) {
if (!$this->getEngine()->getCommitHookMode()) {
$root = $this->getEngine()->getWorkingCopy()->getProjectRoot();
$path = Filesystem::resolvePath($message->getPath(), $root);
$message->setPath(Filesystem::readablePath($path, $root));
}
$this->messages[] = $message;
return $message;
}
public function getLintMessages() {
return $this->messages;
}
protected function raiseLintAtLine(
$line,
$char,
$code,
$desc,
$original = null,
$replacement = null) {
$message = id(new ArcanistLintMessage())
->setPath($this->getActivePath())
->setLine($line)
->setChar($char)
->setCode($this->getLintMessageFullCode($code))
->setSeverity($this->getLintMessageSeverity($code))
->setName($this->getLintMessageName($code))
->setDescription($desc)
->setOriginalText($original)
->setReplacementText($replacement);
return $this->addLintMessage($message);
}
protected function raiseLintAtPath(
$code,
$desc) {
return $this->raiseLintAtLine(null, null, $code, $desc, null, null);
}
protected function raiseLintAtOffset(
$offset,
$code,
$desc,
$original = null,
$replacement = null) {
$path = $this->getActivePath();
$engine = $this->getEngine();
if ($offset === null) {
$line = null;
$char = null;
} else {
list($line, $char) = $engine->getLineAndCharFromOffset($path, $offset);
}
return $this->raiseLintAtLine(
$line + 1,
$char + 1,
$code,
$desc,
$original,
$replacement);
}
public function willLintPath($path) {
$this->stopAllLinters = false;
$this->activePath = $path;
}
public function canRun() {
return true;
}
public function willLintPaths(array $paths) {
return;
}
abstract public function lintPath($path);
abstract public function getLinterName();
public function getVersion() {
return null;
}
public function didRunLinters() {
// This is a hook.
}
protected function isCodeEnabled($code) {
$severity = $this->getLintMessageSeverity($code);
return $this->getEngine()->isSeverityEnabled($severity);
}
public function getLintSeverityMap() {
return array();
}
public function getLintNameMap() {
return array();
}
public function getCacheGranularity() {
return self::GRANULARITY_FILE;
}
/**
* If this linter is selectable via `.arclint` configuration files, return
* a short, human-readable name to identify it. For example, `"jshint"` or
* `"pep8"`.
*
* If you do not implement this method, the linter will not be selectable
* through `.arclint` files.
*/
public function getLinterConfigurationName() {
return null;
}
public function getLinterConfigurationOptions() {
+ if (!$this->canCustomizeLintSeverities()) {
+ return array();
+ }
+
return array(
'severity' => 'optional map<string|int, string>',
'severity.rules' => 'optional map<string, string>',
);
}
public function setLinterConfigurationValue($key, $value) {
$sev_map = array(
'error' => ArcanistLintSeverity::SEVERITY_ERROR,
'warning' => ArcanistLintSeverity::SEVERITY_WARNING,
'autofix' => ArcanistLintSeverity::SEVERITY_AUTOFIX,
'advice' => ArcanistLintSeverity::SEVERITY_ADVICE,
'disabled' => ArcanistLintSeverity::SEVERITY_DISABLED,
);
switch ($key) {
case 'severity':
+ if (!$this->canCustomizeLintSeverities()) {
+ break;
+ }
+
$custom = array();
foreach ($value as $code => $severity) {
if (empty($sev_map[$severity])) {
$valid = implode(', ', array_keys($sev_map));
throw new Exception(
pht(
'Unknown lint severity "%s". Valid severities are: %s.',
$severity,
$valid));
}
$code = $this->getLintCodeFromLinterConfigurationKey($code);
$custom[$code] = $severity;
}
$this->setCustomSeverityMap($custom);
return;
+
case 'severity.rules':
+ if (!$this->canCustomizeLintSeverities()) {
+ break;
+ }
+
foreach ($value as $rule => $severity) {
if (@preg_match($rule, '') === false) {
throw new Exception(
pht(
'Severity rule "%s" is not a valid regular expression.',
$rule));
}
if (empty($sev_map[$severity])) {
$valid = implode(', ', array_keys($sev_map));
throw new Exception(
pht(
'Unknown lint severity "%s". Valid severities are: %s.',
$severity,
$valid));
}
}
$this->setCustomSeverityRules($value);
return;
}
throw new Exception("Incomplete implementation: {$key}!");
}
+ protected function canCustomizeLintSeverities() {
+ return true;
+ }
+
protected function shouldLintBinaryFiles() {
return false;
}
protected function shouldLintDeletedFiles() {
return false;
}
protected function shouldLintDirectories() {
return false;
}
/**
* Map a configuration lint code to an `arc` lint code. Primarily, this is
* intended for validation, but can also be used to normalize case or
* otherwise be more permissive in accepted inputs.
*
* If the code is not recognized, you should throw an exception.
*
* @param string Code specified in configuration.
* @return string Normalized code to use in severity map.
*/
protected function getLintCodeFromLinterConfigurationKey($code) {
return $code;
}
/**
* Retrieve an old lint configuration value from `.arcconfig` or a similar
* source.
*
* Modern linters should use @{method:getConfig} to read configuration from
* `.arclint`.
*
* @param string Configuration key to retrieve.
* @param wild Default value to return if key is not present in config.
* @return wild Configured value, or default if no configuration exists.
*/
protected function getDeprecatedConfiguration($key, $default = null) {
// If we're being called in a context without an engine (probably from
// `arc linters`), just return the default value.
if (!$this->engine) {
return $default;
}
$config = $this->getEngine()->getConfigurationManager();
// Construct a sentinel object so we can tell if we're reading config
// or not.
$sentinel = (object)array();
$result = $config->getConfigFromAnySource($key, $sentinel);
// If we read config, warn the user that this mechanism is deprecated and
// discouraged.
if ($result !== $sentinel) {
$console = PhutilConsole::getConsole();
$console->writeErr(
"**%s**: %s\n",
pht('Deprecation Warning'),
pht(
'Configuration option "%s" is deprecated. Generally, linters should '.
'now be configured using an `.arclint` file. See "Arcanist User '.
'Guide: Lint" in the documentation for more information.',
$key));
return $result;
}
return $default;
}
}
diff --git a/src/lint/linter/ArcanistNoLintLinter.php b/src/lint/linter/ArcanistNoLintLinter.php
index 67ef6922..e219103f 100644
--- a/src/lint/linter/ArcanistNoLintLinter.php
+++ b/src/lint/linter/ArcanistNoLintLinter.php
@@ -1,38 +1,42 @@
<?php
/**
* Stops other linters from running on code marked with a nolint annotation.
*/
final class ArcanistNoLintLinter extends ArcanistLinter {
public function getInfoName() {
return pht('Lint Disabler');
}
public function getInfoDescription() {
return pht(
'Allows you to disable all lint messages for a file by putting "%s" in '.
'the file body.',
'@'.'nolint');
}
public function getLinterName() {
return 'NOLINT';
}
public function getLinterPriority() {
return 0.25;
}
public function getLinterConfigurationName() {
return 'nolint';
}
+ protected function canCustomizeLintSeverities() {
+ return false;
+ }
+
public function lintPath($path) {
$data = $this->getData($path);
if (preg_match('/@'.'nolint/', $data)) {
$this->stopAllLinters();
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Oct 11, 11:44 (1 h, 25 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
984316
Default Alt Text
(18 KB)
Attached To
Mode
R118 Arcanist - fork
Attached
Detach File
Event Timeline
Log In to Comment