diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 618f5d01..1ead77c8 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -12,15 +12,15 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code from GitHub - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v4.2.2 with: fetch-depth: 2 - - name: Set up Python 3.11 - uses: actions/setup-python@v4 + - name: Set up Python 3.13 + uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.13" - name: Install dependencies - run: pip install -U -r requirements.txt + run: pip install -U -r docs/requirements-doc.txt - name: Make docs run: | cd docs diff --git a/.github/workflows/primer.yaml b/.github/workflows/primer.yaml index 6b0f825b..c3aa6c5d 100644 --- a/.github/workflows/primer.yaml +++ b/.github/workflows/primer.yaml @@ -12,18 +12,18 @@ jobs: permissions: write-all steps: - name: Check out working version - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v4.2.2 with: fetch-depth: 2 - name: Check out changeable version - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v4.2.2 with: path: program_to_test fetch-depth: 0 - - name: Set up Python 3.11 - uses: actions/setup-python@v4 + - name: Set up Python 3.13 + uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.13" - name: Install dependencies run: pip install -U -r requirements.txt - name: Run primer @@ -49,16 +49,14 @@ jobs: git checkout $GITHUB_SHA cd .. python -m pydocstringformatter._testutils.primer.primer --step-two - - name: Upload primer diff - uses: actions/upload-artifact@v3.1.1 - with: - name: primer_diffs - path: .pydocstringformatter_primer_tests/fulldiff.txt - - name: Save PR number + - name: Save PR number and copy fulldiff.txt run: | echo ${{ github.event.pull_request.number }} | tee pr_number.txt - - name: Upload PR number - uses: actions/upload-artifact@v3.1.1 + cp .pydocstringformatter_primer_tests/fulldiff.txt fulldiff.txt + - name: Upload primer diff and PR number + uses: actions/upload-artifact@v4.6.2 with: name: primer_diffs - path: pr_number.txt + path: | + pr_number.txt + fulldiff.txt diff --git a/.github/workflows/primer_comment.yaml b/.github/workflows/primer_comment.yaml index 2d391bc9..4da85942 100644 --- a/.github/workflows/primer_comment.yaml +++ b/.github/workflows/primer_comment.yaml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download diffs - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | const fs = require('fs'); @@ -39,7 +39,7 @@ jobs: - run: unzip diff.zip - name: Post comment id: post-comment - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 7dfff55f..5153f867 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -6,23 +6,24 @@ on: jobs: publish-to-pypi: - name: Publish to pypi runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/pydocstringformatter + permissions: + id-token: write steps: - name: Check out code from GitHub - uses: actions/checkout@v3.2.0 - - name: Set up Python 3.11 - uses: actions/setup-python@v4 + uses: actions/checkout@v4.2.2 + - name: Set up Python 3.x + uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.x" - name: Install dependencies run: | - pip install -U -r requirements.txt - pip install -U build + pip install -r requirements.txt + pip install build - name: Build package run: python -m build - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 92be92ed..d5800070 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -13,13 +13,13 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8, 3.9, "3.10", "3.11", "3.12-dev"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] steps: - name: Check out code from GitHub - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v4.2.2 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -27,8 +27,9 @@ jobs: - name: Run pytest run: pytest -vv --cov - name: Upload coverage artifact - uses: actions/upload-artifact@v3.1.1 + uses: actions/upload-artifact@v4.6.2 with: + include-hidden-files: true name: coverage-linux-${{ matrix.python-version }} path: .coverage @@ -38,17 +39,17 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8, 3.9, "3.10", "3.11", "3.12-dev"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] steps: - name: Check out code from GitHub - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v4.2.2 - name: Set temp directory run: echo "TEMP=$env:USERPROFILE\AppData\Local\Temp" >> $env:GITHUB_ENV # Workaround to set correct temp directory on Windows # https://github.com/actions/virtual-environments/issues/712 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -56,8 +57,9 @@ jobs: - name: Run pytest run: pytest -vv --cov - name: Upload coverage artifact - uses: actions/upload-artifact@v3.1.1 + uses: actions/upload-artifact@v4.6.2 with: + include-hidden-files: true name: coverage-windows-${{ matrix.python-version }} path: .coverage @@ -67,33 +69,35 @@ jobs: needs: ["tests-linux", "tests-windows"] steps: - name: Check out code from GitHub - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v4.2.2 - name: Set up Python 3.11 id: python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install dependencies run: pip install -U -r requirements-coverage.txt - name: Download all coverage artifacts - uses: actions/download-artifact@v3.0.1 + uses: actions/download-artifact@v4.3.0 - name: Combine Linux coverage results run: | coverage combine coverage-linux*/.coverage coverage xml -o coverage-linux.xml - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v5 with: fail_ci_if_error: true verbose: true flags: linux files: coverage-linux.xml + token: ${{ secrets.CODECOV_TOKEN }} - name: Combine Windows coverage results run: | coverage combine coverage-windows*/.coverage coverage xml -o coverage-windows.xml - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v5 with: fail_ci_if_error: true verbose: true flags: windows files: coverage-windows.xml + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index b0fa8e8f..4b6dc342 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ pydocstringformatter.egg-info dist/ docs/_build + +.coverage diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7660487b..5b888259 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v5.0.0 hooks: - id: trailing-whitespace exclude: "tests/data/format/no_whitespace_stripper|tests/data/format/whitespace_stripper|tests/data/format/quotes_type|tests/data/format/newlines|tests/test_config.py" @@ -10,7 +10,7 @@ repos: - id: check-toml exclude: &test-data "tests/data" - repo: https://github.com/PyCQA/autoflake - rev: v2.0.0 + rev: v2.3.1 hooks: - id: autoflake exclude: *test-data @@ -21,31 +21,31 @@ repos: - --remove-duplicate-keys - --remove-unused-variables - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + rev: v3.20.0 hooks: - id: pyupgrade args: [--py38-plus] - repo: https://github.com/PyCQA/isort - rev: 5.11.4 + rev: 6.0.1 hooks: - id: isort - repo: https://github.com/psf/black - rev: 22.12.0 + rev: 25.1.0 hooks: - id: black - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 + rev: 7.3.0 hooks: - id: flake8 exclude: *test-data - - repo: https://github.com/pycqa/pylint - rev: v2.15.9 + - repo: https://github.com/pylint-dev/pylint + rev: v3.3.7 hooks: - id: pylint exclude: *test-data args: ["--disable=import-error, cyclic-import"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.991 + rev: v1.16.1 hooks: - id: mypy args: [--config-file=pyproject.toml] @@ -53,13 +53,13 @@ repos: additional_dependencies: [pytest-stub==1.1.0, types-docutils~=0.17.5, sphinx~=4.4] - repo: https://github.com/DanielNoord/pydocstringformatter - rev: v0.7.2 + rev: v0.7.3 hooks: - id: pydocstringformatter exclude: *test-data args: [] - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.0-alpha.4 + rev: v4.0.0-alpha.8 hooks: - id: prettier exclude: *test-data diff --git a/.readthedocs.yaml b/.readthedocs.yaml index c14a572d..d00b8be2 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -12,6 +12,6 @@ sphinx: configuration: docs/conf.py build: - os: ubuntu-20.04 + os: "ubuntu-lts-latest" tools: - python: "3.10" + python: "latest" diff --git a/README.md b/README.md index ad715ca5..04d25e3a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ guides.) See [What it does](#what-it-does) for currently supported auto-formatting. -### Rationale +**Rationale** This project is heavily inspired by [`docformatter`](https://github.com/PyCQA/docformatter). diff --git a/docs/_ext/link_fixer.py b/docs/_ext/link_fixer.py deleted file mode 100644 index 1f067cda..00000000 --- a/docs/_ext/link_fixer.py +++ /dev/null @@ -1,65 +0,0 @@ -from __future__ import annotations - -import re -from typing import Any - -from docutils import nodes -from sphinx import addnodes, application -from sphinx.transforms import SphinxTransform - -DOCUMENTATION_LINK = "https://pydocstringformatter.readthedocs.io/en/latest/" - - -class LinkTransformer(SphinxTransform): - """Transformer to make all internal links actually internal. - - For example: - Transforms https://pydocstringformatter.readthedocs.io/en/latest/usage.html - into a link to docs/usage.rst. - """ - - # Set priority very low so that we run after everything else has been done - default_priority = 1000 - - docs_regex = re.compile(r".*[/\\]docs[/\\]") - - def apply(self, **_: dict[str, Any]) -> None: - """Apply the transformation.""" - for node in self.document.traverse(nodes.reference): - if ( - "refuri" in node - and node["refuri"].startswith(DOCUMENTATION_LINK) - # Don't 'fix' the ReadTheDocs badge - and not node["refuri"].endswith("?badge=latest") - ): - # Get the ref link - link = node["refuri"].replace(DOCUMENTATION_LINK, "") - link = link.replace(".html", ".rst") - - # Get the source link - source_link = self.document["source"] - source_link = re.sub(self.docs_regex, "", source_link) - source_link = source_link.replace(".rst", "") - - # Create pending xref - ref = addnodes.pending_xref( - refdoc=source_link, - reftype="myst", - reftarget=link, - refexplicit=True, - refdomain=True, - refwarn=True, - ) - - # Make inline and add text for link - inline = nodes.inline(classes=["xref", "myst"]) - inline += node.children[0] - ref += inline - - # Replace previous link - node.parent.replace(node, ref) - - -def setup(app: application.Sphinx) -> None: - """Required function to register the Sphinx extension.""" - app.add_transform(LinkTransformer) diff --git a/docs/conf.py b/docs/conf.py index ad889af9..ad3f8ca7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,7 +23,7 @@ # -- General configuration --------------------------------------------------- -extensions = ["myst_parser", "docs._ext.usage_page", "docs._ext.link_fixer"] +extensions = ["myst_parser", "docs._ext.usage_page"] myst_heading_anchors = 2 source_suffix = [".rst", ".md"] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] diff --git a/docs/development.rst b/docs/development.rst index 0c38d8e2..57dc4319 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -4,7 +4,8 @@ Development Linting and checks ------------------ -Use ``pre-commit install`` to install the pre-commit hook for the repository. +Use ``pip`` to install ``pre-commit`` and then use ``pre-commit install`` to +install the pre-commit hook for the repository. Creating a new formatter ------------------------ diff --git a/docs/requirements-doc.txt b/docs/requirements-doc.txt index 7dc83032..467211d3 100644 --- a/docs/requirements-doc.txt +++ b/docs/requirements-doc.txt @@ -1,5 +1,5 @@ -e . -myst-parser~=0.18 -sphinx~=5.3.0 -furo==2022.12.7 -types-docutils~=0.19 +myst-parser~=4.0 +sphinx~=8.2.3 +furo==2024.8.6 +types-docutils~=0.21 diff --git a/pydocstringformatter/__init__.py b/pydocstringformatter/__init__.py index deb27e63..9451e6cf 100644 --- a/pydocstringformatter/__init__.py +++ b/pydocstringformatter/__init__.py @@ -8,7 +8,7 @@ PydocstringFormatterError, ) -__version__ = "0.7.3" +__version__ = "0.7.5" def run_docstring_formatter(argv: list[str] | None = None) -> None: diff --git a/pydocstringformatter/_configuration/boolean_option_action.py b/pydocstringformatter/_configuration/boolean_option_action.py index 41d90827..ce34113a 100644 --- a/pydocstringformatter/_configuration/boolean_option_action.py +++ b/pydocstringformatter/_configuration/boolean_option_action.py @@ -12,7 +12,7 @@ class BooleanOptionalAction(argparse.Action): This action class is only available in 3.9+. Hence the need to backport it. """ - # pylint: disable-next=too-many-arguments + # pylint: disable-next=too-many-arguments,too-many-positional-arguments def __init__( self, option_strings: Sequence[str], @@ -27,17 +27,17 @@ def __init__( # Non-argparse changes assert help, "All BooleanOptionalAction's should have a help message." - # Rest of implementation directly copied from argparse + # Rest of implementation directly copied from argparse, expect for the asserts _option_strings = [] for option_string in option_strings: _option_strings.append(option_string) - if option_string.startswith("--"): - option_string = "--no-" + option_string[2:] - _option_strings.append(option_string) + assert option_string.startswith("--") + option_string = "--no-" + option_string[2:] + _option_strings.append(option_string) - if help is not None and default is not None: - help += " (default: %(default)s)" + assert help is not None and default is not None + help += " (default: %(default)s)" super().__init__( option_strings=_option_strings, @@ -63,8 +63,8 @@ def __call__( "BooleanOptionalAction can't be a positional argument. " f"Something is wrong with {self.option_strings[0]}" ) - if option_string in self.option_strings: - setattr(namespace, self.dest, not option_string.startswith("--no-")) + assert option_string in self.option_strings + setattr(namespace, self.dest, not option_string.startswith("--no-")) def format_usage(self) -> str: """Return usage string.""" diff --git a/pydocstringformatter/_formatting/base.py b/pydocstringformatter/_formatting/base.py index 882246f8..a00cbaa0 100644 --- a/pydocstringformatter/_formatting/base.py +++ b/pydocstringformatter/_formatting/base.py @@ -73,7 +73,7 @@ def treat_token(self, tokeninfo: tokenize.TokenInfo) -> tokenize.TokenInfo: class StringAndQuotesFormatter(Formatter): """Base class for string formatter that needs access to the quotes.""" - quotes_regex = re.compile(r"""['"]{1,3}""") + quotes_regex = re.compile(r"""^('{3}|'|"{3}|")""") """Pattern to match against opening quotes.""" @abc.abstractmethod diff --git a/pydocstringformatter/_formatting/formatters_default.py b/pydocstringformatter/_formatting/formatters_default.py index d397d75d..674ddcc4 100644 --- a/pydocstringformatter/_formatting/formatters_default.py +++ b/pydocstringformatter/_formatting/formatters_default.py @@ -36,7 +36,7 @@ def treat_string(self, tokeninfo: tokenize.TokenInfo, _: int) -> str: or self.config.summary_quotes_same_line # Config for multi-line or self.potential_single_line.match(new_string) # Potential single line ): - new_string = re.sub(r"\n *", "", new_string, 1) + new_string = re.sub(r"\n *", "", new_string, count=1) return new_string @@ -44,7 +44,9 @@ class CapitalizeFirstLetterFormatter(StringFormatter): """Capitalize the first letter of the docstring if appropriate.""" name = "capitalize-first-letter" - first_letter_re = re.compile(r"""['"]{1,3}\s*(\w)""", re.DOTALL) + first_letter_re = re.compile( + StringAndQuotesFormatter.quotes_regex.pattern + r"""\s*(\w)""", re.DOTALL + ) def treat_string(self, tokeninfo: tokenize.TokenInfo, _: int) -> str: new_string = None @@ -77,7 +79,6 @@ def treat_summary( # Without a description we need to consider the length including closing quotes if not description_exists: - # Calculate length without the ending quotes length_without_ending = indent_length + quotes_length + len(summary) diff --git a/pydocstringformatter/_formatting/formatters_numpydoc.py b/pydocstringformatter/_formatting/formatters_numpydoc.py index a11473e9..7f063734 100644 --- a/pydocstringformatter/_formatting/formatters_numpydoc.py +++ b/pydocstringformatter/_formatting/formatters_numpydoc.py @@ -83,9 +83,9 @@ def treat_sections( # Avoid adding trailing whitespace # Colon ending first line is suggested for long # "See Also" links - section_lines[ - index - ] = f"{line_name.rstrip():s} : {line_type.lstrip():s}" + section_lines[index] = ( + f"{line_name.rstrip():s} : {line_type.lstrip():s}" + ) return sections diff --git a/pydocstringformatter/_testutils/__init__.py b/pydocstringformatter/_testutils/__init__.py index c0a95bd6..eb1506e8 100644 --- a/pydocstringformatter/_testutils/__init__.py +++ b/pydocstringformatter/_testutils/__init__.py @@ -17,6 +17,8 @@ LOGGER = logging.getLogger(__name__) +UPDATE_OUTPUT_OPTION = "--update-expected-output" + class FormatterAsserter(contextlib.AbstractContextManager): # type: ignore[type-arg] """ContextManager used to assert that a Formatter does something on a docstring. @@ -84,4 +86,10 @@ def __exit__( return None -__all__ = ["FormatterAsserter", "MakeAFormatter", "MakeBFormatter", "AddBFormatter"] +__all__ = [ + "FormatterAsserter", + "MakeAFormatter", + "MakeBFormatter", + "AddBFormatter", + "UPDATE_OUTPUT_OPTION", +] diff --git a/pyproject.toml b/pyproject.toml index 47ea447f..ce4a68d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ description = "A tool to automatically format Python docstrings that tries to fo readme = "README.md" license = {text = "MIT"} classifiers = [ - "Development Status :: 4 - Beta", + "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", @@ -28,6 +28,9 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development :: Quality Assurance", ] @@ -55,6 +58,11 @@ include = ["pydocstringformatter*"] [tool.pytest] testpaths = "tests" +[tool.pytest.ini_options] +filterwarnings = [ + "error" +] + [tool.mypy] files = "pydocstringformatter,tests" exclude = "tests/data.*" @@ -70,7 +78,6 @@ ignore_missing_imports = true load-plugins=[ "pylint.extensions.check_elif", "pylint.extensions.code_style", - "pylint.extensions.comparetozero", "pylint.extensions.confusing_elif", "pylint.extensions.docparams", "pylint.extensions.docstyle", @@ -97,6 +104,7 @@ known_third_party = ["pytest"] skip_glob = "tests/data/**" [tool.coverage.run] +branch = true relative_files = true [tool.black] diff --git a/requirements-coverage.txt b/requirements-coverage.txt index e427e0c3..4af33dee 100644 --- a/requirements-coverage.txt +++ b/requirements-coverage.txt @@ -1,2 +1,2 @@ -e . -coverage[toml]==7.0.1 +coverage[toml]==7.9.2 diff --git a/requirements.txt b/requirements.txt index 0f4ecd7e..1b221c47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,8 @@ # Coverage + local install of package +-e . -r requirements-coverage.txt -# Requirements for docs building --r docs/requirements-doc.txt # Requirements for testing and linting -pytest==7.2.0 -pytest-cov==4.0.0 +pytest==8.4.1 +pytest-cov==6.2.1 gitpython>=3 -pre-commit==2.21.0 diff --git a/tests/conftest.py b/tests/conftest.py index 5c815689..66a31c9d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,8 @@ import pytest +from pydocstringformatter._testutils import UPDATE_OUTPUT_OPTION + @pytest.fixture def test_file(tmp_path: Path) -> str: @@ -10,3 +12,14 @@ def test_file(tmp_path: Path) -> str: with open(filename, "w", encoding="utf-8") as file: file.write('"""A multi-line\ndocstring."""') return str(filename) + + +def pytest_addoption(parser: pytest.Parser) -> None: + """Add command line options to pytest.""" + parser.addoption( + UPDATE_OUTPUT_OPTION, + action="store_true", + help="Update the expected output for tests. This will overwrite the output " + "files to be as the tests are currently producing them.", + default=False, + ) diff --git a/tests/data/config/valid_toml_without_section/pyproject.toml b/tests/data/config/valid_toml_without_section/pyproject.toml new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/config/valid_toml_without_section/test_package/correct_docstring.py b/tests/data/config/valid_toml_without_section/test_package/correct_docstring.py new file mode 100644 index 00000000..2ff9bc63 --- /dev/null +++ b/tests/data/config/valid_toml_without_section/test_package/correct_docstring.py @@ -0,0 +1 @@ +"""A docstring""" diff --git a/tests/data/format/capitalization_first_letter/letter_after_quote.py b/tests/data/format/capitalization_first_letter/letter_after_quote.py new file mode 100644 index 00000000..24bbecae --- /dev/null +++ b/tests/data/format/capitalization_first_letter/letter_after_quote.py @@ -0,0 +1,11 @@ +def a(self): + "'a.m.' or 'p.m.'" + +def b(self): + """'a.m.' or 'p.m.'""" + +def c(self): + "'''a.m.''' or 'p.m.'" + +def d(self): + """'''a.m.''' or 'p.m.'""" diff --git a/tests/data/format/capitalization_first_letter/letter_after_quote.py.out b/tests/data/format/capitalization_first_letter/letter_after_quote.py.out new file mode 100644 index 00000000..71f0c33c --- /dev/null +++ b/tests/data/format/capitalization_first_letter/letter_after_quote.py.out @@ -0,0 +1,11 @@ +def a(self): + """'a.m.' or 'p.m.'.""" + +def b(self): + """'a.m.' or 'p.m.'.""" + +def c(self): + """'''a.m.''' or 'p.m.'.""" + +def d(self): + """'''a.m.''' or 'p.m.'.""" diff --git a/tests/data/format/linewrap_summary/function_docstrings.py b/tests/data/format/linewrap_summary/function_docstrings.py index cc4a9da9..a24e4c78 100644 --- a/tests/data/format/linewrap_summary/function_docstrings.py +++ b/tests/data/format/linewrap_summary/function_docstrings.py @@ -39,3 +39,11 @@ def func(): # We should re-add the quotes to line length if they will never be on the first line. class LinesChunk: """The LinesChunk object computes and stores the hash of some consecutive stripped lines of a lineset.""" + + +# Test for multiple periods at the end of the line +def func(): + """A very long summary line that needs to be wrapped, A very long summary line that needs to be wrapp... + + A description that is not too long. + """ diff --git a/tests/data/format/linewrap_summary/function_docstrings.py.out b/tests/data/format/linewrap_summary/function_docstrings.py.out index 9160c296..102de0ee 100644 --- a/tests/data/format/linewrap_summary/function_docstrings.py.out +++ b/tests/data/format/linewrap_summary/function_docstrings.py.out @@ -54,3 +54,13 @@ class LinesChunk: lines of a lineset. """ + + +# Test for multiple periods at the end of the line +def func(): + """A very long summary line that needs to be wrapped, A very long summary line that. + + needs to be wrapp... + + A description that is not too long. + """ diff --git a/tests/data/format/numpydoc/numpydoc_style_without_type.args b/tests/data/format/numpydoc/numpydoc_style_without_type.args new file mode 100644 index 00000000..203bfdb5 --- /dev/null +++ b/tests/data/format/numpydoc/numpydoc_style_without_type.args @@ -0,0 +1,3 @@ +--style=numpydoc +--no-final-period +--no-closing-quotes diff --git a/tests/data/format/numpydoc/numpydoc_style_without_type.py b/tests/data/format/numpydoc/numpydoc_style_without_type.py new file mode 100644 index 00000000..0c87dc61 --- /dev/null +++ b/tests/data/format/numpydoc/numpydoc_style_without_type.py @@ -0,0 +1,13 @@ +def sincos(theta): + """Returns + ---- + sin: float + the sine of theta + cos: float + the cosine of theta + Parameters + ----- + theta: + the angle at which to calculate the sine and cosine. +""" + return math.sin(theta), math.cos(theta) diff --git a/tests/data/format/numpydoc/numpydoc_style_without_type.py.out b/tests/data/format/numpydoc/numpydoc_style_without_type.py.out new file mode 100644 index 00000000..31486c2e --- /dev/null +++ b/tests/data/format/numpydoc/numpydoc_style_without_type.py.out @@ -0,0 +1,14 @@ +def sincos(theta): + """Parameters + ------------- + theta: + the angle at which to calculate the sine and cosine. + + Returns + ------- + sin : float + the sine of theta + cos : float + the cosine of theta + """ + return math.sin(theta), math.cos(theta) diff --git a/tests/data/format/quotes_type/mix_with_double_at_start.py b/tests/data/format/quotes_type/mix_with_double_at_start.py new file mode 100644 index 00000000..24bbecae --- /dev/null +++ b/tests/data/format/quotes_type/mix_with_double_at_start.py @@ -0,0 +1,11 @@ +def a(self): + "'a.m.' or 'p.m.'" + +def b(self): + """'a.m.' or 'p.m.'""" + +def c(self): + "'''a.m.''' or 'p.m.'" + +def d(self): + """'''a.m.''' or 'p.m.'""" diff --git a/tests/data/format/quotes_type/mix_with_double_at_start.py.out b/tests/data/format/quotes_type/mix_with_double_at_start.py.out new file mode 100644 index 00000000..71f0c33c --- /dev/null +++ b/tests/data/format/quotes_type/mix_with_double_at_start.py.out @@ -0,0 +1,11 @@ +def a(self): + """'a.m.' or 'p.m.'.""" + +def b(self): + """'a.m.' or 'p.m.'.""" + +def c(self): + """'''a.m.''' or 'p.m.'.""" + +def d(self): + """'''a.m.''' or 'p.m.'.""" diff --git a/tests/data/format/quotes_type/mix_with_single_at_start.py b/tests/data/format/quotes_type/mix_with_single_at_start.py new file mode 100644 index 00000000..5f194c81 --- /dev/null +++ b/tests/data/format/quotes_type/mix_with_single_at_start.py @@ -0,0 +1,11 @@ +def a(self): + '"a.m." or "p.m."' + +def b(self): + '''"a.m." or "p.m."''' + +def c(self): + '"""a.m.""" or "p.m."' + +def d(self): + '''"""a.m.""" or "p.m."''' diff --git a/tests/data/format/quotes_type/mix_with_single_at_start.py.out b/tests/data/format/quotes_type/mix_with_single_at_start.py.out new file mode 100644 index 00000000..3d55babe --- /dev/null +++ b/tests/data/format/quotes_type/mix_with_single_at_start.py.out @@ -0,0 +1,11 @@ +def a(self): + """"a.m." or "p.m.".""" + +def b(self): + """"a.m." or "p.m.".""" + +def c(self): + """"""a.m.""" or "p.m.".""" + +def d(self): + """"""a.m.""" or "p.m.".""" diff --git a/tests/test_config.py b/tests/test_config.py index 618b6bae..0a668b26 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -307,3 +307,8 @@ def test_non_bool_boolopt_in_toml(self, monkeypatch: pytest.MonkeyPatch) -> None ) assert error_msg in str(err.value) + + def test_valid_toml_without_section(self, monkeypatch: pytest.MonkeyPatch) -> None: + """Test that we leave a projecttoml without a section for this tool alone.""" + monkeypatch.chdir(CONFIG_DATA / "valid_toml_without_section") + _Run(["test_package"]) diff --git a/tests/test_conflicting_formatters.py b/tests/test_conflicting_formatters.py index d38e99d0..7a087607 100644 --- a/tests/test_conflicting_formatters.py +++ b/tests/test_conflicting_formatters.py @@ -14,7 +14,7 @@ @contextmanager -def patched_run(formatters: list[Formatter]) -> Generator[type[_Run], None, None]: +def patched_run(formatters: list[Formatter]) -> Generator[type[_Run]]: """Patches formatters so Run only uses the provided formatters.""" old_formatters = _formatting.FORMATTERS _formatting.FORMATTERS = formatters diff --git a/tests/test_formatting.py b/tests/test_formatting.py index 5f1a7615..2d5a5660 100644 --- a/tests/test_formatting.py +++ b/tests/test_formatting.py @@ -7,6 +7,7 @@ import pytest import pydocstringformatter +from pydocstringformatter._testutils import UPDATE_OUTPUT_OPTION HERE = Path(__file__) TEST_DATA = HERE.parent / "data" / "format" @@ -28,7 +29,10 @@ ids=TEST_NAMES, ) def test_formatting( - test_file: str, capsys: pytest.CaptureFixture[str], tmp_path: pathlib.Path + test_file: str, + capsys: pytest.CaptureFixture[str], + tmp_path: pathlib.Path, + request: pytest.FixtureRequest, ) -> None: """Test that we correctly format all files in the format directory. @@ -76,7 +80,23 @@ def test_formatting( [temp_file_name, "--write"] + additional_args ) - output = capsys.readouterr() - assert output.err == error_message.format(testfile=os.path.abspath(temp_file_name)) + error_output = capsys.readouterr() + assert error_output.err == error_message.format( + testfile=os.path.abspath(temp_file_name) + ) with open(temp_file_name, "rb") as f: - assert f.read() == expected_output + output = f.read() + try: + assert output.decode("utf-8") == expected_output.decode("utf-8") + except AssertionError as e: # pragma: no cover + if request.config.getoption(UPDATE_OUTPUT_OPTION): + with open(test_name, "wb") as fw: + fw.write(output) + pytest.fail( + "Updated expected output. Please check the changes and commit them." + ) + + raise AssertionError( + f"Output of '{Path(test_file).stem}' does not match expected output. " + f"Run with '{UPDATE_OUTPUT_OPTION}' to update the expected output." + ) from e diff --git a/tests/test_utils.py b/tests/test_utils.py index aae25753..71312027 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -149,7 +149,7 @@ def test_encoding_of_console_messages( Regression test for: https://github.com/DanielNoord/pydocstringformatter/issues/13 """ - sys.stdout.reconfigure(encoding="cp1252") # type: ignore[attr-defined] + sys.stdout.reconfigure(encoding="cp1252") # type: ignore[union-attr] with open(test_file, "w", encoding="utf-8") as file: file.write('"""A multi-line.\n\ndocstring.\n"""') @@ -176,3 +176,14 @@ def test_formatter_comparer() -> None: for section in expected_sections: assert section in diff + + diff = compare_formatters(tokeninfo, MakeAFormatter(), MakeBFormatter()) + + expected_sections = [ + "--- make-a-formatter vs make-b-formatter\n", + '-"""AAA AA AAA"""\n', + '+"""BBB BB BBB"""\n', + ] + + for section in expected_sections: + assert section in diff
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: