diff --git a/python/mozlint/mozlint/util/implementation.py b/python/mozlint/mozlint/util/implementation.py new file mode 100644 index 000000000000..60cdab55a6e0 --- /dev/null +++ b/python/mozlint/mozlint/util/implementation.py @@ -0,0 +1,36 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import signal +from abc import ABC, abstractmethod + +from mozprocess import ProcessHandlerMixin + + +class LintProcess(ProcessHandlerMixin, ABC): + + def __init__(self, config, *args, **kwargs): + self.config = config + self.results = [] + + kwargs['universal_newlines'] = True + kwargs['processOutputLine'] = [self.process_line] + ProcessHandlerMixin.__init__(self, *args, **kwargs) + + @abstractmethod + def process_line(self, line): + """Process a single line of output. + + The implementation is responsible for creating one or more :class:`~mozlint.result.Issue`s + and storing them somewhere accessible. + + Args: + line (str): The line of output to process. + """ + pass + + def run(self, *args, **kwargs): + orig = signal.signal(signal.SIGINT, signal.SIG_IGN) + ProcessHandlerMixin.run(self, *args, **kwargs) + signal.signal(signal.SIGINT, orig) diff --git a/tools/lint/shell/__init__.py b/tools/lint/shell/__init__.py index 46a1b0927a4b..2e49a6dc7143 100644 --- a/tools/lint/shell/__init__.py +++ b/tools/lint/shell/__init__.py @@ -4,15 +4,14 @@ import os import json -import signal from json.decoder import JSONDecodeError import mozpack.path as mozpath from mozfile import which -from mozlint import result from mozpack.files import FileFinder -from mozprocess import ProcessHandlerMixin +from mozlint import result +from mozlint.util.implementation import LintProcess SHELLCHECK_NOT_FOUND = """ Unable to locate shellcheck, please ensure it is installed and in @@ -24,12 +23,7 @@ https://shellcheck.net or your system's package manager. results = [] -class ShellcheckProcess(ProcessHandlerMixin): - def __init__(self, config, *args, **kwargs): - self.config = config - kwargs['universal_newlines'] = True - kwargs['processOutputLine'] = [self.process_line] - ProcessHandlerMixin.__init__(self, *args, **kwargs) +class ShellcheckProcess(LintProcess): def process_line(self, line): try: @@ -47,12 +41,7 @@ class ShellcheckProcess(ProcessHandlerMixin): 'column': entry['column'], 'rule': entry['code'], } - results.append(result.from_config(self.config, **res)) - - def run(self, *args, **kwargs): - orig = signal.signal(signal.SIGINT, signal.SIG_IGN) - ProcessHandlerMixin.run(self, *args, **kwargs) - signal.signal(signal.SIGINT, orig) + results.append(result.from_config(self.config, *res)) def determine_shell_from_script(path): diff --git a/tools/lint/spell/__init__.py b/tools/lint/spell/__init__.py index 8b275cfd67d7..6c492fb4deb7 100644 --- a/tools/lint/spell/__init__.py +++ b/tools/lint/spell/__init__.py @@ -3,7 +3,6 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. import os -import signal import re # py2-compat @@ -13,9 +12,10 @@ except ImportError: JSONDecodeError = ValueError from mozfile import which + from mozlint import result from mozlint.util import pip -from mozprocess import ProcessHandlerMixin +from mozlint.util.implementation import LintProcess here = os.path.abspath(os.path.dirname(__file__)) CODESPELL_REQUIREMENTS_PATH = os.path.join(here, 'codespell_requirements.txt') @@ -38,14 +38,7 @@ results = [] CODESPELL_FORMAT_REGEX = re.compile(r'(.*):(.*): (.*) ==> (.*)$') -class CodespellProcess(ProcessHandlerMixin): - def __init__(self, config, *args, **kwargs): - self.config = config - kwargs = { - 'processOutputLine': [self.process_line], - 'universal_newlines': True, - } - ProcessHandlerMixin.__init__(self, *args, **kwargs) +class CodespellProcess(LintProcess): def process_line(self, line): try: @@ -60,18 +53,14 @@ class CodespellProcess(ProcessHandlerMixin): m = re.match(r'^[a-z][A-Z][a-z]*', typo) if m: return - res = {'path': abspath, - 'message': typo.strip() + " ==> " + correct, - 'level': 'error', - 'lineno': line, - } + res = { + 'path': abspath, + 'message': typo.strip() + " ==> " + correct, + 'level': 'error', + 'lineno': line, + } results.append(result.from_config(self.config, **res)) - def run(self, *args, **kwargs): - orig = signal.signal(signal.SIGINT, signal.SIG_IGN) - ProcessHandlerMixin.run(self, *args, **kwargs) - signal.signal(signal.SIGINT, orig) - def run_process(config, cmd): proc = CodespellProcess(config, cmd) diff --git a/tools/lint/yamllint_/__init__.py b/tools/lint/yamllint_/__init__.py index 5f848f79de48..05aebe84a849 100644 --- a/tools/lint/yamllint_/__init__.py +++ b/tools/lint/yamllint_/__init__.py @@ -4,14 +4,14 @@ import re import os -import signal import subprocess from collections import defaultdict from mozfile import which + from mozlint import result from mozlint.pathutils import get_ancestors_by_name -from mozprocess import ProcessHandlerMixin +from mozlint.util.implementation import LintProcess YAMLLINT_FORMAT_REGEX = re.compile('(.*):(.*):(.*): \[(error|warning)\] (.*) \((.*)\)$') @@ -19,12 +19,7 @@ YAMLLINT_FORMAT_REGEX = re.compile('(.*):(.*):(.*): \[(error|warning)\] (.*) \(( results = [] -class YAMLLintProcess(ProcessHandlerMixin): - def __init__(self, config, *args, **kwargs): - self.config = config - kwargs['processOutputLine'] = [self.process_line] - kwargs['universal_newlines'] = True - ProcessHandlerMixin.__init__(self, *args, **kwargs) +class YAMLLintProcess(LintProcess): def process_line(self, line): try: @@ -44,13 +39,6 @@ class YAMLLintProcess(ProcessHandlerMixin): results.append(result.from_config(self.config, **res)) - def run(self, *args, **kwargs): - # protect against poor SIGINT handling. Handle it here instead - # so we can kill the process without a cryptic traceback. - orig = signal.signal(signal.SIGINT, signal.SIG_IGN) - ProcessHandlerMixin.run(self, *args, **kwargs) - signal.signal(signal.SIGINT, orig) - def get_yamllint_binary(mc_root): """