]> git.madduck.net Git - etc/vim.git/commitdiff

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Treat comments less magically
authorŁukasz Langa <lukasz@langa.pl>
Wed, 21 Mar 2018 01:42:29 +0000 (18:42 -0700)
committerŁukasz Langa <lukasz@langa.pl>
Wed, 21 Mar 2018 01:55:19 +0000 (18:55 -0700)
README.md
black.py
tests/comments.py
tests/comments2.py
tests/empty_lines.py
tests/import_spacing.py

index 70668f3180a0d7d18d2074dba9dcb81a1d259006..b7c6229214fc55549aedd8c46d750d2795998a8a 100644 (file)
--- a/README.md
+++ b/README.md
@@ -183,6 +183,22 @@ explains it.  The tl;dr is "it's like highway speed limits, we won't
 bother you if you overdo it by a few km/h".
 
 
+### Empty lines
+
+*Black* will allow single empty lines left by the original editors,
+except when they're added within parenthesized expressions.  Since such
+expressions are always reformatted to fit minimal space, this whitespace
+is lost.
+
+It will also insert proper spacing before and after function definitions.
+It's one line before and after inner functions and two lines before and
+after module-level functions.  *Black* will put those empty lines also
+between the function definition and any standalone comments that
+immediately precede the given function.  If you want to comment on the
+entire function, use a docstring or put a leading comment in the function
+body.
+
+
 ### Editor integration
 
 There is currently no integration with any text editors. Vim and
index 54044a5f564630289d7236e95fa0548d0049342b..81efa59a50669070b4005e4f4c5a73916ab3c43a 100644 (file)
--- a/black.py
+++ b/black.py
@@ -1,4 +1,5 @@
 #!/usr/bin/env python3
+
 import asyncio
 from asyncio.base_events import BaseEventLoop
 from concurrent.futures import Executor, ProcessPoolExecutor
@@ -218,7 +219,6 @@ def format_str(src_contents: str, line_length: int) -> FileContent:
     """Reformats a string and returns new contents."""
     src_node = lib2to3_parse(src_contents)
     dst_contents = ""
-    comments: List[Line] = []
     lines = LineGenerator()
     elt = EmptyLineTracker()
     py36 = is_python36(src_node)
@@ -230,21 +230,8 @@ def format_str(src_contents: str, line_length: int) -> FileContent:
         before, after = elt.maybe_empty_lines(current_line)
         for _ in range(before):
             dst_contents += str(empty_line)
-        if not current_line.is_comment:
-            for comment in comments:
-                dst_contents += str(comment)
-            comments = []
-            for line in split_line(current_line, line_length=line_length, py36=py36):
-                dst_contents += str(line)
-        else:
-            comments.append(current_line)
-    if comments:
-        if elt.previous_defs:
-            # Separate postscriptum comments from the last module-level def.
-            dst_contents += str(empty_line)
-            dst_contents += str(empty_line)
-        for comment in comments:
-            dst_contents += str(comment)
+        for line in split_line(current_line, line_length=line_length, py36=py36):
+            dst_contents += str(line)
     return dst_contents
 
 
@@ -662,10 +649,6 @@ class EmptyLineTracker:
         (two on module-level), as well as providing an extra empty line after flow
         control keywords to make them more prominent.
         """
-        if current_line.is_comment:
-            # Don't count standalone comments towards previous empty lines.
-            return 0, 0
-
         before, after = self._maybe_empty_lines(current_line)
         before -= self.previous_after
         self.previous_after = after
@@ -673,10 +656,14 @@ class EmptyLineTracker:
         return before, after
 
     def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
+        max_allowed = 1
+        if current_line.is_comment and current_line.depth == 0:
+            max_allowed = 2
         if current_line.leaves:
             # Consume the first leaf's extra newlines.
             first_leaf = current_line.leaves[0]
-            before = int('\n' in first_leaf.prefix)
+            before = first_leaf.prefix.count('\n')
+            before = min(before, max(before, max_allowed))
             first_leaf.prefix = ''
         else:
             before = 0
@@ -730,7 +717,6 @@ class LineGenerator(Visitor[Line]):
     in ways that will no longer stringify to valid Python code on the tree.
     """
     current_line: Line = Factory(Line)
