summaryrefslogtreecommitdiff
path: root/.gitlab
diff options
context:
space:
mode:
authorBen Gamari <ben@smart-cactus.org>2018-12-13 13:53:27 -0500
committerBen Gamari <ben@smart-cactus.org>2018-12-13 21:59:20 -0500
commite9f68a1529687b11e22cf2b28e119b043dded6a6 (patch)
tree64199b60c6e966bd1bd1ba8b50f8cb35ba51bff5 /.gitlab
parent9b8713e8bf905c17251a0fad22eee690c4e50f0c (diff)
downloadhaskell-e9f68a1529687b11e22cf2b28e119b043dded6a6.tar.gz
gitlab-ci: Add linters
These are taken from our previous arcanist linters as well as the gitolite hooks but with some heavy refactoring.
Diffstat (limited to '.gitlab')
-rwxr-xr-x.gitlab/linters/check-cpp.py22
-rwxr-xr-x.gitlab/linters/check-makefiles.py19
-rw-r--r--.gitlab/linters/linter.py109
3 files changed, 150 insertions, 0 deletions
diff --git a/.gitlab/linters/check-cpp.py b/.gitlab/linters/check-cpp.py
new file mode 100755
index 0000000000..144ab7dd6e
--- /dev/null
+++ b/.gitlab/linters/check-cpp.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+
+# A linter to warn for ASSERT macros which are separated from their argument
+# list by a space, which Clang's CPP barfs on
+
+from linter import run_linters, RegexpLinter
+
+linters = [
+ RegexpLinter(r'ASSERT\s+\(',
+ message='CPP macros should not have a space between the macro name and their argument list'),
+ RegexpLinter(r'ASSERT2\s+\(',
+ message='CPP macros should not have a space between the macro name and their argument list'),
+ RegexpLinter(r'#ifdef\s+',
+ message='`#if defined(x)` is preferred to `#ifdef x`'),
+ RegexpLinter(r'#if\s+defined\s+',
+ message='`#if defined(x)` is preferred to `#if defined x`'),
+ RegexpLinter(r'#ifndef\s+',
+ message='`#if !defined(x)` is preferred to `#ifndef x`'),
+]
+
+if __name__ == '__main__':
+ run_linters(linters)
diff --git a/.gitlab/linters/check-makefiles.py b/.gitlab/linters/check-makefiles.py
new file mode 100755
index 0000000000..c97838beb5
--- /dev/null
+++ b/.gitlab/linters/check-makefiles.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+
+"""
+Warn for use of `--interactive` inside Makefiles (#11468).
+
+Encourage the use of `$(TEST_HC_OPTS_INTERACTIVE)` instead of
+`$(TEST_HC_OPTS) --interactive -ignore-dot-ghci -v0`. It's too easy to
+forget one of those flags when adding a new test.
+"""
+
+from linter import run_linters, RegexpLinter
+
+linters = [
+ RegexpLinter(r'--interactive',
+ message = "Warning: Use `$(TEST_HC_OPTS_INTERACTIVE)` instead of `--interactive -ignore-dot-ghci -v0`.")
+]
+
+if __name__ == '__main__':
+ run_linters(linters) #$, subdir='testsuite')
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)