| #!/usr/bin/env python3 |
| |
| # Copyright (c) 2009 Giampaolo Rodola'. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """A super simple linter to check C syntax.""" |
| |
| |
| import argparse |
| import sys |
| |
| |
| warned = False |
| |
| |
| def warn(path, line, lineno, msg): |
| global warned |
| warned = True |
| print(f"{path}:{lineno}: {msg}", file=sys.stderr) |
| |
| |
| def check_line(path, line, idx, lines): |
| s = line |
| lineno = idx + 1 |
| eof = lineno == len(lines) |
| if s.endswith(' \n'): |
| warn(path, line, lineno, "extra space at EOL") |
| elif '\t' in line: |
| warn(path, line, lineno, "line has a tab") |
| elif s.endswith('\r\n'): |
| warn(path, line, lineno, "Windows line ending") |
| # end of global block, e.g. "}newfunction...": |
| elif s == "}\n": |
| if not eof: |
| nextline = lines[idx + 1] |
| # "#" is a pre-processor line |
| if ( |
| nextline != '\n' |
| and nextline.strip()[0] != '#' |
| and nextline.strip()[:2] != '*/' |
| ): |
| warn(path, line, lineno, "expected 1 blank line") |
| |
| sls = s.lstrip() |
| if sls.startswith('//') and sls[2] != ' ' and line.strip() != '//': |
| warn(path, line, lineno, "no space after // comment") |
| # e.g. "if(..." after keywords |
| keywords = ("if", "else", "while", "do", "enum", "for") |
| for kw in keywords: |
| if sls.startswith(kw + '('): |
| warn(path, line, lineno, f"missing space between {kw!r} and '('") |
| # eof |
| if eof and not line.endswith('\n'): |
| warn(path, line, lineno, "no blank line at EOF") |
| |
| ss = s.strip() |
| if ss.startswith(("printf(", "printf (")): |
| if not ss.endswith(("// NOQA", "// NOQA")): |
| warn(path, line, lineno, "printf() statement") |
| |
| |
| def process(path): |
| with open(path) as f: |
| lines = f.readlines() |
| for idx, line in enumerate(lines): |
| check_line(path, line, idx, lines) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument('paths', nargs='+', help='path(s) to a file(s)') |
| args = parser.parse_args() |
| for path in args.paths: |
| process(path) |
| if warned: |
| sys.exit(1) |
| |
| |
| if __name__ == '__main__': |
| main() |