pax_global_header00006660000000000000000000000064141756165710014527gustar00rootroot0000000000000052 comment=4c3b3854f7fafc1ec56f7e4b58774aee722aace7 python-lsp-black-1.1.0/000077500000000000000000000000001417561657100147155ustar00rootroot00000000000000python-lsp-black-1.1.0/.github/000077500000000000000000000000001417561657100162555ustar00rootroot00000000000000python-lsp-black-1.1.0/.github/workflows/000077500000000000000000000000001417561657100203125ustar00rootroot00000000000000python-lsp-black-1.1.0/.github/workflows/python.yml000066400000000000000000000011241417561657100223540ustar00rootroot00000000000000name: Python on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: python -m pip install -e .[dev] - name: pre-commit checks uses: pre-commit/action@v2.0.2 - name: Tests run: pytest -v tests/ python-lsp-black-1.1.0/.gitignore000066400000000000000000000001111417561657100166760ustar00rootroot00000000000000.venv .mypy_cache *.egg-info *.pyc .pytest_cache .ropeproject build dist python-lsp-black-1.1.0/.pre-commit-config.yaml000066400000000000000000000011431417561657100211750ustar00rootroot00000000000000repos: - repo: https://github.com/PyCQA/isort rev: 5.10.1 hooks: - id: isort - repo: https://github.com/psf/black rev: 21.11b1 hooks: - id: black exclude: fixtures args: [--check, --config=pyproject.toml] - repo: https://gitlab.com/pycqa/flake8 rev: 3.9.2 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.910-1 hooks: - id: mypy additional_dependencies: [black, types-pkg_resources, types-setuptools, types-toml] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.0.1 hooks: - id: check-merge-conflict - id: debug-statements python-lsp-black-1.1.0/CHANGELOG.md000066400000000000000000000063231417561657100165320ustar00rootroot00000000000000## Version 1.1.0 (2022-01-30) ### Issues Closed * [Issue 29](https://github.com/python-lsp/python-lsp-black/issues/29) - TypeError when formatting with Black 22.1 ([PR 30](https://github.com/python-lsp/python-lsp-black/pull/30) by [@wlcx](https://github.com/wlcx)) * [Issue 25](https://github.com/python-lsp/python-lsp-black/issues/25) - Support global config file for black ([PR 19](https://github.com/python-lsp/python-lsp-black/pull/19) by [@jdost](https://github.com/jdost)) In this release 2 issues were closed. ### Pull Requests Merged * [PR 30](https://github.com/python-lsp/python-lsp-black/pull/30) - Fix TypeError when formatting with black 22.1.0+, by [@wlcx](https://github.com/wlcx) ([29](https://github.com/python-lsp/python-lsp-black/issues/29)) * [PR 19](https://github.com/python-lsp/python-lsp-black/pull/19) - Support global config as a fallback, by [@jdost](https://github.com/jdost) ([25](https://github.com/python-lsp/python-lsp-black/issues/25)) In this release 2 pull requests were closed. ## Version 1.0.1 (2021-12-01) ### Issues Closed * [Issue 20](https://github.com/python-lsp/python-lsp-black/issues/20) - Formatting fails silently * [Issue 12](https://github.com/python-lsp/python-lsp-black/issues/12) - Fix MyPy linting * [Issue 9](https://github.com/python-lsp/python-lsp-black/issues/9) - Ignore virtualenv in linters * [Issue 8](https://github.com/python-lsp/python-lsp-black/issues/8) - Add Development section to README * [Issue 7](https://github.com/python-lsp/python-lsp-black/issues/7) - Add pre-commit checks In this release 5 issues were closed. ### Pull Requests Merged * [PR 23](https://github.com/python-lsp/python-lsp-black/pull/23) - Add pre-commit hooks, by [@haplo](https://github.com/haplo) * [PR 22](https://github.com/python-lsp/python-lsp-black/pull/22) - Log black errors to stderr, by [@haplo](https://github.com/haplo) ([20](https://github.com/python-lsp/python-lsp-black/issues/20)) * [PR 14](https://github.com/python-lsp/python-lsp-black/pull/14) - Add virtualenv to gitignore and Python 3.9 to black target versions, by [@haplo](https://github.com/haplo) * [PR 13](https://github.com/python-lsp/python-lsp-black/pull/13) - Install MyPy stubs, by [@haplo](https://github.com/haplo) ([12](https://github.com/python-lsp/python-lsp-black/issues/12)) * [PR 11](https://github.com/python-lsp/python-lsp-black/pull/11) - Add Development section to README, by [@haplo](https://github.com/haplo) ([8](https://github.com/python-lsp/python-lsp-black/issues/8)) * [PR 10](https://github.com/python-lsp/python-lsp-black/pull/10) - Exclude venv and other directories from linters, by [@haplo](https://github.com/haplo) ([9](https://github.com/python-lsp/python-lsp-black/issues/9)) In this release 6 pull request was closed. ## Version 1.0.0 (2021/05/18) ### Issues Closed - [Issue 3](https://github.com/python-lsp/python-lsp-black/issues/3) - Update README and add RELEASE instructions - [Issue 2](https://github.com/python-lsp/python-lsp-black/issues/2) - Release v1.0.0 In this release 2 issues were closed. ### Pull Requests Merged - [PR 1](https://github.com/python-lsp/python-lsp-black/pull/1) - PR: Python LSP server migration, by [@andfoy](https://github.com/andfoy) In this release 1 pull request was closed. python-lsp-black-1.1.0/LICENSE000066400000000000000000000021371417561657100157250ustar00rootroot00000000000000MIT License Copyright (c) 2018-2020 Rupert Bedford Copyright (c) 2021 Python LSP contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. python-lsp-black-1.1.0/MAINTENANCE.md000066400000000000000000000010041417561657100167540ustar00rootroot00000000000000The following are instructions to maintain python-lsp-black 1. Releases are tracked using Github milestones, which can be created and closed under the `Issues > Milestones` page: https://github.com/python-lsp/python-lsp-black/milestones. 1. If a issue will be fixed as part of a particular release, then its milestone should be the release-corresponding one. 1. Please make sure that PRs are also tracked under a specific milestone. 1. Please follow the [RELEASE.md](./RELEASE.md) file when making a release. python-lsp-black-1.1.0/Makefile000066400000000000000000000005731417561657100163620ustar00rootroot00000000000000lint: pre-commit run -a black: pre-commit run -a black flake8: pre-commit run -a flake8 isort: pre-commit run -a isort mypy: pre-commit run -a mypy test: pytest -v . build: lint test python3 setup.py sdist bdist_wheel test-upload: twine upload --repository-url https://test.pypi.org/legacy/ dist/* upload: twine upload dist/* clean: rm -rf dist .PHONY: build python-lsp-black-1.1.0/README.md000066400000000000000000000040661417561657100162020ustar00rootroot00000000000000# python-lsp-black [![PyPI](https://img.shields.io/pypi/v/pyls-black.svg)](https://pypi.org/project/python-lsp-black) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Python](https://github.com/python-lsp/python-lsp-black/actions/workflows/python.yml/badge.svg)](https://github.com/python-lsp/python-lsp-black/actions/workflows/python.yml) > [Black](https://github.com/psf/black) plugin for the [Python LSP Server](https://github.com/python-lsp/python-lsp-server). ## Install In the same `virtualenv` as `python-lsp-server`: ```shell pip install python-lsp-black ``` # Usage To avoid unexpected results you should make sure `yapf` and `autopep8` are not installed. - `python-lsp-black` can either format an entire file or just the selected text. - The code will only be formatted if it is syntactically valid Python. - Text selections are treated as if they were a separate Python file. Unfortunately this means you can't format an indented block of code. - `python-lsp-black` will use your project's [pyproject.toml](https://github.com/psf/black#pyprojecttoml) if it has one. - `python-lsp-black` only officially supports the latest stable version of [black](https://github.com/psf/black). An effort is made to keep backwards-compatibility but older black versions will not be actively tested. # Development To install the project for development you need to specify the dev optional dependencies: ```shell python -m venv .venv . .venv/bin/activate pip install -e .[dev] ``` This project uses [pre-commit](https://pre-commit.com/) hooks to control code quality, install them to run them when creating a git commit, thus avoiding seeing errors when you create a pull request: ```shell pre-commit install ``` To run tests: ```shell make test ``` To run linters: ```shell make lint # just a shortcut to pre-commit run -a make # black, flake8, isort, mypy ``` To upgrade the version of the pre-commit hooks: ```shell pre-commit autoupdate # check and git commit changes to .pre-commit-config.yaml ``` python-lsp-black-1.1.0/RELEASE.md000066400000000000000000000014341417561657100163210ustar00rootroot00000000000000To release a new version of python-lsp-black: 1. git fetch upstream && git checkout upstream/master 1. Close milestone on GitHub 1. git clean -xfdi 1. Update CHANGELOG.md with loghub: `loghub python-lsp/python-lsp-black --milestone vX.X.X` 1. git add -A && git commit -m "Update Changelog" 1. Update release version in `setup.cfg` (set release version, remove '.dev0') 1. git add -A && git commit -m "Release vX.X.X" 1. python setup.py sdist 1. python setup.py bdist_wheel 1. twine check dist/\* 1. twine upload dist/\* 1. git tag -a vX.X.X -m "Release vX.X.X" 1. Update development version in `setup.cfg` (add '.dev0' and increment minor) 1. git add -A && git commit -m "Back to work" 1. git push upstream master 1. git push upstream --tags 1. Draft a new release in GitHub using the new tag. python-lsp-black-1.1.0/pylsp_black/000077500000000000000000000000001417561657100172205ustar00rootroot00000000000000python-lsp-black-1.1.0/pylsp_black/__init__.py000066400000000000000000000000001417561657100213170ustar00rootroot00000000000000python-lsp-black-1.1.0/pylsp_black/plugin.py000066400000000000000000000106651417561657100211000ustar00rootroot00000000000000import logging import os from pathlib import Path from typing import Dict, Optional import black import toml from pylsp import hookimpl logger = logging.getLogger(__name__) GLOBAL_CONFIG: Optional[Path] = None try: if os.name == "nt": GLOBAL_CONFIG = Path.home() / ".black" elif "XDG_CONFIG_HOME" in os.environ: GLOBAL_CONFIG = Path(os.environ["XDG_CONFIG_HOME"]) / "black" else: GLOBAL_CONFIG = Path.home() / ".config" / "black" except Exception as e: logger.error("Error determining black global config file path: %s", e) else: if GLOBAL_CONFIG is not None and GLOBAL_CONFIG.exists(): logger.info("Found black global config file at %s", GLOBAL_CONFIG) @hookimpl(tryfirst=True) def pylsp_format_document(document): return format_document(document) @hookimpl(tryfirst=True) def pylsp_format_range(document, range): range["start"]["character"] = 0 range["end"]["line"] += 1 range["end"]["character"] = 0 return format_document(document, range) def format_document(document, range=None): if range: start = range["start"]["line"] end = range["end"]["line"] text = "".join(document.lines[start:end]) else: text = document.source range = { "start": {"line": 0, "character": 0}, "end": {"line": len(document.lines), "character": 0}, } config = load_config(document.path) try: formatted_text = format_text(text=text, config=config) except black.NothingChanged: # raised when the file is already formatted correctly return [] return [{"range": range, "newText": formatted_text}] def format_text(*, text, config): mode = black.FileMode( target_versions=config["target_version"], line_length=config["line_length"], is_pyi=config["pyi"], string_normalization=not config["skip_string_normalization"], ) try: # will raise black.NothingChanged, we want to bubble that exception up return black.format_file_contents(text, fast=config["fast"], mode=mode) except ( # raised when the file has syntax errors ValueError, # raised when the file being formatted has an indentation error IndentationError, # raised when black produces invalid Python code or formats the file # differently on the second pass AssertionError, ) as e: # errors will show on lsp stderr stream logger.error("Error formatting with black: %s", e) raise black.NothingChanged from e def load_config(filename: str) -> Dict: defaults = { "line_length": 88, "fast": False, "pyi": filename.endswith(".pyi"), "skip_string_normalization": False, "target_version": set(), } root = black.find_project_root((filename,)) # Black 22.1.0+ returns a tuple if isinstance(root, tuple): pyproject_filename = root[0] / "pyproject.toml" else: pyproject_filename = root / "pyproject.toml" if not pyproject_filename.is_file(): if GLOBAL_CONFIG is not None and GLOBAL_CONFIG.exists(): pyproject_filename = GLOBAL_CONFIG logger.info("Using global black config at %s", pyproject_filename) else: logger.info("Using defaults: %r", defaults) return defaults try: pyproject_toml = toml.load(str(pyproject_filename)) except (toml.TomlDecodeError, OSError): logger.warning( "Error decoding pyproject.toml, using defaults: %r", defaults, ) return defaults file_config = pyproject_toml.get("tool", {}).get("black", {}) file_config = { key.replace("--", "").replace("-", "_"): value for key, value in file_config.items() } config = { key: file_config.get(key, default_value) for key, default_value in defaults.items() } if file_config.get("target_version"): target_version = set( black.TargetVersion[x.upper()] for x in file_config["target_version"] ) elif file_config.get("py36"): target_version = { black.TargetVersion.PY36, black.TargetVersion.PY37, black.TargetVersion.PY38, black.TargetVersion.PY39, } else: target_version = set() config["target_version"] = target_version logger.info("Using config from %s: %r", pyproject_filename, config) return config python-lsp-black-1.1.0/pyproject.toml000066400000000000000000000002271417561657100176320ustar00rootroot00000000000000[tool.black] target-version = ['py36', 'py37', 'py38', 'py39'] exclude = ''' /( \.venv | \.git | \.mypy_cache | build | dist | fixtures )/ ''' python-lsp-black-1.1.0/setup.cfg000066400000000000000000000023031417561657100165340ustar00rootroot00000000000000[metadata] name = python-lsp-black version = 1.1.0 author = Python LSP contributors author_email = f@fidelramos.net description = Black plugin for the Python LSP Server url = https://github.com/python-lsp/python-lsp-black long_description = file: README.md long_description_content_type = text/markdown project_urls = Bug Tracker = https://github.com/python-lsp/python-lsp-black/issues Changelog = https://github.com/python-lsp/python-lsp-black/blob/master/CHANGELOG.md Source Code = https://github.com/python-lsp/python-lsp-black classifiers = Programming Language :: Python License :: OSI Approved :: MIT License Operating System :: OS Independent [options] packages = find: install_requires = python-lsp-server; black>=19.3b0; toml python_requires = >= 3.6 [options.entry_points] pylsp = pylsp_black = pylsp_black.plugin [options.extras_require] # add any types-* packages to .pre-commit-config.yaml mypy additional_dependencies dev = isort>=5.0; flake8; pre-commit; pytest; mypy; pytest; types-pkg_resources; types-setuptools; types-toml [flake8] max-line-length = 88 ignore = E203 exclude = .venv [mypy] ignore_missing_imports = true [isort] profile = black skip_glob = [".venv"] python-lsp-black-1.1.0/setup.py000066400000000000000000000001051417561657100164230ustar00rootroot00000000000000from setuptools import setup if __name__ == "__main__": setup() python-lsp-black-1.1.0/tests/000077500000000000000000000000001417561657100160575ustar00rootroot00000000000000python-lsp-black-1.1.0/tests/fixtures/000077500000000000000000000000001417561657100177305ustar00rootroot00000000000000python-lsp-black-1.1.0/tests/fixtures/config/000077500000000000000000000000001417561657100211755ustar00rootroot00000000000000python-lsp-black-1.1.0/tests/fixtures/config/config.txt000066400000000000000000000000531417561657100232010ustar00rootroot00000000000000run(these, arguments, should, be, wrapped) python-lsp-black-1.1.0/tests/fixtures/config/pyproject.toml000066400000000000000000000001301417561657100241030ustar00rootroot00000000000000[tool.black] line-length = 20 --fast = true pyi = true skip-string-normalization = true python-lsp-black-1.1.0/tests/fixtures/formatted.pyi000066400000000000000000000000551417561657100224400ustar00rootroot00000000000000def foo() -> None: ... def bar() -> int: ... python-lsp-black-1.1.0/tests/fixtures/formatted.txt000066400000000000000000000000231417561657100224510ustar00rootroot00000000000000a = "hello" b = 42 python-lsp-black-1.1.0/tests/fixtures/invalid.txt000066400000000000000000000000141417561657100221120ustar00rootroot00000000000000 x = 1+2 python-lsp-black-1.1.0/tests/fixtures/py36/000077500000000000000000000000001417561657100205315ustar00rootroot00000000000000python-lsp-black-1.1.0/tests/fixtures/py36/pyproject.toml000066400000000000000000000000311417561657100234370ustar00rootroot00000000000000[tool.black] py36 = true python-lsp-black-1.1.0/tests/fixtures/pyproject.toml000066400000000000000000000000001417561657100226320ustar00rootroot00000000000000python-lsp-black-1.1.0/tests/fixtures/target_version/000077500000000000000000000000001417561657100227635ustar00rootroot00000000000000python-lsp-black-1.1.0/tests/fixtures/target_version/pyproject.toml000066400000000000000000000000471417561657100257000ustar00rootroot00000000000000[tool.black] target-version = ['py39'] python-lsp-black-1.1.0/tests/fixtures/unformatted.pyi000066400000000000000000000000661417561657100230050ustar00rootroot00000000000000def foo() -> None: ... def bar() -> int: ... python-lsp-black-1.1.0/tests/fixtures/unformatted.txt000066400000000000000000000000241417561657100230150ustar00rootroot00000000000000a = 'hello' b = 42 python-lsp-black-1.1.0/tests/test_plugin.py000066400000000000000000000131531417561657100207710ustar00rootroot00000000000000# Standard library imports import types from pathlib import Path from unittest.mock import Mock # Third-party imports import black import pkg_resources import pytest # Python LSP imports from pylsp import uris from pylsp.workspace import Document, Workspace # Local imports from pylsp_black.plugin import load_config, pylsp_format_document, pylsp_format_range here = Path(__file__).parent fixtures_dir = here / "fixtures" @pytest.fixture def workspace(tmpdir): """Return a workspace.""" return Workspace(uris.from_fs_path(str(tmpdir)), Mock()) @pytest.fixture def unformatted_document(workspace): path = fixtures_dir / "unformatted.txt" uri = f"file:/{path}" return Document(uri, workspace) @pytest.fixture def unformatted_pyi_document(workspace): path = fixtures_dir / "unformatted.pyi" uri = f"file:/{path}" return Document(uri, workspace) @pytest.fixture def formatted_document(workspace): path = fixtures_dir / "formatted.txt" uri = f"file:/{path}" return Document(uri, workspace) @pytest.fixture def formatted_pyi_document(workspace): path = fixtures_dir / "formatted.pyi" uri = f"file:/{path}" return Document(uri, workspace) @pytest.fixture def invalid_document(workspace): path = fixtures_dir / "invalid.txt" uri = f"file:/{path}" return Document(uri, workspace) @pytest.fixture def config_document(workspace): path = fixtures_dir / "config" / "config.txt" uri = f"file:/{path}" return Document(uri, workspace) def test_pylsp_format_document(unformatted_document, formatted_document): result = pylsp_format_document(unformatted_document) assert result == [ { "range": { "start": {"line": 0, "character": 0}, "end": {"line": 2, "character": 0}, }, "newText": formatted_document.source, } ] def test_pyls_format_pyi_document(unformatted_pyi_document, formatted_pyi_document): result = pylsp_format_document(unformatted_pyi_document) assert result == [ { "range": { "start": {"line": 0, "character": 0}, "end": {"line": 5, "character": 0}, }, "newText": formatted_pyi_document.source, } ] def test_pylsp_format_document_unchanged(formatted_document): result = pylsp_format_document(formatted_document) assert result == [] def test_pyls_format_pyi_document_unchanged(formatted_pyi_document): result = pylsp_format_document(formatted_pyi_document) assert result == [] def test_pylsp_format_document_syntax_error(invalid_document): result = pylsp_format_document(invalid_document) assert result == [] def test_pylsp_format_document_with_config(config_document): result = pylsp_format_document(config_document) assert result == [ { "range": { "start": {"line": 0, "character": 0}, "end": {"line": 1, "character": 0}, }, "newText": ( "run(\n" " these,\n" " arguments,\n" " should,\n" " be,\n" " wrapped,\n" ")\n" ), } ] @pytest.mark.parametrize( ("start", "end", "expected"), [(0, 0, 'a = "hello"\n'), (1, 1, "b = 42\n"), (0, 1, 'a = "hello"\nb = 42\n')], ) def test_pylsp_format_range(unformatted_document, start, end, expected): range = { "start": {"line": start, "character": 0}, "end": {"line": end, "character": 0}, } result = pylsp_format_range(unformatted_document, range=range) assert result == [ { "range": { "start": {"line": start, "character": 0}, "end": {"line": end + 1, "character": 0}, }, "newText": expected, } ] def test_pylsp_format_range_unchanged(formatted_document): range = {"start": {"line": 0, "character": 0}, "end": {"line": 1, "character": 0}} result = pylsp_format_range(formatted_document, range=range) assert result == [] def test_pylsp_format_range_syntax_error(invalid_document): range = {"start": {"line": 0, "character": 0}, "end": {"line": 1, "character": 0}} result = pylsp_format_range(invalid_document, range=range) assert result == [] def test_load_config(): config = load_config(str(fixtures_dir / "config" / "example.py")) # TODO split into smaller tests assert config == { "line_length": 20, "target_version": set(), "pyi": True, "fast": True, "skip_string_normalization": True, } def test_load_config_target_version(): config = load_config(str(fixtures_dir / "target_version" / "example.py")) assert config["target_version"] == {black.TargetVersion.PY39} def test_load_config_py36(): config = load_config(str(fixtures_dir / "py36" / "example.py")) assert config["target_version"] == { black.TargetVersion.PY36, black.TargetVersion.PY37, black.TargetVersion.PY38, black.TargetVersion.PY39, } def test_load_config_defaults(): config = load_config(str(fixtures_dir / "example.py")) assert config == { "line_length": 88, "target_version": set(), "pyi": False, "fast": False, "skip_string_normalization": False, } def test_entry_point(): distribution = pkg_resources.get_distribution("python-lsp-black") entry_point = distribution.get_entry_info("pylsp", "pylsp_black") assert entry_point is not None module = entry_point.load() assert isinstance(module, types.ModuleType)