Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F1389800
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
28 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 66ed8b35..d2696b29 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1,142 +1,144 @@
<?php
/**
* This file is automatically generated. Use 'phutil_mapper.php' to rebuild it.
* @generated
*/
phutil_register_library_map(array(
'class' =>
array(
'ArcanistAmendWorkflow' => 'workflow/amend',
'ArcanistApacheLicenseLinter' => 'lint/linter/apachelicense',
'ArcanistApacheLicenseLinterTestCase' => 'lint/linter/apachelicense/__tests__',
'ArcanistBaseUnitTestEngine' => 'unit/engine/base',
'ArcanistBaseWorkflow' => 'workflow/base',
'ArcanistBranchWorkflow' => 'workflow/branch',
'ArcanistBundle' => 'parser/bundle',
'ArcanistCallConduitWorkflow' => 'workflow/call-conduit',
'ArcanistChooseInvalidRevisionException' => 'exception',
'ArcanistChooseNoRevisionsException' => 'exception',
'ArcanistCommitWorkflow' => 'workflow/commit',
'ArcanistConfiguration' => 'configuration',
'ArcanistCoverWorkflow' => 'workflow/cover',
'ArcanistDiffChange' => 'parser/diff/change',
'ArcanistDiffChangeType' => 'parser/diff/changetype',
'ArcanistDiffHunk' => 'parser/diff/hunk',
'ArcanistDiffParser' => 'parser/diff',
'ArcanistDiffParserTestCase' => 'parser/diff/__tests__',
'ArcanistDiffUtils' => 'difference',
'ArcanistDiffWorkflow' => 'workflow/diff',
'ArcanistDifferentialCommitMessage' => 'differential/commitmessage',
'ArcanistDifferentialCommitMessageParserException' => 'differential/commitmessage',
'ArcanistDifferentialRevisionRef' => 'differential/revision',
'ArcanistExportWorkflow' => 'workflow/export',
'ArcanistFilenameLinter' => 'lint/linter/filename',
'ArcanistGeneratedLinter' => 'lint/linter/generated',
'ArcanistGitAPI' => 'repository/api/git',
'ArcanistGitHookPreReceiveWorkflow' => 'workflow/git-hook-pre-receive',
'ArcanistHelpWorkflow' => 'workflow/help',
'ArcanistInstallCertificateWorkflow' => 'workflow/install-certificate',
'ArcanistLiberateLintEngine' => 'lint/engine/liberate',
'ArcanistLiberateWorkflow' => 'workflow/liberate',
'ArcanistLicenseLinter' => 'lint/linter/license',
'ArcanistLintEngine' => 'lint/engine/base',
'ArcanistLintJSONRenderer' => 'lint/renderer',
'ArcanistLintMessage' => 'lint/message',
'ArcanistLintPatcher' => 'lint/patcher',
'ArcanistLintRenderer' => 'lint/renderer',
'ArcanistLintResult' => 'lint/result',
'ArcanistLintSeverity' => 'lint/severity',
'ArcanistLintSummaryRenderer' => 'lint/renderer',
'ArcanistLintWorkflow' => 'workflow/lint',
'ArcanistLinter' => 'lint/linter/base',
'ArcanistLinterTestCase' => 'lint/linter/base/test',
'ArcanistListWorkflow' => 'workflow/list',
'ArcanistMarkCommittedWorkflow' => 'workflow/mark-committed',
'ArcanistNoEffectException' => 'exception/usage/noeffect',
'ArcanistNoEngineException' => 'exception/usage/noengine',
'ArcanistNoLintLinter' => 'lint/linter/nolint',
'ArcanistNoLintTestCaseMisnamed' => 'lint/linter/nolint/__tests__',
'ArcanistPEP8Linter' => 'lint/linter/pep8',
'ArcanistPatchWorkflow' => 'workflow/patch',
'ArcanistPhutilModuleLinter' => 'lint/linter/phutilmodule',
'ArcanistPhutilTestCase' => 'unit/engine/phutil/testcase',
'ArcanistPhutilTestTerminatedException' => 'unit/engine/phutil/testcase/exception',
'ArcanistPyFlakesLinter' => 'lint/linter/pyflakes',
'ArcanistPyLintLinter' => 'lint/linter/pylint',
'ArcanistRepositoryAPI' => 'repository/api/base',
'ArcanistShellCompleteWorkflow' => 'workflow/shell-complete',
'ArcanistSubversionAPI' => 'repository/api/subversion',
'ArcanistSvnHookPreCommitWorkflow' => 'workflow/svn-hook-pre-commit',
'ArcanistTextLinter' => 'lint/linter/text',
'ArcanistTextLinterTestCase' => 'lint/linter/text/__tests__',
'ArcanistUnitTestResult' => 'unit/result',
'ArcanistUnitWorkflow' => 'workflow/unit',
'ArcanistUsageException' => 'exception/usage',
'ArcanistUserAbortException' => 'exception/usage/userabort',
'ArcanistWorkingCopyIdentity' => 'workingcopyidentity',
'ArcanistXHPASTLinter' => 'lint/linter/xhpast',
'ArcanistXHPASTLinterTestCase' => 'lint/linter/xhpast/__tests__',
'BranchInfo' => 'branch',
+ 'ExampleLintEngine' => 'lint/engine/example',
'PhutilLintEngine' => 'lint/engine/phutil',
'PhutilModuleRequirements' => 'parser/phutilmodule',
'PhutilUnitTestEngine' => 'unit/engine/phutil',
'PhutilUnitTestEngineTestCase' => 'unit/engine/phutil/__tests__',
'UnitTestableArcanistLintEngine' => 'lint/engine/test',
),
'function' =>
array(
),
'requires_class' =>
array(
'ArcanistAmendWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistApacheLicenseLinter' => 'ArcanistLicenseLinter',
'ArcanistApacheLicenseLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistBranchWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistCallConduitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistCommitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistCoverWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistDiffParserTestCase' => 'ArcanistPhutilTestCase',
'ArcanistDiffWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistExportWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistFilenameLinter' => 'ArcanistLinter',
'ArcanistGeneratedLinter' => 'ArcanistLinter',
'ArcanistGitAPI' => 'ArcanistRepositoryAPI',
'ArcanistGitHookPreReceiveWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistHelpWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistInstallCertificateWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistLiberateLintEngine' => 'ArcanistLintEngine',
'ArcanistLiberateWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistLicenseLinter' => 'ArcanistLinter',
'ArcanistLintWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistLinterTestCase' => 'ArcanistPhutilTestCase',
'ArcanistListWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistMarkCommittedWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistNoEffectException' => 'ArcanistUsageException',
'ArcanistNoEngineException' => 'ArcanistUsageException',
'ArcanistNoLintLinter' => 'ArcanistLinter',
'ArcanistNoLintTestCaseMisnamed' => 'ArcanistLinterTestCase',
'ArcanistPEP8Linter' => 'ArcanistLinter',
'ArcanistPatchWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistPhutilModuleLinter' => 'ArcanistLinter',
'ArcanistPyFlakesLinter' => 'ArcanistLinter',
'ArcanistPyLintLinter' => 'ArcanistLinter',
'ArcanistShellCompleteWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistSubversionAPI' => 'ArcanistRepositoryAPI',
'ArcanistSvnHookPreCommitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistTextLinter' => 'ArcanistLinter',
'ArcanistTextLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistUnitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistUserAbortException' => 'ArcanistUsageException',
'ArcanistXHPASTLinter' => 'ArcanistLinter',
'ArcanistXHPASTLinterTestCase' => 'ArcanistLinterTestCase',
+ 'ExampleLintEngine' => 'ArcanistLintEngine',
'PhutilLintEngine' => 'ArcanistLintEngine',
'PhutilUnitTestEngine' => 'ArcanistBaseUnitTestEngine',
'PhutilUnitTestEngineTestCase' => 'ArcanistPhutilTestCase',
'UnitTestableArcanistLintEngine' => 'ArcanistLintEngine',
),
'requires_interface' =>
array(
),
));
diff --git a/src/lint/engine/base/ArcanistLintEngine.php b/src/lint/engine/base/ArcanistLintEngine.php
index 6cae48f6..84ff3357 100644
--- a/src/lint/engine/base/ArcanistLintEngine.php
+++ b/src/lint/engine/base/ArcanistLintEngine.php
@@ -1,237 +1,273 @@
<?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.
*/
/**
- * Manages lint execution.
+ * Manages lint execution. When you run 'arc lint' or 'arc diff', Arcanist
+ * checks your .arcconfig to see if you have specified a lint engine in the
+ * key "lint_engine". The engine must extend this class. For example:
+ *
+ * lang=js
+ * {
+ * // ...
+ * "lint_engine" : "ExampleLintEngine",
+ * // ...
+ * }
+ *
+ * The lint engine is given a list of paths (generally, the paths that you
+ * modified in your change) and determines which linters to run on them. The
+ * linters themselves are responsible for actually analyzing file text and
+ * finding warnings and errors. For example, if the modified paths include some
+ * JS files and some Python files, you might want to run JSLint on the JS files
+ * and PyLint on the Python files.
+ *
+ * You can also run multiple linters on a single file. For instance, you might
+ * run one linter on all text files to make sure they don't have trailing
+ * whitespace, or enforce tab vs space rules, or make sure there are enough
+ * curse words in them.
+ *
+ * Because lint engines are pretty custom to the rules of a project, you will
+ * generally need to build your own. Fortunately, it's pretty easy (and you
+ * can use the prebuilt //linters//, you just need to write a little glue code
+ * to tell Arcanist which linters to run). For a simple example of how to build
+ * a lint engine, see @{class:ExampleLintEngine}.
+ *
+ * You can test an engine like this:
+ *
+ * arc lint --engine ExampleLintEngine --lintall some_file.py
+ *
+ * ...which will show you all the lint issues raised in the file.
+ *
+ * See @{article@phabricator:Arcanist User Guide: Customizing Lint, Unit Tests
+ * and Workflows} for more information about configuring lint engines.
*
* @group lint
*/
abstract class ArcanistLintEngine {
protected $workingCopy;
protected $fileData = array();
protected $charToLine = array();
protected $lineToFirstChar = array();
private $results = array();
private $minimumSeverity = null;
private $changedLines = array();
private $commitHookMode = false;
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);
}
public function setFileData($data) {
$this->fileData = $data + $this->fileData;
return $this;
}
public function setCommitHookMode($mode) {
$this->commitHookMode = $mode;
return $this;
}
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 pathExists($path) {
if ($this->getCommitHookMode()) {
return (idx($this->fileData, $path) !== null);
} else {
$disk_path = $this->getFilePathOnDisk($path);
return Filesystem::pathExists($disk_path);
}
}
public function getFilePathOnDisk($path) {
return Filesystem::resolvePath(
$path,
$this->getWorkingCopy()->getProjectRoot());
}
public function setMinimumSeverity($severity) {
$this->minimumSeverity = $severity;
return $this;
}
public function getCommitHookMode() {
return $this->commitHookMode;
}
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($message->getPath());
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) {
$disk_path = $this->getFilePathOnDisk($path);
$result->setFilePathOnDisk($disk_path);
if (isset($this->fileData[$path])) {
$result->setData($this->fileData[$path]);
} else if ($disk_path && Filesystem::pathExists($disk_path)) {
// TODO: this may cause us to, e.g., load a large binary when we only
// raised an error about its filename. We could refine this by looking
// through the lint messages and doing this load only if any of them
// have original/replacement text or something like that.
try {
$this->fileData[$path] = Filesystem::readFile($disk_path);
$result->setData($this->fileData[$path]);
} catch (FilesystemException $ex) {
// Ignore this, it's noncritical that we access this data and it
// might be unreadable or a directory or whatever else for plenty
// of legitimate reasons.
}
}
}
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);
}
}
diff --git a/src/lint/engine/example/ExampleLintEngine.php b/src/lint/engine/example/ExampleLintEngine.php
new file mode 100644
index 00000000..d99e7e81
--- /dev/null
+++ b/src/lint/engine/example/ExampleLintEngine.php
@@ -0,0 +1,72 @@
+<?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.
+ */
+
+/**
+ * This a simple example lint engine which just applies the
+ * @{class:ArcanistPyLintLinter} to any Python files. For a more complex
+ * example, see @{class:PhutilLintEngine}.
+ *
+ * @group linter
+ */
+class ExampleLintEngine extends ArcanistLintEngine {
+
+ public function buildLinters() {
+
+ // This is a list of paths which the user wants to lint. Either they
+ // provided them explicitly, or arc figured them out from a commit or set
+ // of changes. The engine needs to return a list of ArcanistLinter objects,
+ // representing the linters which should be run on these files.
+ $paths = $this->getPaths();
+
+ // The ArcanistPyLintLinter runs "PyLint" (an open source python linter) on
+ // files you give it. There are several linters available by default like
+ // this one which you can use out of the box, or you can write your own.
+ // Linters are responsible for actually analyzing the contents of a file
+ // and raising warnings and errors.
+ $pylint_linter = new ArcanistPyLintLinter();
+
+ foreach ($paths as $path) {
+ if (!preg_match('/\.py$/', $path)) {
+ // This isn't a python file, so don't try to apply the PyLint linter
+ // to it.
+ continue;
+ }
+
+ if (preg_match('@^externals/@', $path)) {
+ // This is just an example of how to exclude a path so it doesn't get
+ // linted. If you put third-party code in an externals/ directory, you
+ // can just have your lint engine ignore it.
+ continue;
+ }
+
+ // Add the path, to tell the linter it should examine the source code
+ // to try to find problems.
+ $pylint_linter->addPath($path);
+ }
+
+ // We only built one linter, but you can build more than one (e.g., a
+ // Javascript linter for JS), and return a list of linters to execute. You
+ // can also add a path to more than one linter (for example, if you want
+ // to run a Python linter and a more general text linter on every .py file).
+
+ return array(
+ $pylint_linter,
+ );
+ }
+
+}
diff --git a/src/lint/engine/example/__init__.php b/src/lint/engine/example/__init__.php
new file mode 100644
index 00000000..fd81aada
--- /dev/null
+++ b/src/lint/engine/example/__init__.php
@@ -0,0 +1,13 @@
+<?php
+/**
+ * This file is automatically generated. Lint this module to rebuild it.
+ * @generated
+ */
+
+
+
+phutil_require_module('arcanist', 'lint/engine/base');
+phutil_require_module('arcanist', 'lint/linter/pylint');
+
+
+phutil_require_source('ExampleLintEngine.php');
diff --git a/src/lint/linter/pylint/ArcanistPyLintLinter.php b/src/lint/linter/pylint/ArcanistPyLintLinter.php
index 7d5cf545..2e56bd9e 100644
--- a/src/lint/linter/pylint/ArcanistPyLintLinter.php
+++ b/src/lint/linter/pylint/ArcanistPyLintLinter.php
@@ -1,168 +1,229 @@
<?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.
*/
/**
- * Uses "PyLint" to detect various errors in Python code.
+ * Uses "PyLint" to detect various errors in Python code. To use this linter,
+ * you must install pylint and configure which codes you want to be reported as
+ * errors, warnings and advice.
+ *
+ * You should be able to install pylint with ##sudo easy_install pylint##. If
+ * your system is unusual, you can manually specify the location of pylint and
+ * its dependencies by configuring these keys in your .arcrc:
+ *
+ * lint.pylint.prefix
+ * lint.pylint.logilab_astng.prefix
+ * lint.pylint.logilab_common.prefix
+ *
+ * You can specify additional command-line options to pass to PyLint by setting
+ * this key:
+ *
+ * lint.pylint.options
+ *
+ * Now, configure which warnings and errors you want PyLint to raise by setting
+ * these keys:
+ *
+ * lint.pylint.codes.error
+ * lint.pylint.codes.warning
+ * lint.pylint.codes.advice
+ *
+ * Set these to regular expressions -- for instance, if you want to raise all
+ * PyLint errors as Arcanist errors, set this for ##lint.pylint.codes.error##:
+ *
+ * ^E.*
+ *
+ * You can also match more granular errors:
+ *
+ * ^E(0001|0002)$
+ *
+ * You can also provide a list of regular expressions.
*
* @group linter
*/
class ArcanistPyLintLinter extends ArcanistLinter {
private function getMessageCodeSeverity($code) {
+
+ $working_copy = $this->getEngine()->getWorkingCopy();
+
// The config file defines how PyLint message codes translate to
// arcanist severities. The config options provide regex's to
// match against the message codes generated by PyLint. Severity's
// are matched in the order of errors, warnings, then advice.
// The first severity that matches, in that order, is returned.
- $working_copy = $this->getEngine()->getWorkingCopy();
+ $error_regexp = $working_copy->getConfig('lint.pylint.codes.error');
+ $warning_regexp = $working_copy->getConfig('lint.pylint.codes.warning');
+ $advice_regexp = $working_copy->getConfig('lint.pylint.codes.advice');
+
+ if (!$error_regexp && !$warning_regexp && !$advice_regexp) {
+ throw new ArcanistUsageException(
+ "You are invoking the PyLint linter but have not configured any of ".
+ "'lint.pylint.codes.error', 'lint.pylint.codes.warning', or ".
+ "'lint.pylint.codes.advice'. Consult the documentation for ".
+ "ArcanistPyLintLinter.");
+ }
+
$code_map = array(
- ArcanistLintSeverity::SEVERITY_ERROR =>
- $working_copy->getConfig('lint.pylint.codes.error'),
- ArcanistLintSeverity::SEVERITY_WARNING =>
- $working_copy->getConfig('lint.pylint.codes.warning'),
- ArcanistLintSeverity::SEVERITY_ADVICE =>
- $working_copy->getConfig('lint.pylint.codes.advice'),
+ ArcanistLintSeverity::SEVERITY_ERROR => $error_regexp,
+ ArcanistLintSeverity::SEVERITY_WARNING => $warning_regexp,
+ ArcanistLintSeverity::SEVERITY_ADVICE => $advice_regexp,
);
foreach ($code_map as $sev => $codes) {
- if ($codes !== null) {
- foreach ($codes as $code_re) {
- if (preg_match("/{$code_re}/", $code)) {
- return $sev;
- }
+ if ($codes === null) {
+ continue;
+ }
+ if (!is_array($codes)) {
+ $codes = array($codes);
+ }
+ foreach ($codes as $code_re) {
+ if (preg_match("/{$code_re}/", $code)) {
+ return $sev;
}
}
}
// If the message code doesn't match any of the provided regex's,
// then just disable it.
return ArcanistLintSeverity::SEVERITY_DISABLED;
}
private function getPyLintPath() {
$pylint_bin = "pylint";
// Use the PyLint prefix specified in the config file
$working_copy = $this->getEngine()->getWorkingCopy();
$prefix = $working_copy->getConfig('lint.pylint.prefix');
if ($prefix !== null) {
$pylint_bin = $prefix."/bin/".$pylint_bin;
}
+ if (!Filesystem::pathExists($pylint_bin)) {
+
+ list($err) = exec_manual('which %s', $pylint_bin);
+ if ($err) {
+ throw new ArcanistUsageException(
+ "PyLint does not appear to be installed on this system. Install it ".
+ "(e.g., with 'sudo easy_install pylint') or configure ".
+ "'lint.pylint.prefix' in your .arcconfig to point to the directory ".
+ "where it resides.");
+ }
+ }
+
return $pylint_bin;
}
private function getPyLintPythonPath() {
// Get non-default install locations for pylint and its dependencies
// libraries.
$working_copy = $this->getEngine()->getWorkingCopy();
$prefixes = array(
$working_copy->getConfig('lint.pylint.prefix'),
$working_copy->getConfig('lint.pylint.logilab_astng.prefix'),
$working_copy->getConfig('lint.pylint.logilab_common.prefix'),
);
// Add the libraries to the python search path
$python_path = array();
foreach ($prefixes as $prefix) {
if ($prefix !== null) {
$python_path[] = $prefix.'/lib/python2.6/site-packages';
}
}
$python_path[] = '';
return implode(":", $python_path);
}
private function getPyLintOptions() {
// Options to pass the PyLint
// - '-rn': don't print lint report/summary at end
// - '-iy': show message codes for lint warnings/errors
$options = array('-rn', '-iy');
// Add any options defined in the config file for PyLint
$working_copy = $this->getEngine()->getWorkingCopy();
$config_options = $working_copy->getConfig('lint.pylint.options');
if ($config_options !== null) {
$options += $config_options;
}
return implode(" ", $options);
}
public function willLintPaths(array $paths) {
return;
}
public function getLinterName() {
return 'PyLint';
}
public function getLintSeverityMap() {
return array();
}
public function getLintNameMap() {
return array();
}
public function lintPath($path) {
$pylint_bin = $this->getPyLintPath();
$python_path = $this->getPyLintPythonPath();
$options = $this->getPyLintOptions();
$path_on_disk = $this->getEngine()->getFilePathOnDisk($path);
try {
list($stdout, $_) = execx(
"/usr/bin/env PYTHONPATH=%s\$PYTHONPATH ".
"{$pylint_bin} {$options} {$path_on_disk}",
$python_path);
} catch (CommandException $e) {
// PyLint will return a non-zero exit code if warnings/errors are found.
// Therefore we detect command failure by checking that the stderr is
// some non-expected value.
if ($e->getStderr() !== "No config file found, ".
"using default configuration\n") {
throw $e;
}
$stdout = $e->getStdout();
}
$lines = explode("\n", $stdout);
$messages = array();
foreach ($lines as $line) {
$matches = null;
if (!preg_match('/([A-Z]\d+): *(\d+): *(.*)$/', $line, $matches)) {
continue;
}
foreach ($matches as $key => $match) {
$matches[$key] = trim($match);
}
+
$message = new ArcanistLintMessage();
$message->setPath($path);
$message->setLine($matches[2]);
$message->setCode($matches[1]);
$message->setName($this->getLinterName()." ".$matches[1]);
$message->setDescription($matches[3]);
$message->setSeverity($this->getMessageCodeSeverity($matches[1]));
$this->addLintMessage($message);
}
}
}
diff --git a/src/lint/linter/pylint/__init__.php b/src/lint/linter/pylint/__init__.php
index 621bede3..a4df923f 100644
--- a/src/lint/linter/pylint/__init__.php
+++ b/src/lint/linter/pylint/__init__.php
@@ -1,16 +1,18 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
+phutil_require_module('arcanist', 'exception/usage');
phutil_require_module('arcanist', 'lint/linter/base');
phutil_require_module('arcanist', 'lint/message');
phutil_require_module('arcanist', 'lint/severity');
+phutil_require_module('phutil', 'filesystem');
phutil_require_module('phutil', 'future/exec');
phutil_require_source('ArcanistPyLintLinter.php');
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Wed, Mar 26, 10:44 (3 h, 16 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
619377
Default Alt Text
(28 KB)
Attached To
Mode
R118 Arcanist - fork
Attached
Detach File
Event Timeline
Log In to Comment