diff --git a/wpiformat/wpiformat/__init__.py b/wpiformat/wpiformat/__init__.py index 1ce8d8c..5508687 100644 --- a/wpiformat/wpiformat/__init__.py +++ b/wpiformat/wpiformat/__init__.py @@ -8,6 +8,7 @@ import sys from wpiformat.bracecomment import BraceComment +from wpiformat.bracenewline import BraceNewline from wpiformat.cidentlist import CIdentList from wpiformat.clangformat import ClangFormat from wpiformat.clangtidy import ClangTidy @@ -469,6 +470,7 @@ def main(): # tasks so it can clean up their formatting. task_pipeline = [ BraceComment(), + BraceNewline(), CIdentList(), EofNewline(), GTestName(), diff --git a/wpiformat/wpiformat/bracenewline.py b/wpiformat/wpiformat/bracenewline.py new file mode 100644 index 0000000..99466a3 --- /dev/null +++ b/wpiformat/wpiformat/bracenewline.py @@ -0,0 +1,66 @@ +"""This task ensures braces are followed by two line separators.""" + +import re + +from wpiformat.task import Task + + +class BraceNewline(Task): + def should_process_file(self, config_file, name): + return ( + config_file.is_c_file(name) + or config_file.is_cpp_file(name) + or name.endswith("java") + ) + + def run_pipeline(self, config_file, name, lines): + linesep = Task.get_linesep(lines) + + comment_regex = re.compile("//|/\*|\*/") + + lines_list = lines.split(linesep) + + in_multiline_comment = False + for i in range(len(lines_list)): + match = comment_regex.search(lines_list[i]) + if not match: + line = lines_list[i].rstrip() + else: + # While in a multiline comment, we only care about "*/" + if in_multiline_comment: + if match.group() == "*/": + line = lines_list[i][match.start() + len("*/") :].rstrip() + in_multiline_comment = False + else: + line = lines_list[i][0 : match.start()].rstrip() + + # If multiline comment is starting + if match.group() == "/*": + line = lines_list[i][0 : match.start()] + in_multiline_comment = True + + # If comment ends on same line, handle it immediately + comment_end = lines_list[i].find("*/") + if comment_end != -1: + line += lines_list[i][comment_end + len("*/") :] + line = line.rstrip() + in_multiline_comment = False + + if in_multiline_comment: + continue + + # If line with "}" isn't at end of file + if i + 1 < len(lines_list) and line.endswith("}"): + next_line = lines_list[i + 1].lstrip() + + # If next line is already empty, there's nothing to do + if len(next_line) > 0: + if ( + next_line[0] != "}" + and "else" not in next_line + and "#endif" not in next_line + ): + lines_list.insert(i + 1, "") + i += 1 + + return (linesep.join(lines_list), True) diff --git a/wpiformat/wpiformat/test/test_bracenewline.py b/wpiformat/wpiformat/test/test_bracenewline.py new file mode 100644 index 0000000..011a918 --- /dev/null +++ b/wpiformat/wpiformat/test/test_bracenewline.py @@ -0,0 +1,156 @@ +import os + +from .test_tasktest import * +from wpiformat.bracenewline import BraceNewline + + +def test_bracenewline(): + test = TaskTest(BraceNewline()) + + # Brackets on same line + test.add_input( + "./Test.cpp", "void func1() {}" + os.linesep + "void func2() {}" + os.linesep + ) + test.add_output( + "void func1() {}" + os.linesep + os.linesep + "void func2() {}" + os.linesep, + True, + ) + + # Brackets on next line + test.add_input( + "./Test.cpp", + "void func1() {" + + os.linesep + + "}" + + os.linesep + + "void func2() {" + + os.linesep + + "}" + + os.linesep, + ) + test.add_output( + "void func1() {" + + os.linesep + + "}" + + os.linesep + + os.linesep + + "void func2() {" + + os.linesep + + "}" + + os.linesep, + True, + ) + + # Comments after brackets + test.add_input( + "./Test.cpp", + "void func1() {" + + os.linesep + + "} // This is a comment" + + os.linesep + + "void func2() {" + + os.linesep + + "} /* This is a comment */" + + os.linesep + + "void func3() {" + + os.linesep + + "}" + + os.linesep, + ) + test.add_output( + "void func1() {" + + os.linesep + + "} // This is a comment" + + os.linesep + + os.linesep + + "void func2() {" + + os.linesep + + "} /* This is a comment */" + + os.linesep + + os.linesep + + "void func3() {" + + os.linesep + + "}" + + os.linesep, + True, + ) + + # Don't add line separators to uncondensed if statements (after last brace + # is OK) + test.add_input( + "./Test.cpp", + "void func1() {" + + os.linesep + + " if (1) {" + + os.linesep + + " }" + + os.linesep + + " else {" + + os.linesep + + " }" + + os.linesep + + "}" + + os.linesep, + ) + test.add_latest_input_as_output(True) + + # Don't add line separators to condensed if statements (after last brace + # is OK) + test.add_input( + "./Test.cpp", + "void func1() {" + + os.linesep + + " if (1) {" + + os.linesep + + " } else if () {" + + os.linesep + + " } else {" + + os.linesep + + " // comment" + + os.linesep + + " }" + + os.linesep + + "}" + + os.linesep, + ) + test.add_latest_input_as_output(True) + + # Don't add line separators before #endif statements + test.add_input( + "./Main.cpp", + "using decay_t = typename decay::type;" + + os.linesep + + "} // namespace std" + + os.linesep + + "#endif" + + os.linesep, + ) + test.add_latest_input_as_output(True) + + # Don't insert line separators within multiline comments + test.add_input( + "./Main.java", + "/* to fine tune the pid loop." + + os.linesep + + " *" + + os.linesep + + " * @return the {@link PIDController} used by this {@link PIDSubsystem}" + + os.linesep + + " */" + + os.linesep + + "public PIDController getPIDController() {" + + os.linesep, + ) + test.add_latest_input_as_output(True) + + # Don't insert line separators within single line comments + test.add_input( + "./Main.java", + "// @return the {@link PIDController} used by this {@link PIDSubsystem}" + + os.linesep + + "public PIDController getPIDController() {" + + os.linesep, + ) + test.add_latest_input_as_output(True) + + test.run(OutputType.FILE)