1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2025-01-10 14:51:05 +01:00
phorge-arcanist/src/lint/linter/ArcanistLinter.php

625 lines
15 KiB
PHP
Raw Normal View History

2011-01-10 00:22:25 +01:00
<?php
/**
* Implements lint rules, like syntax checks for a specific language.
*
* @task info Human Readable Information
Split large path lists into blocks when linting Summary: Fixes T5097. When linting a large list of paths (e.g., with `--everything`), do this internally: $chunks = array_chunk($paths, 32); foreach ($chunks as $chunk) { $this->lintChunk($chunk); } This keeps the advantages of parallelism and artifact sharing for future-based linters, without having memory usage grow in an unbounded way. These callbacks changed: - `willLintPath()`: Useless, no meaningful implementations. Internalized the required side effect and broke the hook. - `didRunLinters()`: Now useless, with no meaningful implementations. Broke the hook. - `didLintPaths()`: New hook which executes opposite `willLintPaths()`. - `lintPath()`: Linters no longer need to implement this method. XHPAST now has an explicit way to release shared futures. These minor changes also happened: - Formalized the "linter ID", which is a semi-durable identifier for the cache. - Removed linter -> exception explicit mapping, which was unused. We now just collect exceptions. - We do the `canRun()` checks first (and separately) now. - Share more service call profiling code. - Fix an issue where the test harness would use the path on disk, even if configuration set a different path. Test Plan: - Ran `arc lint --everything` in `arcanist/`. - With no chunking, saw **unstable** memory usage with a peak at 941 MB. - With chunk size 32, saw **stable** memory usage with a peak at 269 MB. - With chunk size 8, saw **stable** memory usage with a peak at 180 MB. - Ran with `--trace` and saw profiling information. - Created this diff. Reviewers: joshuaspence Reviewed By: joshuaspence Subscribers: epriestley Maniphest Tasks: T5097 Differential Revision: https://secure.phabricator.com/D12501
2015-04-22 14:15:57 +02:00
* @task state Runtime State
* @task exec Executing Linters
* @stable
*/
2011-01-10 00:22:25 +01:00
abstract class ArcanistLinter {
const GRANULARITY_FILE = 1;
const GRANULARITY_DIRECTORY = 2;
const GRANULARITY_REPOSITORY = 3;
const GRANULARITY_GLOBAL = 4;
Split large path lists into blocks when linting Summary: Fixes T5097. When linting a large list of paths (e.g., with `--everything`), do this internally: $chunks = array_chunk($paths, 32); foreach ($chunks as $chunk) { $this->lintChunk($chunk); } This keeps the advantages of parallelism and artifact sharing for future-based linters, without having memory usage grow in an unbounded way. These callbacks changed: - `willLintPath()`: Useless, no meaningful implementations. Internalized the required side effect and broke the hook. - `didRunLinters()`: Now useless, with no meaningful implementations. Broke the hook. - `didLintPaths()`: New hook which executes opposite `willLintPaths()`. - `lintPath()`: Linters no longer need to implement this method. XHPAST now has an explicit way to release shared futures. These minor changes also happened: - Formalized the "linter ID", which is a semi-durable identifier for the cache. - Removed linter -> exception explicit mapping, which was unused. We now just collect exceptions. - We do the `canRun()` checks first (and separately) now. - Share more service call profiling code. - Fix an issue where the test harness would use the path on disk, even if configuration set a different path. Test Plan: - Ran `arc lint --everything` in `arcanist/`. - With no chunking, saw **unstable** memory usage with a peak at 941 MB. - With chunk size 32, saw **stable** memory usage with a peak at 269 MB. - With chunk size 8, saw **stable** memory usage with a peak at 180 MB. - Ran with `--trace` and saw profiling information. - Created this diff. Reviewers: joshuaspence Reviewed By: joshuaspence Subscribers: epriestley Maniphest Tasks: T5097 Differential Revision: https://secure.phabricator.com/D12501
2015-04-22 14:15:57 +02:00
private $id;
protected $paths = array();
private $filteredPaths = null;
protected $data = array();
2011-01-10 00:22:25 +01:00
protected $engine;
protected $activePath;
protected $messages = array();
protected $stopAllLinters = false;
private $customSeverityMap = array();
private $customSeverityRules = array();
2011-01-10 00:22:25 +01:00
/* -( Human Readable Information )---------------------------------------- */
Split large path lists into blocks when linting Summary: Fixes T5097. When linting a large list of paths (e.g., with `--everything`), do this internally: $chunks = array_chunk($paths, 32); foreach ($chunks as $chunk) { $this->lintChunk($chunk); } This keeps the advantages of parallelism and artifact sharing for future-based linters, without having memory usage grow in an unbounded way. These callbacks changed: - `willLintPath()`: Useless, no meaningful implementations. Internalized the required side effect and broke the hook. - `didRunLinters()`: Now useless, with no meaningful implementations. Broke the hook. - `didLintPaths()`: New hook which executes opposite `willLintPaths()`. - `lintPath()`: Linters no longer need to implement this method. XHPAST now has an explicit way to release shared futures. These minor changes also happened: - Formalized the "linter ID", which is a semi-durable identifier for the cache. - Removed linter -> exception explicit mapping, which was unused. We now just collect exceptions. - We do the `canRun()` checks first (and separately) now. - Share more service call profiling code. - Fix an issue where the test harness would use the path on disk, even if configuration set a different path. Test Plan: - Ran `arc lint --everything` in `arcanist/`. - With no chunking, saw **unstable** memory usage with a peak at 941 MB. - With chunk size 32, saw **stable** memory usage with a peak at 269 MB. - With chunk size 8, saw **stable** memory usage with a peak at 180 MB. - Ran with `--trace` and saw profiling information. - Created this diff. Reviewers: joshuaspence Reviewed By: joshuaspence Subscribers: epriestley Maniphest Tasks: T5097 Differential Revision: https://secure.phabricator.com/D12501
2015-04-22 14:15:57 +02:00
/**
* 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));
}
Split large path lists into blocks when linting Summary: Fixes T5097. When linting a large list of paths (e.g., with `--everything`), do this internally: $chunks = array_chunk($paths, 32); foreach ($chunks as $chunk) { $this->lintChunk($chunk); } This keeps the advantages of parallelism and artifact sharing for future-based linters, without having memory usage grow in an unbounded way. These callbacks changed: - `willLintPath()`: Useless, no meaningful implementations. Internalized the required side effect and broke the hook. - `didRunLinters()`: Now useless, with no meaningful implementations. Broke the hook. - `didLintPaths()`: New hook which executes opposite `willLintPaths()`. - `lintPath()`: Linters no longer need to implement this method. XHPAST now has an explicit way to release shared futures. These minor changes also happened: - Formalized the "linter ID", which is a semi-durable identifier for the cache. - Removed linter -> exception explicit mapping, which was unused. We now just collect exceptions. - We do the `canRun()` checks first (and separately) now. - Share more service call profiling code. - Fix an issue where the test harness would use the path on disk, even if configuration set a different path. Test Plan: - Ran `arc lint --everything` in `arcanist/`. - With no chunking, saw **unstable** memory usage with a peak at 941 MB. - With chunk size 32, saw **stable** memory usage with a peak at 269 MB. - With chunk size 8, saw **stable** memory usage with a peak at 180 MB. - Ran with `--trace` and saw profiling information. - Created this diff. Reviewers: joshuaspence Reviewed By: joshuaspence Subscribers: epriestley Maniphest Tasks: T5097 Differential Revision: https://secure.phabricator.com/D12501
2015-04-22 14:15:57 +02:00
/* -( Runtime State )------------------------------------------------------ */
/**
* @task state
*/
final public function getActivePath() {
return $this->activePath;
}
/**
* @task state
*/
final public function setActivePath($path) {
$this->stopAllLinters = false;
$this->activePath = $path;
return $this;
}
/**
* @task state
*/
final public function setEngine(ArcanistLintEngine $engine) {
$this->engine = $engine;
return $this;
}
/**
* @task state
*/
final protected function getEngine() {
return $this->engine;
}
/**
* Set the internal ID for this linter.
*
* This ID is assigned automatically by the @{class:ArcanistLintEngine}.
*
* @param string Unique linter ID.
* @return this
* @task state
*/
final public function setLinterID($id) {
$this->id = $id;
return $this;
}
/**
* Get the internal ID for this linter.
*
* Retrieves an internal linter ID managed by the @{class:ArcanistLintEngine}.
* This ID is a unique scalar which distinguishes linters in a list.
*
* @return string Unique linter ID.
* @task state
*/
final public function getLinterID() {
return $this->id;
}
/* -( Executing Linters )-------------------------------------------------- */
/**
* Hook called before a list of paths are linted.
*
* Parallelizable linters can start multiple requests in parallel here,
* to improve performance. They can implement @{method:didLintPaths} to
* collect results.
*
* Linters which are not parallelizable should normally ignore this callback
* and implement @{method:lintPath} instead.
*
* @param list<string> A list of paths to be linted
* @return void
* @task exec
*/
public function willLintPaths(array $paths) {
return;
}
/**
* Hook called for each path to be linted.
*
* Linters which are not parallelizable can do work here.
*
* Linters which are parallelizable may want to ignore this callback and
* implement @{method:willLintPaths} and @{method:didLintPaths} instead.
*
* @param string Path to lint.
* @return void
* @task exec
*/
public function lintPath($path) {
return;
}
/**
* Hook called after a list of paths are linted.
*
* Parallelizable linters can collect results here.
*
* Linters which are not paralleizable should normally ignore this callback
* and implement @{method:lintPath} instead.
*
* @param list<string> A list of paths which were linted.
* @return void
* @task exec
*/
public function didLintPaths(array $paths) {
return;
}
/**
* Obsolete hook which was invoked before a path was linted.
*
* WARNING: This is an obsolete hook which is not called. If you maintain
* a linter which relies on it, update to use @{method:lintPath} instead.
*
* @task exec
*/
final public function willLintPath($path) {
// TODO: Remove this method after some time. In the meantime, the "final"
// will fatal subclasses which implement this hook and point at the API
// change so maintainers get fewer surprises.
throw new PhutilMethodNotImplementedException();
}
/**
* Obsolete hook which was invoked after linters ran.
*
* WARNING: This is an obsolete hook which is not called. If you maintain
* a linter which relies on it, update to use @{method:didLintPaths} instead.
*
* @return void
* @task exec
*/
final public function didRunLinters() {
// TODO: Remove this method after some time. In the meantime, the "final"
// will fatal subclasses which implement this hook and point at the API
// change so maintainers get fewer surprises.
throw new PhutilMethodNotImplementedException();
}
Ready more linters and linter functions for .arclint Summary: Ref T3186. Ref T2039. Continues work on readying linters for `.arclint`. - **Ruby**: Make this an ExternalLinter. - **Priority**: Currently, linters have an implicit "correct" order (notably, the "NoLint" linter needs to run before other linters). Make this explicit by introducing `getLinterPriority()`. - **Binaries**: Currently, linters manually reject binary files. Instead, reject binary files by default (linters can override this if they do want to lint binary files). - **Deleted Files**: Currently, linters manually reject deleted files (usually in engines). Instead, reject deleted files by default (linters can override this). - **Severity**: Move this `.arclint` config option up to top level. - **willLintPaths()**: This method is abstract, but almost all linters provide a trivial implementation. Provide a trivial implementation in the base class. - **getLintSeverityMap()/getLintNameMap()**: A bunch of linters have empty implementations; these are redundant. Remove them. - **Spelling**: clean up some dead / test-only / unconventional code. - **`.arclint`**: Allow the filename, generated, nolint, text, spelling and ruby linters to be configured via `.arclint`. Test Plan: https://github.com/epriestley/arclint-examples/commit/458beca3d65b64d52ed612904ae66eb837118b94 Ran unit tests. Reviewers: btrahan Reviewed By: btrahan CC: Firehed, aran Maniphest Tasks: T2039, T3186 Differential Revision: https://secure.phabricator.com/D6805
2013-08-26 14:37:10 +02:00
public function getLinterPriority() {
return 1.0;
}
/**
* TODO: This should be `final`.
*/
2011-01-10 00:22:25 +01:00
public function setCustomSeverityMap(array $map) {
$this->customSeverityMap = $map;
return $this;
}
final public function setCustomSeverityRules(array $rules) {
$this->customSeverityRules = $rules;
return $this;
}
final public function getProjectRoot() {
$engine = $this->getEngine();
if (!$engine) {
throw new Exception(
pht(
'You must call %s before you can call %s.',
'setEngine()',
__FUNCTION__.'()'));
}
$working_copy = $engine->getWorkingCopy();
if (!$working_copy) {
return null;
}
return $working_copy->getProjectRoot();
}
final 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,
);
}
final public function stopAllLinters() {
2011-01-10 00:22:25 +01:00
$this->stopAllLinters = true;
return $this;
}
final public function didStopAllLinters() {
2011-01-10 00:22:25 +01:00
return $this->stopAllLinters;
}
final public function addPath($path) {
2011-01-10 00:22:25 +01:00
$this->paths[$path] = $path;
$this->filteredPaths = null;
2011-01-10 00:22:25 +01:00
return $this;
}
final public function setPaths(array $paths) {
$this->paths = $paths;
$this->filteredPaths = null;
return $this;
}
Ready more linters and linter functions for .arclint Summary: Ref T3186. Ref T2039. Continues work on readying linters for `.arclint`. - **Ruby**: Make this an ExternalLinter. - **Priority**: Currently, linters have an implicit "correct" order (notably, the "NoLint" linter needs to run before other linters). Make this explicit by introducing `getLinterPriority()`. - **Binaries**: Currently, linters manually reject binary files. Instead, reject binary files by default (linters can override this if they do want to lint binary files). - **Deleted Files**: Currently, linters manually reject deleted files (usually in engines). Instead, reject deleted files by default (linters can override this). - **Severity**: Move this `.arclint` config option up to top level. - **willLintPaths()**: This method is abstract, but almost all linters provide a trivial implementation. Provide a trivial implementation in the base class. - **getLintSeverityMap()/getLintNameMap()**: A bunch of linters have empty implementations; these are redundant. Remove them. - **Spelling**: clean up some dead / test-only / unconventional code. - **`.arclint`**: Allow the filename, generated, nolint, text, spelling and ruby linters to be configured via `.arclint`. Test Plan: https://github.com/epriestley/arclint-examples/commit/458beca3d65b64d52ed612904ae66eb837118b94 Ran unit tests. Reviewers: btrahan Reviewed By: btrahan CC: Firehed, aran Maniphest Tasks: T2039, T3186 Differential Revision: https://secure.phabricator.com/D6805
2013-08-26 14:37:10 +02:00
/**
* Filter out paths which this linter doesn't act on (for example, because
* they are binaries and the linter doesn't apply to binaries).
*
* @param list<string>
* @return list<string>
Ready more linters and linter functions for .arclint Summary: Ref T3186. Ref T2039. Continues work on readying linters for `.arclint`. - **Ruby**: Make this an ExternalLinter. - **Priority**: Currently, linters have an implicit "correct" order (notably, the "NoLint" linter needs to run before other linters). Make this explicit by introducing `getLinterPriority()`. - **Binaries**: Currently, linters manually reject binary files. Instead, reject binary files by default (linters can override this if they do want to lint binary files). - **Deleted Files**: Currently, linters manually reject deleted files (usually in engines). Instead, reject deleted files by default (linters can override this). - **Severity**: Move this `.arclint` config option up to top level. - **willLintPaths()**: This method is abstract, but almost all linters provide a trivial implementation. Provide a trivial implementation in the base class. - **getLintSeverityMap()/getLintNameMap()**: A bunch of linters have empty implementations; these are redundant. Remove them. - **Spelling**: clean up some dead / test-only / unconventional code. - **`.arclint`**: Allow the filename, generated, nolint, text, spelling and ruby linters to be configured via `.arclint`. Test Plan: https://github.com/epriestley/arclint-examples/commit/458beca3d65b64d52ed612904ae66eb837118b94 Ran unit tests. Reviewers: btrahan Reviewed By: btrahan CC: Firehed, aran Maniphest Tasks: T2039, T3186 Differential Revision: https://secure.phabricator.com/D6805
2013-08-26 14:37:10 +02:00
*/
final private function filterPaths(array $paths) {
Ready more linters and linter functions for .arclint Summary: Ref T3186. Ref T2039. Continues work on readying linters for `.arclint`. - **Ruby**: Make this an ExternalLinter. - **Priority**: Currently, linters have an implicit "correct" order (notably, the "NoLint" linter needs to run before other linters). Make this explicit by introducing `getLinterPriority()`. - **Binaries**: Currently, linters manually reject binary files. Instead, reject binary files by default (linters can override this if they do want to lint binary files). - **Deleted Files**: Currently, linters manually reject deleted files (usually in engines). Instead, reject deleted files by default (linters can override this). - **Severity**: Move this `.arclint` config option up to top level. - **willLintPaths()**: This method is abstract, but almost all linters provide a trivial implementation. Provide a trivial implementation in the base class. - **getLintSeverityMap()/getLintNameMap()**: A bunch of linters have empty implementations; these are redundant. Remove them. - **Spelling**: clean up some dead / test-only / unconventional code. - **`.arclint`**: Allow the filename, generated, nolint, text, spelling and ruby linters to be configured via `.arclint`. Test Plan: https://github.com/epriestley/arclint-examples/commit/458beca3d65b64d52ed612904ae66eb837118b94 Ran unit tests. Reviewers: btrahan Reviewed By: btrahan CC: Firehed, aran Maniphest Tasks: T2039, T3186 Differential Revision: https://secure.phabricator.com/D6805
2013-08-26 14:37:10 +02:00
$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)) {
Ready more linters and linter functions for .arclint Summary: Ref T3186. Ref T2039. Continues work on readying linters for `.arclint`. - **Ruby**: Make this an ExternalLinter. - **Priority**: Currently, linters have an implicit "correct" order (notably, the "NoLint" linter needs to run before other linters). Make this explicit by introducing `getLinterPriority()`. - **Binaries**: Currently, linters manually reject binary files. Instead, reject binary files by default (linters can override this if they do want to lint binary files). - **Deleted Files**: Currently, linters manually reject deleted files (usually in engines). Instead, reject deleted files by default (linters can override this). - **Severity**: Move this `.arclint` config option up to top level. - **willLintPaths()**: This method is abstract, but almost all linters provide a trivial implementation. Provide a trivial implementation in the base class. - **getLintSeverityMap()/getLintNameMap()**: A bunch of linters have empty implementations; these are redundant. Remove them. - **Spelling**: clean up some dead / test-only / unconventional code. - **`.arclint`**: Allow the filename, generated, nolint, text, spelling and ruby linters to be configured via `.arclint`. Test Plan: https://github.com/epriestley/arclint-examples/commit/458beca3d65b64d52ed612904ae66eb837118b94 Ran unit tests. Reviewers: btrahan Reviewed By: btrahan CC: Firehed, aran Maniphest Tasks: T2039, T3186 Differential Revision: https://secure.phabricator.com/D6805
2013-08-26 14:37:10 +02:00
continue;
}
if (!$this->shouldLintSymbolicLinks() && $engine->isSymbolicLink($path)) {
continue;
}
Ready more linters and linter functions for .arclint Summary: Ref T3186. Ref T2039. Continues work on readying linters for `.arclint`. - **Ruby**: Make this an ExternalLinter. - **Priority**: Currently, linters have an implicit "correct" order (notably, the "NoLint" linter needs to run before other linters). Make this explicit by introducing `getLinterPriority()`. - **Binaries**: Currently, linters manually reject binary files. Instead, reject binary files by default (linters can override this if they do want to lint binary files). - **Deleted Files**: Currently, linters manually reject deleted files (usually in engines). Instead, reject deleted files by default (linters can override this). - **Severity**: Move this `.arclint` config option up to top level. - **willLintPaths()**: This method is abstract, but almost all linters provide a trivial implementation. Provide a trivial implementation in the base class. - **getLintSeverityMap()/getLintNameMap()**: A bunch of linters have empty implementations; these are redundant. Remove them. - **Spelling**: clean up some dead / test-only / unconventional code. - **`.arclint`**: Allow the filename, generated, nolint, text, spelling and ruby linters to be configured via `.arclint`. Test Plan: https://github.com/epriestley/arclint-examples/commit/458beca3d65b64d52ed612904ae66eb837118b94 Ran unit tests. Reviewers: btrahan Reviewed By: btrahan CC: Firehed, aran Maniphest Tasks: T2039, T3186 Differential Revision: https://secure.phabricator.com/D6805
2013-08-26 14:37:10 +02:00
$keep[] = $path;
}
return $keep;
}
final public function getPaths() {
if ($this->filteredPaths === null) {
$this->filteredPaths = $this->filterPaths(array_values($this->paths));
}
return $this->filteredPaths;
2011-01-10 00:22:25 +01:00
}
final public function addData($path, $data) {
2011-01-10 00:22:25 +01:00
$this->data[$path] = $data;
return $this;
}
final protected function getData($path) {
2011-01-10 00:22:25 +01:00
if (!array_key_exists($path, $this->data)) {
$this->data[$path] = $this->getEngine()->loadData($path);
2011-01-10 00:22:25 +01:00
}
return $this->data[$path];
}
public function getCacheVersion() {
return 0;
}
Split large path lists into blocks when linting Summary: Fixes T5097. When linting a large list of paths (e.g., with `--everything`), do this internally: $chunks = array_chunk($paths, 32); foreach ($chunks as $chunk) { $this->lintChunk($chunk); } This keeps the advantages of parallelism and artifact sharing for future-based linters, without having memory usage grow in an unbounded way. These callbacks changed: - `willLintPath()`: Useless, no meaningful implementations. Internalized the required side effect and broke the hook. - `didRunLinters()`: Now useless, with no meaningful implementations. Broke the hook. - `didLintPaths()`: New hook which executes opposite `willLintPaths()`. - `lintPath()`: Linters no longer need to implement this method. XHPAST now has an explicit way to release shared futures. These minor changes also happened: - Formalized the "linter ID", which is a semi-durable identifier for the cache. - Removed linter -> exception explicit mapping, which was unused. We now just collect exceptions. - We do the `canRun()` checks first (and separately) now. - Share more service call profiling code. - Fix an issue where the test harness would use the path on disk, even if configuration set a different path. Test Plan: - Ran `arc lint --everything` in `arcanist/`. - With no chunking, saw **unstable** memory usage with a peak at 941 MB. - With chunk size 32, saw **stable** memory usage with a peak at 269 MB. - With chunk size 8, saw **stable** memory usage with a peak at 180 MB. - Ran with `--trace` and saw profiling information. - Created this diff. Reviewers: joshuaspence Reviewed By: joshuaspence Subscribers: epriestley Maniphest Tasks: T5097 Differential Revision: https://secure.phabricator.com/D12501
2015-04-22 14:15:57 +02:00
final public function getLintMessageFullCode($short_code) {
2011-01-10 00:22:25 +01:00
return $this->getLinterName().$short_code;
}
final public function getLintMessageSeverity($code) {
2011-01-10 00:22:25 +01:00
$map = $this->customSeverityMap;
if (isset($map[$code])) {
return $map[$code];
}
foreach ($this->customSeverityRules as $rule => $severity) {
if (preg_match($rule, $code)) {
return $severity;
}
}
$map = $this->getLintSeverityMap();
if (isset($map[$code])) {
return $map[$code];
}
return $this->getDefaultMessageSeverity($code);
}
protected function getDefaultMessageSeverity($code) {
2011-01-10 00:22:25 +01:00
return ArcanistLintSeverity::SEVERITY_ERROR;
}
final public function isMessageEnabled($code) {
return ($this->getLintMessageSeverity($code) !==
ArcanistLintSeverity::SEVERITY_DISABLED);
}
final public function getLintMessageName($code) {
2011-01-10 00:22:25 +01:00
$map = $this->getLintNameMap();
if (isset($map[$code])) {
return $map[$code];
}
return pht('Unknown lint message!');
2011-01-10 00:22:25 +01:00
}
final protected function addLintMessage(ArcanistLintMessage $message) {
$root = $this->getProjectRoot();
$path = Filesystem::resolvePath($message->getPath(), $root);
$message->setPath(Filesystem::readablePath($path, $root));
2011-01-10 00:22:25 +01:00
$this->messages[] = $message;
return $message;
}
final public function getLintMessages() {
2011-01-10 00:22:25 +01:00
return $this->messages;
}
final protected function raiseLintAtLine(
2011-01-10 00:22:25 +01:00
$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);
2011-01-10 00:22:25 +01:00
return $this->addLintMessage($message);
2011-01-10 00:22:25 +01:00
}
final protected function raiseLintAtPath($code, $desc) {
2011-01-10 00:22:25 +01:00
return $this->raiseLintAtLine(null, null, $code, $desc, null, null);
}
final protected function raiseLintAtOffset(
2011-01-10 00:22:25 +01:00
$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);
}
2011-01-10 00:22:25 +01:00
return $this->raiseLintAtLine(
$line + 1,
$char + 1,
$code,
$desc,
$original,
$replacement);
}
public function canRun() {
return true;
}
2011-01-10 00:22:25 +01:00
abstract public function getLinterName();
Add "ArcanistSingleLintEngine" and "ArcanistScriptAndRegexLinter" Summary: Request from @csilvers. Allow installs to get most linter features with regexes, configuration and external scripts if they are hesitant to write phutil libraries. - Add "ArcanistSingleLintEngine", which implements the smallest possible engine behavior (run one linter on every path). - Add "ArcanistScriptAndRegexLinter", which uses a script and a regex to parse lint output from other scripts. Depends on D2618. Test Plan: Basics: $ arc set-config lint.engine ArcanistSingleLintEngine Set key 'lint.engine' = 'ArcanistSingleLintEngine'. $ arc set-config lint.engine.single.linter ArcanistScriptAndRegexLinter Set key 'lint.engine.single.linter' = 'ArcanistScriptAndRegexLinter'. $ arc set-config linter.scriptandregex.script 'echo derp #' Set key 'linter.scriptandregex.script' = 'echo derp #'. $ arc set-config linter.scriptandregex.regex '/^(?P<message>.*)$/m' Set key 'linter.scriptandregex.regex' = '/^(?P<message>.*)$/m'. $ arc lint >>> Lint for .arcconfig: Error (S&RX) Lint derp 1 { 2 "project_id" : "arcanist", 3 "conduit_uri" : "https://secure.phabricator.com/", Throw: $ arc set-config linter.scriptandregex.regex '/^(?P<throw>.*)$/m' Set key 'linter.scriptandregex.regex' = '/^(?P<throw>.*)$/m' (was '/^(?P<message>.*)$/m'). $ arc lint Usage Exception: ArcanistScriptAndRegexLinter: configuration captured a 'throw' named capturing group, 'derp'. Script output: derp Ignore: $ arc set-config linter.scriptandregex.regex '/^(?P<ignore>.*)$/m' Set key 'linter.scriptandregex.regex' = '/^(?P<ignore>.*)$/m' (was '/^(?P<throw>.*)$/m'). $ arc lint OKAY No lint warnings. Severity: $ arc set-config linter.scriptandregex.regex '/^(?P<warning>.)(?P<message>.*)$/m' Set key 'linter.scriptandregex.regex' = '/^(?P<warning>.)(?P<message>.*)$/m' (was '/^(?P<ignore>.*)$/m'). $ arc lint >>> Lint for src/lint/engine/single/ArcanistSingleLintEngine.php: Warning (S&RX) Lint erp 1 <?php 2 3 /* Reviewers: csilvers, btrahan, vrana Reviewed By: csilvers CC: aran Differential Revision: https://secure.phabricator.com/D2619
2012-05-31 21:09:01 +02:00
public function getVersion() {
return null;
}
final protected function isCodeEnabled($code) {
$severity = $this->getLintMessageSeverity($code);
return $this->getEngine()->isSeverityEnabled($severity);
}
Add "ArcanistSingleLintEngine" and "ArcanistScriptAndRegexLinter" Summary: Request from @csilvers. Allow installs to get most linter features with regexes, configuration and external scripts if they are hesitant to write phutil libraries. - Add "ArcanistSingleLintEngine", which implements the smallest possible engine behavior (run one linter on every path). - Add "ArcanistScriptAndRegexLinter", which uses a script and a regex to parse lint output from other scripts. Depends on D2618. Test Plan: Basics: $ arc set-config lint.engine ArcanistSingleLintEngine Set key 'lint.engine' = 'ArcanistSingleLintEngine'. $ arc set-config lint.engine.single.linter ArcanistScriptAndRegexLinter Set key 'lint.engine.single.linter' = 'ArcanistScriptAndRegexLinter'. $ arc set-config linter.scriptandregex.script 'echo derp #' Set key 'linter.scriptandregex.script' = 'echo derp #'. $ arc set-config linter.scriptandregex.regex '/^(?P<message>.*)$/m' Set key 'linter.scriptandregex.regex' = '/^(?P<message>.*)$/m'. $ arc lint >>> Lint for .arcconfig: Error (S&RX) Lint derp 1 { 2 "project_id" : "arcanist", 3 "conduit_uri" : "https://secure.phabricator.com/", Throw: $ arc set-config linter.scriptandregex.regex '/^(?P<throw>.*)$/m' Set key 'linter.scriptandregex.regex' = '/^(?P<throw>.*)$/m' (was '/^(?P<message>.*)$/m'). $ arc lint Usage Exception: ArcanistScriptAndRegexLinter: configuration captured a 'throw' named capturing group, 'derp'. Script output: derp Ignore: $ arc set-config linter.scriptandregex.regex '/^(?P<ignore>.*)$/m' Set key 'linter.scriptandregex.regex' = '/^(?P<ignore>.*)$/m' (was '/^(?P<throw>.*)$/m'). $ arc lint OKAY No lint warnings. Severity: $ arc set-config linter.scriptandregex.regex '/^(?P<warning>.)(?P<message>.*)$/m' Set key 'linter.scriptandregex.regex' = '/^(?P<warning>.)(?P<message>.*)$/m' (was '/^(?P<ignore>.*)$/m'). $ arc lint >>> Lint for src/lint/engine/single/ArcanistSingleLintEngine.php: Warning (S&RX) Lint erp 1 <?php 2 3 /* Reviewers: csilvers, btrahan, vrana Reviewed By: csilvers CC: aran Differential Revision: https://secure.phabricator.com/D2619
2012-05-31 21:09:01 +02:00
public function getLintSeverityMap() {
return array();
}
public function getLintNameMap() {
return array();
}
2011-01-10 00:22:25 +01:00
public function getCacheGranularity() {
return self::GRANULARITY_FILE;
}
Lay groundwork for configuration-driven linters Summary: Ref T2039. That task has a bunch of discussion, but basically we do a poor job of serving the midrange of lint configuration right now. If you have something simple, the default linters work. If you have something complex, building your own engine lets you do whatever you want. But many users want something in between, which isn't really well accommodated. The idea is to let you write a `.arclint` file, which looks something like this: { "linters" : { "css" : { "type" : "csslint", "include" : "(\.css$)", "exclude" : "(^externals/)", "bin" : "/usr/local/bin/csslint" }, "js" : { "type" : "jshint", "include" : "(\.js$)", "exclude" : "(^externals/)", "bin" : "support/bin/jshint", "interpreter" : "/usr/local/bin/node" } } } ...which will provide a bunch of common options around lint severity, interpreter and binary locaitons, included and excluded files, etc. This implements some basics, and very rough support in the Filename linter. Test Plan: Generated a `.arclint` file and saw it apply filename lint correctly. Used `debug` mode and tried invalid regexps. { "debug" : true, "linters" : { "filename" : { "type" : "filename", "exclude" : ["@^externals/@"] } } } Next steps include: - Provide an external linter archetype (T3186) and expose a common set of configuration here ("bin", "interpreter", "flags", "severity"). - Provide a `.arcunit` file which works similarly (it can probably be simpler). Reviewers: btrahan, Firehed Reviewed By: btrahan CC: aran Maniphest Tasks: T2039 Differential Revision: https://secure.phabricator.com/D6797
2013-08-23 01:02:16 +02:00
/**
* 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();
}
Ready more linters and linter functions for .arclint Summary: Ref T3186. Ref T2039. Continues work on readying linters for `.arclint`. - **Ruby**: Make this an ExternalLinter. - **Priority**: Currently, linters have an implicit "correct" order (notably, the "NoLint" linter needs to run before other linters). Make this explicit by introducing `getLinterPriority()`. - **Binaries**: Currently, linters manually reject binary files. Instead, reject binary files by default (linters can override this if they do want to lint binary files). - **Deleted Files**: Currently, linters manually reject deleted files (usually in engines). Instead, reject deleted files by default (linters can override this). - **Severity**: Move this `.arclint` config option up to top level. - **willLintPaths()**: This method is abstract, but almost all linters provide a trivial implementation. Provide a trivial implementation in the base class. - **getLintSeverityMap()/getLintNameMap()**: A bunch of linters have empty implementations; these are redundant. Remove them. - **Spelling**: clean up some dead / test-only / unconventional code. - **`.arclint`**: Allow the filename, generated, nolint, text, spelling and ruby linters to be configured via `.arclint`. Test Plan: https://github.com/epriestley/arclint-examples/commit/458beca3d65b64d52ed612904ae66eb837118b94 Ran unit tests. Reviewers: btrahan Reviewed By: btrahan CC: Firehed, aran Maniphest Tasks: T2039, T3186 Differential Revision: https://secure.phabricator.com/D6805
2013-08-26 14:37:10 +02:00
return array(
'severity' => array(
'type' => 'optional map<string|int, string>',
'help' => pht(
'Provide a map from lint codes to adjusted severity levels: error, '.
'warning, advice, autofix or disabled.'),
),
'severity.rules' => array(
'type' => 'optional map<string, string>',
'help' => pht(
'Provide a map of regular expressions to severity levels. All '.
'matching codes have their severity adjusted.'),
),
Ready more linters and linter functions for .arclint Summary: Ref T3186. Ref T2039. Continues work on readying linters for `.arclint`. - **Ruby**: Make this an ExternalLinter. - **Priority**: Currently, linters have an implicit "correct" order (notably, the "NoLint" linter needs to run before other linters). Make this explicit by introducing `getLinterPriority()`. - **Binaries**: Currently, linters manually reject binary files. Instead, reject binary files by default (linters can override this if they do want to lint binary files). - **Deleted Files**: Currently, linters manually reject deleted files (usually in engines). Instead, reject deleted files by default (linters can override this). - **Severity**: Move this `.arclint` config option up to top level. - **willLintPaths()**: This method is abstract, but almost all linters provide a trivial implementation. Provide a trivial implementation in the base class. - **getLintSeverityMap()/getLintNameMap()**: A bunch of linters have empty implementations; these are redundant. Remove them. - **Spelling**: clean up some dead / test-only / unconventional code. - **`.arclint`**: Allow the filename, generated, nolint, text, spelling and ruby linters to be configured via `.arclint`. Test Plan: https://github.com/epriestley/arclint-examples/commit/458beca3d65b64d52ed612904ae66eb837118b94 Ran unit tests. Reviewers: btrahan Reviewed By: btrahan CC: Firehed, aran Maniphest Tasks: T2039, T3186 Differential Revision: https://secure.phabricator.com/D6805
2013-08-26 14:37:10 +02:00
);
}
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,
);
Ready more linters and linter functions for .arclint Summary: Ref T3186. Ref T2039. Continues work on readying linters for `.arclint`. - **Ruby**: Make this an ExternalLinter. - **Priority**: Currently, linters have an implicit "correct" order (notably, the "NoLint" linter needs to run before other linters). Make this explicit by introducing `getLinterPriority()`. - **Binaries**: Currently, linters manually reject binary files. Instead, reject binary files by default (linters can override this if they do want to lint binary files). - **Deleted Files**: Currently, linters manually reject deleted files (usually in engines). Instead, reject deleted files by default (linters can override this). - **Severity**: Move this `.arclint` config option up to top level. - **willLintPaths()**: This method is abstract, but almost all linters provide a trivial implementation. Provide a trivial implementation in the base class. - **getLintSeverityMap()/getLintNameMap()**: A bunch of linters have empty implementations; these are redundant. Remove them. - **Spelling**: clean up some dead / test-only / unconventional code. - **`.arclint`**: Allow the filename, generated, nolint, text, spelling and ruby linters to be configured via `.arclint`. Test Plan: https://github.com/epriestley/arclint-examples/commit/458beca3d65b64d52ed612904ae66eb837118b94 Ran unit tests. Reviewers: btrahan Reviewed By: btrahan CC: Firehed, aran Maniphest Tasks: T2039, T3186 Differential Revision: https://secure.phabricator.com/D6805
2013-08-26 14:37:10 +02:00
switch ($key) {
case 'severity':
if (!$this->canCustomizeLintSeverities()) {
break;
}
Ready more linters and linter functions for .arclint Summary: Ref T3186. Ref T2039. Continues work on readying linters for `.arclint`. - **Ruby**: Make this an ExternalLinter. - **Priority**: Currently, linters have an implicit "correct" order (notably, the "NoLint" linter needs to run before other linters). Make this explicit by introducing `getLinterPriority()`. - **Binaries**: Currently, linters manually reject binary files. Instead, reject binary files by default (linters can override this if they do want to lint binary files). - **Deleted Files**: Currently, linters manually reject deleted files (usually in engines). Instead, reject deleted files by default (linters can override this). - **Severity**: Move this `.arclint` config option up to top level. - **willLintPaths()**: This method is abstract, but almost all linters provide a trivial implementation. Provide a trivial implementation in the base class. - **getLintSeverityMap()/getLintNameMap()**: A bunch of linters have empty implementations; these are redundant. Remove them. - **Spelling**: clean up some dead / test-only / unconventional code. - **`.arclint`**: Allow the filename, generated, nolint, text, spelling and ruby linters to be configured via `.arclint`. Test Plan: https://github.com/epriestley/arclint-examples/commit/458beca3d65b64d52ed612904ae66eb837118b94 Ran unit tests. Reviewers: btrahan Reviewed By: btrahan CC: Firehed, aran Maniphest Tasks: T2039, T3186 Differential Revision: https://secure.phabricator.com/D6805
2013-08-26 14:37:10 +02:00
$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;
Ready more linters and linter functions for .arclint Summary: Ref T3186. Ref T2039. Continues work on readying linters for `.arclint`. - **Ruby**: Make this an ExternalLinter. - **Priority**: Currently, linters have an implicit "correct" order (notably, the "NoLint" linter needs to run before other linters). Make this explicit by introducing `getLinterPriority()`. - **Binaries**: Currently, linters manually reject binary files. Instead, reject binary files by default (linters can override this if they do want to lint binary files). - **Deleted Files**: Currently, linters manually reject deleted files (usually in engines). Instead, reject deleted files by default (linters can override this). - **Severity**: Move this `.arclint` config option up to top level. - **willLintPaths()**: This method is abstract, but almost all linters provide a trivial implementation. Provide a trivial implementation in the base class. - **getLintSeverityMap()/getLintNameMap()**: A bunch of linters have empty implementations; these are redundant. Remove them. - **Spelling**: clean up some dead / test-only / unconventional code. - **`.arclint`**: Allow the filename, generated, nolint, text, spelling and ruby linters to be configured via `.arclint`. Test Plan: https://github.com/epriestley/arclint-examples/commit/458beca3d65b64d52ed612904ae66eb837118b94 Ran unit tests. Reviewers: btrahan Reviewed By: btrahan CC: Firehed, aran Maniphest Tasks: T2039, T3186 Differential Revision: https://secure.phabricator.com/D6805
2013-08-26 14:37:10 +02:00
}
throw new Exception(pht('Incomplete implementation: %s!', $key));
}
protected function canCustomizeLintSeverities() {
return true;
}
Ready more linters and linter functions for .arclint Summary: Ref T3186. Ref T2039. Continues work on readying linters for `.arclint`. - **Ruby**: Make this an ExternalLinter. - **Priority**: Currently, linters have an implicit "correct" order (notably, the "NoLint" linter needs to run before other linters). Make this explicit by introducing `getLinterPriority()`. - **Binaries**: Currently, linters manually reject binary files. Instead, reject binary files by default (linters can override this if they do want to lint binary files). - **Deleted Files**: Currently, linters manually reject deleted files (usually in engines). Instead, reject deleted files by default (linters can override this). - **Severity**: Move this `.arclint` config option up to top level. - **willLintPaths()**: This method is abstract, but almost all linters provide a trivial implementation. Provide a trivial implementation in the base class. - **getLintSeverityMap()/getLintNameMap()**: A bunch of linters have empty implementations; these are redundant. Remove them. - **Spelling**: clean up some dead / test-only / unconventional code. - **`.arclint`**: Allow the filename, generated, nolint, text, spelling and ruby linters to be configured via `.arclint`. Test Plan: https://github.com/epriestley/arclint-examples/commit/458beca3d65b64d52ed612904ae66eb837118b94 Ran unit tests. Reviewers: btrahan Reviewed By: btrahan CC: Firehed, aran Maniphest Tasks: T2039, T3186 Differential Revision: https://secure.phabricator.com/D6805
2013-08-26 14:37:10 +02:00
protected function shouldLintBinaryFiles() {
return false;
}
protected function shouldLintDeletedFiles() {
return false;
}
protected function shouldLintDirectories() {
return false;
}
protected function shouldLintSymbolicLinks() {
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;
}
2011-01-10 00:22:25 +01:00
}