-    standalone_comments: List[Leaf] = Factory(list)
 
     def line(self, indent: int = 0) -> Iterator[Line]:
         """Generate a line.
@@ -761,33 +747,22 @@ class LineGenerator(Visitor[Line]):
                     yield from self.line()
 
                 else:
-                    # regular standalone comment, to be processed later (see
-                    # docstring in `generate_comments()`
-                    self.standalone_comments.append(comment)
-            normalize_prefix(node, inside_brackets=any_open_brackets)
-            if node.type not in WHITESPACE:
-                for comment in self.standalone_comments:
+                    # regular standalone comment
                     yield from self.line()
 
                     self.current_line.append(comment)
                     yield from self.line()
 
-                self.standalone_comments = []
+            normalize_prefix(node, inside_brackets=any_open_brackets)
+            if node.type not in WHITESPACE:
                 self.current_line.append(node)
         yield from super().visit_default(node)
 
-    def visit_suite(self, node: Node) -> Iterator[Line]:
-        """Body of a statement after a colon."""
-        children = iter(node.children)
-        # Process newline before indenting.  It might contain an inline
-        # comment that should go right after the colon.
-        newline = next(children)
-        yield from self.visit(newline)
+    def visit_INDENT(self, node: Node) -> Iterator[Line]:
         yield from self.line(+1)
+        yield from self.visit_default(node)
 
-        for child in children:
-            yield from self.visit(child)
-
+    def visit_DEDENT(self, node: Node) -> Iterator[Line]:
         yield from self.line(-1)
 
     def visit_stmt(self, node: Node, keywords: Set[str]) -> Iterator[Line]:
@@ -1138,30 +1113,40 @@ def generate_comments(leaf: Leaf) -> Iterator[Leaf]:
     Inline comments are emitted as regular token.COMMENT leaves.  Standalone
     are emitted with a fake STANDALONE_COMMENT token identifier.
     """
-    if not leaf.prefix:
+    p = leaf.prefix
+    if not p:
         return
 
-    if '#' not in leaf.prefix:
+    if '#' not in p:
         return
 
-    before_comment, content = leaf.prefix.split('#', 1)
-    content = content.rstrip()
-    if content and (content[0] not in {' ', '!', '#'}):
-        content = ' ' + content
-    is_standalone_comment = (
-        '\n' in before_comment or '\n' in content or leaf.type == token.ENDMARKER
-    )
-    if not is_standalone_comment:
-        # simple trailing comment
-        yield Leaf(token.COMMENT, value='#' + content)
-        return
-
-    for line in ('#' + content).split('\n'):
+    nlines = 0
+    for index, line in enumerate(p.split('\n')):
         line = line.lstrip()
+        if not line:
+            nlines += 1
         if not line.startswith('#'):
             continue
 
