diff --git a/shopify_python/google_styleguide.py b/shopify_python/google_styleguide.py index bc4af42..5e2bd72 100644 --- a/shopify_python/google_styleguide.py +++ b/shopify_python/google_styleguide.py @@ -87,6 +87,9 @@ class GoogleStyleGuideChecker(checkers.BaseChecker): 'lambda-func', "For common operations like multiplication, use the functions from the operator module" "instead of lambda functions. For example, prefer operator.mul to lambda x, y: x * y."), + 'C6015': ('No blank line after a class definition', + 'blank-line-after-class-required', + 'Missing a blank line after a class definition'), } options = ( @@ -173,6 +176,9 @@ def visit_raise(self, node): # type: (astroid.Raise) -> None def visit_if(self, node): self.__use_cond_expr(node) # type: (astroid.If) -> None + def visit_classdef(self, node): # type: (astroid.ClassDef) -> None + self.__class_def_check(node) + @staticmethod def __get_module_names(node): # type: (astroid.ImportFrom) -> typing.Generator[str, None, None] for name in node.names: @@ -322,3 +328,17 @@ def __lambda_func(self, node): # type: (astroid.Lambda) -> None lambda_fun = "lambda " + left + ', ' + right + ": " + " ".join([left, node.ops[0][0], right]) op_fun = "operator." + operator self.add_message('lambda-func', node=node, args={'op': op_fun, 'lambda_fun': lambda_fun}) + + def __class_def_check(self, node): # type: (astroid.ClassDef) -> None + """Enforce a blank line after a class definition line.""" + prev_line = node.lineno + + for element in node.body: + curr_line = element.lineno + blank_lines = curr_line - prev_line - 1 + if isinstance(element, astroid.FunctionDef) and blank_lines < 1: + self.add_message('blank-line-after-class-required', node=node) + break + elif isinstance(element, astroid.FunctionDef): + break + prev_line = curr_line diff --git a/tests/functional/blank_line_after_class_required.py b/tests/functional/blank_line_after_class_required.py new file mode 100644 index 0000000..c8c6ce6 --- /dev/null +++ b/tests/functional/blank_line_after_class_required.py @@ -0,0 +1,4 @@ +# pylint:disable=missing-docstring,invalid-name,too-few-public-methods +class SomeClass(object): # [blank-line-after-class-required] + def apply(self): + pass diff --git a/tests/functional/blank_line_after_class_required.txt b/tests/functional/blank_line_after_class_required.txt new file mode 100644 index 0000000..72e780b --- /dev/null +++ b/tests/functional/blank_line_after_class_required.txt @@ -0,0 +1 @@ +blank-line-after-class-required:2:SomeClass:No blank line after a class definition \ No newline at end of file diff --git a/tests/shopify_python/test_google_styleguide.py b/tests/shopify_python/test_google_styleguide.py index 2326264..a417470 100644 --- a/tests/shopify_python/test_google_styleguide.py +++ b/tests/shopify_python/test_google_styleguide.py @@ -332,3 +332,49 @@ def binaryfnc(): """.format(expression)) with self.assertNoMessages(): self.walk(binary_root) + + def test_class_def_blank_line(self): + root = astroid.builder.parse(""" + class SomePipeline(object): + def apply(content): + return content.withColumn('zero', F.lit(0.0)) + + class Fact(object): + INPUTS = {} + def apply(self): + pass + + class Stage(object): + INPUTS = {} + + OUTPUTS = {} + def apply(self): + pass + """) + with self.assertAddsMessages( + *[pylint.testutils.Message('blank-line-after-class-required', node=root.body[0]), + pylint.testutils.Message('blank-line-after-class-required', node=root.body[1]), + pylint.testutils.Message('blank-line-after-class-required', node=root.body[2])] + ): + self.walk(root) + + with self.assertNoMessages(): + self.walk(astroid.builder.parse(""" + class SomePipeline(object): + + def apply(content): + return content.withColumn('zero', F.lit(0.0)) + + class FirstStage(object): + pass + + class SecondStage(object): + INPUTS = {} + OUTPUTS = {} + + def check(): + pass + + def apply(): + pass + """))