Repository: ambv/black
Pull request #180 (2018-04-29 07:23:05)

Removing empty parentheses after class name
Hi @ambv,
This is WIP.
would like to get early feedback on this as this is my first PR request.
This is working but 2 tests are failing, I am working on them to fix(if you have any suggestion to pass, would be appreciated). I am also in working on adding new tests after fixing the failed tests.


coveralls: ## Pull Request Test Coverage Report for [Build 305](https://coveralls.io/builds/16747806)

* **5** of **7**  **(71.43%)** changed or added relevant lines in **1** file are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.09%**) to **94.771%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [black.py](https://coveralls.io/builds/16747806/source?filename=black.py#L1101) | 5 | 7 | 71.43%
<!-- | **Total:** | **5** | **7** | **71.43%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/16747806/badge)](https://coveralls.io/builds/16747806) |
| :-- | --: |
| Change from base [Build 297](https://coveralls.io/builds/16745447): |  -0.09% |
| Covered Lines: | 1776 |
| Relevant Lines: | 1874 |

---
##### 💛  - [Coveralls](https://coveralls.io)

ambv: Nice! Thanks for working on this. ✨ 🍰 ✨  

High-level comments:
- LineGenerator doesn't need `leaves` and also shouldn't modify the line directly, that breaks the line's BracketTracker;
- your transformation fits `Line.append()` best:
   - before you append the leaf, check if it's a token.COLON
   - if it is, check whether:
      - you are a class definition; and
      - the current number of leaves is 4; and
      - the previous two leaves are token.LPAR, token.RPAR
   - if so, pop() the two last leaves

That will ensure you are only deleting the right colons, and there is always the right amount of leaves so we will have no index errors, and that BracketTracker is happy.
skapil: @ambv please have a look 👀 

Patch:
diff --git a/.gitignore b/.gitignore
index a796585..c59b667 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,6 @@ _build
 .DS_Store
 .vscode
 docs/_static/pypi.svg
+.tox
+__pycache__
+black.egg-info
diff --git a/README.md b/README.md
index 31f92c9..d3f296c 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
 
 
 *Black* is the uncompromising Python code formatter.  By using it, you
-agree to cease control over minutiae of hand-formatting.  In return,
+agree to cede control over minutiae of hand-formatting.  In return,
 *Black* gives you speed, determinism, and freedom from `pycodestyle`
 nagging about formatting.  You will save time and mental energy for
 more important matters.
@@ -180,8 +180,9 @@ If a line of "from" imports cannot fit in the allotted length, it's always split
 into one per line.  Imports tend to change often and this minimizes diffs, as well
 as enables readers of code to easily find which commit introduced a particular
 import.  This exception also makes *Black* compatible with
-[isort](https://pypi.org/p/isort/).  Use `multi_line_output=3` and
-`include_trailing_comma=True` in your isort config.
+[isort](https://pypi.org/p/isort/).  Use `multi_line_output=3`,
+`include_trailing_comma=True`, `force_grid_wrap=0`, and `line_length=88` in your
+isort config.
 
 
 ### Line length
diff --git a/black.py b/black.py
index 5e087d1..cfb5028 100644
--- a/black.py
+++ b/black.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python3
-
 import asyncio
 import pickle
 from asyncio.base_events import BaseEventLoop
@@ -194,6 +192,7 @@ def main(
         write_back = WriteBack.YES
     report = Report(check=check, quiet=quiet)
     if len(sources) == 0:
+        out("No paths given. Nothing to do 😴")
         ctx.exit(0)
         return
 
@@ -744,6 +743,9 @@ def append(self, leaf: Leaf, preformatted: bool = False) -> None:
         if not has_value:
             return
 
+        if token.COLON == leaf.type and self.is_class_parenth_empty:
+            del self.leaves[-2:]
+
         if self.leaves and not preformatted:
             # Note: at this point leaf.prefix should be empty except for
             # imports, for which we only preserve newlines.
@@ -841,6 +843,19 @@ def is_yield(self) -> bool:
             and self.leaves[0].value == "yield"
         )
 
+    @property
+    def is_class_parenth_empty(self) -> bool:
+        """Is this class parentheses blank?"""
+        return (
+            bool(self)
+            and len(self.leaves) == 4
+            and self.is_class
+            and self.leaves[2].type == token.LPAR
+            and self.leaves[2].value == "("
+            and self.leaves[3].type == token.RPAR
+            and self.leaves[3].value == ")"
+        )
+
     def contains_standalone_comments(self, depth_limit: int = sys.maxsize) -> bool:
         """If so, needs to be split before emitting."""
         for leaf in self.leaves:
@@ -1126,6 +1141,7 @@ def line(self, indent: int = 0, type: Type[Line] = Line) -> Iterator[Line]:
 
         If any lines were generated, set up a new current_line.
         """
+
         if not self.current_line:
             if self.current_line.__class__ == type:
                 self.current_line.depth += indent
diff --git a/blib2to3/pgen2/token.py b/blib2to3/pgen2/token.py
old mode 100755
new mode 100644
index 1a67955..c37b0d5
--- a/blib2to3/pgen2/token.py
+++ b/blib2to3/pgen2/token.py
@@ -1,5 +1,3 @@
-#! /usr/bin/env python3
-
 """Token constants (from "token.h")."""
 
 #  Taken from Python (r53757) and modified to include some tokens
diff --git a/docs/index.rst b/docs/index.rst
index df59f71..a0b176b 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -4,7 +4,7 @@
 The uncompromising code formatter
 =================================
 
-By using *Black*, you agree to cease control over minutiae of
+By using *Black*, you agree to cede control over minutiae of
 hand-formatting. In return, *Black* gives you speed, determinism, and
 freedom from `pycodestyle` nagging about formatting. You will save time
 and mental energy for more important matters.


Test patch:
diff --git a/tests/class_blank_parentheses.py b/tests/class_blank_parentheses.py
new file mode 100644
index 0000000..b4dcb20
--- /dev/null
+++ b/tests/class_blank_parentheses.py
@@ -0,0 +1,71 @@
+class SimpleClassWithBlankParentheses():
+    pass
+
+
+class ClassWithSpaceParentheses():
+    first_test_data = 90
+    second_test_data = 100
+
+    def test_func(self):
+        return None
+
+
+class ClassWithEmptyFunc(object):
+
+    def func_with_blank_parentheses():
+        return 5
+
+
+def public_func_with_blank_parentheses():
+    return None
+
+
+def class_under_the_func_with_blank_parentheses():
+
+    class InsideFunc():
+        pass
+
+
+class NormalClass():
+
+    def func_for_testing(self, first, second):
+        sum = first + second
+        return sum
+
+
+# output
+
+
+class SimpleClassWithBlankParentheses:
+    pass
+
+
+class ClassWithSpaceParentheses:
+    first_test_data = 90
+    second_test_data = 100
+
+    def test_func(self):
+        return None
+
+
+class ClassWithEmptyFunc(object):
+
+    def func_with_blank_parentheses():
+        return 5
+
+
+def public_func_with_blank_parentheses():
+    return None
+
+
+def class_under_the_func_with_blank_parentheses():
+
+    class InsideFunc:
+        pass
+
+
+class NormalClass:
+
+    def func_for_testing(self, first, second):
+        sum = first + second
+        return sum
diff --git a/tests/test_black.py b/tests/test_black.py
index 02926d1..951b298 100644
--- a/tests/test_black.py
+++ b/tests/test_black.py
@@ -325,6 +325,14 @@ def test_fmtonoff(self) -> None:
         black.assert_equivalent(source, actual)
         black.assert_stable(source, actual, line_length=ll)
 
+    @patch("black.dump_to_file", dump_to_stderr)
+    def test_remove_empty_parentheses_after_class(self) -> None:
+        source, expected = read_data("class_blank_parentheses")
+        actual = fs(source)
+        self.assertFormatEqual(expected, actual)
+        black.assert_equivalent(source, actual)
+        black.assert_stable(source, actual, line_length=ll)
+
     def test_report(self) -> None:
         report = black.Report()
         out_lines = []
@@ -636,6 +644,12 @@ def test_check_diff_use_together(self) -> None:
             )
             self.assertEqual(result.exit_code, 1)
 
+    def test_no_files(self) -> None:
+        with cache_dir():
+            # Without an argument, black exits with error code 0.
+            result = CliRunner().invoke(black.main, [])
+            self.assertEqual(result.exit_code, 0)
+
     def test_read_cache_line_lengths(self) -> None:
         with cache_dir() as workspace:
             path = (workspace / "file.py").resolve()