1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-22 14:52:40 +01:00

Provide better documentation for lint engines, linters, and the PyLint linter

Summary:
Document the relationship between lint engines and linters. Provide an example
linter. Improve the documentation of PyLintLinter, which has a bunch of
configuration stuff which you had to dig into the code to get.

Test Plan:
Ran "arc lint --engine ExampleLintEngine --lintall derp.py" on a file with a
Python syntax error in it. Read documentation.

Reviewed By: j3kuntz
Reviewers: j3kuntz, andrewjcg
CC: aran, j3kuntz
Differential Revision: 557
This commit is contained in:
epriestley 2011-06-29 19:32:03 -07:00
parent 4a8e247e66
commit 8234dd8088
6 changed files with 200 additions and 14 deletions

View file

@ -79,6 +79,7 @@ phutil_register_library_map(array(
'ArcanistXHPASTLinter' => 'lint/linter/xhpast', 'ArcanistXHPASTLinter' => 'lint/linter/xhpast',
'ArcanistXHPASTLinterTestCase' => 'lint/linter/xhpast/__tests__', 'ArcanistXHPASTLinterTestCase' => 'lint/linter/xhpast/__tests__',
'BranchInfo' => 'branch', 'BranchInfo' => 'branch',
'ExampleLintEngine' => 'lint/engine/example',
'PhutilLintEngine' => 'lint/engine/phutil', 'PhutilLintEngine' => 'lint/engine/phutil',
'PhutilModuleRequirements' => 'parser/phutilmodule', 'PhutilModuleRequirements' => 'parser/phutilmodule',
'PhutilUnitTestEngine' => 'unit/engine/phutil', 'PhutilUnitTestEngine' => 'unit/engine/phutil',
@ -131,6 +132,7 @@ phutil_register_library_map(array(
'ArcanistUserAbortException' => 'ArcanistUsageException', 'ArcanistUserAbortException' => 'ArcanistUsageException',
'ArcanistXHPASTLinter' => 'ArcanistLinter', 'ArcanistXHPASTLinter' => 'ArcanistLinter',
'ArcanistXHPASTLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistXHPASTLinterTestCase' => 'ArcanistLinterTestCase',
'ExampleLintEngine' => 'ArcanistLintEngine',
'PhutilLintEngine' => 'ArcanistLintEngine', 'PhutilLintEngine' => 'ArcanistLintEngine',
'PhutilUnitTestEngine' => 'ArcanistBaseUnitTestEngine', 'PhutilUnitTestEngine' => 'ArcanistBaseUnitTestEngine',
'PhutilUnitTestEngineTestCase' => 'ArcanistPhutilTestCase', 'PhutilUnitTestEngineTestCase' => 'ArcanistPhutilTestCase',

View file

@ -17,7 +17,43 @@
*/ */
/** /**
* 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 * @group lint
*/ */

View file

@ -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,
);
}
}

View file

@ -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');

View file

@ -17,37 +17,85 @@
*/ */
/** /**
* 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 * @group linter
*/ */
class ArcanistPyLintLinter extends ArcanistLinter { class ArcanistPyLintLinter extends ArcanistLinter {
private function getMessageCodeSeverity($code) { private function getMessageCodeSeverity($code) {
$working_copy = $this->getEngine()->getWorkingCopy();
// The config file defines how PyLint message codes translate to // The config file defines how PyLint message codes translate to
// arcanist severities. The config options provide regex's to // arcanist severities. The config options provide regex's to
// match against the message codes generated by PyLint. Severity's // match against the message codes generated by PyLint. Severity's
// are matched in the order of errors, warnings, then advice. // are matched in the order of errors, warnings, then advice.
// The first severity that matches, in that order, is returned. // 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( $code_map = array(
ArcanistLintSeverity::SEVERITY_ERROR => ArcanistLintSeverity::SEVERITY_ERROR => $error_regexp,
$working_copy->getConfig('lint.pylint.codes.error'), ArcanistLintSeverity::SEVERITY_WARNING => $warning_regexp,
ArcanistLintSeverity::SEVERITY_WARNING => ArcanistLintSeverity::SEVERITY_ADVICE => $advice_regexp,
$working_copy->getConfig('lint.pylint.codes.warning'),
ArcanistLintSeverity::SEVERITY_ADVICE =>
$working_copy->getConfig('lint.pylint.codes.advice'),
); );
foreach ($code_map as $sev => $codes) { foreach ($code_map as $sev => $codes) {
if ($codes !== null) { if ($codes === null) {
continue;
}
if (!is_array($codes)) {
$codes = array($codes);
}
foreach ($codes as $code_re) { foreach ($codes as $code_re) {
if (preg_match("/{$code_re}/", $code)) { if (preg_match("/{$code_re}/", $code)) {
return $sev; return $sev;
} }
} }
} }
}
// If the message code doesn't match any of the provided regex's, // If the message code doesn't match any of the provided regex's,
// then just disable it. // then just disable it.
@ -64,6 +112,18 @@ class ArcanistPyLintLinter extends ArcanistLinter {
$pylint_bin = $prefix."/bin/".$pylint_bin; $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; return $pylint_bin;
} }
@ -154,6 +214,7 @@ class ArcanistPyLintLinter extends ArcanistLinter {
foreach ($matches as $key => $match) { foreach ($matches as $key => $match) {
$matches[$key] = trim($match); $matches[$key] = trim($match);
} }
$message = new ArcanistLintMessage(); $message = new ArcanistLintMessage();
$message->setPath($path); $message->setPath($path);
$message->setLine($matches[2]); $message->setLine($matches[2]);

View file

@ -6,10 +6,12 @@
phutil_require_module('arcanist', 'exception/usage');
phutil_require_module('arcanist', 'lint/linter/base'); phutil_require_module('arcanist', 'lint/linter/base');
phutil_require_module('arcanist', 'lint/message'); phutil_require_module('arcanist', 'lint/message');
phutil_require_module('arcanist', 'lint/severity'); phutil_require_module('arcanist', 'lint/severity');
phutil_require_module('phutil', 'filesystem');
phutil_require_module('phutil', 'future/exec'); phutil_require_module('phutil', 'future/exec');