pull/579/merge
Mathieu Rul 2 years ago committed by GitHub
commit 1a2ca8d55b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -45,7 +45,7 @@ build-backend = "setuptools.build_meta"
requires = ["setuptools >= 61"]
[tool.setuptools]
packages = ["yamllint", "yamllint.conf", "yamllint.rules"]
packages = ["yamllint", "yamllint.conf", "yamllint.formatters", "yamllint.rules"]
[tool.setuptools.package-data]
yamllint = ["conf/*.yaml"]

@ -40,9 +40,6 @@ class RuleTestCase(unittest.TestCase):
for key in kwargs:
assert key.startswith('problem')
if len(kwargs[key]) > 2:
if kwargs[key][2] == 'syntax':
rule_id = None
else:
rule_id = kwargs[key][2]
else:
rule_id = self.rule_id

@ -582,6 +582,94 @@ class CommandLineTestCase(unittest.TestCase):
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (0, expected_out, ''))
def test_run_format_sarif(self):
path = os.path.join(self.wd, 'a.yaml')
with RunContext(self) as ctx:
cli.run((path, '--format', 'sarif'))
expected_out = (
'{"$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": "1.32.0", "informationUri": '
'"https://yamllint.readthedocs.io", "rules": [{"id": '
'"trailing-spaces", "name": "TrailingSpaces", '
'"defaultConfiguration": {"level": "error"}, "properties": {'
'"description": "trailing spaces", "tags": [], "queryUri": '
'"https://yamllint.readthedocs.io/en/v1.32.0/rules.html#module'
'-yamllint.rules.trailing_spaces"}, "shortDescription": {"text": '
'"trailing spaces"}, "fullDescription": {"text": "trailing '
'spaces"}, "helpUri": '
'"https://yamllint.readthedocs.io/en/v1.32.0/rules.html#module'
'-yamllint.rules.trailing_spaces", "help": {"text": "More info: '
'https://yamllint.readthedocs.io/en/v1.32.0/rules.html#module'
'-yamllint.rules.trailing_spaces", "markdown": "[More info]('
'https://yamllint.readthedocs.io/en/v1.32.0/rules.html#module'
'-yamllint.rules.trailing_spaces)"}}, {"id": '
'"new-line-at-end-of-file", "name": "NewLineAtEndOfFile", '
'"defaultConfiguration": {"level": "error"}, "properties": {'
'"description": "no new line character at the end of file", '
'"tags": [], "queryUri": '
'"https://yamllint.readthedocs.io/en/v1.32.0/rules.html#module'
'-yamllint.rules.new_line_at_end_of_file"}, "shortDescription": '
'{"text": "no new line character at the end of file"}, '
'"fullDescription": {"text": "no new line character at the end '
'of file"}, "helpUri": '
'"https://yamllint.readthedocs.io/en/v1.32.0/rules.html#module'
'-yamllint.rules.new_line_at_end_of_file", "help": {"text": '
'"More info: https://yamllint.readthedocs.io/en/v1.32.0/rules'
'.html#module-yamllint.rules.new_line_at_end_of_file", '
'"markdown": "[More info]('
'https://yamllint.readthedocs.io/en/v1.32.0/rules.html#module'
'-yamllint.rules.new_line_at_end_of_file)"}}]}}, "results": [{'
'"ruleId": "trailing-spaces", "ruleIndex": 0, "message": {'
'"text": "trailing spaces (trailing-spaces)"}, "locations": [{'
'"physicalLocation": {"artifactLocation": {"uri": "%s", '
'"uriBaseId": "%%SRCROOT%%"}, "region": {"startLine": 2, '
'"startColumn": 4}}}]}, {"ruleId": "new-line-at-end-of-file", '
'"ruleIndex": 1, "message": {"text": "no new line character at '
'the end of file (new-line-at-end-of-file)"}, "locations": [{'
'"physicalLocation": {"artifactLocation": {"uri": "%s", '
'"uriBaseId": "%%SRCROOT%%"}, "region": {"startLine": 3, '
'"startColumn": 4}}}]}]}]}\n'
% (path, path))
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_format_sarif_warning(self):
path = os.path.join(self.wd, 'warn.yaml')
with RunContext(self) as ctx:
cli.run((path, '--format', 'sarif'))
expected_out = (
'{"$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": "1.32.0", "informationUri": '
'"https://yamllint.readthedocs.io", "rules": [{"id": '
'"document-start", "name": "DocumentStart", '
'"defaultConfiguration": {"level": "warning"}, "properties": {'
'"description": "missing document start \\"---\\"", "tags": [], '
'"queryUri": "https://yamllint.readthedocs.io/en/v1.32.0/rules'
'.html#module-yamllint.rules.document_start"}, '
'"shortDescription": {"text": "missing document start '
'\\"---\\""}, "fullDescription": {"text": "missing document '
'start \\"---\\""}, "helpUri": '
'"https://yamllint.readthedocs.io/en/v1.32.0/rules.html#module'
'-yamllint.rules.document_start", "help": {"text": "More info: '
'https://yamllint.readthedocs.io/en/v1.32.0/rules.html#module'
'-yamllint.rules.document_start", "markdown": "[More info]('
'https://yamllint.readthedocs.io/en/v1.32.0/rules.html#module'
'-yamllint.rules.document_start)"}}]}}, "results": [{"ruleId": '
'"document-start", "ruleIndex": 0, "message": {"text": "missing '
'document start \\"---\\" (document-start)"}, "locations": [{'
'"physicalLocation": {"artifactLocation": {"uri": "%s", '
'"uriBaseId": "%%SRCROOT%%"}, "region": {"startLine": 1, '
'"startColumn": 1}}}]}]}]}\n'
% path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (0, expected_out, ''))
def test_run_format_github(self):
path = os.path.join(self.wd, 'a.yaml')

