From d24519c9415c9128a6518e2bfb0d0a1097ff6cf7 Mon Sep 17 00:00:00 2001 From: QuentinN42 Date: Sat, 5 Feb 2022 11:24:48 +0100 Subject: [PATCH] feat: add codeclimate output --- yamllint/cli.py | 2 +- yamllint/format.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/yamllint/cli.py b/yamllint/cli.py index 471a2e0..d9e364e 100644 --- a/yamllint/cli.py +++ b/yamllint/cli.py @@ -59,7 +59,7 @@ def run(argv=None): help='custom configuration (as YAML source)') parser.add_argument('-f', '--format', choices=('parsable', 'standard', 'colored', 'github', - 'json', 'junitxml', 'auto'), + 'json', 'junitxml', 'codeclimate', 'auto'), default='auto', help='format for parsing output') parser.add_argument('-s', '--strict', action='store_true', diff --git a/yamllint/format.py b/yamllint/format.py index c2bbde1..565e89b 100644 --- a/yamllint/format.py +++ b/yamllint/format.py @@ -10,6 +10,13 @@ import datetime from yamllint.linter import PROBLEM_LEVELS +CODECLIMATE_SEVERITY = { + None: "info", + 'warning': "minor", + 'error': "major", +} + + def supports_color(): supported_platform = not (platform.system() == 'Windows' and not ('ANSICON' in os.environ or @@ -34,6 +41,12 @@ def escape_xml(text): return text +def severity_from_level(level): + if isinstance(level, int): + level = PROBLEM_LEVELS[level] + return CODECLIMATE_SEVERITY[level] + + class Formater(object): """Any formater.""" # the formater name @@ -286,6 +299,66 @@ class JunitFormater(Formater): return {**problem.dict, "file": file} +class CodeclimateFormater(Formater): + """The codeclimate formater.""" + name = 'codeclimate' + + def show_problems_for_all_files(self, all_problems): + """Show all problems of all files.""" + lst = [] + for k, v in all_problems.items(): + lst += self.show_problems_for_file(v, k) + return json.dumps(lst, indent=4) + '\n' + + def show_problems_for_file(self, problems, file): + """Show all problems of a specific file.""" + return list(map(self.show_problem, problems, [file] * len(problems))) + + def show_problem(self, problem, file): + """Show all problems of a specific file. + + Using the codeclimate format. + https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#data-types + + + * `type` -- **Required**. Must always be "issue". + * `check_name` -- **Required**. A unique name representing the static analysis check that emitted this issue. + * `description` -- **Required**. A string explaining the issue that was detected. + * `content` -- **Optional**. A markdown snippet describing the issue, including deeper explanations and links to other resources. + * `categories` -- **Required**. At least one category indicating the nature of the issue being reported. + * `location` -- **Required**. A `Location` object representing the place in the source code where the issue was discovered. + * `trace` -- **Optional.** A `Trace` object representing other interesting source code locations related to this issue. + * `remediation_points` -- **Optional**. An integer indicating a rough estimate of how long it would take to resolve the reported issue. + * `severity` -- **Optional**. A `Severity` string (`info`, `minor`, `major`, `critical`, or `blocker`) describing the potential impact of the issue found. + * `fingerprint` -- **Optional**. A unique, deterministic identifier for the specific issue being reported to allow a user to exclude it from future analyses. + + For now the categories doc is empty, just put Style. + https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#categories + + I don't find a value of remdiation_points, just set it at 1k. + + I don't know how to calculate the fingerprint, maybe with a sha but it will be slow. + """ # noqa + return { + "type": "issue", + "check_name": problem.rule, + "description": problem.desc, + "content": problem.message, + "categories": ["Style"], + "location": { + "path": file, + "positions": { + "begin": { + "line": problem.line, + "column": problem.column + }, + } + }, + "remediation_points": 1_000, + "severity": severity_from_level(problem.level) + } + + def max_level(all_problems): """Return the max level of all problems.""" all_levels = [problem.level for problems in all_problems.values() for problem in problems]