diff --git a/.automation/test/css/.stylelintrc_bad.json b/.automation/test/css/.stylelintrc_bad.json new file mode 100644 index 00000000000..acbaddcb8bd --- /dev/null +++ b/.automation/test/css/.stylelintrc_bad.json @@ -0,0 +1,11 @@ +{ + "extends": "stylelint-config-standard", + "rules": { + "comment-empty-line-before": [ + "always", + { + "severity": "warning" + } + ] + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 95d02dd9351..86a991ee5ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), Note: Can be used with `oxsecurity/megalinter@beta` in your GitHub Action mega-linter.yml file, or with `oxsecurity/megalinter:beta` docker image - Core + - Addition of warnings to reporters and logic changes to surface warnings even when there are no errors. Addition of `cli_lint_warning_count` / `cli_lint_warning_regex` variables to the JSON schema. [#4476](https://github.com/oxsecurity/megalinter/issues/4476) - New linters diff --git a/megalinter/Linter.py b/megalinter/Linter.py index 2656e7eba41..4d533a1fca4 100644 --- a/megalinter/Linter.py +++ b/megalinter/Linter.py @@ -129,6 +129,8 @@ def __init__(self, params=None, linter_config=None): self.cli_lint_extra_args_after = [] self.cli_lint_errors_count = None self.cli_lint_errors_regex = None + self.cli_lint_warnings_count = None + self.cli_lint_warnings_regex = None # Default arg name for configurations to use in linter version call self.cli_version_arg_name = "--version" self.cli_version_extra_args = [] # Extra arguments to send to cli everytime @@ -376,6 +378,7 @@ def __init__(self, params=None, linter_config=None): self.return_code = 0 self.number_errors = 0 self.total_number_errors = 0 + self.total_number_warnings = 0 self.number_fixed = 0 self.files_lint_results = [] self.start_perf = None @@ -828,6 +831,9 @@ def run(self, run_commands_before_linters=None, run_commands_after_linters=None) index = index + 1 return_code, stdout = self.process_linter(file) file_errors_number = 0 + file_warnings_number = 0 + file_warnings_number = self.get_total_number_warnings(stdout) + self.total_number_warnings += file_warnings_number if return_code > 0: file_status = "error" self.status = "warning" if self.disable_errors is True else "error" @@ -835,23 +841,34 @@ def run(self, run_commands_before_linters=None, run_commands_after_linters=None) self.return_code if self.disable_errors is True else 1 ) self.number_errors += 1 + # Calls external functions to count the number of warnings and errors file_errors_number = self.get_total_number_errors(stdout) + self.total_number_errors += file_errors_number self.update_files_lint_results( - [file], return_code, file_status, stdout, file_errors_number + [file], + return_code, + file_status, + stdout, + file_errors_number, + file_warnings_number, ) else: # Lint all workspace in one command return_code, stdout = self.process_linter() self.stdout = stdout + # Count warnings regardless of return code + self.total_number_warnings += self.get_total_number_warnings(stdout) if return_code != 0: self.status = "warning" if self.disable_errors is True else "error" self.return_code = 0 if self.disable_errors is True else 1 self.number_errors += 1 self.total_number_errors += self.get_total_number_errors(stdout) + elif self.total_number_warnings > 0: + self.status = "warning" # Build result for list of files if self.cli_lint_mode == "list_of_files": - self.update_files_lint_results(self.files, None, None, None, None) + self.update_files_lint_results(self.files, None, None, None, None, None) # Set return code to 0 if failures in this linter must not make the MegaLinter run fail if self.return_code != 0: @@ -884,6 +901,7 @@ def run(self, run_commands_before_linters=None, run_commands_after_linters=None) reporter.produce_report() except Exception as e: logging.error("Unable to process reporter " + reporter.name + str(e)) + return self def replace_vars(self, variables): @@ -900,7 +918,13 @@ def replace_vars(self, variables): return variables_with_replacements def update_files_lint_results( - self, linted_files, return_code, file_status, stdout, file_errors_number + self, + linted_files, + return_code, + file_status, + stdout, + file_errors_number, + file_warnings_number, ): if self.try_fix is True: updated_files = utils.list_updated_files(self.github_workspace) @@ -924,6 +948,7 @@ def update_files_lint_results( "stdout": stdout, "fixed": fixed, "errors_number": file_errors_number, + "warnings_number": file_warnings_number, } ] @@ -1384,6 +1409,7 @@ def get_sarif_arguments(self): # Find number of errors in linter stdout log def get_total_number_errors(self, stdout: str): total_errors = 0 + # Count using SARIF output file if self.output_sarif is True: try: @@ -1433,17 +1459,17 @@ def get_total_number_errors(self, stdout: str): + stdout ) return total_errors - # Get number with a single regex. + # Get number with a single regex. Used when linter prints out Found _ errors elif self.cli_lint_errors_count == "regex_number": reg = self.get_regex(self.cli_lint_errors_regex) m = re.search(reg, utils.normalize_log_string(stdout)) if m: total_errors = int(m.group(1)) - # Count the number of occurrences of a regex corresponding to an error in linter log + # Count the number of occurrences of a regex corresponding to an error in linter log (parses linter log) elif self.cli_lint_errors_count == "regex_count": reg = self.get_regex(self.cli_lint_errors_regex) total_errors = len(re.findall(reg, utils.normalize_log_string(stdout))) - # Sum of all numbers found in linter logs with a regex + # Sum of all numbers found in linter logs with a regex. Found when each file prints out total number of errors elif self.cli_lint_errors_count == "regex_sum": reg = self.get_regex(self.cli_lint_errors_regex) matches = re.findall(reg, utils.normalize_log_string(stdout)) @@ -1473,11 +1499,48 @@ def get_total_number_errors(self, stdout: str): f"Unable to get number of errors with {self.cli_lint_errors_count} " f"and {str(self.cli_lint_errors_regex)}" ) + + # If no regex is defined, return 0 errors if there is a success or 1 error if there are any if self.status == "success": return 0 else: return 1 + # Find number of warnings in linter stdout log + def get_total_number_warnings(self, stdout: str): + total_warnings = None + + # Get number with a single regex. + if self.cli_lint_warnings_count == "regex_number": + reg = self.get_regex(self.cli_lint_warnings_regex) + m = re.search(reg, utils.normalize_log_string(stdout)) + if m: + total_warnings = int(m.group(1)) + # Count the number of occurrences of a regex corresponding to an error in linter log (parses linter log) + elif self.cli_lint_warnings_count == "regex_count": + reg = self.get_regex(self.cli_lint_warnings_regex) + total_warnings = len(re.findall(reg, utils.normalize_log_string(stdout))) + # Sum of all numbers found in linter logs with a regex. Found when each file prints out total number of errors + elif self.cli_lint_warnings_count == "regex_sum": + reg = self.get_regex(self.cli_lint_warnings_regex) + matches = re.findall(reg, utils.normalize_log_string(stdout)) + total_warnings = sum(int(m) for m in matches) + # Count all lines of the linter log + elif self.cli_lint_warnings_count == "total_lines": + total_warnings = sum( + not line.isspace() and line != "" for line in stdout.splitlines() + ) + if self.cli_lint_warnings_count is not None and total_warnings is None: + logging.warning( + f"Unable to get number of warnings with {self.cli_lint_warnings_count} " + f"and {str(self.cli_lint_warnings_regex)}" + ) + + if total_warnings is None: + total_warnings = 0 + + return total_warnings + # Build the CLI command to get linter version (can be overridden if --version is not the way to get the version) def build_version_command(self): cmd = [*self.cli_executable_version] @@ -1501,8 +1564,8 @@ def build_help_command(self): def complete_text_reporter_report(self, _reporter_self): return [] - def pre_test(self): + def pre_test(self, test_name): pass - def post_test(self): + def post_test(self, test_name): pass diff --git a/megalinter/descriptors/css.megalinter-descriptor.yml b/megalinter/descriptors/css.megalinter-descriptor.yml index fda51b9aa6e..976b3c250d5 100644 --- a/megalinter/descriptors/css.megalinter-descriptor.yml +++ b/megalinter/descriptors/css.megalinter-descriptor.yml @@ -9,7 +9,8 @@ file_extensions: - ".saas" linters: # StyleLint - - linter_name: stylelint + - class: StyleLintLinter + linter_name: stylelint name: CSS_STYLELINT linter_url: https://stylelint.io linter_rules_url: https://stylelint.io/user-guide/rules/list @@ -22,6 +23,10 @@ linters: config_file_name: .stylelintrc.json cli_config_arg_name: "--config" cli_lint_fix_arg_name: "--fix" + cli_lint_errors_count: regex_number + cli_lint_errors_regex: "([0-9]+) errors" + cli_lint_warnings_count: regex_number + cli_lint_warnings_regex: "([0-9]+) warnings" examples: - "stylelint myfile.css" - "stylelint --config .stylelintrc.json myfile.css myfile2.css myfile3.css" diff --git a/megalinter/descriptors/php.megalinter-descriptor.yml b/megalinter/descriptors/php.megalinter-descriptor.yml index ee48980d74b..f6df8281bad 100644 --- a/megalinter/descriptors/php.megalinter-descriptor.yml +++ b/megalinter/descriptors/php.megalinter-descriptor.yml @@ -43,7 +43,9 @@ linters: cli_sarif_args: - "--report=\\Bartlett\\Sarif\\Converter\\Reporter\\PhpCsReport" cli_lint_errors_count: regex_number - cli_lint_errors_regex: "FOUND ([0-9]+) ERRORS" + cli_lint_errors_regex: "FOUND ([0-9]+) ERRORS?" + cli_lint_warnings_count: regex_number + cli_lint_warnings_regex: "AND ([0-9]+) WARNINGS?" examples: - "phpcs myfile.php" - "phpcs --standard=phpcs.xml myfile.php" diff --git a/megalinter/descriptors/schemas/megalinter-descriptor.jsonschema.json b/megalinter/descriptors/schemas/megalinter-descriptor.jsonschema.json index 0629af0345f..acc2139d893 100644 --- a/megalinter/descriptors/schemas/megalinter-descriptor.jsonschema.json +++ b/megalinter/descriptors/schemas/megalinter-descriptor.jsonschema.json @@ -712,6 +712,33 @@ "title": "Linting mode", "type": "string" }, + "cli_lint_warnings_count": { + "$id": "#/properties/linters/items/properties/cli_lint_warnings_count", + "description": "Defines how to count warnings from log file. regex_number, regex_count, regex_sum, or total_lines", + "enum": [ + "regex_number", + "regex_count", + "regex_sum", + "total_lines" + ], + "examples": [ + "regex_number", + "regex_count", + "regex_sum", + "total_lines" + ], + "title": "Lint errors count mode", + "type": "string" + }, + "cli_lint_warnings_regex": { + "$id": "#/properties/linters/items/properties/cli_lint_warnings_regex", + "description": "Regex allowing to extract the number of warnings from linter output logs", + "examples": [ + "Issues found: (.*) in .* files" + ], + "title": "Lint warnings number regex", + "type": "string" + }, "cli_sarif_args": { "$id": "#/properties/linters/items/properties/cli_sarif_args", "default": [], diff --git a/megalinter/descriptors/spell.megalinter-descriptor.yml b/megalinter/descriptors/spell.megalinter-descriptor.yml index 41bd0d5072e..7cd35bc10a1 100644 --- a/megalinter/descriptors/spell.megalinter-descriptor.yml +++ b/megalinter/descriptors/spell.megalinter-descriptor.yml @@ -114,7 +114,9 @@ linters: cli_config_arg_name: "--config" config_file_name: .vale.ini cli_lint_errors_count: regex_number - cli_lint_errors_regex: ([0-9]+) errors, [0-9]+ warnings and [0-9]+ suggestions in [0-9]+ files + cli_lint_errors_regex: "([0-9]+) errors?" + cli_lint_warnings_count: regex_number + cli_lint_warnings_regex: "([0-9]+) warnings?" examples: - "vale README.md file1.md file2.md file3.md" - "vale --config .vale.ini README.md file1.md file2.md file3.md" diff --git a/megalinter/descriptors/sql.megalinter-descriptor.yml b/megalinter/descriptors/sql.megalinter-descriptor.yml index d2b62479757..152a6f496a4 100644 --- a/megalinter/descriptors/sql.megalinter-descriptor.yml +++ b/megalinter/descriptors/sql.megalinter-descriptor.yml @@ -50,6 +50,10 @@ linters: linter_rules_inline_disable_url: https://github.com/tsqllint/tsqllint#disabling-rules-with-inline-comments config_file_name: ".tsqllintrc" cli_lint_mode: list_of_files + cli_lint_errors_count: regex_number + cli_lint_errors_regex: "([0-9]+) Errors" + cli_lint_warnings_count: regex_number + cli_lint_warnings_regex: "([0-9]+) Warnings" cli_config_arg_name: "--config" cli_help_arg_name: "--help" cli_version_arg_name: "--version" diff --git a/megalinter/descriptors/terraform.megalinter-descriptor.yml b/megalinter/descriptors/terraform.megalinter-descriptor.yml index 341f1159a2e..9ba7c7da6ce 100644 --- a/megalinter/descriptors/terraform.megalinter-descriptor.yml +++ b/megalinter/descriptors/terraform.megalinter-descriptor.yml @@ -30,6 +30,10 @@ linters: The default configuration enables all supported languages and rules, which may not be optimal for every project. linter_icon_png_url: https://raw.githubusercontent.com/oxsecurity/megalinter/main/docs/assets/icons/linters/tflint.png cli_lint_mode: project + cli_lint_errors_count: regex_count + cli_lint_errors_regex: "Error:" + cli_lint_warnings_count: regex_count + cli_lint_warnings_regex: "Warning:" config_file_name: .tflint.hcl cli_config_extra_args: - --recursive diff --git a/megalinter/descriptors/yaml.megalinter-descriptor.yml b/megalinter/descriptors/yaml.megalinter-descriptor.yml index 31d157f0958..32817a86175 100644 --- a/megalinter/descriptors/yaml.megalinter-descriptor.yml +++ b/megalinter/descriptors/yaml.megalinter-descriptor.yml @@ -20,6 +20,10 @@ linters: config_file_name: ".prettierrc.json" cli_config_arg_name: "--config" cli_lint_mode: list_of_files + cli_lint_errors_count: regex_count + cli_lint_errors_regex: "\\[error\\]" + cli_lint_warnings_count: regex_count + cli_lint_warnings_regex: "\\[warn\\]" cli_lint_extra_args: - "--check" cli_lint_fix_arg_name: "--write" diff --git a/megalinter/linters/CSpellLinter.py b/megalinter/linters/CSpellLinter.py index 8cb30ae5327..1e5652d83c5 100644 --- a/megalinter/linters/CSpellLinter.py +++ b/megalinter/linters/CSpellLinter.py @@ -137,7 +137,7 @@ def complete_text_reporter_report(self, reporter_self): ) return additional_report.splitlines() - def pre_test(self): + def pre_test(self, test_name): config.set_value( self.request_id, "SPELL_CSPELL_FILE_EXTENSIONS", [".js", ".md"] ) diff --git a/megalinter/linters/JavaScriptStandardLinter.py b/megalinter/linters/JavaScriptStandardLinter.py index 41d53dc7c11..4d86758fdd5 100644 --- a/megalinter/linters/JavaScriptStandardLinter.py +++ b/megalinter/linters/JavaScriptStandardLinter.py @@ -7,8 +7,8 @@ class JavaScriptStandardLinter(Linter): - def pre_test(self): + def pre_test(self, test_name): utilstest.write_eslintignore() - def post_test(self): + def post_test(self, test_name): utilstest.delete_eslintignore() diff --git a/megalinter/linters/ProselintLinter.py b/megalinter/linters/ProselintLinter.py index 99583d015e3..ba5c1e0ed97 100644 --- a/megalinter/linters/ProselintLinter.py +++ b/megalinter/linters/ProselintLinter.py @@ -7,7 +7,7 @@ class ProselintLinter(Linter): - def pre_test(self): + def pre_test(self, test_name): config.set_value( self.request_id, "SPELL_PROSELINT_FILE_EXTENSIONS", [".js", ".md"] ) diff --git a/megalinter/linters/PyrightLinter.py b/megalinter/linters/PyrightLinter.py index 14287170bf7..7b0ac8d467d 100644 --- a/megalinter/linters/PyrightLinter.py +++ b/megalinter/linters/PyrightLinter.py @@ -9,7 +9,7 @@ class PyrightLinter(Linter): - def pre_test(self): + def pre_test(self, test_name): # The file must be in the root of the repository so we create it temporarily for the test. # By default pyright ignores files starting with "." so we override this behavior # to work with the .automation folder @@ -26,5 +26,5 @@ def pre_test(self): ]""" ) - def post_test(self): + def post_test(self, test_name): os.remove(os.path.join(os.getcwd(), "pyproject.toml")) diff --git a/megalinter/linters/RuffFormatLinter.py b/megalinter/linters/RuffFormatLinter.py index 9b8a8be3d25..76c73c028d9 100644 --- a/megalinter/linters/RuffFormatLinter.py +++ b/megalinter/linters/RuffFormatLinter.py @@ -8,5 +8,5 @@ class RuffFormatLinter(Linter): - def pre_test(self): + def pre_test(self, test_name): config.set_value(self.request_id, "PYTHON_DEFAULT_STYLE", "ruff") diff --git a/megalinter/linters/StyleLintLinter.py b/megalinter/linters/StyleLintLinter.py new file mode 100644 index 00000000000..64d632315c0 --- /dev/null +++ b/megalinter/linters/StyleLintLinter.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +""" +Use StyleLint to lint css, scss and saas files +""" + +from megalinter import Linter, config + + +class StyleLintLinter(Linter): + def pre_test(self, test_name): + if (test_name == 'test_failure'): + config.set_value( + self.request_id, "CSS_STYLELINT_CONFIG_FILE", '.stylelintrc_bad.json' + ) diff --git a/megalinter/linters/TfLintLinter.py b/megalinter/linters/TfLintLinter.py index 620199afa5e..637b009db00 100644 --- a/megalinter/linters/TfLintLinter.py +++ b/megalinter/linters/TfLintLinter.py @@ -37,7 +37,7 @@ def before_lint_files(self): self.pre_commands = [] self.pre_commands.append(tflint_pre_command) - def pre_test(self): + def pre_test(self, test_name): config.set_value( self.request_id, "TERRAFORM_TFLINT_UNSECURED_ENV_VARIABLES", "GITHUB_TOKEN" ) diff --git a/megalinter/linters/TypeScriptStandardLinter.py b/megalinter/linters/TypeScriptStandardLinter.py index c1335eb9778..9ec289e264b 100644 --- a/megalinter/linters/TypeScriptStandardLinter.py +++ b/megalinter/linters/TypeScriptStandardLinter.py @@ -7,8 +7,8 @@ class TypeScriptStandardLinter(Linter): - def pre_test(self): + def pre_test(self, test_name): utilstest.write_eslintignore() - def post_test(self): + def post_test(self, test_name): utilstest.delete_eslintignore() diff --git a/megalinter/linters/ValeLinter.py b/megalinter/linters/ValeLinter.py index 9a2ef8e1e17..4f06b784b89 100644 --- a/megalinter/linters/ValeLinter.py +++ b/megalinter/linters/ValeLinter.py @@ -8,5 +8,5 @@ class ValeLinter(Linter): - def pre_test(self): + def pre_test(self, test_name): config.set_value(self.request_id, "SPELL_VALE_FILE_EXTENSIONS", [".js", ".md"]) diff --git a/megalinter/linters/XmlLintLinter.py b/megalinter/linters/XmlLintLinter.py index fd6e18074ff..120040c448d 100644 --- a/megalinter/linters/XmlLintLinter.py +++ b/megalinter/linters/XmlLintLinter.py @@ -30,6 +30,6 @@ def build_lint_command(self, file=None): ) return cmd - def pre_test(self): + def pre_test(self, test_name): config.set_value(self.request_id, "XML_XMLLINT_AUTOFORMAT", "true") config.set_value(self.request_id, "XML_XMLLINT_CLI_LINT_MODE", "file") diff --git a/megalinter/reporters/ConsoleLinterReporter.py b/megalinter/reporters/ConsoleLinterReporter.py index e53b34e4389..fb2c78f558e 100644 --- a/megalinter/reporters/ConsoleLinterReporter.py +++ b/megalinter/reporters/ConsoleLinterReporter.py @@ -46,6 +46,7 @@ def produce_report(self): base_phrase = f"Linted [{self.master.descriptor_id}] files with [{self.master.linter_name}]" elapse = str(round(self.master.elapsed_time_s, 2)) + "s" total_errors = str(self.master.total_number_errors) + total_warnings = str(self.master.total_number_warnings) if self.master.return_code == 0 and self.master.status == "success": logging.info( log_section_start( @@ -58,7 +59,8 @@ def produce_report(self): log_section_start( f"processed-{self.master.name}", c.yellow( - f"✅ {base_phrase}: Found {total_errors} non blocking error(s) - ({elapse})" + f"⚠️ {base_phrase}: Found {total_errors} non blocking error(s) " + + f"and {total_warnings} non blocking warning(s) - ({elapse})" ), ) ) @@ -67,7 +69,7 @@ def produce_report(self): log_section_start( f"processed-{self.master.name}", c.red( - f"❌ {base_phrase}: Found {total_errors} error(s) - ({elapse})" + f"❌ {base_phrase}: Found {total_errors} error(s) and {total_warnings} warning(s) - ({elapse})" ), ) ) @@ -124,7 +126,9 @@ def produce_report(self): file_nm = utils.normalize_log_string(res["file"]) if self.master.cli_lint_mode == "file": file_errors = str(res.get("errors_number", 0)) - line = f"[{self.master.linter_name}] {file_nm} - {res['status'].upper()} - {file_errors} error(s)" + file_warnings = str(res.get("warnings_number", 0)) + line = f"[{self.master.linter_name}] {file_nm} - {res['status'].upper()} - " + + f"{file_errors} error(s) and {file_warnings} warning(s)" else: line = f"[{self.master.linter_name}] {file_nm}" if res["fixed"] is True: diff --git a/megalinter/reporters/ConsoleReporter.py b/megalinter/reporters/ConsoleReporter.py index ea4fd7a8d67..86718218314 100644 --- a/megalinter/reporters/ConsoleReporter.py +++ b/megalinter/reporters/ConsoleReporter.py @@ -58,7 +58,15 @@ def initialize(self): logging.info(log_section_end("megalinter-file-listing")) def produce_report(self): - table_header = ["Descriptor", "Linter", "Mode", "Files", "Fixed", "Errors"] + table_header = [ + "Descriptor", + "Linter", + "Mode", + "Files", + "Fixed", + "Errors", + "Warnings", + ] if self.master.show_elapsed_time is True: table_header += ["Elapsed time"] table_data = [table_header] @@ -77,12 +85,12 @@ def produce_report(self): ) ) errors = str(linter.total_number_errors) + warnings = str(linter.total_number_warnings) if linter.cli_lint_mode == "project": found = "n/a" nb_fixed_cell = "yes" if nb_fixed_cell != "" else nb_fixed_cell else: found = str(len(linter.files)) - table_line = [ status + " " + linter.descriptor_id, linter.linter_name, @@ -90,6 +98,7 @@ def produce_report(self): found, nb_fixed_cell, errors, + warnings, ] if self.master.show_elapsed_time is True: table_line += [str(round(linter.elapsed_time_s, 2)) + "s"] diff --git a/megalinter/reporters/GithubStatusReporter.py b/megalinter/reporters/GithubStatusReporter.py index 033a7872d3f..5aca7824984 100644 --- a/megalinter/reporters/GithubStatusReporter.py +++ b/megalinter/reporters/GithubStatusReporter.py @@ -51,7 +51,9 @@ def produce_report(self): run_id = config.get(self.master.request_id, "GITHUB_RUN_ID") success_msg = "No errors were found in the linting process" error_not_blocking = "Errors were detected but are considered not blocking" - error_msg = f"Found {self.master.total_number_errors}, please check logs" + error_msg = ( + f"Found {self.master.total_number_errors} errors, please check logs" + ) url = f"{github_api_url}/repos/{github_repo}/statuses/{sha}" headers = { "accept": "application/vnd.github.v3+json", diff --git a/megalinter/reporters/JsonReporter.py b/megalinter/reporters/JsonReporter.py index ee71cd7ce12..6819a117e3f 100644 --- a/megalinter/reporters/JsonReporter.py +++ b/megalinter/reporters/JsonReporter.py @@ -41,6 +41,7 @@ class JsonReporter(Reporter): "return_code", "number_errors", "total_number_errors", + "total_number_warnings", "number_fixed", "files_lint_results", "elapsed_time_s", diff --git a/megalinter/tests/test_megalinter/LinterTestRoot.py b/megalinter/tests/test_megalinter/LinterTestRoot.py index 23c06d94633..12b98e698bc 100644 --- a/megalinter/tests/test_megalinter/LinterTestRoot.py +++ b/megalinter/tests/test_megalinter/LinterTestRoot.py @@ -34,33 +34,33 @@ def test_success(self): self.request_id = str(uuid.uuid1()) utilstest.linter_test_setup({"request_id": self.request_id}) linter = self.get_linter_instance(self.request_id) - linter.pre_test() + linter.pre_test('test_success') utilstest.test_linter_success(linter, self) - linter.post_test() + linter.post_test('test_success') def test_failure(self): self.request_id = str(uuid.uuid1()) utilstest.linter_test_setup({"request_id": self.request_id}) linter = self.get_linter_instance(self.request_id) - linter.pre_test() + linter.pre_test('test_failure') utilstest.test_linter_failure(linter, self) - linter.post_test() + linter.post_test('test_failure') def test_get_linter_version(self): self.request_id = str(uuid.uuid1()) utilstest.linter_test_setup({"request_id": self.request_id}) linter = self.get_linter_instance(self.request_id) - linter.pre_test() + linter.pre_test('test_get_linter_version') utilstest.test_get_linter_version(linter, self) - linter.post_test() + linter.post_test('test_get_linter_version') def test_get_linter_help(self): self.request_id = str(uuid.uuid1()) utilstest.linter_test_setup({"request_id": self.request_id}) linter = self.get_linter_instance(self.request_id) - linter.pre_test() + linter.pre_test('test_get_linter_help') utilstest.test_get_linter_help(linter, self) - linter.post_test() + linter.post_test('test_get_linter_help') def test_report_tap(self): self.request_id = str(uuid.uuid1()) @@ -68,9 +68,9 @@ def test_report_tap(self): {"request_id": self.request_id, "report_type": "tap"} ) linter = self.get_linter_instance(self.request_id) - linter.pre_test() + linter.pre_test('test_report_tap') utilstest.test_linter_report_tap(linter, self) - linter.post_test() + linter.post_test('test_report_tap') def test_report_sarif(self): self.request_id = str(uuid.uuid1()) @@ -78,14 +78,14 @@ def test_report_sarif(self): {"request_id": self.request_id, "report_type": "SARIF"} ) linter = self.get_linter_instance(self.request_id) - linter.pre_test() + linter.pre_test('test_report_sarif') utilstest.test_linter_report_sarif(linter, self) - linter.post_test() + linter.post_test('test_report_sarif') def test_format_fix(self): self.request_id = str(uuid.uuid1()) utilstest.linter_test_setup({"request_id": self.request_id}) linter = self.get_linter_instance(self.request_id) - linter.pre_test() + linter.pre_test('test_format_fix') utilstest.test_linter_format_fix(linter, self) - linter.post_test() + linter.post_test('test_format_fix') diff --git a/megalinter/utils_reporter.py b/megalinter/utils_reporter.py index ba6c9b9b794..d1e1ab174a7 100644 --- a/megalinter/utils_reporter.py +++ b/megalinter/utils_reporter.py @@ -19,7 +19,7 @@ def build_markdown_summary(reporter_self, action_run_url=""): - table_header = ["Descriptor", "Linter", "Files", "Fixed", "Errors"] + table_header = ["Descriptor", "Linter", "Files", "Fixed", "Errors", "Warnings"] if reporter_self.master.show_elapsed_time is True: table_header += ["Elapsed time"] table_data_raw = [table_header] @@ -51,6 +51,11 @@ def build_markdown_summary(reporter_self, action_run_url=""): if linter.number_errors > 0 else "no" ) + warnings_cell = ( + log_link(f"{linter.total_number_warnings}", action_run_url) + if linter.total_number_warnings > 0 + else "no" + ) # Count using files else: found = str(len(linter.files)) @@ -59,12 +64,18 @@ def build_markdown_summary(reporter_self, action_run_url=""): if linter.number_errors > 0 else linter.number_errors ) + warnings_cell = ( + log_link(f"{linter.total_number_warnings}", action_run_url) + if linter.total_number_warnings > 0 + else linter.total_number_warnings + ) table_line = [ first_col, linter_link, found, nb_fixed_cell, errors_cell, + warnings_cell, ] if reporter_self.master.show_elapsed_time is True: table_line += [str(round(linter.elapsed_time_s, 2)) + "s"] diff --git a/megalinter/utilstest.py b/megalinter/utilstest.py index 3887d920a31..d2a87e2f091 100644 --- a/megalinter/utilstest.py +++ b/megalinter/utilstest.py @@ -249,10 +249,14 @@ def test_linter_failure(linter, test_self): } env_vars_failure.update(linter.test_variables) mega_linter, output = call_mega_linter(env_vars_failure) + # Check linter run test_self.assertTrue( len(mega_linter.linters) > 0, "Linters have been created and run" ) + + mega_linter_linter = mega_linter.linters[0] + # Check console output if linter.cli_lint_mode == "file": if len(linter.file_names_regex) > 0 and len(linter.file_extensions) == 0: @@ -266,12 +270,18 @@ def test_linter_failure(linter, test_self): test_self.assertRegex(output, rf"\[{linter_name}\] .*bad.* - ERROR") test_self.assertNotRegex(output, rf"\[{linter_name}\] .*bad.* - SUCCESS") elif linter.descriptor_id != "SPELL": # This log doesn't appear in SPELL linters - test_self.assertRegex( - output, - rf"Linted \[{linter.descriptor_id}\] files with \[{linter_name}\]: Found", - ) - - mega_linter_linter = mega_linter.linters[0] + if (mega_linter_linter.status == 'error'): + test_self.assertRegex( + output, + rf"Linted \[{linter.descriptor_id}\] files with \[{linter_name}\]: Found " + + rf"[0-9]+ error\(s\) and [0-9]+ warning\(s\)", + ) + else: + test_self.assertRegex( + output, + rf"Linted \[{linter.descriptor_id}\] files with \[{linter_name}\]: Found " + + rf"[0-9]+ non blocking error\(s\) and [0-9]+ non blocking warning\(s\)", + ) # Check text reporter output log if mega_linter_linter.disable_errors is True: @@ -294,9 +304,20 @@ def test_linter_failure(linter, test_self): ): test_self.assertTrue( mega_linter_linter.total_number_errors > 1, - "Unable to count number of errors from logs with count method " + "Unable to get number of errors from logs with " + f"{mega_linter_linter.cli_lint_errors_count} and " - + f"regex {mega_linter_linter.cli_lint_errors_regex}", + + f"{mega_linter_linter.cli_lint_errors_regex}", + ) + + # Check if number of warnings is correctly generated + if ( + mega_linter_linter.cli_lint_warnings_count is not None + ): + test_self.assertTrue( + mega_linter_linter.total_number_warnings > 1, + "Unable to get number of warnings from logs with " + + f"{mega_linter_linter.cli_lint_warnings_count} and " + + f"{mega_linter_linter.cli_lint_warnings_regex}", ) # Copy error logs in documentation