diff --git a/pyls/config/flake8_conf.py b/pyls/config/flake8_conf.py index 47b6cfad..d79814dd 100644 --- a/pyls/config/flake8_conf.py +++ b/pyls/config/flake8_conf.py @@ -26,6 +26,7 @@ ('ignore', 'plugins.flake8.ignore', list), ('max-line-length', 'plugins.flake8.maxLineLength', int), ('select', 'plugins.flake8.select', list), + ('per-file-ignores', 'plugins.flake8.perFileIgnores', list), ] diff --git a/pyls/config/source.py b/pyls/config/source.py index c3442e01..b17ba0e3 100644 --- a/pyls/config/source.py +++ b/pyls/config/source.py @@ -67,6 +67,8 @@ def _get_opt(config, key, option, opt_type): def _parse_list_opt(string): + if string.startswith("\n"): + return [s.strip().rstrip(",") for s in string.split("\n") if s.strip()] return [s.strip() for s in string.split(",") if s.strip()] diff --git a/pyls/plugins/flake8_lint.py b/pyls/plugins/flake8_lint.py index 4f2e054e..974e7f65 100644 --- a/pyls/plugins/flake8_lint.py +++ b/pyls/plugins/flake8_lint.py @@ -6,6 +6,13 @@ from subprocess import Popen, PIPE from pyls import hookimpl, lsp +try: + + from pathlib import PurePath +except ImportError: + from pathlib2 import PurePath + + log = logging.getLogger(__name__) FIX_IGNORES_RE = re.compile(r'([^a-zA-Z0-9_,]*;.*(\W+||$))') @@ -22,12 +29,21 @@ def pyls_lint(workspace, document): settings = config.plugin_settings('flake8', document_path=document.path) log.debug("Got flake8 settings: %s", settings) + ignores = settings.get("ignore", []) + per_file_ignores = settings.get("perFileIgnores") + + if per_file_ignores: + for path in per_file_ignores: + file_pat, errors = path.split(":") + if PurePath(document.path).match(file_pat): + ignores.extend(errors.split(",")) + opts = { 'config': settings.get('config'), 'exclude': settings.get('exclude'), 'filename': settings.get('filename'), 'hang-closing': settings.get('hangClosing'), - 'ignore': settings.get('ignore'), + 'ignore': ignores or None, 'max-line-length': settings.get('maxLineLength'), 'select': settings.get('select'), } diff --git a/setup.py b/setup.py index 12782990..8938ca1e 100755 --- a/setup.py +++ b/setup.py @@ -9,6 +9,7 @@ install_requires = [ 'configparser; python_version<"3.0"', 'future>=0.14.0; python_version<"3"', + 'pathlib2>=2.3.5; python_version<"3"', 'backports.functools_lru_cache; python_version<"3.2"', 'jedi>=0.17.2,<0.18.0', 'python-jsonrpc-server>=0.4.0', diff --git a/test/plugins/test_flake8_lint.py b/test/plugins/test_flake8_lint.py index 75adf4ea..04bdf499 100644 --- a/test/plugins/test_flake8_lint.py +++ b/test/plugins/test_flake8_lint.py @@ -84,3 +84,77 @@ def test_flake8_executable_param(workspace): call_args = popen_mock.call_args.args[0] assert flake8_executable in call_args + + +def get_flake8_cfg_settings(workspace, config_str): + """Write a ``setup.cfg``, load it in the workspace, and return the flake8 settings. + + This function creates a ``setup.cfg``; you'll have to delete it yourself. + """ + + with open(os.path.join(workspace.root_path, "setup.cfg"), "w+") as f: + f.write(config_str) + + workspace.update_config({"pyls": {"configurationSources": ["flake8"]}}) + + return workspace._config.plugin_settings("flake8") + + +def test_flake8_multiline(workspace): + config_str = r"""[flake8] +exclude = + blah/, + file_2.py + """ + + doc_str = "print('hi')\nimport os\n" + + doc_uri = uris.from_fs_path(os.path.join(workspace.root_path, "blah/__init__.py")) + workspace.put_document(doc_uri, doc_str) + + flake8_settings = get_flake8_cfg_settings(workspace, config_str) + + assert "exclude" in flake8_settings + assert len(flake8_settings["exclude"]) == 2 + + with patch('pyls.plugins.flake8_lint.Popen') as popen_mock: + mock_instance = popen_mock.return_value + mock_instance.communicate.return_value = [bytes(), bytes()] + + doc = workspace.get_document(doc_uri) + flake8_lint.pyls_lint(workspace, doc) + + call_args = popen_mock.call_args.args[0] + assert call_args == ["flake8", "-", "--exclude=blah/,file_2.py"] + + os.unlink(os.path.join(workspace.root_path, "setup.cfg")) + + +def test_flake8_per_file_ignores(workspace): + config_str = r"""[flake8] +ignores = F403 +per-file-ignores = + **/__init__.py:F401,E402 + test_something.py:E402, +exclude = + file_1.py + file_2.py + """ + + doc_str = "print('hi')\nimport os\n" + + doc_uri = uris.from_fs_path(os.path.join(workspace.root_path, "blah/__init__.py")) + workspace.put_document(doc_uri, doc_str) + + flake8_settings = get_flake8_cfg_settings(workspace, config_str) + + assert "perFileIgnores" in flake8_settings + assert len(flake8_settings["perFileIgnores"]) == 2 + assert "exclude" in flake8_settings + assert len(flake8_settings["exclude"]) == 2 + + doc = workspace.get_document(doc_uri) + res = flake8_lint.pyls_lint(workspace, doc) + assert not res + + os.unlink(os.path.join(workspace.root_path, "setup.cfg"))