A tool (and pre-commit hook) to automatically add trailing commas to calls and literals.

Overview

Build Status Azure DevOps coverage pre-commit.ci status

add-trailing-comma

A tool (and pre-commit hook) to automatically add trailing commas to calls and literals.

Installation

pip install add-trailing-comma

As a pre-commit hook

See pre-commit for instructions

Sample .pre-commit-config.yaml:

-   repo: https://github.com/asottile/add-trailing-comma
    rev: v2.1.0
    hooks:
    -   id: add-trailing-comma

multi-line method invocation style -- why?

# Sample of *ideal* syntax
function_call(
    argument,
    5 ** 5,
    kwarg=foo,
)
  • the initial paren is at the end of the line
  • each argument is indented one level further than the function name
  • the last parameter (unless the call contains an unpacking (*args / **kwargs)) has a trailing comma

This has the following benefits:

  • arbitrary indentation is avoided:

    # I hear you like 15 space indents
    # oh your function name changed? guess you get to reindent :)
    very_long_call(arg,
                   arg,
                   arg)
  • adding / removing a parameter preserves git blame and is a minimal diff:

     # with no trailing commas
     x(
    -    arg
    +    arg,
    +    arg2
     )
     # with trailing commas
     x(
         arg,
    +    arg2,
     )

Implemented features

trailing commas for function calls

 x(
     arg,
-    arg
+    arg,
 )

trailing commas for function calls with unpackings

If --py35-plus is passed, add-trailing-comma will also perform the following change:

 x(
-    *args
+    *args,
 )
 y(
-    **kwargs
+    **kwargs,
 )

Note that this would cause a SyntaxError in earlier python versions.

trailing commas for tuple / list / dict / set literals

 x = [
-    1, 2, 3
+    1, 2, 3,
 ]

trailing commas for function definitions

 def func(
         arg1,
-        arg2
+        arg2,
 ):
 async def func(
         arg1,
-        arg2
+        arg2,
 ):

trailing commas for function definitions with unpacking arguments

If --py36-plus is passed, add-trailing-comma will also perform the following change:

 def f(
-    *args
+    *args,
 ): pass


 def g(
-    **kwargs
+    **kwargs,
 ): pass


 def h(
-    *, kw=1
+    *, kw=1,
 ): pass

Note that this would cause a SyntaxError in earlier python versions.

trailing commas for from imports

 from os import (
     path,
-    makedirs
+    makedirs,
 )

trailing comma for class definitions

 class C(
     Base1,
-    Base2
+    Base2,
 ):
     pass

unhug trailing paren

 x(
     arg1,
-    arg2)
+    arg2,
+)

unhug leading paren

-function_name(arg1,
-              arg2)
+function_name(
+    arg1,
+    arg2,
+)

match closing brace indentation

 x = [
     1,
     2,
     3,
-    ]
+]

remove unnecessary commas

yes yes, I realize the tool is called add-trailing-comma 😆

