Page MenuHomeSealhub

No OneTemporary

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

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)

Event Timeline