Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Surfacing warnings during successful runs #4556

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .automation/test/css/.stylelintrc_bad.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "stylelint-config-standard",
"rules": {
"comment-empty-line-before": [
"always",
{
"severity": "warning"
}
]
}
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
79 changes: 71 additions & 8 deletions megalinter/Linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -828,30 +831,44 @@ 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"
self.return_code = (
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:
Expand Down Expand Up @@ -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):
Expand All @@ -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)
Expand All @@ -924,6 +948,7 @@ def update_files_lint_results(
"stdout": stdout,
"fixed": fixed,
"errors_number": file_errors_number,
"warnings_number": file_warnings_number,
}
]

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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]
Expand All @@ -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
7 changes: 6 additions & 1 deletion megalinter/descriptors/css.megalinter-descriptor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down
4 changes: 3 additions & 1 deletion megalinter/descriptors/php.megalinter-descriptor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": [],
Expand Down
4 changes: 3 additions & 1 deletion megalinter/descriptors/spell.megalinter-descriptor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 4 additions & 0 deletions megalinter/descriptors/sql.megalinter-descriptor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 4 additions & 0 deletions megalinter/descriptors/terraform.megalinter-descriptor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions megalinter/descriptors/yaml.megalinter-descriptor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion megalinter/linters/CSpellLinter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
)
4 changes: 2 additions & 2 deletions megalinter/linters/JavaScriptStandardLinter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
2 changes: 1 addition & 1 deletion megalinter/linters/ProselintLinter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
)
4 changes: 2 additions & 2 deletions megalinter/linters/PyrightLinter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"))
2 changes: 1 addition & 1 deletion megalinter/linters/RuffFormatLinter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
14 changes: 14 additions & 0 deletions megalinter/linters/StyleLintLinter.py
Original file line number Diff line number Diff line change
@@ -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'
)
2 changes: 1 addition & 1 deletion megalinter/linters/TfLintLinter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Loading
Loading