From 73e8798369e7615be1add34177e823bd1beb0299 Mon Sep 17 00:00:00 2001 From: Mathieu Rul Date: Fri, 16 Jun 2023 15:21:39 +0200 Subject: [PATCH] Add support for SARIF format Signed-off-by: Mathieu Rul --- yamllint/cli.py | 6 +- yamllint/formatters/sarif.py | 113 +++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 yamllint/formatters/sarif.py diff --git a/yamllint/cli.py b/yamllint/cli.py index ae84ac1..90b3b76 100644 --- a/yamllint/cli.py +++ b/yamllint/cli.py @@ -22,7 +22,7 @@ import sys from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION from yamllint import linter from yamllint.config import YamlLintConfig, YamlLintConfigError -from yamllint.formatters import colored, github, parsable, standard +from yamllint.formatters import colored, github, parsable, sarif, standard from yamllint.linter import PROBLEM_LEVELS @@ -59,6 +59,8 @@ def show_results(results, args_format, no_warn): return parsable.format_results(results, no_warn) elif args_format == 'github': return github.format_results(results, no_warn) + elif args_format == 'sarif': + return sarif.format_results(results, no_warn) elif args_format == 'colored': return colored.format_results(results, no_warn) else: @@ -98,7 +100,7 @@ def run(argv=None): help='list files to lint and exit') parser.add_argument('-f', '--format', choices=('parsable', 'standard', 'colored', 'github', - 'auto'), + 'sarif', 'auto'), default='auto', help='format for parsing output') parser.add_argument('-s', '--strict', action='store_true', diff --git a/yamllint/formatters/sarif.py b/yamllint/formatters/sarif.py new file mode 100644 index 0000000..ec0945e --- /dev/null +++ b/yamllint/formatters/sarif.py @@ -0,0 +1,113 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import json + +from yamllint import APP_VERSION +from yamllint.linter import PROBLEM_LEVELS + + +def format_results(results, no_warn): + max_level = 0 + + sarif = { + '$schema': 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json', + 'version': '2.1.0', + 'runs': [ + { + 'tool': { + 'driver': { + 'name': 'yamllint', + 'version': APP_VERSION, + 'informationUri': 'https://yamllint.readthedocs.io', + 'rules': [] + }, + }, + 'results': [] + } + ] + } + + rules = {} + max_rule_index = 0 + + for file in results: + for problem in results[file]: + max_level = max(max_level, PROBLEM_LEVELS[problem.level]) + + if problem.rule in rules: + rule_index = rules[problem.rule] + else: + rule_index = max_rule_index + rules[problem.rule] = max_rule_index + sarif['runs'][0]['tool']['driver']['rules'].append(format_rule(problem)) + max_rule_index += 1 + + sarif['runs'][0]['results'].append(format_result(rule_index, problem, file)) + + print(json.dumps(sarif)) + + return max_level + + +def format_rule(problem): + uri = 'https://yamllint.readthedocs.io/en/v{}/rules.html#module-yamllint.rules.{}'.format(APP_VERSION, problem.rule) + return { + 'id': problem.rule, + 'name': ''.join([word.capitalize() for word in problem.rule.split('-')]), + 'defaultConfiguration': { + 'level': problem.level + }, + 'properties': { + 'description': problem.desc, + 'tags': [], + 'queryUri': uri, + }, + 'shortDescription': { + 'text': problem.desc + }, + 'fullDescription': { + 'text': problem.desc + }, + 'helpUri': uri, + 'help': { + 'text': 'More info: {}'.format(uri), + 'markdown': '[More info]({})'.format(uri) + } + } + + +def format_result(rule_index, problem, filename): + return { + 'ruleId': problem.rule, + 'ruleIndex': rule_index, + 'message': { + 'text': problem.message + }, + 'locations': [ + { + 'physicalLocation': { + 'artifactLocation': { + 'uri': filename, + 'uriBaseId': '%SRCROOT%' + }, + 'region': { + 'startLine': problem.line, + 'startColumn': problem.column + } + } + } + ] + }