summaryrefslogtreecommitdiff
path: root/.gitlab/linters/linter.py
diff options
context:
space:
mode:
Diffstat (limited to '.gitlab/linters/linter.py')
-rw-r--r--.gitlab/linters/linter.py109
1 files changed, 109 insertions, 0 deletions
diff --git a/.gitlab/linters/linter.py b/.gitlab/linters/linter.py
new file mode 100644
index 0000000000..ec4f3581a8
--- /dev/null
+++ b/.gitlab/linters/linter.py
@@ -0,0 +1,109 @@
+"""
+Utilities for linters
+"""
+
+import os
+import sys
+import re
+import textwrap
+import subprocess
+from typing import List, Optional
+from collections import namedtuple
+
+def lint_failure(file, line_no, line_content, message):
+ """ Print a lint failure message. """
+ wrapper = textwrap.TextWrapper(initial_indent=' ',
+ subsequent_indent=' ')
+ body = wrapper.fill(message)
+ msg = '''
+ {file}:
+
+ |
+ {line_no:5d} | {line_content}
+ |
+
+ {body}
+ '''.format(file=file, line_no=line_no,
+ line_content=line_content,
+ body=body)
+
+ print(textwrap.dedent(msg))
+
+def get_changed_files(base_commit, head_commit,
+ subdir: str = '.'):
+ """ Get the files changed by the given range of commits. """
+ cmd = ['git', 'diff', '--name-only',
+ base_commit, head_commit, '--', subdir]
+ files = subprocess.check_output(cmd)
+ return files.decode('UTF-8').split('\n')
+
+Warning = namedtuple('Warning', 'path,line_no,line_content,message')
+
+class Linter(object):
+ """
+ A :class:`Linter` must implement :func:`lint`, which looks at the
+ given path and calls :func:`add_warning` for any lint issues found.
+ """
+ def __init__(self):
+ self.warnings = [] # type: List[Warning]
+
+ def add_warning(self, w: Warning):
+ self.warnings.append(w)
+
+ def lint(self, path):
+ pass
+
+class LineLinter(Linter):
+ """
+ A :class:`LineLinter` must implement :func:`lint_line`, which looks at
+ the given line from a file and calls :func:`add_warning` for any lint
+ issues found.
+ """
+ def lint(self, path):
+ if os.path.isfile(path):
+ with open(path, 'r') as f:
+ for line_no, line in enumerate(f):
+ self.lint_line(path, line_no+1, line)
+
+ def lint_line(self, path, line_no, line):
+ pass
+
+class RegexpLinter(LineLinter):
+ """
+ A :class:`RegexpLinter` produces the given warning message for
+ all lines matching the given regular expression.
+ """
+ def __init__(self, regex, message):
+ LineLinter.__init__(self)
+ self.re = re.compile(regex)
+ self.message = message
+
+ def lint_line(self, path, line_no, line):
+ if self.re.search(line):
+ w = Warning(path=path, line_no=line_no, line_content=line[:-1],
+ message=self.message)
+ self.add_warning(w)
+
+def run_linters(linters: List[Linter],
+ subdir: str = '.') -> None:
+ import argparse
+ parser = argparse.ArgumentParser()
+ parser.add_argument('base', help='Base commit')
+ parser.add_argument('head', help='Head commit')
+ args = parser.parse_args()
+
+ for path in get_changed_files(args.base, args.head, subdir):
+ if path.startswith('.gitlab/linters'):
+ continue
+ for linter in linters:
+ linter.lint(path)
+
+ warnings = [warning
+ for linter in linters
+ for warning in linter.warnings]
+ warnings = sorted(warnings, key=lambda x: (x.path, x.line_no))
+ for w in warnings:
+ lint_failure(w.path, w.line_no, w.line_content, w.message)
+
+ if len(warnings) > 0:
+ sys.exit(1)