mirror of
https://we.phorge.it/source/arcanist.git
synced 2025-01-10 14:51:05 +01:00
200 lines
5.3 KiB
PHP
200 lines
5.3 KiB
PHP
|
<?php
|
||
|
|
||
|
/*
|
||
|
* Copyright 2011 Facebook, Inc.
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
abstract class ArcanistLintEngine {
|
||
|
|
||
|
protected $workingCopy;
|
||
|
protected $fileData = array();
|
||
|
|
||
|
protected $charToLine = array();
|
||
|
protected $lineToFirstChar = array();
|
||
|
private $results = array();
|
||
|
private $minimumSeverity = null;
|
||
|
|
||
|
private $changedLines = array();
|
||
|
|
||
|
public function __construct() {
|
||
|
|
||
|
}
|
||
|
|
||
|
public function setWorkingCopy(ArcanistWorkingCopyIdentity $working_copy) {
|
||
|
$this->workingCopy = $working_copy;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
public function getWorkingCopy() {
|
||
|
return $this->workingCopy;
|
||
|
}
|
||
|
|
||
|
public function setPaths($paths) {
|
||
|
$this->paths = $paths;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
public function getPaths() {
|
||
|
return $this->paths;
|
||
|
}
|
||
|
|
||
|
public function setPathChangedLines($path, array $changed) {
|
||
|
$this->changedLines[$path] = array_fill_keys($changed, true);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
public function getPathChangedLines($path) {
|
||
|
return idx($this->changedLines, $path);
|
||
|
}
|
||
|
|
||
|
protected function loadData($path) {
|
||
|
if (!isset($this->fileData[$path])) {
|
||
|
$disk_path = $this->getFilePathOnDisk($path);
|
||
|
$this->fileData[$path] = Filesystem::readFile($disk_path);
|
||
|
}
|
||
|
return $this->fileData[$path];
|
||
|
}
|
||
|
|
||
|
public function getFilePathOnDisk($path) {
|
||
|
return $path;
|
||
|
}
|
||
|
|
||
|
public function setMinimumSeverity($severity) {
|
||
|
$this->minimumSeverity = $severity;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
public function getCommitHookMode() {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public function run() {
|
||
|
$stopped = array();
|
||
|
$linters = $this->buildLinters();
|
||
|
|
||
|
if (!$linters) {
|
||
|
throw new ArcanistNoEffectException("No linters to run.");
|
||
|
}
|
||
|
|
||
|
$have_paths = false;
|
||
|
foreach ($linters as $linter) {
|
||
|
if ($linter->getPaths()) {
|
||
|
$have_paths = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!$have_paths) {
|
||
|
throw new ArcanistNoEffectException("No paths are lintable.");
|
||
|
}
|
||
|
|
||
|
foreach ($linters as $linter) {
|
||
|
$linter->setEngine($this);
|
||
|
$paths = $linter->getPaths();
|
||
|
|
||
|
foreach ($paths as $key => $path) {
|
||
|
// Make sure each path has a result generated, even if it is empty
|
||
|
// (i.e., the file has no lint messages).
|
||
|
$result = $this->getResultForPath($path);
|
||
|
if (isset($stopped[$path])) {
|
||
|
unset($paths[$key]);
|
||
|
}
|
||
|
}
|
||
|
$paths = array_values($paths);
|
||
|
|
||
|
if ($paths) {
|
||
|
$linter->willLintPaths($paths);
|
||
|
foreach ($paths as $path) {
|
||
|
$linter->willLintPath($path);
|
||
|
$linter->lintPath($path);
|
||
|
if ($linter->didStopAllLinters()) {
|
||
|
$stopped[$path] = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$minimum = $this->minimumSeverity;
|
||
|
foreach ($linter->getLintMessages() as $message) {
|
||
|
if (!ArcanistLintSeverity::isAtLeastAsSevere($message, $minimum)) {
|
||
|
continue;
|
||
|
}
|
||
|
// When a user runs "arc diff", we default to raising only warnings on
|
||
|
// lines they have changed (errors are still raised anywhere in the
|
||
|
// file).
|
||
|
$changed = $this->getPathChangedLines($path);
|
||
|
if ($changed !== null && !$message->isError()) {
|
||
|
if (empty($changed[$message->getLine()])) {
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
$result = $this->getResultForPath($message->getPath());
|
||
|
$result->addMessage($message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach ($this->results as $path => $result) {
|
||
|
if (isset($this->fileData[$path])) {
|
||
|
// Only set the data if any linter loaded it. The goal here is to
|
||
|
// avoid binaries when we don't actually care about their contents,
|
||
|
// for performance.
|
||
|
$result->setData($this->fileData[$path]);
|
||
|
$result->setFilePathOnDisk($this->getFilePathOnDisk($path));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $this->results;
|
||
|
}
|
||
|
|
||
|
abstract protected function buildLinters();
|
||
|
|
||
|
private function getResultForPath($path) {
|
||
|
if (empty($this->results[$path])) {
|
||
|
$result = new ArcanistLintResult();
|
||
|
$result->setPath($path);
|
||
|
$this->results[$path] = $result;
|
||
|
}
|
||
|
return $this->results[$path];
|
||
|
}
|
||
|
|
||
|
public function getLineAndCharFromOffset($path, $offset) {
|
||
|
if (!isset($this->charToLine[$path])) {
|
||
|
$char_to_line = array();
|
||
|
$line_to_first_char = array();
|
||
|
|
||
|
$lines = explode("\n", $this->loadData($path));
|
||
|
$line_number = 0;
|
||
|
$line_start = 0;
|
||
|
foreach ($lines as $line) {
|
||
|
$len = strlen($line) + 1; // Account for "\n".
|
||
|
$line_to_first_char[] = $line_start;
|
||
|
$line_start += $len;
|
||
|
for ($ii = 0; $ii < $len; $ii++) {
|
||
|
$char_to_line[] = $line_number;
|
||
|
}
|
||
|
$line_number++;
|
||
|
}
|
||
|
$this->charToLine[$path] = $char_to_line;
|
||
|
$this->lineToFirstChar[$path] = $line_to_first_char;
|
||
|
}
|
||
|
|
||
|
$line = $this->charToLine[$path][$offset];
|
||
|
$char = $offset - $this->lineToFirstChar[$path][$line];
|
||
|
|
||
|
return array($line, $char);
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|