From 22baa7c1e7d4ee3f1999d3be59ad67952ae24071 Mon Sep 17 00:00:00 2001 From: Daniel Chiang Date: Wed, 14 Oct 2020 18:45:01 -0700 Subject: [PATCH] add SortedAttributesRule --- fixit/rules/sorted_attributes_rule.py | 119 ++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 fixit/rules/sorted_attributes_rule.py diff --git a/fixit/rules/sorted_attributes_rule.py b/fixit/rules/sorted_attributes_rule.py new file mode 100644 index 00000000..a54037d0 --- /dev/null +++ b/fixit/rules/sorted_attributes_rule.py @@ -0,0 +1,119 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from __future__ import annotations + +from typing import List, Union + +import libcst as cst +import libcst.matchers as m + +from fixit import CstLintRule, InvalidTestCase as Invalid, ValidTestCase as Valid + + +LineType = Union[cst.BaseSmallStatement, cst.BaseStatement] + + +class SortedAttributesRule(CstLintRule): + """ + Ever wanted to sort a bunch of class attributes alphabetically? + Well now it's easy! Just add "@sorted-attributes" in the doc string of + a class definition and lint will automatically sort all attributes alphabetically. + + Feel free to add other methods and such -- it should only affect class attributes. + """ + + INVALID = [ + Invalid( + """ + class MyUnsortedConstants: + \"\"\" + @sorted-attributes + \"\"\" + z = "hehehe" + B = 'aaa234' + A = 'zzz123' + cab = "foo bar" + Daaa = "banana" + + @classmethod + def get_foo(cls) -> str: + return "some random thing" + """, + expected_replacement=""" + class MyUnsortedConstants: + \"\"\" + @sorted-attributes + \"\"\" + A = 'zzz123' + B = 'aaa234' + Daaa = "banana" + cab = "foo bar" + z = "hehehe" + + @classmethod + def get_foo(cls) -> str: + return "some random thing" + """, + ) + ] + ONCALL_SHORTNAME = "ig_creation_backend" + VALID = [ + Valid( + """ + class MyConstants: + \"\"\" + @sorted-attributes + \"\"\" + A = 'zzz123' + B = 'aaa234' + + class MyUnsortedConstants: + B = 'aaa234' + A = 'zzz123' + """ + ) + ] + MESSAGE: str = "It appears you are using the @sorted directive and the class variables are unsorted. See the lint autofix suggestion." + + def visit_ClassDef(self, node: cst.ClassDef) -> None: + doc_string = node.get_docstring() + if not doc_string or "@sorted-attributes" not in doc_string: + return + + found_any_assign: bool = False + pre_assign_lines: List[LineType] = [] + assign_lines: List[LineType] = [] + post_assign_lines: List[LineType] = [] + + def _add_unmatched_line(line: LineType) -> None: + post_assign_lines.append( + line + ) if found_any_assign else pre_assign_lines.append(line) + + for line in node.body.body: + if m.matches( + line, m.SimpleStatementLine(body=[m.Assign(targets=[m.AssignTarget()])]) + ): + found_any_assign = True + assign_lines.append(line) + else: + _add_unmatched_line(line) + continue + + sorted_assign_lines = sorted( + assign_lines, key=lambda line: line.body[0].targets[0].target.value + ) + if sorted_assign_lines == assign_lines: + return + self.report( + node, + self.MESSAGE, + replacement=node.with_changes( + body=node.body.with_changes( + body=pre_assign_lines + sorted_assign_lines + post_assign_lines + ) + ), + )