#!/usr/bin/env python """ This script checks for missing or forbidden blank lines before or after particular Vim commands. This script ensures that VimL scripts are padded correctly, so they are easier to read. """ import sys import re INDENTATION_RE = re.compile(r'^ *') COMMENT_LINE_RE = re.compile(r'^ *"') COMMAND_RE = re.compile(r'^ *([a-zA-Z\\]+)') OPERATOR_END_RE = re.compile(r'(&&|\|\||\+|-|\*\| /)$') START_BLOCKS = set(['if', 'for', 'while', 'try', 'function']) END_BLOCKS = set(['endif', 'endfor', 'endwhile', 'endtry', 'endfunction']) MIDDLE_BLOCKS = set(['else', 'elseif', 'catch', 'finally']) TERMINATORS = set(['return', 'throw']) WHITESPACE_BEFORE_SET = START_BLOCKS | TERMINATORS WHITESPACE_FORBIDDEN_BEFORE_SET = END_BLOCKS | MIDDLE_BLOCKS WHITESPACE_AFTER_SET = END_BLOCKS WHITESPACE_FORBIDDEN_AFTER_SET = START_BLOCKS | MIDDLE_BLOCKS SAME_INDENTATION_SET = set(['\\']) def remove_comment_lines(line_iter): for line_number, line in enumerate(line_iter, 1): if not COMMENT_LINE_RE.match(line): yield (line_number, line) def check_lines(line_iter): previous_indentation_level = None previous_command = None previous_line_blank = False for line_number, line in remove_comment_lines(line_iter): if len(line) == 0: # Check for commands where we shouldn't have blank lines after # them, like `else` or the start of blocks like `function`. if ( previous_command is not None and previous_command in WHITESPACE_FORBIDDEN_AFTER_SET ): yield ( line_number, 'Blank line forbidden after `%s`' % (previous_command,) ) previous_line_blank = True previous_command = None else: indentation_level = INDENTATION_RE.match(line).end() command_match = COMMAND_RE.match(line) if command_match: command = command_match.group(1) if ( command in SAME_INDENTATION_SET and previous_indentation_level is not None and indentation_level != previous_indentation_level ): yield ( line_number, 'Line continuation should match previous indentation' ) if ( previous_indentation_level is not None and indentation_level != previous_indentation_level and abs(indentation_level - previous_indentation_level) != 4 # noqa ): yield ( line_number, 'Indentation should be 4 spaces' ) # Check for commands requiring blank lines before them, if they # aren't at the start of a block. if ( command in WHITESPACE_BEFORE_SET and previous_indentation_level is not None and indentation_level == previous_indentation_level and previous_line_blank is False ): yield ( line_number, 'Blank line required before `%s`' % (command,) ) # Check for commands where we shouldn't have blank lines before # them, like `else` or the end of blocks like `endfunction`. if ( command in WHITESPACE_FORBIDDEN_BEFORE_SET and previous_line_blank is True ): yield ( line_number - 1, 'Blank line forbidden before `%s`' % (command,) ) # Check for commands requiring blank lines after them, if they # aren't at the end of a block. if ( previous_command is not None and previous_command in WHITESPACE_AFTER_SET and previous_indentation_level is not None and indentation_level == previous_indentation_level and previous_line_blank is False ): yield ( line_number - 1, 'Blank line required after `%s`' % (command,) ) previous_command = command previous_line_blank = False previous_indentation_level = indentation_level if OPERATOR_END_RE.search(line): yield ( line_number, 'Put operators at the start of lines instead' ) def main(): status = 0 for filename in sys.argv[1:]: with open(filename) as vim_file: line_iter = (line.rstrip() for line in vim_file) for line_number, message in check_lines(line_iter): print('%s:%d %s' % (filename, line_number, message)) status = 1 sys.exit(status) if __name__ == "__main__": main()