From 2a9fa165e25f858ac76d3fd5ecf2283f3dad52e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borja=20Dom=C3=ADnguez?= Date: Mon, 17 Feb 2025 23:27:38 +0100 Subject: [PATCH] Surfacing warnings during successful runs (#4556) * Warning Feature quick build * wip * wip * [MegaLinter] Apply linters fixes * wip * wip * wip * wip * wip * wip * wip * wip * wip * [MegaLinter] Apply linters fixes * wip * wip --------- Co-authored-by: Daniel Li Co-authored-by: bdovaz <950602+bdovaz@users.noreply.github.com> --- .automation/test/css/.stylelintrc_bad.json | 11 +++ .automation/test/php/php_bad_2.php | 2 +- .automation/test/php/php_good_1.php | 10 +++ .automation/test/php/php_good_2.php | 22 +++++- .automation/test/php/phpcs.xml | 12 +++ .automation/test/spell_vale/.vale.ini | 2 + .automation/test/spell_vale/spell_bad_2.md | 10 ++- .automation/test/sql_tsql/.tsqllintrc | 32 ++++++++ .automation/test/sql_tsql/sql_bad_1.sql | 7 +- .../.terraform/modules/modules.json | 1 + .../bad/nested_module_vars/main.tf | 6 ++ .../bad/nested_module_vars/module/main.tf | 11 +++ .../nested_module_vars/module/module/main.tf | 4 + .../module.tf | 13 +++ .../module1/resource.tf | 15 ++++ .../resource.tf | 15 ++++ .../.terraform/modules/modules.json | 1 + .../bad/nested_modules/module.tf | 7 ++ .../bad/nested_modules/module/module.tf | 13 +++ .../nested_modules/module/module1/resource.tf | 24 ++++++ .../terraform_tflint/bad/terraform_bad_1.tf | 19 +++++ .../terraform_tflint/good/terraform_good_1.tf | 28 +++++++ .../terraform_tflint/good/terraform_good_2.tf | 34 ++++++++ .../terraform_tflint/good/terraform_good_3.tf | 30 +++++++ .../reports/ERROR-TERRAFORM_TFLINT.txt | 15 ++++ .../reports/SUCCESS-TERRAFORM_TFLINT.txt | 7 ++ CHANGELOG.md | 1 + megalinter/Linter.py | 79 +++++++++++++++++-- .../descriptors/css.megalinter-descriptor.yml | 7 +- .../descriptors/php.megalinter-descriptor.yml | 6 +- .../megalinter-descriptor.jsonschema.json | 27 +++++++ .../spell.megalinter-descriptor.yml | 4 +- .../descriptors/sql.megalinter-descriptor.yml | 4 + .../terraform.megalinter-descriptor.yml | 5 ++ .../yaml.megalinter-descriptor.yml | 4 + megalinter/linters/CSpellLinter.py | 2 +- .../linters/JavaScriptStandardLinter.py | 4 +- megalinter/linters/ProselintLinter.py | 2 +- megalinter/linters/PyrightLinter.py | 4 +- megalinter/linters/RuffFormatLinter.py | 2 +- megalinter/linters/StyleLintLinter.py | 14 ++++ megalinter/linters/TfLintLinter.py | 2 +- .../linters/TypeScriptStandardLinter.py | 4 +- megalinter/linters/ValeLinter.py | 2 +- megalinter/linters/XmlLintLinter.py | 2 +- megalinter/reporters/ConsoleLinterReporter.py | 10 ++- megalinter/reporters/ConsoleReporter.py | 13 ++- megalinter/reporters/GithubStatusReporter.py | 4 +- megalinter/reporters/JsonReporter.py | 1 + .../tests/test_megalinter/LinterTestRoot.py | 28 +++---- megalinter/utils_reporter.py | 13 ++- megalinter/utilstest.py | 35 ++++++-- 52 files changed, 569 insertions(+), 61 deletions(-) create mode 100644 .automation/test/css/.stylelintrc_bad.json create mode 100644 .automation/test/php/phpcs.xml create mode 100644 .automation/test/sql_tsql/.tsqllintrc create mode 100644 .automation/test/terraform_tflint/bad/nested_module_vars/.terraform/modules/modules.json create mode 100644 .automation/test/terraform_tflint/bad/nested_module_vars/main.tf create mode 100644 .automation/test/terraform_tflint/bad/nested_module_vars/module/main.tf create mode 100644 .automation/test/terraform_tflint/bad/nested_module_vars/module/module/main.tf create mode 100644 .automation/test/terraform_tflint/bad/nested_modules/.terraform/modules/07be448a6067a2bba065bff4beea229d/module.tf create mode 100644 .automation/test/terraform_tflint/bad/nested_modules/.terraform/modules/07be448a6067a2bba065bff4beea229d/module1/resource.tf create mode 100644 .automation/test/terraform_tflint/bad/nested_modules/.terraform/modules/a8d8930bc3c2ae53bf6e3bbcb3083d7b/resource.tf create mode 100644 .automation/test/terraform_tflint/bad/nested_modules/.terraform/modules/modules.json create mode 100644 .automation/test/terraform_tflint/bad/nested_modules/module.tf create mode 100644 .automation/test/terraform_tflint/bad/nested_modules/module/module.tf create mode 100644 .automation/test/terraform_tflint/bad/nested_modules/module/module1/resource.tf create mode 100644 .automation/test/terraform_tflint/bad/terraform_bad_1.tf create mode 100644 .automation/test/terraform_tflint/good/terraform_good_1.tf create mode 100644 .automation/test/terraform_tflint/good/terraform_good_2.tf create mode 100644 .automation/test/terraform_tflint/good/terraform_good_3.tf create mode 100644 .automation/test/terraform_tflint/reports/ERROR-TERRAFORM_TFLINT.txt create mode 100644 .automation/test/terraform_tflint/reports/SUCCESS-TERRAFORM_TFLINT.txt create mode 100644 megalinter/linters/StyleLintLinter.py 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/.automation/test/php/php_bad_2.php b/.automation/test/php/php_bad_2.php index 106fe977b42..35a548a7daf 100644 --- a/.automation/test/php/php_bad_2.php +++ b/.automation/test/php/php_bad_2.php @@ -7,7 +7,7 @@ function takesAnInt(int $i) { return [$i, "hello"]; } -$data = ["some text", 5]; +$data = [ "some text", 5 ]; takesAnInt($data[0]); $condition = rand(0, 5); diff --git a/.automation/test/php/php_good_1.php b/.automation/test/php/php_good_1.php index 16898d93f0f..b29e3ee7d3a 100644 --- a/.automation/test/php/php_good_1.php +++ b/.automation/test/php/php_good_1.php @@ -1,3 +1,13 @@ + * @license https://opensource.org/licenses/MIT MIT License + * @link http://localhost/ + */ + echo "Hello World!", PHP_EOL; diff --git a/.automation/test/php/php_good_2.php b/.automation/test/php/php_good_2.php index 051369aa37d..98611ccfe6b 100644 --- a/.automation/test/php/php_good_2.php +++ b/.automation/test/php/php_good_2.php @@ -1,19 +1,39 @@ + * PHP version 8 + * + * @category Template_Class + * @package Template_Class + * @author Author + * @license https://opensource.org/licenses/MIT MIT License + * @link http://localhost/ + */ + +/** + * Summary of helloName + * @param string $name test + * @return string[] */ function helloName(string $name): array { return ["hello", $name]; } +/** + * Summary of helloMegalinter + * @return void + */ function helloMegalinter(): void { $hello = helloName("MegaLinter"); echo implode(" ", $hello) . PHP_EOL; } +/** + * Summary of helloOrWorld + * @return void + */ function helloOrWorld(): void { $random = rand(0, 10); diff --git a/.automation/test/php/phpcs.xml b/.automation/test/php/phpcs.xml new file mode 100644 index 00000000000..37d069afffc --- /dev/null +++ b/.automation/test/php/phpcs.xml @@ -0,0 +1,12 @@ + + + + error + + + warning + + + warning + + diff --git a/.automation/test/spell_vale/.vale.ini b/.automation/test/spell_vale/.vale.ini index f0e785fae95..5567f8eddba 100644 --- a/.automation/test/spell_vale/.vale.ini +++ b/.automation/test/spell_vale/.vale.ini @@ -6,3 +6,5 @@ Packages = Microsoft [*] BasedOnStyles = Vale, Microsoft + +Vale.Spelling = warning diff --git a/.automation/test/spell_vale/spell_bad_2.md b/.automation/test/spell_vale/spell_bad_2.md index f06fbb081c2..2d6d1453df0 100644 --- a/.automation/test/spell_vale/spell_bad_2.md +++ b/.automation/test/spell_vale/spell_bad_2.md @@ -1,5 +1,7 @@ -- Correct some broken links in `README` from "Mega-Linter" to "MegaLinter" (#1030) -- Correct some broken links in `README` from "Mega-Linter" to "MegaLinter" (#1030) -- Correct some broken links in `README` from "Mega-Linter" to "MegaLinter" (#1030) +Are you actualy going? -This is so advantageous to have an URL ! \ No newline at end of file +Are you actualy going? + +and and + +the the diff --git a/.automation/test/sql_tsql/.tsqllintrc b/.automation/test/sql_tsql/.tsqllintrc new file mode 100644 index 00000000000..0080ae3a295 --- /dev/null +++ b/.automation/test/sql_tsql/.tsqllintrc @@ -0,0 +1,32 @@ +{ + "rules": { + "case-sensitive-variables": "warning", + "conditional-begin-end": "error", + "count-star": "error", + "cross-database-transaction": "error", + "data-compression": "error", + "data-type-length": "error", + "delete-where": "error", + "disallow-cursors": "error", + "full-text": "error", + "information-schema": "error", + "keyword-capitalization": "error", + "linked-server": "error", + "multi-table-alias": "error", + "named-constraint": "error", + "non-sargable": "error", + "object-property": "error", + "print-statement": "error", + "schema-qualify": "error", + "select-star": "error", + "semicolon-termination": "error", + "set-ansi": "error", + "set-nocount": "error", + "set-quoted-identifier": "error", + "set-transaction-isolation-level": "error", + "set-variable": "error", + "update-where": "error", + "upper-lower": "error", + "unicode-string": "error" + } +} diff --git a/.automation/test/sql_tsql/sql_bad_1.sql b/.automation/test/sql_tsql/sql_bad_1.sql index 66e385e9812..bc046e4786f 100644 --- a/.automation/test/sql_tsql/sql_bad_1.sql +++ b/.automation/test/sql_tsql/sql_bad_1.sql @@ -1,2 +1,5 @@ -SELECT; -GO; \ No newline at end of file +DECLARE @VariableName INT, + @SomeOtherVariable INT; + +SELECT @variableName = 1; +SELECT @someOtherVariable = 1; diff --git a/.automation/test/terraform_tflint/bad/nested_module_vars/.terraform/modules/modules.json b/.automation/test/terraform_tflint/bad/nested_module_vars/.terraform/modules/modules.json new file mode 100644 index 00000000000..97fae8bdbf8 --- /dev/null +++ b/.automation/test/terraform_tflint/bad/nested_module_vars/.terraform/modules/modules.json @@ -0,0 +1 @@ +{"Modules":[{"Key":"","Source":"","Dir":"."},{"Key":"module1","Source":"./module","Dir":"module"},{"Key":"module1.module2","Source":"./module","Dir":"module/module"}]} \ No newline at end of file diff --git a/.automation/test/terraform_tflint/bad/nested_module_vars/main.tf b/.automation/test/terraform_tflint/bad/nested_module_vars/main.tf new file mode 100644 index 00000000000..12ca4bbe6af --- /dev/null +++ b/.automation/test/terraform_tflint/bad/nested_module_vars/main.tf @@ -0,0 +1,6 @@ +module "module1" { + source = "./module" + + foo = "foo" + bar = "bar" +} diff --git a/.automation/test/terraform_tflint/bad/nested_module_vars/module/main.tf b/.automation/test/terraform_tflint/bad/nested_module_vars/module/main.tf new file mode 100644 index 00000000000..841e1198d85 --- /dev/null +++ b/.automation/test/terraform_tflint/bad/nested_module_vars/module/main.tf @@ -0,0 +1,11 @@ +variable "foo" {} +variable "bar" {} +variable "baz" {} + +module "module2" { + source = "./module" + + red = "${var.foo}-${var.bar}" + blue = "blue" + green = "${var.foo}-${var.baz}-${path.module}" +} diff --git a/.automation/test/terraform_tflint/bad/nested_module_vars/module/module/main.tf b/.automation/test/terraform_tflint/bad/nested_module_vars/module/module/main.tf new file mode 100644 index 00000000000..b089b0dbc89 --- /dev/null +++ b/.automation/test/terraform_tflint/bad/nested_module_vars/module/module/main.tf @@ -0,0 +1,4 @@ +variable "red" {} +variable "blue" {} +variable "green" {} +variable "yellow" {} diff --git a/.automation/test/terraform_tflint/bad/nested_modules/.terraform/modules/07be448a6067a2bba065bff4beea229d/module.tf b/.automation/test/terraform_tflint/bad/nested_modules/.terraform/modules/07be448a6067a2bba065bff4beea229d/module.tf new file mode 100644 index 00000000000..53fbf87ecb1 --- /dev/null +++ b/.automation/test/terraform_tflint/bad/nested_modules/.terraform/modules/07be448a6067a2bba065bff4beea229d/module.tf @@ -0,0 +1,13 @@ +variable "override" { + default = "baz" +} +variable "no_default" {} +variable "unknown" {} + +module "test" { + source = "./module1" + + override = "${var.override}" + no_default = "${var.no_default}" + unknown = "${var.unknown}" +} diff --git a/.automation/test/terraform_tflint/bad/nested_modules/.terraform/modules/07be448a6067a2bba065bff4beea229d/module1/resource.tf b/.automation/test/terraform_tflint/bad/nested_modules/.terraform/modules/07be448a6067a2bba065bff4beea229d/module1/resource.tf new file mode 100644 index 00000000000..a5b1c64fc0c --- /dev/null +++ b/.automation/test/terraform_tflint/bad/nested_modules/.terraform/modules/07be448a6067a2bba065bff4beea229d/module1/resource.tf @@ -0,0 +1,15 @@ +variable "override" { + default = "baz" +} +variable "no_default" {} +variable "unknown" {} + +resource "aws_instance" "web" { + ami = "ami-b73b63a0" + instance_type = "t1.2xlarge" + + tags { + Name = "HelloWorld" + } +} + diff --git a/.automation/test/terraform_tflint/bad/nested_modules/.terraform/modules/a8d8930bc3c2ae53bf6e3bbcb3083d7b/resource.tf b/.automation/test/terraform_tflint/bad/nested_modules/.terraform/modules/a8d8930bc3c2ae53bf6e3bbcb3083d7b/resource.tf new file mode 100644 index 00000000000..a5b1c64fc0c --- /dev/null +++ b/.automation/test/terraform_tflint/bad/nested_modules/.terraform/modules/a8d8930bc3c2ae53bf6e3bbcb3083d7b/resource.tf @@ -0,0 +1,15 @@ +variable "override" { + default = "baz" +} +variable "no_default" {} +variable "unknown" {} + +resource "aws_instance" "web" { + ami = "ami-b73b63a0" + instance_type = "t1.2xlarge" + + tags { + Name = "HelloWorld" + } +} + diff --git a/.automation/test/terraform_tflint/bad/nested_modules/.terraform/modules/modules.json b/.automation/test/terraform_tflint/bad/nested_modules/.terraform/modules/modules.json new file mode 100644 index 00000000000..ed4ed6bad7a --- /dev/null +++ b/.automation/test/terraform_tflint/bad/nested_modules/.terraform/modules/modules.json @@ -0,0 +1 @@ +{"Modules":[{"Key":"root","Source":"./module","Dir":"module"},{"Key":"root.test","Source":"./module1","Dir":"module/module1"},{"Key":"1.root;./module","Source":"./module","Dir":".terraform/modules/07be448a6067a2bba065bff4beea229d"},{"Key":"1.root;./module|test;./module1","Source":"./module1","Dir":".terraform/modules/a8d8930bc3c2ae53bf6e3bbcb3083d7b"},{"Key":"","Source":"","Dir":"."}]} \ No newline at end of file diff --git a/.automation/test/terraform_tflint/bad/nested_modules/module.tf b/.automation/test/terraform_tflint/bad/nested_modules/module.tf new file mode 100644 index 00000000000..cb53aad1ff2 --- /dev/null +++ b/.automation/test/terraform_tflint/bad/nested_modules/module.tf @@ -0,0 +1,7 @@ +module "root" { + source = "./module" + + override = "foo" + no_default = "bar" + unknown = "${data.aws.ami.id}" +} diff --git a/.automation/test/terraform_tflint/bad/nested_modules/module/module.tf b/.automation/test/terraform_tflint/bad/nested_modules/module/module.tf new file mode 100644 index 00000000000..53fbf87ecb1 --- /dev/null +++ b/.automation/test/terraform_tflint/bad/nested_modules/module/module.tf @@ -0,0 +1,13 @@ +variable "override" { + default = "baz" +} +variable "no_default" {} +variable "unknown" {} + +module "test" { + source = "./module1" + + override = "${var.override}" + no_default = "${var.no_default}" + unknown = "${var.unknown}" +} diff --git a/.automation/test/terraform_tflint/bad/nested_modules/module/module1/resource.tf b/.automation/test/terraform_tflint/bad/nested_modules/module/module1/resource.tf new file mode 100644 index 00000000000..220b080a24e --- /dev/null +++ b/.automation/test/terraform_tflint/bad/nested_modules/module/module1/resource.tf @@ -0,0 +1,24 @@ +variable "override" { + default = "baz" +} +variable "no_default" {} +variable "unknown" {} + +resource "aws_instance" "web" { + ami = "ami-b73b63a0" + instance_type = "t1.2xlarge" + + tags { + Name = "HelloWorld" + } +} + +resource "aws_instance" "web2" { + ami = "ami-b73b63a1" + instance_type = "t1.2xlarge" + + tags { + Name = "HelloWorld2" + } +} + diff --git a/.automation/test/terraform_tflint/bad/terraform_bad_1.tf b/.automation/test/terraform_tflint/bad/terraform_bad_1.tf new file mode 100644 index 00000000000..d00f6fb178b --- /dev/null +++ b/.automation/test/terraform_tflint/bad/terraform_bad_1.tf @@ -0,0 +1,19 @@ +terraform { + required_version = ">= 1.8.5" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.55.0" # https://registry.terraform.io/providers/hashicorp/aws/latest + } + } +} + +provider "template" {} + +provider "aws" { + region = "us-west-2" +} + +resource "aws_s3_bucket" "example" { + bucket = "my-tf-test-bucket-${random_id.bucket_suffix.hex}" +} diff --git a/.automation/test/terraform_tflint/good/terraform_good_1.tf b/.automation/test/terraform_tflint/good/terraform_good_1.tf new file mode 100644 index 00000000000..f162bfdf50f --- /dev/null +++ b/.automation/test/terraform_tflint/good/terraform_good_1.tf @@ -0,0 +1,28 @@ +terraform { + required_version = ">= 1.8.5" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.55.0" # https://registry.terraform.io/providers/hashicorp/aws/latest + } + } +} + +provider "aws" { + region = "us-west-2" +} + +resource "aws_s3_bucket" "example" { + bucket = "my-tf-test-bucket-${random_id.bucket_suffix.hex}" +} + +resource "aws_s3_bucket_versioning" "example" { + bucket = aws_s3_bucket.example.id + versioning_configuration { + status = "Enabled" + } +} + +resource "random_id" "bucket_suffix" { + byte_length = 4 +} diff --git a/.automation/test/terraform_tflint/good/terraform_good_2.tf b/.automation/test/terraform_tflint/good/terraform_good_2.tf new file mode 100644 index 00000000000..e62ab84c598 --- /dev/null +++ b/.automation/test/terraform_tflint/good/terraform_good_2.tf @@ -0,0 +1,34 @@ +terraform { + required_version = ">= 1.8.5" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">= 3.109.0" # https://registry.terraform.io/providers/hashicorp/azurerm/latest + } + } +} + +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "East US" +} + +resource "azurerm_storage_account" "example" { + name = "storageacc${random_id.suffix.hex}" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "GRS" + + tags = { + environment = "staging" + } +} + +resource "random_id" "suffix" { + byte_length = 8 +} diff --git a/.automation/test/terraform_tflint/good/terraform_good_3.tf b/.automation/test/terraform_tflint/good/terraform_good_3.tf new file mode 100644 index 00000000000..b0370f7dedb --- /dev/null +++ b/.automation/test/terraform_tflint/good/terraform_good_3.tf @@ -0,0 +1,30 @@ +terraform { + required_version = ">= 1.8.5" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 5.34.0" # https://registry.terraform.io/providers/hashicorp/google/latest + } + } +} + +provider "google" { + project = "my-project-id" + region = "us-central1" +} + +resource "google_storage_bucket" "example" { + name = "my-bucket-${random_id.suffix.hex}" + location = "US" + force_destroy = true + + uniform_bucket_level_access = true + + versioning { + enabled = true + } +} + +resource "random_id" "suffix" { + byte_length = 4 +} diff --git a/.automation/test/terraform_tflint/reports/ERROR-TERRAFORM_TFLINT.txt b/.automation/test/terraform_tflint/reports/ERROR-TERRAFORM_TFLINT.txt new file mode 100644 index 00000000000..fadc45e9244 --- /dev/null +++ b/.automation/test/terraform_tflint/reports/ERROR-TERRAFORM_TFLINT.txt @@ -0,0 +1,15 @@ +Results of tflint linter (version 0.21.0) +See documentation on https://megalinter.io/descriptors/terraform_tflint/ +----------------------------------------------- + +[ERROR] .automation/test/terraform/bad/terraform_bad_1.tf + Failed to load configurations. 1 error(s) occurred: + + Error: Invalid expression + + on .automation/test/terraform/bad/terraform_bad_1.tf line 3, in resource "aws_instance" "bad": + 3: instance_type = # invalid type! + 4: } + + Expected the start of an expression, but found an invalid expression token. + diff --git a/.automation/test/terraform_tflint/reports/SUCCESS-TERRAFORM_TFLINT.txt b/.automation/test/terraform_tflint/reports/SUCCESS-TERRAFORM_TFLINT.txt new file mode 100644 index 00000000000..b6683c37ded --- /dev/null +++ b/.automation/test/terraform_tflint/reports/SUCCESS-TERRAFORM_TFLINT.txt @@ -0,0 +1,7 @@ +Results of tflint linter (version 0.21.0) +See documentation on https://megalinter.io/descriptors/terraform_tflint/ +----------------------------------------------- + +[SUCCESS] .automation/test/terraform/good/terraform_good_1.tf + + diff --git a/CHANGELOG.md b/CHANGELOG.md index 29ec160ce0b..65775d3a767 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 aaaa33cc906..f3cb581a3e0 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, } ] @@ -1388,6 +1413,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: @@ -1437,17 +1463,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)) @@ -1477,11 +1503,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] @@ -1505,8 +1568,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 3213746e4a1..ab45cb78372 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 33900f40616..8cb3994b2d2 100644 --- a/megalinter/descriptors/php.megalinter-descriptor.yml +++ b/megalinter/descriptors/php.megalinter-descriptor.yml @@ -42,8 +42,10 @@ linters: cli_config_arg_name: "--standard=" 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_count: regex_sum + cli_lint_errors_regex: "FOUND ([0-9]+) ERRORS?" + cli_lint_warnings_count: regex_sum + 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 7606f1c7ee9..356691574d7 100644 --- a/megalinter/descriptors/spell.megalinter-descriptor.yml +++ b/megalinter/descriptors/spell.megalinter-descriptor.yml @@ -122,7 +122,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 c5de88ed3ad..c6abfd5d34e 100644 --- a/megalinter/descriptors/sql.megalinter-descriptor.yml +++ b/megalinter/descriptors/sql.megalinter-descriptor.yml @@ -49,6 +49,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 f877fd5ac98..87be5a952a3 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 @@ -43,6 +47,7 @@ linters: - name: PAT_GITHUB_COM default_value: "" description: If you have issues with tflint --init, create a GitHub Personal Access Token and set its value to PAT_GITHUB_COM variable. + test_folder: terraform_tflint examples: - "tflint" - "tflint -c .tflint.hcl" diff --git a/megalinter/descriptors/yaml.megalinter-descriptor.yml b/megalinter/descriptors/yaml.megalinter-descriptor.yml index d5481904775..7ad519f3468 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 37c2d5e3598..90c5a62e1e1 100644 --- a/megalinter/linters/CSpellLinter.py +++ b/megalinter/linters/CSpellLinter.py @@ -144,7 +144,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..566b33448a1 --- /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..8bc1c1e4fac 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()} - " + line += 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..2ddb07b7e17 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..0d4c8ba2fac 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 " + + r"[0-9]+ error\(s\) and [0-9]+ warning\(s\)", + ) + else: + test_self.assertRegex( + output, + rf"Linted \[{linter.descriptor_id}\] files with \[{linter_name}\]: Found " + + r"[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,18 @@ 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