diff --git a/src/flake8/checker.py b/src/flake8/checker.py index b32fd3e..c8b27b5 100644 --- a/src/flake8/checker.py +++ b/src/flake8/checker.py @@ -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 = ( diff --git a/src/flake8/main/options.py b/src/flake8/main/options.py index b13a4f3..ff5523c 100644 --- a/src/flake8/main/options.py +++ b/src/flake8/main/options.py @@ -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)', diff --git a/tests/fixtures/example-code/script b/tests/fixtures/example-code/script new file mode 100644 index 0000000..4265cc3 --- /dev/null +++ b/tests/fixtures/example-code/script @@ -0,0 +1 @@ +#!/usr/bin/env python diff --git a/tests/unit/test_checker_manager.py b/tests/unit/test_checker_manager.py index 02397f0..0984e3f 100644 --- a/tests/unit/test_checker_manager.py +++ b/tests/unit/test_checker_manager.py @@ -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