@ -17,7 +17,7 @@ from tests.common import RuleTestCase
class YamlLintTestCase(RuleTestCase):
rule_id = None # syntax error
rule_id = 'syntax' # syntax error
def test_syntax_errors(self):
self.check('---\n'

@ -22,6 +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, sarif, standard
from yamllint.linter import PROBLEM_LEVELS
@ -46,63 +47,7 @@ def supports_color():
hasattr(sys.stdout, 'isatty') and sys.stdout.isatty())
class Format:
@staticmethod
def parsable(problem, filename):
return ('%(file)s:%(line)s:%(column)s: [%(level)s] %(message)s' %
{'file': filename,
'line': problem.line,
'column': problem.column,
'level': problem.level,
'message': problem.message})
@staticmethod
def standard(problem, filename):
line = ' %d:%d' % (problem.line, problem.column)
line += max(12 - len(line), 0) * ' '
line += problem.level
line += max(21 - len(line), 0) * ' '
line += problem.desc
if problem.rule:
line += ' (%s)' % problem.rule
return line
@staticmethod
def standard_color(problem, filename):
line = ' \033[2m%d:%d\033[0m' % (problem.line, problem.column)
line += max(20 - len(line), 0) * ' '
if problem.level == 'warning':
line += '\033[33m%s\033[0m' % problem.level
else:
line += '\033[31m%s\033[0m' % problem.level
line += max(38 - len(line), 0) * ' '
line += problem.desc
if problem.rule:
line += ' \033[2m(%s)\033[0m' % problem.rule
return line
@staticmethod
def github(problem, filename):
line = '::'
line += problem.level
line += ' file=' + filename + ','
line += 'line=' + format(problem.line) + ','
line += 'col=' + format(problem.column)
line += '::'
line += format(problem.line)
line += ':'
line += format(problem.column)
line += ' '
if problem.rule:
line += '[' + problem.rule + '] '
line += problem.desc
return line
def show_problems(problems, file, args_format, no_warn):
max_level = 0
first = True
def show_results(results, args_format, no_warn):
if args_format == 'auto':
if ('GITHUB_ACTIONS' in os.environ and
'GITHUB_WORKFLOW' in os.environ):
@ -110,35 +55,16 @@ def show_problems(problems, file, args_format, no_warn):
elif supports_color():
args_format = 'colored'
for problem in problems:
max_level = max(max_level, PROBLEM_LEVELS[problem.level])
if no_warn and (problem.level != 'error'):
continue
if args_format == 'parsable':
print(Format.parsable(problem, file))
return parsable.format_results(results, no_warn)
elif args_format == 'github':
if first:
print('::group::%s' % file)
first = False
print(Format.github(problem, file))
return github.format_results(results, no_warn)
elif args_format == 'sarif':
return sarif.format_results(results, no_warn)
elif args_format == 'colored':
if first:
print('\033[4m%s\033[0m' % file)
first = False
print(Format.standard_color(problem, file))
return colored.format_results(results, no_warn)
else:
if first:
print(file)
first = False
print(Format.standard(problem, file))
if not first and args_format == 'github':
print('::endgroup::')
if not first and args_format != 'parsable':
print('')
return max_level
return standard.format_results(results, no_warn)
def find_project_config_filepath(path='.'):
@ -174,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',
@ -225,8 +151,7 @@ def run(argv=None):
print(file)
sys.exit(0)
max_level = 0
results = {}
for file in find_files_recursively(args.files, conf):
filepath = file[2:] if file.startswith('./') else file
try:
@ -235,9 +160,7 @@ def run(argv=None):
except OSError as e:
print(e, file=sys.stderr)
sys.exit(-1)
prob_level = show_problems(problems, file, args_format=args.format,
no_warn=args.no_warnings)
max_level = max(max_level, prob_level)
results[filepath] = problems
# read yaml from stdin
if args.stdin:
@ -246,9 +169,10 @@ def run(argv=None):
except OSError as e:
print(e, file=sys.stderr)
sys.exit(-1)
prob_level = show_problems(problems, 'stdin', args_format=args.format,
results['stdin'] = problems
max_level = show_results(results, args_format=args.format,
no_warn=args.no_warnings)
max_level = max(max_level, prob_level)
if max_level == PROBLEM_LEVELS['error']:
return_code = 1

@ -0,0 +1,48 @@
# 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 <http://www.gnu.org/licenses/>.
from yamllint.linter import PROBLEM_LEVELS
def format_results(results, no_warn):
max_level = 0
for file in results:
print('\033[4m%s\033[0m' % file)
for problem in results[file]:
max_level = max(max_level, PROBLEM_LEVELS[problem.level])
if no_warn and (problem.level != 'error'):
continue
print(format_problem(problem))
print('')
return max_level
def format_problem(problem):
line = ' \033[2m%d:%d\033[0m' % (problem.line, problem.column)
line += max(20 - len(line), 0) * ' '
if problem.level == 'warning':
line += '\033[33m%s\033[0m' % problem.level
else:
line += '\033[31m%s\033[0m' % problem.level
line += max(38 - len(line), 0) * ' '
line += problem.desc
if problem.rule:
line += ' \033[2m(%s)\033[0m' % problem.rule
return line

@ -0,0 +1,52 @@
# 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 <http://www.gnu.org/licenses/>.
from yamllint.linter import PROBLEM_LEVELS
def format_results(results, no_warn):
max_level = 0
for file in results:
print('::group::%s' % file)
for problem in results[file]:
max_level = max(max_level, PROBLEM_LEVELS[problem.level])
if no_warn and (problem.level != 'error'):
continue
print(format_problem(problem, file))
print('::endgroup::')
print('')
return max_level
def format_problem(problem, filename):
line = '::'
line += problem.level
line += ' file=' + filename + ','
line += 'line=' + format(problem.line) + ','
line += 'col=' + format(problem.column)
line += '::'
line += format(problem.line)
line += ':'
line += format(problem.column)
line += ' '
if problem.rule:
line += '[' + problem.rule + '] '
line += problem.desc
return line

@ -0,0 +1,39 @@
# 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 <http://www.gnu.org/licenses/>.
from yamllint.linter import PROBLEM_LEVELS
def format_results(results, no_warn):
max_level = 0
for file in results:
for problem in results[file]:
max_level = max(max_level, PROBLEM_LEVELS[problem.level])
if no_warn and (problem.level != 'error'):
continue
print(format_problem(problem, file))
return max_level
def format_problem(problem, filename):
return ('%(file)s:%(line)s:%(column)s: [%(level)s] %(message)s' %
{'file': filename,
'line': problem.line,
'column': problem.column,
'level': problem.level,
'message': problem.message})

@ -0,0 +1,120 @@
# 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 <http://www.gnu.org/licenses/>.
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%s/rules.html#module-yamllint' \
'.rules.%s' % (APP_VERSION, problem.rule.replace('-', '_'))
name = ''.join([word.capitalize() for word in problem.rule.split('-')])
return {
'id': problem.rule,
'name': name,
'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
}
}
}
]
}

@ -0,0 +1,45 @@
# 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 <http://www.gnu.org/licenses/>.
from yamllint.linter import PROBLEM_LEVELS
def format_results(results, no_warn):
max_level = 0
for file in results:
print(file)
for problem in results[file]:
max_level = max(max_level, PROBLEM_LEVELS[problem.level])
if no_warn and (problem.level != 'error'):
continue
print(format_problem(problem))
print('')
return max_level
def format_problem(problem):
line = ' %d:%d' % (problem.line, problem.column)
line += max(12 - len(line), 0) * ' '
line += problem.level
line += max(21 - len(line), 0) * ' '
line += problem.desc
if problem.rule:
line += ' (%s)' % problem.rule
return line

@ -180,7 +180,8 @@ def get_syntax_error(buffer):
except yaml.error.MarkedYAMLError as e:
problem = LintProblem(e.problem_mark.line + 1,
e.problem_mark.column + 1,
'syntax error: ' + e.problem + ' (syntax)')
'syntax error: ' + e.problem,
'syntax')
problem.level = 'error'
return problem

Loading…
Cancel
Save