# Copyright 2015 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Calculate the number of blank lines between top-level entities. Calculates how many blank lines we need between classes, functions, and other entities at the same level. CalculateBlankLines(): the main function exported by this module. Annotations: newlines: The number of newlines required before the node. """ from yapf.yapflib import py3compat from yapf.yapflib import pytree_utils from yapf.yapflib import pytree_visitor from yapf.yapflib import style _NO_BLANK_LINES = 1 _ONE_BLANK_LINE = 2 _TWO_BLANK_LINES = 3 _PYTHON_STATEMENTS = frozenset({ 'small_stmt', 'expr_stmt', 'print_stmt', 'del_stmt', 'pass_stmt', 'break_stmt', 'continue_stmt', 'return_stmt', 'raise_stmt', 'yield_stmt', 'import_stmt', 'global_stmt', 'exec_stmt', 'assert_stmt', 'if_stmt', 'while_stmt', 'for_stmt', 'try_stmt', 'with_stmt', 'nonlocal_stmt', 'async_stmt', 'simple_stmt' }) def CalculateBlankLines(tree): """Run the blank line calculator visitor over the tree. This modifies the tree in place. Arguments: tree: the top-level pytree node to annotate with subtypes. """ blank_line_calculator = _BlankLineCalculator() blank_line_calculator.Visit(tree) class _BlankLineCalculator(pytree_visitor.PyTreeVisitor): """_BlankLineCalculator - see file-level docstring for a description.""" def __init__(self): self.class_level = 0 self.function_level = 0 self.last_comment_lineno = 0 self.last_was_decorator = False self.last_was_class_or_function = False def Visit_simple_stmt(self, node): # pylint: disable=invalid-name self.DefaultNodeVisit(node) if pytree_utils.NodeName(node.children[0]) == 'COMMENT': self.last_comment_lineno = node.children[0].lineno def Visit_decorator(self, node): # pylint: disable=invalid-name if (self.last_comment_lineno and self.last_comment_lineno == node.children[0].lineno - 1): _SetNumNewlines(node.children[0], _NO_BLANK_LINES) else: _SetNumNewlines(node.children[0], self._GetNumNewlines(node)) for child in node.children: self.Visit(child) self.last_was_decorator = True def Visit_classdef(self, node): # pylint: disable=invalid-name self.last_was_class_or_function = False index = self._SetBlankLinesBetweenCommentAndClassFunc(node) self.last_was_decorator = False self.class_level += 1 for child in node.children[index:]: self.Visit(child) self.class_level -= 1 self.last_was_class_or_function = True def Visit_funcdef(self, node): # pylint: disable=invalid-name self.last_was_class_or_function = False index = self._SetBlankLinesBetweenCommentAndClassFunc(node) if _AsyncFunction(node): index = self._SetBlankLinesBetweenCommentAndClassFunc( node.prev_sibling.parent) _SetNumNewlines(node.children[0], None) else: index = self._SetBlankLinesBetweenCommentAndClassFunc(node) self.last_was_decorator = False self.function_level += 1 for child in node.children[index:]: self.Visit(child) self.function_level -= 1 self.last_was_class_or_function = True def DefaultNodeVisit(self, node): """Override the default visitor for Node. This will set the blank lines required if the last entity was a class or function. Arguments: node: (pytree.Node) The node to visit. """ if self.last_was_class_or_function: if pytree_utils.NodeName(node) in _PYTHON_STATEMENTS: leaf = pytree_utils.FirstLeafNode(node) _SetNumNewlines(leaf, self._GetNumNewlines(leaf)) self.last_was_class_or_function = False super(_BlankLineCalculator, self).DefaultNodeVisit(node) def _SetBlankLinesBetweenCommentAndClassFunc(self, node): """Set the number of blanks between a comment and class or func definition. Class and function definitions have leading comments as children of the classdef and functdef nodes. Arguments: node: (pytree.Node) The classdef or funcdef node. Returns: The index of the first child past the comment nodes. """ index = 0 while pytree_utils.IsCommentStatement(node.children[index]): # Standalone comments are wrapped in a simple_stmt node with the comment # node as its only child. self.Visit(node.children[index].children[0]) if not self.last_was_decorator: _SetNumNewlines(node.children[index].children[0], _ONE_BLANK_LINE) index += 1 if (index and node.children[index].lineno - 1 == node.children[index - 1].children[0].lineno): _SetNumNewlines(node.children[index], _NO_BLANK_LINES) else: if self.last_comment_lineno + 1 == node.children[index].lineno: num_newlines = _NO_BLANK_LINES else: num_newlines = self._GetNumNewlines(node) _SetNumNewlines(node.children[index], num_newlines) return index def _GetNumNewlines(self, node): if self.last_was_decorator: return _NO_BLANK_LINES elif self._IsTopLevel(node): return 1 + style.Get('BLANK_LINES_AROUND_TOP_LEVEL_DEFINITION') return _ONE_BLANK_LINE def _IsTopLevel(self, node): return (not (self.class_level or self.function_level) and _StartsInZerothColumn(node)) def _SetNumNewlines(node, num_newlines): pytree_utils.SetNodeAnnotation(node, pytree_utils.Annotation.NEWLINES, num_newlines) def _StartsInZerothColumn(node): return (pytree_utils.FirstLeafNode(node).column == 0 or (_AsyncFunction(node) and node.prev_sibling.column == 0)) def _AsyncFunction(node): return (py3compat.PY3 and node.prev_sibling and pytree_utils.NodeName(node.prev_sibling) == 'ASYNC')