From 23ae264554e97529951eb9c5be7f47640f4c6ec4 Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Tue, 2 Jun 2026 16:24:29 -0400 Subject: [PATCH] Exit code is 0 for warnings, unless MAX_WARNINGS parameter is set and is less than or equal to the number of warnings. Added two new parameters: --max-warnings: when set, it creates a limit to the number of allowed warnings before DAYamlChecker exits with a nonzero value --format github: should be enabled when run in a github action so that appropriate error and warning annotations can be parsed by the github action --- src/dayamlchecker/messages.py | 43 ++++++++++++ src/dayamlchecker/yaml_structure.py | 100 ++++++++++++++++------------ 2 files changed, 101 insertions(+), 42 deletions(-) diff --git a/src/dayamlchecker/messages.py b/src/dayamlchecker/messages.py index 2fedc18..9287203 100644 --- a/src/dayamlchecker/messages.py +++ b/src/dayamlchecker/messages.py @@ -1170,3 +1170,46 @@ def make_finding( line_number=line_number, context=context, ) + +def escape_data(value: str) -> str: + return ( + value + .replace("%", "%25") + .replace("\r", "%0D") + .replace("\n", "%0A") + ) + +def escape_property(value: str) -> str: + return ( + escape_data(value) + .replace(":", "%3A") + .replace(",", "%2C") + ) + +def print_github_annotation(d: Finding) -> None: + kind = d.severity + if kind == "info": + kind = "notice" + + props = [] + + if getattr(d, "file_name", None): + props.append(f"file={escape_property(str(d.file_name))}") + if getattr(d, "line_number", None): + props.append(f"line={d.line_number}") + if getattr(d, "column", None): + props.append(f"col={d.column}") + if getattr(d, "end_line", None): + props.append(f"endLine={d.end_line}") + if getattr(d, "end_column", None): + props.append(f"endColumn={d.end_column}") + if getattr(d, "code", None): + props.append(f"title={escape_property(d.code)}") + + prop_text = ",".join(props) + message = escape_data(d.message) + + if prop_text: + print(f"::{kind} {prop_text}::{message}") + else: + print(f"::{kind}::{message}") diff --git a/src/dayamlchecker/yaml_structure.py b/src/dayamlchecker/yaml_structure.py index 9251729..add50e0 100644 --- a/src/dayamlchecker/yaml_structure.py +++ b/src/dayamlchecker/yaml_structure.py @@ -1954,10 +1954,10 @@ def process_file( input_file, lint_mode: str = DEFAULT_LINT_MODE, runtime_options: Optional[RuntimeOptions] = None, -) -> int: +) -> list[Finding]: """ Returns: - int: the number of errors found in the input_file + list[Finding]: the list of findings found in the input_file """ for dumb_da_file in [ "pgcodecache.yml", @@ -1968,42 +1968,12 @@ def process_file( "examples.yml", ]: if input_file.endswith(dumb_da_file): - print() - print(f"ignoring {dumb_da_file}") - return 0 + return [] all_errors = find_errors( input_file, lint_mode=lint_mode, runtime_options=runtime_options ) - - if len(all_errors) == 0: - print(".", end="") - return 0 - error_count = 0 - warning_count = 0 - info_count = 0 - for err in all_errors: - if err.severity == "info": - info_count += 1 - elif err.severity == "warning": - warning_count += 1 - else: - error_count += 1 - blocking_count = error_count + warning_count - print() - if info_count > 0: - print( - f"Found {len(all_errors)} issues ({error_count} errors, {warning_count} warnings, {info_count} infos):" - ) - elif warning_count > 0: - print( - f"Found {len(all_errors)} issues ({error_count} errors, {warning_count} warnings):" - ) - else: - print(f"Found {len(all_errors)} errors:") - for err in all_errors: - print(f"{err}") - return blocking_count + return all_errors def main(argv: Optional[list[str]] = None) -> int: @@ -2141,6 +2111,18 @@ def main(argv: Optional[list[str]] = None) -> int: default="warning", help="How to report URLs that could not be reached at all (default: warning)", ) + parser.add_argument( + "--format", + choices=("text", "github"), + default="text", + help="Output format for findings (default: text)", + ) + parser.add_argument( + "--max-warnings", + type=int, + default=None, + help="Maximum number of warnings allowed before failing with a non-zero exit code", + ) args = parser.parse_args(argv) lint_mode = ACCESSIBILITY_LINT_MODE if args.wcag else DEFAULT_LINT_MODE @@ -2164,15 +2146,16 @@ def main(argv: Optional[list[str]] = None) -> int: print("No YAML files found.", file=sys.stderr) return 1 - failed = False + from dayamlchecker.messages import print_github_annotation + + all_findings = [] for input_file in yaml_files: - error_count = process_file( + findings = process_file( str(input_file), lint_mode=lint_mode, runtime_options=runtime_options, ) - if error_count > 0: - failed = True + all_findings.extend(findings) if args.url_check: url_check_root = ( @@ -2191,11 +2174,44 @@ def main(argv: Optional[list[str]] = None) -> int: document_severity=args.document_url_severity, unreachable_severity=args.unreachable_url_severity, ) - print_url_check_report(url_check_result) - if url_check_result.has_errors(): - failed = True + all_findings.extend(url_check_result.issues) + + had_error = False + warning_count = sum(1 for f in all_findings if f.severity == "warning") + + if args.format == "github": + for finding in all_findings: + if finding.severity == "error": + had_error = True + print_github_annotation(finding) + else: + error_count = sum(1 for f in all_findings if f.severity == "error") + info_count = sum(1 for f in all_findings if f.severity == "info") + + if len(all_findings) == 0: + print("No issues found.") + else: + if info_count > 0: + print(f"Found {len(all_findings)} issues ({error_count} errors, {warning_count} warnings, {info_count} infos):") + elif warning_count > 0: + print(f"Found {len(all_findings)} issues ({error_count} errors, {warning_count} warnings):") + else: + print(f"Found {len(all_findings)} errors:") + for err in all_findings: + print(f"{err}") + + if error_count > 0: + had_error = True + + if args.max_warnings is not None and warning_count > args.max_warnings: + msg = f"Too many warnings: {warning_count} (max allowed is {args.max_warnings})" + if args.format == "github": + print(f"::error::{msg}") + else: + print(f"Error: {msg}", file=sys.stderr) + had_error = True - return 1 if failed else 0 + return 1 if had_error else 0 if __name__ == "__main__":