-[1, 2, 3,]
-[1, 2, 3, ]
+[1, 2, 3]
+[1, 2, 3]
Comments
  • False negative with statement ending with parens

    False negative with statement ending with parens

    if (foo and
        bar()):
        pass
    

    I expect to be rewritten to

    if (
        foo and
        bar()
    ):
        pass
    

    But I imagine the same code that prevents this from being rewritten is firing:

    foo('bar {}'.format(
        'baz',
    ))
    

    which I don't think should be rewritten.

    Need to come up with a more clever heuristic here.

    enhancement 
    opened by asottile 14
  • RFE: --diff and --recursive

    RFE: --diff and --recursive

    I think the following would be some good enhancements to this tool:

    • --diff like isort, which prints the suggested changes on stdout, instead of modifying the files.

      This is great for CI checks where a contributor gets to see the required changes to fix the situation directly.

    • --recursive option, to specify an entire project directory without globs.

      This would make CLI invocations easier.

    I'll be happy to write PRs for them if @asottile thinks they're reasonable.

    opened by pradyunsg 8
  • Not adding trailing comma

    Not adding trailing comma

    I have some code that black formatted like this actual

                extended_retry_message = textwrap.dedent(
                    f"""\
                    {retry_message[0]}
                    {retry_blurb}
                """
                )  # noqa: N400
    

    expected

                extended_retry_message = textwrap.dedent(
                    f"""\
                    {retry_message[0]}
                    {retry_blurb}
                """,
                )  # noqa: N400
    

    another actual

                            assert all(
                                (
                                    v == result["new_expectations"][k]
                                    for k, v in {
                                        "jenkins_id": jn.jenkins_id,
                                    }.items()
                                )
                            )
    
    

    expected

                            assert all(
                                (
                                    v == result["new_expectations"][k]
                                    for k, v in {
                                        "jenkins_id": jn.jenkins_id,
                                    }.items()
                                ),
                            )
    
    

    i have add-trailing-comma running right after black with --py36-plus and it doesnt add the trailing comma to that function call

     -   repo: https://github.com/asottile/add-trailing-comma
         rev: v2.2.1
         hooks:
         -   id: add-trailing-comma
             args: [--py36-plus]
    
    opened by merc1031 7
  • Trailing comma for function which enforces keyword arguments (`f(*, a, b, ...)`)

    Trailing comma for function which enforces keyword arguments (`f(*, a, b, ...)`)

    Didn't initially realise that args had to be on separate lines for functions in order for add-trailing-comma to modify (in original comment). Realised that and added to # edit.


    original comment

    # requirements.txt
    appdirs==1.4.4
    cfgv==3.3.0
    distlib==0.3.1
    filelock==3.0.12
    identify==2.2.6
    nodeenv==1.6.0
    pre-commit==2.13.0
    PyYAML==5.4.1
    six==1.16.0
    toml==0.10.2
    virtualenv==20.4.7
    
    #.pre-commit-config.yaml 
    minimum_pre_commit_version: 2.10.0
    repos:
      - repo: https://github.com/asottile/add-trailing-comma
        rev: v2.1.0
        hooks:
          - id: add-trailing-comma
            args: [--py36-plus]
    

    Running pre-commit as: pre-commit run --all-files I'm expecting (hoping...) with the following test file:

    def A(*, a):
        return True
    
    def B(a,b):
        return True
    
    def C(*args):
        return True
    
    x = [1, 2, 3]
    
    def function_call(a, b, c):
        return True 
    
    function_call(
        a=1, b=2, c=3
    )
    

    Gives the diff:

    diff --git a/test.py b/test.py
    index 6bc7d22..edf345f 100644
    --- a/test.py
    +++ b/test.py
    @@ -13,5 +13,5 @@ def function_call(a, b, c):
         return True
    
     function_call(
    -    a=1, b=2, c=3
    +    a=1, b=2, c=3,
     )
    

    Though the diff that I was expecting (...hoping?) for was:

    diff --git a/test.py b/test.py
    index 6bc7d22..768e441 100644
    --- a/test.py
    +++ b/test.py
    @@ -1,17 +1,17 @@
    -def A(*, a):
    +def A(*, a,):
         return True
    
    -def B(a,b):
    +def B(a,b,):
         return True
    
    -def C(*args):
    +def C(*args,):
         return True
    
    -x = [1, 2, 3]
    +x = [1, 2, 3,]
    
    -def function_call(a, b, c):
    +def function_call(a, b, c,):
         return True
    
     function_call(
    -    a=1, b=2, c=3
    +    a=1, b=2, c=3,
     )
    

    There's probably something that I'm missing - but having looked through the readme I'm not sure what.

    edit

    Given the following file:

    def A(*, 
    a):
        return True
    
    def B(a,
    b):
        return True
    
    def C(*args
    ):
        return True
    
    x = [1, 2, 3]
    
    def function_call(a, b, 
    c):
        return True 
    
    function_call(
        a=1, b=2, c=3
    )
    

    the diff is as expected having run : pre-commit run --all-files:

    diff --git a/test.py b/test.py
    index f37eb5c..361e427 100644
    --- a/test.py
    +++ b/test.py
    @@ -1,21 +1,28 @@
    -def A(*,
    -a):
    +def A(
    +    *,
    +    a,
    +):
         return True
    
    -def B(a,
    -b):
    +def B(
    +    a,
    +    b,
    +):
         return True
    
    -def C(*args
    +def C(
    +    *args,
     ):
         return True
    
     x = [1, 2, 3]
    
    -def function_call(a, b,
    -c):
    +def function_call(
    +    a, b,
    +    c,
    +):
         return True
    
     function_call(
    -    a=1, b=2, c=3
    +    a=1, b=2, c=3,
     )
    

    Seems I didn't realise that they had to be on separate lines in order for add-trailing-comma to pick up on them.

    If there's a way to set this so that add-trailing-comma added a trailing comma to functions all the time that would be ideal.

    If not then this may have shifted from an issue to a feature request and can probably be closed 😄

    opened by geo7 7
  • python 3.9.1 assertion error on Windows 10

    python 3.9.1 assertion error on Windows 10

    I ran into the following error when committing: VSCode console output:

    > git -c user.useConfigOnly=true commit --quiet --allow-empty-message --file -
    Trim Trailing Whitespace.................................................Passed
    Fix End of Files.........................................................Passed
    Check docstring is first.................................................Passed
    Debug Statements (Python)................................................Passed
    Fix requirements.txt.................................(no files to check)Skipped
    Check for added large files..............................................Passed
    Check python ast.........................................................Passed
    Check builtin type constructor use.......................................Passed
    Check for case conflicts.................................................Passed
    Detect Destroyed Symlinks................................................Passed
    Check for merge conflicts................................................Passed
    Check Yaml...........................................(no files to check)Skipped
    Debug Statements (Python)................................................Passed
    Detect Private Key.......................................................Passed
    fix UTF-8 byte order marker..............................................Passed
    Add trailing commas......................................................Failed
    - hook id: add-trailing-comma
    - exit code: 1
    
    Traceback (most recent call last):
      File "c:\program files\python39\lib\runpy.py", line 197, in _run_module_as_main
        return _run_code(code, main_globals, None,
      File "c:\program files\python39\lib\runpy.py", line 87, in _run_code
        exec(code, run_globals)
      File "C:\Users\navdh\.cache\pre-commit\repoy6phnb7j\py_env-python3.9\Scripts\add-trailing-comma.EXE\__main__.py", line 7, in <module>
      File "c:\users\navdh\.cache\pre-commit\repoy6phnb7j\py_env-python3.9\lib\site-packages\add_trailing_comma.py", line 498, in main
        ret |= fix_file(filename, args)
      File "c:\users\navdh\.cache\pre-commit\repoy6phnb7j\py_env-python3.9\lib\site-packages\add_trailing_comma.py", line 470, in fix_file
        contents_text = _fix_src(contents_text, args.py35_plus, args.py36_plus)
      File "c:\users\navdh\.cache\pre-commit\repoy6phnb7j\py_env-python3.9\lib\site-packages\add_trailing_comma.py", line 401, in _fix_src
        tokens, _find_call(call, i, tokens),
      File "c:\users\navdh\.cache\pre-commit\repoy6phnb7j\py_env-python3.9\lib\site-packages\add_trailing_comma.py", line 239, in _find_call
        raise AssertionError('Past end?')
    AssertionError: Past end?```
    
    Platform: Windows 10 21H1 OS Build 19043.906
    Python Version: 3.9.1
    

    My config:

    repos:
        - repo: https://github.com/pre-commit/pre-commit-hooks
          rev: v3.4.0
          hooks:
              - id: trailing-whitespace
              - id: end-of-file-fixer
              - id: check-docstring-first
              - id: debug-statements
              - id: requirements-txt-fixer
              - id: check-added-large-files
              - id: check-ast
              - id: check-builtin-literals
              - id: check-case-conflict
              - id: destroyed-symlinks
              - id: check-merge-conflict
              - id: check-yaml
              - id: debug-statements
              - id: detect-private-key
              - id: fix-byte-order-marker
        - repo: https://github.com/asottile/add-trailing-comma
          rev: v2.0.1
          hooks:
              - id: add-trailing-comma
                args: [--py36-plus]
    
    opened by infinity-plus 7
  • Add '-' to read from stdin

    Add '-' to read from stdin

    Tools like ALE and so forth generally pass the buffer of the editor directly to the tool to edit and expect to read it back on stdout. I've added the - to signal to add-trailing-comma to read data from stdin and output to stdout. Note: this is why I always output the text, regardless of whether it changed. ALE would just wipe the buffer if add-trailing-comma returned nothing.

    I modeled this mostly off of tools like black and isort. Black doesn't have any error handling around passing multiple files and -. Black also only briefly mentions it in the docs.

    I've also added two basic tests to check that a (mocked) stdin works.

    opened by theevocater 5
  • Support Windows line endings

    Support Windows line endings

    Previously, since the file was opened as bytes but written as text, \r\n got written as \r\r\n, since it read in \r\n as bytes, passed through the \r, then wrote out \r\n as text, which using Python's universal newline support automatically transforms \n into \r\n on Windows, resulting in \r\r\n. Thus, this was adding bunches of \r characters every time it was run. This PR fixes that.

    opened by evhub 5
  • Disable change to dangling commas on imports

    Disable change to dangling commas on imports

    We are using add-trailing-comma for dangling commas and isort to keep our imports organized. Ideally, both can run as pre-commit hooks. However, the two tools clash on statements like this:

    from my_library.api.database.models import (Model1, Model2, Model3, Model4, Model5, Model6
                                                Model7,)
    

    where isort prefers this style and add-trailing-comma reformats it into:

    from my_library.api.database.models import (
        Model1, Model2, Model3, Model4, Model5, Model6
        Model7,
    )
    

    When trying to commit, no matter the order between isort and add-trailing-comma, the imports are always re-arranged, preventing commit. Disabling add-trailing-comma on imports would allow us to run isort and to use your tool for other parts of the file.

    opened by lynxoid 4
  • [Bug] Adding trailing comma to function with single argument

    [Bug] Adding trailing comma to function with single argument

    Before add-trailing-comma

    def g(dict_representation):
        assert (
            set(dict_representation["cli"].pop("characteristics")
                ) == set(cli_data.pop("characteristics"))
        )
    

    after add-trailing-comma

    def g(dict_representation):
        assert (
            set(
                dict_representation["cli"].pop("characteristics"), # there should not be any comma, because set() gets only one parameter
            ) == set(cli_data.pop("characteristics"))
        )
    
    opened by Jarvis1Tube 4
  • Even if successful, add-trailing-comma reading from stdin returns 1 as a status code in the shell. Is this the intended behavior?

    Even if successful, add-trailing-comma reading from stdin returns 1 as a status code in the shell. Is this the intended behavior?

    Create a test.py:

    # -*- coding: utf-8 -*-
    
    test = [
        1,
        2
    ]
    

    add-trailing-comma - < test.py outputs correctly:

    # -*- coding: utf-8 -*-
    
    test = [
        1,
        2,
    ]
    

    But the return statuscode is 1:

    ❯ echo $?                                                                                                                                                   [11:30:11]
    1
    

    This is a different behavior from other formatters, like black:

    black - < test.py                                                                                                                                         [11:30:46]
    # -*- coding: utf-8 -*-
    
    test = [1, 2]
    reformatted -
    All done! ✨ 🍰 ✨
    1 file reformatted.
     ❯ echo $?                                                                                                                                                   [11:30:54]
    0
    

    Some formatters in editors (like vim-autoformat) expect a 0 statuscode to make sure the formatter call was ok, and this is inconsistent with exit codes with special meanings.

    Since the stdin feature comes from a month old PR (https://github.com/asottile/add-trailing-comma/pull/75), I think it's nice to consider changing this to be standard like other tools.

    question 
    opened by idgserpro 4
  • Enable the `directory` args option in addition to `filenames`

    Enable the `directory` args option in addition to `filenames`

    Hey @asottile ; I am trying to use add-trailing-comma as part of formatting and use flake8-comma as check against it. I can see this as help:

    usage: add-trailing-comma [-h] [--exit-zero-even-if-changed] [--py35-plus] [--py36-plus] [filenames ...]
    

    My understanding is the directory option is not currently available. I have seen black or isort have this ability:

      --src SRC_PATHS, --src-path SRC_PATHS
                            Add an explicitly defined source path (modules within src paths have their imports automatically categorized as first_party). Glob expansion (`*` and `**`) is supported for
                            this option.
    

    I was wondering maybe this can be a very helpful feature. I also appreciate it if you direct me how can I apply this on a directory. For example, lets say I have src/ and tests/ and I wanna apply add-trailing-comma to all .py files. Currently, I am running the snippet below (I use poetry) in my formatting scripts:

    poetry run add-trailing-comma src/**/*.py tests/**/*.py 
    
    opened by amirhessam88 3
Owner
Anthony Sottile
@pre-commit @pytest-dev @tox-dev
Anthony Sottile
Tool for translation type comments to type annotations in Python

com2ann Tool for translation of type comments to type annotations in Python. The tool requires Python 3.8 to run. But the supported target code versio

Ivan Levkivskyi 123 Nov 12, 2022
AST based refactoring tool for Python.

breakfast AST based refactoring tool. (Very early days, not usable yet.) Why 'breakfast'? I don't know about the most important, but it's a good meal.

eric casteleijn 0 Feb 22, 2022
Code generation and code search for Python and Javascript.

Codeon Code generation and code search for Python and Javascript. Similar to GitHub Copilot with one major difference: Code search is leveraged to mak

null 51 Dec 8, 2022
Awesome autocompletion, static analysis and refactoring library for python

Jedi - an awesome autocompletion, static analysis and refactoring library for Python Jedi is a static analysis tool for Python that is typically used

Dave Halter 5.3k Dec 29, 2022
Removes unused imports and unused variables as reported by pyflakes

autoflake Introduction autoflake removes unused imports and unused variables from Python code. It makes use of pyflakes to do this. By default, autofl

Steven Myint 678 Jan 4, 2023
Turn your C++/Java code into a Python-like format for extra style points and to make everyone hates you

Turn your C++/Java code into a Python-like format for extra style points and to make everyone hates you

Tô Đức (Watson) 4 Feb 7, 2022
Codes of CVPR2022 paper: Fixing Malfunctional Objects With Learned Physical Simulation and Functional Prediction

Fixing Malfunctional Objects With Learned Physical Simulation and Functional Prediction Figure 1. Teaser. Introduction This paper studies the problem

Yining Hong 32 Dec 29, 2022
Flake8 extension for enforcing trailing commas in python

Flake8 Extension to enforce better comma placement. Usage If you are using flake8 it's as easy as: pip install flake8-commas Now you can avoid those a

Python Code Quality Authority 127 Sep 3, 2022
A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language.

pyupgrade A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language. Installation pip install pyupgrade As a pre

Anthony Sottile 2.4k Jan 8, 2023
Pre-commit hook for upgrading type hints

This is a pre-commit hook configured to automatically upgrade your type hints to the new native types implemented in PEP 585.

snok 54 Nov 14, 2022
Andrei 1.4k Dec 24, 2022
Python script to commit to your github for a perfect commit streak. This is purely for education purposes, please don't use this script to do bad stuff.

Daily-Git-Commit Commit to repo every day for the perfect commit streak Requirments pip install -r requirements.txt Setup Download this repository. Cr

JareBear 34 Dec 14, 2022
Django package to log request values such as device, IP address, user CPU time, system CPU time, No of queries, SQL time, no of cache calls, missing, setting data cache calls for a particular URL with a basic UI.

django-web-profiler's documentation: Introduction: django-web-profiler is a django profiling tool which logs, stores debug toolbar statistics and also

MicroPyramid 77 Oct 29, 2022
Blender add-on: Add to Cameras menu: View → Camera, View → Add Camera, Camera → View, Previous Camera, Next Camera

Blender add-on: Camera additions In 3D view, it adds these actions to the View|Cameras menu: View → Camera : set the current camera to the 3D view Vie

German Bauer 11 Feb 8, 2022
A Python r2pipe script to automatically create a Frida hook to intercept TLS traffic for Flutter based apps

boring-flutter A Python r2pipe script to automatically create a Frida hook to intercept TLS traffic for Flutter based apps. Currently only supporting

Hamza 64 Oct 18, 2022
Find version automatically based on git tags and commit messages.

GIT-CONVENTIONAL-VERSION Find version automatically based on git tags and commit messages. The tool is very specific in its function, so it is very fl

null 0 Nov 7, 2021
:fishing_pole_and_fish: List of `pre-commit` hooks to ensure the quality of your `dbt` projects.

pre-commit-dbt List of pre-commit hooks to ensure the quality of your dbt projects. BETA NOTICE: This tool is still BETA and may have some bugs, so pl

Offbi 262 Nov 25, 2022
validation for pre-commit.ci configuration

pre-commit-ci-config validation for pre-commit.ci configuration installation pip install pre-commit-ci-config api pre_commit_ci_config.SCHEMA a cfgv s

pre-commit.ci 17 Jul 11, 2022
Cross-platform .NET Core pre-commit hooks

dotnet-core-pre-commit Cross-platform .NET Core pre-commit hooks How to use Add this to your .pre-commit-config.yaml - repo: https://github.com/juan

Juan Odicio 5 Jul 20, 2021
A collection of pre-commit hooks for handling text files.

texthooks A collection of pre-commit hooks for handling text files. In particular, hooks for handling unicode characters which may be undesirable in a

Stephen Rosen 5 Oct 28, 2022