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"] requires = ["setuptools >= 61"]
[tool.setuptools] [tool.setuptools]
packages = ["yamllint", "yamllint.conf", "yamllint.rules"] packages = ["yamllint", "yamllint.conf", "yamllint.formatters", "yamllint.rules"]
[tool.setuptools.package-data] [tool.setuptools.package-data]
yamllint = ["conf/*.yaml"] yamllint = ["conf/*.yaml"]

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

@ -582,6 +582,94 @@ class CommandLineTestCase(unittest.TestCase):
self.assertEqual( self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (0, expected_out, '')) (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): def test_run_format_github(self):
path = os.path.join(self.wd, 'a.yaml') path = os.path.join(self.wd, 'a.yaml')

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

@ -22,6 +22,7 @@ import sys
from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION
from yamllint import linter from yamllint import linter
from yamllint.config import YamlLintConfig, YamlLintConfigError from yamllint.config import YamlLintConfig, YamlLintConfigError
from yamllint.formatters import colored, github, parsable, sarif, standard
from yamllint.linter import PROBLEM_LEVELS from yamllint.linter import PROBLEM_LEVELS
@ -46,63 +47,7 @@ def supports_color():
hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()) hasattr(sys.stdout, 'isatty') and sys.stdout.isatty())
class Format: def show_results(results, args_format, no_warn):
@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
if args_format == 'auto': if args_format == 'auto':
if ('GITHUB_ACTIONS' in os.environ and if ('GITHUB_ACTIONS' in os.environ and
'GITHUB_WORKFLOW' in os.environ): 'GITHUB_WORKFLOW' in os.environ):
@ -110,35 +55,16 @@ def show_problems(problems, file, args_format, no_warn):
elif supports_color(): elif supports_color():
args_format = 'colored' args_format = 'colored'
for problem in problems: if args_format == 'parsable':
max_level = max(max_level, PROBLEM_LEVELS[problem.level]) return parsable.format_results(results, no_warn)
if no_warn and (problem.level != 'error'): elif args_format == 'github':
continue return github.format_results(results, no_warn)
if args_format == 'parsable': elif args_format == 'sarif':
print(Format.parsable(problem, file)) return sarif.format_results(results, no_warn)
elif args_format == 'github': elif args_format == 'colored':
if first: return colored.format_results(results, no_warn)
print('::group::%s' % file) else:
first = False return standard.format_results(results, no_warn)
print(Format.github(problem, file))
elif args_format == 'colored':
if first:
print('\033[4m%s\033[0m' % file)
first = False
print(Format.standard_color(problem, file))
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
def find_project_config_filepath(path='.'): def find_project_config_filepath(path='.'):
@ -174,7 +100,7 @@ def run(argv=None):
help='list files to lint and exit') help='list files to lint and exit')
parser.add_argument('-f', '--format', parser.add_argument('-f', '--format',
choices=('parsable', 'standard', 'colored', 'github', choices=('parsable', 'standard', 'colored', 'github',
'auto'), 'sarif', 'auto'),
default='auto', help='format for parsing output') default='auto', help='format for parsing output')
parser.add_argument('-s', '--strict', parser.add_argument('-s', '--strict',
action='store_true', action='store_true',
@ -225,8 +151,7 @@ def run(argv=None):
print(file) print(file)
sys.exit(0) sys.exit(0)
max_level = 0 results = {}
for file in find_files_recursively(args.files, conf): for file in find_files_recursively(args.files, conf):
filepath = file[2:] if file.startswith('./') else file filepath = file[2:] if file.startswith('./') else file
try: try:
@ -235,9 +160,7 @@ def run(argv=None):
except OSError as e: except OSError as e:
print(e, file=sys.stderr) print(e, file=sys.stderr)
sys.exit(-1) sys.exit(-1)
prob_level = show_problems(problems, file, args_format=args.format, results[filepath] = problems
no_warn=args.no_warnings)
max_level = max(max_level, prob_level)
# read yaml from stdin # read yaml from stdin
if args.stdin: if args.stdin:
@ -246,9 +169,10 @@ def run(argv=None):
except OSError as e: except OSError as e:
print(e, file=sys.stderr) print(e, file=sys.stderr)
sys.exit(-1) sys.exit(-1)
prob_level = show_problems(problems, 'stdin', args_format=args.format, results['stdin'] = problems
no_warn=args.no_warnings)
max_level = max(max_level, prob_level) max_level = show_results(results, args_format=args.format,
no_warn=args.no_warnings)
if max_level == PROBLEM_LEVELS['error']: if max_level == PROBLEM_LEVELS['error']:
return_code = 1 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: except yaml.error.MarkedYAMLError as e:
problem = LintProblem(e.problem_mark.line + 1, problem = LintProblem(e.problem_mark.line + 1,
e.problem_mark.column + 1, e.problem_mark.column + 1,
'syntax error: ' + e.problem + ' (syntax)') 'syntax error: ' + e.problem,
'syntax')
problem.level = 'error' problem.level = 'error'
return problem return problem

Loading…
Cancel
Save