2018-09-04 15:51:18 +00:00
|
|
|
#!/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'^ *"')
|
2019-02-06 18:05:13 +00:00
|
|
|
COMMAND_RE = re.compile(r'^ *([a-zA-Z\\]+)')
|
2019-02-10 11:43:48 +00:00
|
|
|
OPERATOR_END_RE = re.compile(r'(&&|\|\||\+|-|\*\| /)$')
|
2018-09-04 15:51:18 +00:00
|
|
|
|
|
|
|
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
|
2019-02-06 18:05:13 +00:00
|
|
|
SAME_INDENTATION_SET = set(['\\'])
|
2018-09-04 15:51:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
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,
|
2019-02-06 18:05:13 +00:00
|
|
|
'Blank line forbidden after `%s`' % (previous_command,)
|
2018-09-04 15:51:18 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
2019-02-06 18:05:13 +00:00
|
|
|
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
|
2019-02-10 11:43:48 +00:00
|
|
|
and abs(indentation_level - previous_indentation_level) != 4 # noqa
|
2019-02-06 18:05:13 +00:00
|
|
|
):
|
|
|
|
yield (
|
|
|
|
line_number,
|
|
|
|
'Indentation should be 4 spaces'
|
|
|
|
)
|
|
|
|
|
2018-09-04 15:51:18 +00:00
|
|
|
# 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
|
|
|
|
|
2019-02-10 11:43:48 +00:00
|
|
|
if OPERATOR_END_RE.search(line):
|
|
|
|
yield (
|
|
|
|
line_number,
|
|
|
|
'Put operators at the start of lines instead'
|
|
|
|
)
|
|
|
|
|
2018-09-04 15:51:18 +00:00
|
|
|
|
|
|
|
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()
|