diff --git a/wpiformat/wpiformat/__init__.py b/wpiformat/wpiformat/__init__.py index 4665b360..17132f99 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.config import Config @@ -336,6 +337,7 @@ def main(): # so it can clean up their formatting. task_pipeline = [ BraceComment(), + BraceNewline(), CIdentList(), EofNewline(), IncludeGuard(), diff --git a/wpiformat/wpiformat/bracenewline.py b/wpiformat/wpiformat/bracenewline.py new file mode 100644 index 00000000..598a4ad8 --- /dev/null +++ b/wpiformat/wpiformat/bracenewline.py @@ -0,0 +1,62 @@ +"""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 00000000..682c8f70 --- /dev/null +++ b/wpiformat/wpiformat/test/test_bracenewline.py @@ -0,0 +1,95 @@ +import os + +from .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, 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, 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, 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)