Lint Python files with a shebang

When a pattern is not passed to flake8 (--filename), look for all files
that end with .py as well as extension-less files that start with a
Python shebang. Helps project lint scripts that may not have an
extension.

Fixes #409
This commit is contained in:
Jon Dufresne 2018-06-06 12:25:26 -07:00
parent a2b7a7e4c5
commit 36a70fd110
4 changed files with 79 additions and 3 deletions

View file

@ -3,6 +3,7 @@ import collections
import errno
import logging
import os
import re
import signal
import sys
import tokenize
@ -54,6 +55,8 @@ class Manager(object):
together and make our output deterministic.
"""
SHEBANG_RE = re.compile(br'^#!.*\bpython[23w]?\b')
def __init__(self, style_guide, arguments, checker_plugins):
"""Initialize our Manager instance.
@ -199,9 +202,26 @@ class Manager(object):
paths = ['.']
filename_patterns = self.options.filename
patterns_exist = True
if not filename_patterns:
filename_patterns = ['*.py']
patterns_exist = False
running_from_vcs = self.options._running_from_vcs
running_from_diff = self.options.diff
def has_shebang(filename):
if patterns_exist:
# If a user explicitly specifies something, e.g. ``*.py``,
# don't inspect the shebang.
return False
if not os.path.isfile(filename):
return False
with open(filename, 'rb') as fp:
line = fp.readline(100)
return bool(self.SHEBANG_RE.match(line))
# NOTE(sigmavirus24): Yes this is a little unsightly, but it's our
# best solution right now.
def should_create_file_checker(filename, argument):
@ -220,8 +240,8 @@ class Manager(object):
explicitly_provided = (not running_from_vcs and
not running_from_diff and
(argument == filename))
return ((explicitly_provided or matches_filename_patterns) or
is_stdin)
return (explicitly_provided or matches_filename_patterns or
is_stdin or has_shebang(filename))
checks = self.checks.to_dictionary()
checkers = (

View file

@ -72,7 +72,7 @@ def register_default_options(option_manager):
)
add_option(
'--filename', metavar='patterns', default='*.py',
'--filename', metavar='patterns',
parse_from_config=True, comma_separated_list=True,
help='Only check for filenames matching the patterns in this comma-'
'separated list. (Default: %default)',

1
tests/fixtures/example-code/script vendored Normal file
View file

@ -0,0 +1 @@
#!/usr/bin/env python

View file

@ -1,5 +1,6 @@
"""Tests for the Manager object for FileCheckers."""
import errno
import os
import mock
import pytest
@ -70,3 +71,57 @@ def test_make_checkers():
for file_checker in manager.checkers:
assert file_checker.filename in files
def test_make_checkers_shebang():
"""Verify that extension-less files with a Python shebang are checked."""
style_guide = style_guide_mock(
filename=[],
exclude=[],
)
checkplugins = mock.Mock()
checkplugins.to_dictionary.return_value = {
'ast_plugins': [],
'logical_line_plugins': [],
'physical_line_plugins': [],
}
with mock.patch('flake8.checker.multiprocessing', None):
manager = checker.Manager(style_guide, [], checkplugins)
path = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', 'fixtures')
)
manager.make_checkers([path])
filenames = [
os.path.relpath(file_checker.filename, path)
for file_checker in manager.checkers
]
assert 'example-code/script' in filenames
def test_make_checkers_explicit_pattern_ignore_shebang():
"""Verify that shebangs are ignored when passing a pattern."""
style_guide = style_guide_mock(
filename=['*.py'],
exclude=[],
)
checkplugins = mock.Mock()
checkplugins.to_dictionary.return_value = {
'ast_plugins': [],
'logical_line_plugins': [],
'physical_line_plugins': [],
}
with mock.patch('flake8.checker.multiprocessing', None):
manager = checker.Manager(style_guide, [], checkplugins)
path = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', 'fixtures')
)
manager.make_checkers([path])
filenames = [
os.path.relpath(file_checker.filename, path)
for file_checker in manager.checkers
]
assert 'example-code/script' not in filenames