Page MenuHomeSealhub

No OneTemporary

diff --git a/README.md b/README.md
index 81a8432..d1b1e55 100644
--- a/README.md
+++ b/README.md
@@ -1,225 +1,244 @@
# Arcanist Linters
This is a collection of custom [Arcanist][] linters that we've written at
Pinterest.
- [Apache Thrift](#apache-thrift)
- [Apache Thrift Generated](#apache-thrift-generated)
- [Checkstyle](#checkstyle)
- [ESLint](#eslint)
+- [Flake8](#flake8)
- [Go Vet](#go-vet)
- [Prettier](#prettier)
- [Prettier ESLint](#prettier-eslint)
- [Python Imports](#python-imports)
- [Python isort](#python-isort)
- [Python Requirements](#python-requirements)
## Linters
### Apache Thrift
Lints for errors in [Apache Thrift][] IDL (schema) files using the `thrift`
compiler.
```json
{
"type": "thrift",
"include": "(\\.thrift$)",
"flags": [
"--allow-64bit-consts"
],
"version": ">= 0.9.0",
"thrift.generators": [
"py:dynamic,utf8strings,new_style,slots",
"java",
"go",
"erl"
],
"thrift.includes": [
".",
"common"
]
}
```
[Apache Thrift]: http://thrift.apache.org/
### Apache Thrift Generated
Lints files generated by the [Apache Thrift][] compiler to ensure they were
generated using a supported Thrift compiler.
```json
{
"type": "thrift-gen",
"include": "(^schemas/.*\\.py$)",
"thrift-gen.version": ">=0.9.3"
}
```
*Note:* Currently only generated Python files are supported.
### Checkstyle
Uses the [Checkstyle](http://checkstyle.sourceforge.net/) tool to check Java
code against a coding standard.
```json
{
"type": "checkstyle",
"include": "(\\.java$)",
"checkstyle.config": "google_check.xml"
}
```
### ESLint
Lints JavaScript and JSX files using [ESLint](https://eslint.org/).
```json
{
"type": "eslint",
"include": "(\\.js$)",
"bin": "./node_modules/.bin/eslint",
"eslint.config": "~/my-eslint.json",
"eslint.env": "browser,node"
}
```
+### Flake8
+
+Lints Python source files using [Flake8](http://flake8.pycqa.org/). This is an
+extended version of the stock `ArcanistFlake8Linter` that adds support for
+checking required Python and extension versions.
+
+```json
+{
+ "type": "flake8ext",
+ "include": "(\\.py$)",
+ "flake8.python": "< 3.0",
+ "flake8.extensions": {
+ "assertive": "1.0.1",
+ "naming": "0.7.0"
+ }
+}
+```
+
### Go Vet
Uses the [Go vet command](https://golang.org/cmd/vet/) to lint for suspicious
code constructs.
```json
{
"type": "govet",
"include": "(^src/example.com/.*\\.go$)"
}
```
### Prettier
Formats JavaScript using [Prettier](https://prettier.io/).
```json
{
"type": "prettier",
"include": "(\\.js$)",
"bin": "./node_modules/.bin/prettier",
"prettier.cwd": "./"
}
```
### Prettier ESLint
Formats JavaScript using [Prettier](https://prettier.io/) and then fixes with [ESLint](https://eslint.org/).
```json
{
"type": "prettier-eslint",
"include": "(\\.js$)",
"bin": "./node_modules/.bin/prettier-eslint",
"prettier-eslint.cwd": "./"
}
```
### Python Imports
Lints for illegal Python module imports.
```json
{
"type": "python-imports",
"python-imports.pattern": "(mock)",
"include": "(\\.py$)",
"exclude": "(^tests/)"
}
```
### Python isort
Lints Python imports using [isort](https://github.com/timothycrosley/isort).
```json
{
"type": "isort",
"include": "(\\.py$)"
}
```
### Python Requirements
Ensures Python package requirements in [requirements.txt files][req-txt] are
sorted, unique, and pinned to exact versions.
```json
{
"type": "requirements-txt",
"include": "(requirements.txt$)"
}
```
Individual requirement lines can be excluded by adding a `# noqa` comment:
```
six>=1.10.0 # noqa: allow any recent version of six
```
[req-txt]: https://pip.readthedocs.org/en/latest/user_guide/#requirements-files
## Installation
In short, you'll need to add this repository to your local machine and tell
Arcanist to load the extension. You either can do this globally or on a
per-project basis.
Once installed, the individual linters can be enabled and configured via the
project's `.arclint` file. See the [Arcanist Lint User Guide][lint-guide] for
details.
## Global Installation
Arcanist can load modules from an absolute path, but because it also searches
for modules one level up from itself on the filesystem, it's convenient to
clone this repository at the same level as `arcanist` and `libphutil`.
```
$ git clone https://github.com/pinterest/arcanist-linters.git pinterest-linters
$ ls
arcanist
pinterest-linters
libphutil
```
Then, tell Arcanist to load the module by editing `~/.arcconfig` (or
`/etc/arcconfig`):
```json
{
"load": ["pinterest-linters"]
}
```
## Project Installation
You can also load `arcanist-linters` on a per-project basis. In that case,
using a [git submodule](https://git-scm.com/docs/git-submodule) is probably
the most convenient approach.
```
$ git submodule add https://github.com/pinterest/arcanist-linters.git .pinterest-linters
$ git submodule update --init
```
Then, enable the module in your project-level `.arcconfig` file:
```json
{
"load": [".pinterest-linters"]
}
```
[Arcanist]: https://secure.phabricator.com/book/phabricator/article/arcanist/
[lint-guide]: https://secure.phabricator.com/book/phabricator/article/arcanist_lint/
diff --git a/__phutil_library_map__.php b/__phutil_library_map__.php
index edd9279..7c9d409 100644
--- a/__phutil_library_map__.php
+++ b/__phutil_library_map__.php
@@ -1,36 +1,38 @@
<?php
/**
* This file is automatically generated. Use 'arc liberate' to rebuild it.
*
* @generated
* @phutil-library-version 2
*/
phutil_register_library_map(array(
'__library_version__' => 2,
'class' => array(
'ApacheThriftGeneratedLinter' => 'src/ApacheThriftGeneratedLinter.php',
'ApacheThriftLinter' => 'src/ApacheThriftLinter.php',
'CheckstyleLinter' => 'src/CheckstyleLinter.php',
'ESLintLinter' => 'src/ESLintLinter.php',
+ 'Flake8Linter' => 'src/Flake8Linter.php',
'GoVetLinter' => 'src/GoVetLinter.php',
'PrettierESLintLinter' => 'src/PrettierESLintLinter.php',
'PrettierLinter' => 'src/PrettierLinter.php',
'PythonImportsLinter' => 'src/PythonImportsLinter.php',
'PythonIsortLinter' => 'src/PythonIsortLinter.php',
'PythonRequirementsLinter' => 'src/PythonRequirementsLinter.php',
),
'function' => array(),
'xmap' => array(
'ApacheThriftGeneratedLinter' => 'ArcanistLinter',
'ApacheThriftLinter' => 'ArcanistExternalLinter',
'CheckstyleLinter' => 'ArcanistExternalLinter',
'ESLintLinter' => 'ArcanistExternalLinter',
+ 'Flake8Linter' => 'ArcanistExternalLinter',
'GoVetLinter' => 'ArcanistExternalLinter',
'PrettierESLintLinter' => 'ArcanistExternalLinter',
'PrettierLinter' => 'ArcanistExternalLinter',
'PythonImportsLinter' => 'ArcanistLinter',
'PythonIsortLinter' => 'ArcanistExternalLinter',
'PythonRequirementsLinter' => 'ArcanistLinter',
),
));
diff --git a/src/Flake8Linter.php b/src/Flake8Linter.php
new file mode 100644
index 0000000..008b45e
--- /dev/null
+++ b/src/Flake8Linter.php
@@ -0,0 +1,244 @@
+<?php
+/**
+ * Copyright 2018 Pinterest, Inc.
+ * Copyright 2014 Phacility, 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.
+ */
+
+/**
+ * Lints Python source files using flake8.
+ *
+ * This is an extended version of the stock ArcanistFlake8Linter that adds
+ * support for checking required Python and extension versions.
+ */
+final class Flake8Linter extends ArcanistExternalLinter {
+
+ private $installInstructions = null;
+ private $pythonVersion = null;
+ private $extensionVersions = array();
+
+ public function getInfoName() {
+ return 'Python Flake8 Linter';
+ }
+
+ public function getInfoDescription() {
+ return pht('Uses flake8 to lint Python source files');
+ }
+
+ public function getInfoURI() {
+ return 'http://flake8.pycqa.org/';
+ }
+
+ public function getLinterName() {
+ return 'flake8';
+ }
+
+ public function getLinterConfigurationName() {
+ return 'flake8ext';
+ }
+
+ public function getLinterConfigurationOptions() {
+ $options = array(
+ 'flake8.install' => array(
+ 'type' => 'optional string',
+ 'help' => pht('Installation instructions.'),
+ ),
+ 'flake8.extensions' => array(
+ 'type' => 'optional map<string, string>',
+ 'help' => pht('Map of extension names to version requirments.'),
+ ),
+ 'flake8.python' => array(
+ 'type' => 'optional string',
+ 'help' => pht('Python version requirement.'),
+ ),
+ );
+
+ return $options + parent::getLinterConfigurationOptions();
+ }
+
+ public function setLinterConfigurationValue($key, $value) {
+ switch ($key) {
+ case 'flake8.install':
+ $this->installInstructions = $value;
+ return;
+ case 'flake8.extensions':
+ $this->extensionVersions = $value;
+ return;
+ case 'flake8.python':
+ $this->pythonVersion = $value;
+ return;
+ }
+
+ return parent::setLinterConfigurationValue($key, $value);
+ }
+
+ public function getDefaultBinary() {
+ return 'flake8';
+ }
+
+ private function checkVersion($version, $compare_to) {
+ $operator = '==';
+
+ $matches = null;
+ if (preg_match('/^([<>]=?|=)\s*(.*)$/', $compare_to, $matches)) {
+ $operator = $matches[1];
+ $compare_to = $matches[2];
+ if ($operator === '=') {
+ $operator = '==';
+ }
+ }
+
+ return version_compare($version, $compare_to, $operator);
+ }
+
+ private function parseExtensionVersions($line) {
+ $regex = '/([\w-]+): (\d+\.\d+(?:\.\d+)?)/';
+ $matches = array();
+ if (!preg_match_all($regex, $line, $matches, PREG_SET_ORDER)) {
+ return array();
+ }
+
+ return ipull($matches, 2, 1);
+ }
+
+ public function getVersion() {
+ list($stdout) = execx('%C --version', $this->getExecutableCommand());
+
+ $regex =
+ '/^(?P<version>\d+\.\d+(?:\.\d+)?) '. # flake8 version
+ '\((?P<extensions>.*)\) '. # extension list
+ '.*(?P<python>\d+\.\d+\.\d+)/'; # python version
+ $matches = array();
+ if (!preg_match($regex, $stdout, $matches)) {
+ return false;
+ }
+
+ if (!empty($this->pythonVersion) &&
+ !$this->checkVersion($matches['python'], $this->pythonVersion)) {
+ $message = pht(
+ '%s requires %s using Python version %s but found Python version %s.',
+ get_class($this),
+ $this->getBinary(),
+ $matches['python'],
+ $this->pythonVersion);
+ throw new ArcanistMissingLinterException($message);
+ }
+
+ if (!empty($this->extensionVersions)) {
+ $versions = $this->parseExtensionVersions($matches['extensions']);
+
+ foreach ($this->extensionVersions as $name => $required) {
+ $installed = array_key_exists($name, $versions);
+
+ if (!$installed || !$this->checkVersion($versions[$name], $required)) {
+ $message = pht(
+ "%s requires flake8 '%s' extension version %s.",
+ get_class($this),
+ $name,
+ $required);
+
+ if ($installed) {
+ $message .= pht(' You have version %s.', $versions[$name]);
+ }
+
+ $instructions = $this->getInstallInstructions();
+ if ($instructions) {
+ $message .= "\n".pht('TO INSTALL: %s', $instructions);
+ }
+
+ throw new ArcanistMissingLinterException($message);
+ }
+ }
+ }
+
+ return $matches['version'];
+ }
+
+ public function getInstallInstructions() {
+ if ($this->installInstructions) {
+ return $this->installInstructions;
+ }
+ return pht('Install flake8 using `%s`.', 'pip install flake8');
+ }
+
+ public function getUpgradeInstructions() {
+ return $this->getInstallInstructions();
+ }
+
+ protected function parseLinterOutput($path, $err, $stdout, $stderr) {
+ $lines = phutil_split_lines($stdout, false);
+
+ // stdin:2: W802 undefined name 'foo' # pyflakes
+ // stdin:3:1: E302 expected 2 blank lines, found 1 # pep8
+ $regexp =
+ '/^(?:.*?):(?P<line>\d+):(?:(?P<char>\d+):)? (?P<code>\S+) (?P<msg>.*)$/';
+
+ $messages = array();
+ foreach ($lines as $line) {
+ $matches = null;
+ if (!preg_match($regexp, $line, $matches)) {
+ continue;
+ }
+ foreach ($matches as $key => $match) {
+ $matches[$key] = trim($match);
+ }
+
+ $message = new ArcanistLintMessage();
+ $message->setPath($path);
+ $message->setLine($matches['line']);
+ if (!empty($matches['char'])) {
+ $message->setChar($matches['char']);
+ }
+ $message->setCode($matches['code']);
+ $message->setName($this->getLinterName().' '.$matches['code']);
+ $message->setDescription($matches['msg']);
+ $message->setSeverity($this->getLintMessageSeverity($matches['code']));
+
+ $messages[] = $message;
+ }
+
+ return $messages;
+ }
+
+ protected function getDefaultMessageSeverity($code) {
+ if (preg_match('/^C/', $code)) {
+ // "C": Cyclomatic complexity
+ return ArcanistLintSeverity::SEVERITY_ADVICE;
+ } else if (preg_match('/^W/', $code)) {
+ // "W": PEP8 Warning
+ return ArcanistLintSeverity::SEVERITY_WARNING;
+ } else {
+ // "E": PEP8 Error
+ // "F": PyFlakes Error
+ // or: Flake8 Extension Message
+ return ArcanistLintSeverity::SEVERITY_ERROR;
+ }
+ }
+
+ protected function getLintCodeFromLinterConfigurationKey($code) {
+ if (!preg_match('/^[A-Z]\d+$/', $code)) {
+ throw new Exception(
+ pht(
+ 'Unrecognized lint message code "%s". Expected a valid flake8 '.
+ 'lint code like "%s", or "%s", or "%s", or "%s".',
+ $code,
+ 'E225',
+ 'W291',
+ 'F811',
+ 'C901'));
+ }
+
+ return $code;
+ }
+}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 8, 06:22 (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1034087
Default Alt Text
(14 KB)

Event Timeline