-        yield Leaf(STANDALONE_COMMENT, line)
+        if index == 0 and leaf.type != token.ENDMARKER:
+            comment_type = token.COMMENT  # simple trailing comment
+        else:
+            comment_type = STANDALONE_COMMENT
+        yield Leaf(comment_type, make_comment(line), prefix='\n' * nlines)
+
+        nlines = 0
+
+
+def make_comment(content: str) -> str:
+    content = content.rstrip()
+    if not content:
+        return '#'
+
+    if content[0] == '#':
+        content = content[1:]
+    if content and content[0] not in {' ', '!', '#'}:
+        content = ' ' + content
+    return '#' + content
 
 
 def split_line(
@@ -1384,9 +1369,11 @@ def normalize_prefix(leaf: Leaf, *, inside_brackets: bool) -> None:
     you'll lose your voting rights.
     """
     if not inside_brackets:
-        spl = leaf.prefix.split('#', 1)
+        spl = leaf.prefix.split('#')
         if '\\' not in spl[0]:
-            nl_count = spl[0].count('\n')
+            nl_count = spl[-1].count('\n')
+            if len(spl) > 1:
+                nl_count -= 1
             leaf.prefix = '\n' * nl_count
             return
 
index e661ba642af52b4c3e9149e1444886ad0d5ffeff..34b38fe8101cfd398fcb80845032d5242ef797ad 100644 (file)
@@ -21,6 +21,8 @@ except ImportError:
 
 
 # Some comment before a function.
+
+
 def function(default=None):
     """Docstring comes first.
 
@@ -43,7 +45,10 @@ def function(default=None):
 GLOBAL_STATE = {'a': a(1), 'b': a(2), 'c': a(3)}
 
 
-# Another comment
+# Another comment!
+# This time two lines.
+
+
 @fast(really=True)
 async def wat():
     async with X.open_async() as x:  # Some more comments
index da4f7a16a06db100275cd6c15608db613464c049..e90c0795da7924335964a53dc00c377663807ab0 100644 (file)
@@ -1,4 +1,5 @@
 # Please keep __all__ alphabetized within each category.
+
 __all__ = [
     # Super-special typing primitives.
     'Any',
@@ -22,6 +23,7 @@ __all__ = [
     'Generator',
 ]
 
+# Comment before function.
 def inline_comments_in_brackets_ruin_everything():
     if typedargslist:
         parameters.children = [
@@ -42,11 +44,14 @@ def inline_comments_in_brackets_ruin_everything():
             # transport hasn't been notified yet?
             and self._proc.poll() is None):
         pass
+    # no newline before or after
     short = [
      # one
      1,
      # two
      2]
+
+    # no newline after
     call(arg1, arg2, """
 short
 """, arg3=True)
@@ -85,6 +90,14 @@ short
         # right
         if element is not None
     ]
+    while True:
+        if False:
+            continue
+
+            # and round and round we go
+        # and round and round we go
+
+    # let's return
     return Node(
         syms.simple_stmt,
         [
@@ -93,6 +106,12 @@ short
         ],
     )
 
+
+#######################
+### SECTION COMMENT ###
+#######################
+
+
 instruction()
 
 # END COMMENTS
@@ -103,6 +122,7 @@ instruction()
 
 
 # Please keep __all__ alphabetized within each category.
+
 __all__ = [
     # Super-special typing primitives.
     'Any',
@@ -124,6 +144,8 @@ __all__ = [
     'Generator',
 ]
 
+# Comment before function.
+
 
 def inline_comments_in_brackets_ruin_everything():
     if typedargslist:
@@ -145,12 +167,15 @@ def inline_comments_in_brackets_ruin_everything():
         and self._proc.poll() is None
     ):
         pass
+    # no newline before or after
     short = [
         # one
         1,
         # two
         2,
     ]
+
+    # no newline after
     call(
         arg1,
         arg2,
@@ -161,6 +186,7 @@ short
     )
 
     ############################################################################
+
     call2(
         # short
         arg1,
@@ -192,12 +218,26 @@ short
         # right
         if element is not None
     ]
+    while True:
+        if False:
+            continue
+
+    # and round and round we go
+    # and round and round we go
+
+    # let's return
     return Node(
         syms.simple_stmt,
         [Node(statement, result), Leaf(token.NEWLINE, '\n')],  # FIXME: \r\n?
     )
 
 
+#######################
+### SECTION COMMENT ###
+#######################
+
+
 instruction()
+
 # END COMMENTS
 # MORE END COMMENTS
index fe86b3854a0e65b1b0620d4c99715ca0199691b6..6419f6132955d80ec4e1039aa6c933d50922006b 100644 (file)
@@ -40,13 +40,9 @@ def f():
                 syms.dictsetmaker,
             }:
                 return NO
-
-
 ###############################################################################
 # SECTION BECAUSE SECTIONS
 ###############################################################################
-
-
 def g():
     NO = ''
     SPACE = ' '
@@ -135,6 +131,8 @@ def f():
 ###############################################################################
 # SECTION BECAUSE SECTIONS
 ###############################################################################
+
+
 def g():
     NO = ''
     SPACE = ' '
@@ -145,6 +143,7 @@ def g():
     v = leaf.value
 
     # Comment because comments
+
     if t in ALWAYS_NO_SPACE:
         pass
     if t == token.COMMENT:
index bd00812a154e5402c8dd75a1485bfd9ad2cb059d..87a5301a323dcd1a5c29e2b5f5b97f7cee1cd125 100644 (file)
@@ -40,6 +40,7 @@ __all__ = (
 """The asyncio package, tracking PEP 3156."""
 
 # flake8: noqa
+
 import sys
 
 # This relies on each of the submodules having an __all__ variable.