From 4ca70d4e486383513d0b3ee929950664d9433192 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 21 Dec 2012 15:27:52 -0800 Subject: [PATCH] Add a flake8 linter Summary: flake8 is the better maintained combination of pep8 and pyflakes Test Plan: There's a test! Reviewers: epriestley Reviewed By: epriestley CC: aran, Korvin, jack Differential Revision: https://secure.phabricator.com/D4082 --- src/__phutil_library_map__.php | 4 + src/lint/linter/ArcanistFlake8Linter.php | 115 ++++++++++++++++++ .../ArcanistFlake8LinterTestCase.php | 19 +++ .../__tests__/python/undefined.lint-test | 7 ++ 4 files changed, 145 insertions(+) create mode 100644 src/lint/linter/ArcanistFlake8Linter.php create mode 100644 src/lint/linter/__tests__/ArcanistFlake8LinterTestCase.php create mode 100644 src/lint/linter/__tests__/python/undefined.lint-test diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e261c099..65465704 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -53,6 +53,8 @@ phutil_register_library_map(array( 'ArcanistExportWorkflow' => 'workflow/ArcanistExportWorkflow.php', 'ArcanistFilenameLinter' => 'lint/linter/ArcanistFilenameLinter.php', 'ArcanistFlagWorkflow' => 'workflow/ArcanistFlagWorkflow.php', + 'ArcanistFlake8Linter' => 'lint/linter/ArcanistFlake8Linter.php', + 'ArcanistFlake8LinterTestCase' => 'lint/linter/__tests__/ArcanistFlake8LinterTestCase.php', 'ArcanistGeneratedLinter' => 'lint/linter/ArcanistGeneratedLinter.php', 'ArcanistGetConfigWorkflow' => 'workflow/ArcanistGetConfigWorkflow.php', 'ArcanistGitAPI' => 'repository/api/ArcanistGitAPI.php', @@ -181,6 +183,8 @@ phutil_register_library_map(array( 'ArcanistExportWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistFilenameLinter' => 'ArcanistLinter', 'ArcanistFlagWorkflow' => 'ArcanistBaseWorkflow', + 'ArcanistFlake8Linter' => 'ArcanistLinter', + 'ArcanistFlake8LinterTestCase' => 'ArcanistArcanistLinterTestCase', 'ArcanistGeneratedLinter' => 'ArcanistLinter', 'ArcanistGetConfigWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistGitAPI' => 'ArcanistRepositoryAPI', diff --git a/src/lint/linter/ArcanistFlake8Linter.php b/src/lint/linter/ArcanistFlake8Linter.php new file mode 100644 index 00000000..113685be --- /dev/null +++ b/src/lint/linter/ArcanistFlake8Linter.php @@ -0,0 +1,115 @@ +getEngine()->getWorkingCopy(); + + $options = $working_copy->getConfig('lint.flake8.options', ''); + + return $options; + } + + public function getFlake8Path() { + $working_copy = $this->getEngine()->getWorkingCopy(); + $prefix = $working_copy->getConfig('lint.flake8.prefix'); + $bin = $working_copy->getConfig('lint.flake8.bin'); + + if ($bin === null && $prefix === null) { + $bin = 'flake8'; + } else { + if ($bin === null) { + $bin = 'flake8'; + } + + if ($prefix !== null) { + if (!Filesystem::pathExists($prefix.'/'.$bin)) { + throw new ArcanistUsageException( + "Unable to find flake8 binary in a specified directory. Make sure ". + "that 'lint.flake8.prefix' and 'lint.flake8.bin' keys are set ". + "correctly. If you'd rather use a copy of flake8 installed ". + "globally, you can just remove these keys from your .arcconfig"); + } + + $bin = csprintf("%s/%s", $prefix, $bin); + + return $bin; + } + + // Look for globally installed flake8 + list($err) = exec_manual('which %s', $bin); + if ($err) { + throw new ArcanistUsageException( + "flake8 does not appear to be installed on this system. Install it ". + "(e.g., with 'easy_install flake8') or configure ". + "'lint.flake8.prefix' in your .arcconfig to point to the directory ". + "where it resides."); + } + } + + return $bin; + } + + public function lintPath($path) { + $flake8_bin = $this->getFlake8Path(); + $options = $this->getFlake8Options(); + + $f = new ExecFuture("%C %C -", $flake8_bin, $options); + $f->write($this->getData($path)); + + list($err, $stdout, $stderr) = $f->resolve(); + + if ($err === 2) { + throw new Exception("flake8 failed to run correctly:\n".$stderr); + } + + $lines = explode("\n", $stdout); + $messages = array(); + foreach ($lines as $line) { + $matches = null; + // stdin:2: W802 undefined name 'foo' # pyflakes + // stdin:3:1: E302 expected 2 blank lines, found 1 # pep8 + if (!preg_match('/^(.*?):(\d+):(?:(\d+):)? (\S+) (.*)$/', + $line, $matches)) { + continue; + } + foreach ($matches as $key => $match) { + $matches[$key] = trim($match); + } + + $severity = ArcanistLintSeverity::SEVERITY_WARNING; + $description = $matches[3]; + + $error_regexp = '/(^undefined|^duplicate|before assignment$)/'; + if (preg_match($error_regexp, $description)) { + $severity = ArcanistLintSeverity::SEVERITY_ERROR; + } + + $message = new ArcanistLintMessage(); + $message->setPath($path); + $message->setLine($matches[2]); + if (!empty($matches[3])) + $message->setChar($matches[3]); + $message->setCode($matches[4]); + $message->setName($this->getLinterName().' '.$matches[4]); + $message->setDescription($description); + $message->setSeverity($severity); + $this->addLintMessage($message); + } + } + +} diff --git a/src/lint/linter/__tests__/ArcanistFlake8LinterTestCase.php b/src/lint/linter/__tests__/ArcanistFlake8LinterTestCase.php new file mode 100644 index 00000000..d09fd78e --- /dev/null +++ b/src/lint/linter/__tests__/ArcanistFlake8LinterTestCase.php @@ -0,0 +1,19 @@ +executeTestsInDirectory( + dirname(__FILE__).'/python/', + $linter, + $working_copy); + } + +} diff --git a/src/lint/linter/__tests__/python/undefined.lint-test b/src/lint/linter/__tests__/python/undefined.lint-test new file mode 100644 index 00000000..72f6dfc2 --- /dev/null +++ b/src/lint/linter/__tests__/python/undefined.lint-test @@ -0,0 +1,7 @@ +x = 'y' + +def hello(): + return foo +~~~~~~~~~~ +warning:1:8 +warning:4: