mirror of
https://we.phorge.it/source/arcanist.git
synced 2025-01-10 14:51:05 +01:00
92a5803d30
Summary: Add linting capability for detecting files which contain syntax introduced by unresolved merge conflicts. The detection is file-type-agnostic (the only requirement is that the file is a text file). Test Plan: Tested in three ways. The first way is to add all three forms of syntax to a file to indicate a merge conflict. HPHP will pick this up as a syntax error before this linter reaches it. The second way is to add the syntax in a comment. In that case, this linter will show three warnings. For example: $ arc lint ArcanistMergeConflictLinter.php >>> Lint for arcanist/src/lint/linter/ArcanistMergeConflictLinter.php: Warning (MERGECONFLICT1) Unresolved merge conflict This syntax indicates there is still an unresolved merge conflict. 20 21 foreach ($lines as $lineno => $line) { 22 /* >>> 23 >>>>>>> 24 25 ======= Warning (MERGECONFLICT1) Unresolved merge conflict This syntax indicates there is still an unresolved merge conflict. 22 /* 23 >>>>>>> 24 >>> 25 ======= 26 27 <<<<<<< Warning (MERGECONFLICT1) Unresolved merge conflict This syntax indicates there is still an unresolved merge conflict. 24 25 ======= 26 >>> 27 <<<<<<< 28 29 */ The last test was to test on various different file types, including JavaScript, PHP, an animated GIF, a PNG, and a Bash file to make sure the file type detection worked. Each of the aforementioned tests passed. Reviewers: vrana, epriestley Reviewed By: epriestley CC: aran, epriestley, Korvin Maniphest Tasks: T2547 Differential Revision: https://secure.phabricator.com/D4966
251 lines
5.6 KiB
PHP
251 lines
5.6 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Implements lint rules, like syntax checks for a specific language.
|
|
*
|
|
* @group linter
|
|
* @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 $config = array();
|
|
|
|
public function setCustomSeverityMap(array $map) {
|
|
$this->customSeverityMap = $map;
|
|
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;
|
|
}
|
|
|
|
public function getPaths() {
|
|
return 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];
|
|
}
|
|
|
|
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 newLintAtLine($line, $char, $code, $desc) {
|
|
return id(new ArcanistLintMessage())
|
|
->setPath($this->getActivePath())
|
|
->setLine($line)
|
|
->setChar($char)
|
|
->setCode($this->getLintMessageFullCode($code))
|
|
->setSeverity($this->getLintMessageSeverity($code))
|
|
->setName($this->getLintMessageName($code))
|
|
->setDescription($desc);
|
|
}
|
|
|
|
protected function raiseLintAtLine(
|
|
$line,
|
|
$char,
|
|
$code,
|
|
$desc,
|
|
$original = null,
|
|
$replacement = null) {
|
|
|
|
$message = $this->newLintAtLine($line, $char, $code, $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;
|
|
}
|
|
|
|
abstract public function willLintPaths(array $paths);
|
|
abstract public function lintPath($path);
|
|
abstract public function getLinterName();
|
|
|
|
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;
|
|
}
|
|
|
|
public function isBinaryFile($path) {
|
|
// Note that we need the lint engine set before this can be used.
|
|
return ArcanistDiffUtils::isHeuristicBinaryFile($this->getData($path));
|
|
}
|
|
|
|
}
|