diff --git a/flake8/plugins/manager.py b/flake8/plugins/manager.py index 21171ef..21639f9 100644 --- a/flake8/plugins/manager.py +++ b/flake8/plugins/manager.py @@ -6,6 +6,7 @@ import pkg_resources from flake8 import exceptions from flake8.plugins import notifier +from flake8 import utils LOG = logging.getLogger(__name__) @@ -296,6 +297,25 @@ class Checkers(PluginTypeManager): namespace = 'flake8.extension' + def checks_expecting(self, argument_name): + """Retrieve checks that expect an argument with the specified name. + + Find all checker plugins that are expecting a specific argument. + """ + for plugin in self.plugins.values(): + parameters = utils.parameters_for(plugin) + if argument_name in parameters: + yield plugin + + @property + def physical_line_plugins(self): + """List of plugins that expect the physical lines.""" + plugins = getattr(self, '_physical_line_plugins', []) + if not plugins: + plugins = list(self.checks_expecting('physical_line')) + self._physical_line_plugins = plugins + return plugins + class Listeners(PluginTypeManager, NotifierBuilderMixin): """All of the listeners registered through entry-points.""" diff --git a/flake8/utils.py b/flake8/utils.py index fbc90bd..cd1cf72 100644 --- a/flake8/utils.py +++ b/flake8/utils.py @@ -1,5 +1,6 @@ """Utility methods for flake8.""" import fnmatch as _fnmatch +import inspect import io import os import sys @@ -144,3 +145,40 @@ def fnmatch(filename, patterns, default=True): if not patterns: return default return any(_fnmatch.fnmatch(filename, pattern) for pattern in patterns) + + +def parameters_for(plugin): + # type: (flake8.plugins.manager.Plugin) -> List[str] + """Return the parameters for the plugin. + + This will inspect the plugin and return either the function parameters + if the plugin is a function or the parameters for ``__init__`` after + ``self`` if the plugin is a class. + + :param plugin: + The internal plugin object. + :type plugin: + flake8.plugins.manager.Plugin + :returns: + Parameters to the plugin. + :rtype: + list(str) + """ + func = plugin.plugin + is_class = not inspect.isfunction(func) + if is_class: # The plugin is a class + func = plugin.plugin.__init__ + + if sys.version_info < (3, 3): + parameters = inspect.getargspec(func)[0] + else: + parameters = [ + parameter.name + for parameter in inspect.signature(func).parameters.values() + if parameter.kind == parameter.POSITIONAL_OR_KEYWORD + ] + + if is_class: + parameters.remove('self') + + return parameters diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 34b4909..615f327 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -4,6 +4,7 @@ import mock import pytest +from flake8.plugins import manager as plugin_manager from flake8 import utils @@ -86,3 +87,24 @@ def test_filenames_from_a_single_file(): assert len(filenames) == 1 assert ['flake8/__init__.py'] == filenames + + +def test_parameters_for_class_plugin(): + """Verify that we can retrieve the parameters for a class plugin.""" + class FakeCheck(object): + def __init__(self, tree): + pass + + plugin = plugin_manager.Plugin('plugin-name', object()) + plugin._plugin = FakeCheck + assert utils.parameters_for(plugin) == ['tree'] + + +def test_parameters_for_function_plugin(): + """Verify that we retrieve the parameters for a function plugin.""" + def fake_plugin(physical_line, self, tree): + pass + + plugin = plugin_manager.Plugin('plugin-name', object()) + plugin._plugin = fake_plugin + assert utils.parameters_for(plugin) == ['physical_line', 'self', 'tree']