CI: Refactor C++ checks linting setup.
This commit is contained in:
83
tools/lint/clang_format.py
Normal file
83
tools/lint/clang_format.py
Normal file
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
from utils import (
|
||||
run_command,
|
||||
init_environment,
|
||||
append_file,
|
||||
emit_problem_matchers,
|
||||
write_file,
|
||||
add_common_arguments,
|
||||
)
|
||||
|
||||
|
||||
def count_clang_format_errors(log_text: str) -> int:
|
||||
"""
|
||||
Count lines that indicate clang-format violations.
|
||||
The pattern looks for lines ending with "[-Wclang-format-violations]".
|
||||
"""
|
||||
pattern = r"\[-Wclang-format-violations\]$"
|
||||
matches = re.findall(pattern, log_text, re.MULTILINE)
|
||||
logging.debug("Matches found: %s", matches)
|
||||
return len(matches)
|
||||
|
||||
|
||||
def generate_markdown_report(clang_format_errors: int, output: str) -> str:
|
||||
"""Generate a Markdown report section for the clang-format output."""
|
||||
report_lines = []
|
||||
if clang_format_errors > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:pencil2: Clang-format would reformat"
|
||||
f" {clang_format_errors} file(s)</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: Clang-format would reformat no file</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
report_lines.append(output)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
return "\n".join(report_lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run clang-format in dry-run mode on C++ files and append a Markdown report."
|
||||
)
|
||||
add_common_arguments(parser)
|
||||
parser.add_argument(
|
||||
"--clang-style",
|
||||
required=True,
|
||||
help="Clang-format style (e.g., 'file' to use .clang-format or a specific style).",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
init_environment(args)
|
||||
|
||||
cmd = (
|
||||
f"clang-format --dry-run --ferror-limit=1 --verbose "
|
||||
f"--style={args.clang_style} {args.files}"
|
||||
)
|
||||
stdout, stderr, _ = run_command(cmd)
|
||||
output = stdout + "\n" + stderr
|
||||
|
||||
log_file = os.path.join(args.log_dir, "clang-format.log")
|
||||
write_file(log_file, output)
|
||||
emit_problem_matchers(log_file, "clang.json", "clang")
|
||||
|
||||
clang_format_errors = count_clang_format_errors(output)
|
||||
logging.info("Found %s clang-format errors", clang_format_errors)
|
||||
|
||||
report = generate_markdown_report(clang_format_errors, output)
|
||||
append_file(args.report_file, report)
|
||||
|
||||
sys.exit(0 if clang_format_errors == 0 else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
131
tools/lint/clang_tidy.py
Normal file
131
tools/lint/clang_tidy.py
Normal file
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
from utils import (
|
||||
run_command,
|
||||
init_environment,
|
||||
add_common_arguments,
|
||||
emit_problem_matchers,
|
||||
write_file,
|
||||
append_file,
|
||||
)
|
||||
|
||||
|
||||
def generate_markdown_report(
|
||||
errors_count: int,
|
||||
warnings_count: int,
|
||||
notes_count: int,
|
||||
clang_tidy_output: str,
|
||||
enabled_checks_content: str,
|
||||
) -> str:
|
||||
"""Generate a Markdown report section for clang-tidy results and enabled checks."""
|
||||
report_lines = []
|
||||
if errors_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:fire: Clang-Tidy found :fire: {errors_count} errors, "
|
||||
f":warning: {warnings_count} warnings and :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
elif warnings_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:warning: Clang-Tidy found :warning: {warnings_count} "
|
||||
f"warnings and :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
elif notes_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:pencil2: Clang-Tidy found :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: Clang-Tidy found no errors, warnings or notes</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
report_lines.append(clang_tidy_output)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
report_lines.append(
|
||||
"<details><summary>:information_source: Enabled checks</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
report_lines.append(enabled_checks_content)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
return "\n".join(report_lines)
|
||||
|
||||
|
||||
def count_occurrences(pattern: str, text: str) -> int:
|
||||
"""Count all occurrences of a regex pattern in text using MULTILINE mode."""
|
||||
matches = re.findall(pattern, text, re.MULTILINE)
|
||||
logging.info("Pattern '%s' found %d matches", pattern, len(matches))
|
||||
return len(matches)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run clang-tidy on provided C++ files and append a Markdown report."
|
||||
)
|
||||
add_common_arguments(parser)
|
||||
parser.add_argument(
|
||||
"--clang-style",
|
||||
required=True,
|
||||
help="Clang-format style (e.g., 'file' to use .clang-format or a specific style).",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
init_environment(args)
|
||||
|
||||
build_dir = "build"
|
||||
|
||||
clang_tidy_base_cmd = [
|
||||
"clang-tidy",
|
||||
"--quiet",
|
||||
f"--format-style={args.clang_style}",
|
||||
"--export-fixes=clang-tidy.yaml",
|
||||
"-p",
|
||||
build_dir,
|
||||
]
|
||||
|
||||
enabled_cmd = clang_tidy_base_cmd + ["--explain-config"]
|
||||
enabled_stdout, enabled_stderr, _ = run_command(enabled_cmd)
|
||||
enabled_output = enabled_stdout + enabled_stderr
|
||||
enabled_checks_log = os.path.join(args.log_dir, "clang-tidy-enabled-checks.log")
|
||||
write_file(enabled_checks_log, enabled_output)
|
||||
|
||||
clang_cmd = clang_tidy_base_cmd + args.files.split()
|
||||
clang_stdout, clang_stderr, _ = run_command(clang_cmd)
|
||||
clang_tidy_output = clang_stdout + clang_stderr
|
||||
|
||||
clang_tidy_log = os.path.join(args.log_dir, "clang-tidy.log")
|
||||
write_file(clang_tidy_log, clang_tidy_output)
|
||||
emit_problem_matchers(clang_tidy_log, "clang.json", "clang")
|
||||
|
||||
error_pattern = r"^.+:\d+:\d+: error: .+$"
|
||||
warning_pattern = r"^.+:\d+:\d+: warning: .+$"
|
||||
note_pattern = r"^.+:\d+:\d+: note: .+$"
|
||||
|
||||
errors_count = count_occurrences(error_pattern, clang_tidy_output)
|
||||
warnings_count = count_occurrences(warning_pattern, clang_tidy_output)
|
||||
notes_count = count_occurrences(note_pattern, clang_tidy_output)
|
||||
|
||||
logging.info(
|
||||
"Found %d errors, %d warnings, %d notes",
|
||||
errors_count,
|
||||
warnings_count,
|
||||
notes_count,
|
||||
)
|
||||
|
||||
report = generate_markdown_report(
|
||||
errors_count, warnings_count, notes_count, clang_tidy_output, enabled_output
|
||||
)
|
||||
append_file(args.report_file, report)
|
||||
|
||||
sys.exit(0 if errors_count == 0 else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
119
tools/lint/clazy.py
Normal file
119
tools/lint/clazy.py
Normal file
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
|
||||
from utils import (
|
||||
run_command,
|
||||
init_environment,
|
||||
write_file,
|
||||
append_file,
|
||||
emit_problem_matchers,
|
||||
)
|
||||
|
||||
|
||||
def generate_markdown_report(
|
||||
aggregated_output: str, errors_count: int, warnings_count: int, notes_count: int
|
||||
) -> str:
|
||||
"""Generate a Markdown report based on clazy output and issue counts."""
|
||||
report_lines = []
|
||||
if errors_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:fire: Clazy found :fire: {errors_count} errors, "
|
||||
f":warning: {warnings_count} warnings and :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
elif warnings_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:warning: Clazy found :warning: {warnings_count} warnings "
|
||||
f"and :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
elif notes_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:pencil2: Clazy found :pencil2: {notes_count} notes</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: Clazy found no errors, warnings or notes</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append(
|
||||
"[List of checks](https://github.com/KDE/clazy#list-of-checks), "
|
||||
"[This explains some of the clazy warnings](https://www.kdab.com/uncovering-32-qt-best-practices-compile-time-clazy/)"
|
||||
)
|
||||
report_lines.append("````")
|
||||
report_lines.append(aggregated_output)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
return "\n".join(report_lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run Clazy on provided C++ files and append a Markdown report."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--files",
|
||||
required=True,
|
||||
help="A space-separated list (or glob-expanded string) of C++ files to check.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--clazy-checks",
|
||||
required=True,
|
||||
help="Comma-separated list of clazy checks to run.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--log-dir", required=True, help="Directory where the log file will be written."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--report-file",
|
||||
required=True,
|
||||
help="Path to the Markdown report file to append results.",
|
||||
)
|
||||
parser.add_argument("--verbose", action="store_true", help="Enable verbose output.")
|
||||
args = parser.parse_args()
|
||||
init_environment(args)
|
||||
|
||||
build_compile_commands = "build/compile_commands.json"
|
||||
fix_file = "clazy.yaml"
|
||||
|
||||
output = ""
|
||||
file_list = args.files.split()
|
||||
|
||||
for file in file_list:
|
||||
cmd = [
|
||||
"clazy-standalone",
|
||||
f"--export-fixes={fix_file}",
|
||||
f"-checks={args.clazy_checks}",
|
||||
"-p",
|
||||
build_compile_commands,
|
||||
file,
|
||||
]
|
||||
stdout, stderr, _ = run_command(cmd)
|
||||
output += stdout + "\n" + stderr + "\n"
|
||||
|
||||
log_file = os.path.join(args.log_dir, "clazy.log")
|
||||
write_file(log_file, output)
|
||||
emit_problem_matchers(log_file, "clang.json", "clang")
|
||||
|
||||
error_pattern = r"^.+:\d+:\d+: error: .+$"
|
||||
warning_pattern = r"^.+:\d+:\d+: warning: .+$"
|
||||
note_pattern = r"^.+:\d+:\d+: note: .+$"
|
||||
errors_count = len(re.findall(error_pattern, output, re.MULTILINE))
|
||||
warnings_count = len(re.findall(warning_pattern, output, re.MULTILINE))
|
||||
notes_count = len(re.findall(note_pattern, output, re.MULTILINE))
|
||||
|
||||
logging.info(
|
||||
f"Found {errors_count} errors, {warnings_count} warnings, {notes_count} notes"
|
||||
)
|
||||
|
||||
report = generate_markdown_report(output, errors_count, warnings_count, notes_count)
|
||||
append_file(args.report_file, report + "\n")
|
||||
|
||||
sys.exit(0 if errors_count == 0 else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
120
tools/lint/clazy_qt6.py
Normal file
120
tools/lint/clazy_qt6.py
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
|
||||
from utils import (
|
||||
run_command,
|
||||
init_environment,
|
||||
write_file,
|
||||
append_file,
|
||||
emit_problem_matchers,
|
||||
)
|
||||
|
||||
|
||||
def count_occurrences(pattern, text):
|
||||
matches = re.findall(pattern, text, re.MULTILINE)
|
||||
logging.debug(f"Found {len(matches)} matches for pattern: {pattern}")
|
||||
return len(matches)
|
||||
|
||||
|
||||
def generate_markdown_report(
|
||||
output: str, errors_count: int, warnings_count: int, notes_count: int
|
||||
) -> str:
|
||||
"""Generate a Markdown report based on Clazy QT6 output and issue counts."""
|
||||
report_lines = []
|
||||
if errors_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:fire: Clazy found :fire: {errors_count} errors, "
|
||||
f":warning: {warnings_count} warnings and :pencil2: {notes_count} notes for porting to QT6</summary>"
|
||||
)
|
||||
elif warnings_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:warning: Clazy found :warning: {warnings_count} warnings and "
|
||||
f":pencil2: {notes_count} notes for porting to QT6</summary>"
|
||||
)
|
||||
elif notes_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:pencil2: Clazy found :pencil2: {notes_count} notes for porting to QT6</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: Clazy found no errors, warnings or notes for porting to QT6</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
report_lines.append(output)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
return "\n".join(report_lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run Clazy checks for QT6 on provided C++ files and append a Markdown report."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--files",
|
||||
required=True,
|
||||
help="A space-separated list (or glob-expanded string) of C++ files to check.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--clazy-qt6-checks",
|
||||
required=True,
|
||||
help="Comma-separated list of clazy QT6 checks to run.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--log-dir", required=True, help="Directory where the log file will be written."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--report-file",
|
||||
required=True,
|
||||
help="Path to the Markdown report file to append results.",
|
||||
)
|
||||
parser.add_argument("--verbose", action="store_true", help="Enable verbose output.")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Initialize logging and required directories using a shared utility.
|
||||
init_environment(args)
|
||||
|
||||
build_dir = "build"
|
||||
fix_file = "clazyQT6.yaml"
|
||||
|
||||
# Build the clazy command as a list.
|
||||
cmd = [
|
||||
"clazy-standalone",
|
||||
f"--export-fixes={fix_file}",
|
||||
f"-checks={args.clazy_qt6_checks}",
|
||||
"-p",
|
||||
build_dir,
|
||||
] + args.files.split()
|
||||
|
||||
stdout, stderr, _ = run_command(cmd)
|
||||
output = stdout + stderr
|
||||
|
||||
log_file = os.path.join(args.log_dir, "clazyQT6.log")
|
||||
write_file(log_file, output)
|
||||
emit_problem_matchers(log_file, "clang.json", "clang")
|
||||
|
||||
error_pattern = r"^.+:\d+:\d+: error: .+$"
|
||||
warning_pattern = r"^.+:\d+:\d+: warning: .+$"
|
||||
note_pattern = r"^.+:\d+:\d+: note: .+$"
|
||||
errors_count = count_occurrences(error_pattern, output)
|
||||
warnings_count = count_occurrences(warning_pattern, output)
|
||||
notes_count = count_occurrences(note_pattern, output)
|
||||
|
||||
logging.info(
|
||||
f"Found {errors_count} errors, {warnings_count} warnings, {notes_count} notes"
|
||||
)
|
||||
|
||||
report = generate_markdown_report(output, errors_count, warnings_count, notes_count)
|
||||
append_file(args.report_file, report + "\n")
|
||||
|
||||
sys.exit(0 if errors_count == 0 else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
111
tools/lint/codespell.py
Normal file
111
tools/lint/codespell.py
Normal file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import urllib.request
|
||||
import logging
|
||||
|
||||
from utils import (
|
||||
run_command,
|
||||
init_environment,
|
||||
write_file,
|
||||
append_file,
|
||||
emit_problem_matchers,
|
||||
)
|
||||
|
||||
|
||||
def download_dictionary(url, dest):
|
||||
logging.info(f"Downloading dictionary from {url} to {dest}")
|
||||
try:
|
||||
urllib.request.urlretrieve(url, dest)
|
||||
except Exception as e:
|
||||
logging.error(f"Error downloading dictionary: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def generate_markdown_report(misspellings: int, log_file: str) -> str:
|
||||
"""
|
||||
Generate a Markdown report section based on the codespell results and log file.
|
||||
"""
|
||||
report_lines = []
|
||||
if misspellings > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:pencil2: Codespell found {misspellings} misspellings</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: Codespell found no misspellings</summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append(
|
||||
"To ignore false positives, append the word to the [.github/codespellignore]"
|
||||
"(https://github.com/FreeCAD/FreeCAD/blob/master/.github/codespellignore) file (lowercase)"
|
||||
)
|
||||
report_lines.append("````")
|
||||
with open(log_file, "r", encoding="utf-8") as lf:
|
||||
report_lines.append(lf.read())
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
return "\n".join(report_lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run codespell on files and append a Markdown report."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ignore-words",
|
||||
required=True,
|
||||
help="Comma-separated list of words to ignore (from codespellignore).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip", required=True, help="Comma-separated list of file patterns to skip."
|
||||
)
|
||||
args = parser.parse_args()
|
||||
init_environment(args)
|
||||
|
||||
dictionary_file = "dictionary.txt"
|
||||
if not os.path.exists(dictionary_file):
|
||||
dictionary_url = "https://raw.githubusercontent.com/codespell-project/codespell/master/codespell_lib/data/dictionary.txt"
|
||||
logging.info("Dictionary not found; downloading...")
|
||||
download_dictionary(dictionary_url, dictionary_file)
|
||||
|
||||
run_command(["pip", "install", "-q", "codespell"], check=True)
|
||||
|
||||
cmd = [
|
||||
"codespell",
|
||||
"--quiet-level",
|
||||
"3",
|
||||
"--summary",
|
||||
"--count",
|
||||
"--ignore-words",
|
||||
args.ignore_words,
|
||||
"--skip",
|
||||
args.skip,
|
||||
"-D",
|
||||
dictionary_file,
|
||||
] + args.files.split()
|
||||
stdout, stderr, _ = run_command(cmd)
|
||||
output = stdout + "\n" + stderr + "\n"
|
||||
|
||||
log_file = os.path.join(args.log_dir, "codespell.log")
|
||||
write_file(log_file, output)
|
||||
emit_problem_matchers(log_file, "codespell.json", "codespell")
|
||||
|
||||
try:
|
||||
misspellings = int(stdout.strip())
|
||||
except ValueError:
|
||||
logging.info(f"Could not parse misspellings count from output:\n{stdout}")
|
||||
misspellings = 0
|
||||
|
||||
logging.info(f"Found {misspellings} misspellings")
|
||||
|
||||
report = generate_markdown_report(misspellings, log_file)
|
||||
append_file(args.report_file, report + "\n")
|
||||
|
||||
sys.exit(0 if misspellings == 0 else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
65
tools/lint/cpplint.cfg
Normal file
65
tools/lint/cpplint.cfg
Normal file
@@ -0,0 +1,65 @@
|
||||
+build/deprecated,
|
||||
-build/endif_comment,
|
||||
+build/explicit_make_pair,
|
||||
-build/forward_decl,
|
||||
-build/header_guard,
|
||||
+build/include,
|
||||
-build/include_subdir,
|
||||
-build/include_alpha,
|
||||
-build/include_order,
|
||||
+build/include_what_you_use,
|
||||
+build/namespaces_headers,
|
||||
+build/namespaces_literals,
|
||||
-build/namespaces,
|
||||
+build/printf_format,
|
||||
+build/storage_class,
|
||||
-legal/copyright,
|
||||
+readability/alt_tokens,
|
||||
-readability/braces,
|
||||
+readability/casting,
|
||||
+readability/check,
|
||||
+readability/constructors,
|
||||
+readability/fn_size,
|
||||
+readability/inheritance,
|
||||
+readability/multiline_comment,
|
||||
+readability/multiline_string,
|
||||
+readability/namespace,
|
||||
-readability/nolint,
|
||||
+readability/nul,
|
||||
+readability/strings,
|
||||
-readability/todo,
|
||||
+readability/utf8,
|
||||
+runtime/arrays,
|
||||
+runtime/casting,
|
||||
+runtime/explicit,
|
||||
+runtime/int,
|
||||
+runtime/init,
|
||||
+runtime/invalid_increment,
|
||||
+runtime/member_string_references,
|
||||
+runtime/memset,
|
||||
+runtime/operator,
|
||||
+runtime/printf,
|
||||
+runtime/printf_format,
|
||||
+runtime/references,
|
||||
+runtime/string,
|
||||
+runtime/threadsafe_fn,
|
||||
+runtime/vlog,
|
||||
-whitespace/blank_line,
|
||||
-whitespace/braces,
|
||||
-whitespace/comma,
|
||||
-whitespace/comments,
|
||||
-whitespace/empty_conditional_body,
|
||||
-whitespace/empty_if_body,
|
||||
-whitespace/empty_loop_body,
|
||||
-whitespace/end_of_line,
|
||||
-whitespace/ending_newline,
|
||||
-whitespace/forcolon,
|
||||
-whitespace/indent,
|
||||
-whitespace/indent_namespace,
|
||||
-whitespace/line_length,
|
||||
-whitespace/newline,
|
||||
-whitespace/operators,
|
||||
-whitespace/parens,
|
||||
-whitespace/semicolon,
|
||||
-whitespace/tab,
|
||||
-whitespace/todo
|
||||
90
tools/lint/cpplint.py
vendored
Normal file
90
tools/lint/cpplint.py
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
from utils import (
|
||||
run_command,
|
||||
init_environment,
|
||||
write_file,
|
||||
append_file,
|
||||
emit_problem_matchers,
|
||||
add_common_arguments,
|
||||
)
|
||||
|
||||
DEFAULT_LINE_LENGTH_LIMIT = 100
|
||||
|
||||
def load_cpplint_filters(conf_file="cpplint.cfg"):
|
||||
"""Load cpplint filters from a configuration file."""
|
||||
try:
|
||||
with open(conf_file, "r", encoding="utf-8") as f:
|
||||
filters = f.read().strip()
|
||||
return filters
|
||||
except Exception as e:
|
||||
logging.warning(f"Could not load {conf_file}: {e}")
|
||||
return ""
|
||||
|
||||
def count_cpplint_errors(log_text: str) -> int:
|
||||
"""
|
||||
Count the number of cpplint error/warning lines.
|
||||
"""
|
||||
matches = re.findall(r"\[[0-9]+\]$", log_text, re.MULTILINE)
|
||||
return len(matches)
|
||||
|
||||
def generate_markdown_report(aggregated_output: str, errors_count: int) -> str:
|
||||
"""Generate a Markdown report section based on cpplint output and error count."""
|
||||
report_lines = []
|
||||
if errors_count > 0:
|
||||
report_lines.append(
|
||||
f"<details><summary>:warning: CppLint found {errors_count} errors / warnings</summary>"
|
||||
)
|
||||
else:
|
||||
report_lines.append(
|
||||
"<details><summary>:heavy_check_mark: No cpplint errors found </summary>"
|
||||
)
|
||||
report_lines.append("")
|
||||
report_lines.append("````")
|
||||
report_lines.append(aggregated_output)
|
||||
report_lines.append("````")
|
||||
report_lines.append("</details>")
|
||||
report_lines.append("")
|
||||
return "\n".join(report_lines)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run cpplint on provided C++ files and append a Markdown report."
|
||||
)
|
||||
add_common_arguments(parser)
|
||||
args = parser.parse_args()
|
||||
init_environment(args)
|
||||
|
||||
run_command(["pip", "install", "-q", "cpplint"], check=True)
|
||||
|
||||
cpplint_filters = load_cpplint_filters("cpplint.cfg")
|
||||
|
||||
aggregated_output = ""
|
||||
for file in args.files.split():
|
||||
cmd = [
|
||||
"cpplint",
|
||||
f"--filters={cpplint_filters}",
|
||||
f"--linelength={DEFAULT_LINE_LENGTH_LIMIT}",
|
||||
file,
|
||||
]
|
||||
stdout, stderr, _ = run_command(cmd)
|
||||
aggregated_output += stdout + stderr + "\n"
|
||||
|
||||
cpplint_log = os.path.join(args.log_dir, "cpplint.log")
|
||||
write_file(cpplint_log, aggregated_output)
|
||||
emit_problem_matchers(cpplint_log, "cpplint.json", "cpplint")
|
||||
|
||||
errors_count = count_cpplint_errors(aggregated_output)
|
||||
logging.info(f"Found {errors_count} cpplint errors")
|
||||
|
||||
report = generate_markdown_report(aggregated_output, errors_count)
|
||||
append_file(args.report_file, report)
|
||||
|
||||
sys.exit(0 if errors_count == 0 else 1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
75
tools/lint/qt_connections.py
Normal file
75
tools/lint/qt_connections.py
Normal file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from utils import (
|
||||
add_common_arguments,
|
||||
init_environment,
|
||||
write_file,
|
||||
append_file,
|
||||
emit_problem_matchers,
|
||||
)
|
||||
|
||||
|
||||
def check_qt_connections(file_path):
|
||||
"""
|
||||
Scan the file for lines containing 'SIGNAL' or 'SLOT' and return a list of matching lines
|
||||
with line numbers and a suggestion.
|
||||
"""
|
||||
matches = []
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
|
||||
for lineno, line in enumerate(f, start=1):
|
||||
if re.search(r"\b(SIGNAL|SLOT)\b", line):
|
||||
matches.append(
|
||||
f"{file_path}:{lineno}: {line.rstrip()} <-- Consider using functor-based connections"
|
||||
)
|
||||
except Exception as e:
|
||||
logging.debug(f"Error reading {file_path}: {e}")
|
||||
return matches
|
||||
|
||||
|
||||
def generate_markdown_report(all_matches):
|
||||
"""Generate a Markdown report based on the list of QT connection matches."""
|
||||
count = len(all_matches)
|
||||
if count > 0:
|
||||
md_report = (
|
||||
f"<details><summary>:information_source: Found {count} QT string-based connections → consider using QT functor-based connections</summary>\n"
|
||||
"\nFor more information see: https://wiki.qt.io/New_Signal_Slot_Syntax or https://github.com/FreeCAD/FreeCAD/issues/6166\n"
|
||||
"```\n" + "\n".join(all_matches) + "\n```\n</details>\n\n"
|
||||
)
|
||||
else:
|
||||
md_report = ":heavy_check_mark: No string-based connections found\n\n"
|
||||
return md_report
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Check for old Qt string-based connections in files and append a Markdown report."
|
||||
)
|
||||
add_common_arguments(parser)
|
||||
args = parser.parse_args()
|
||||
init_environment(args)
|
||||
|
||||
all_matches = []
|
||||
for file_path in args.files.split():
|
||||
logging.debug(f"Checking file: {file_path}")
|
||||
matches = check_qt_connections(file_path)
|
||||
all_matches.extend(matches)
|
||||
|
||||
log_file = os.path.join(args.log_dir, "qtConnections.log")
|
||||
write_file(log_file, "\n".join(all_matches))
|
||||
emit_problem_matchers(log_file, "grepMatcherWarning.json", "grepMatcher-warning")
|
||||
|
||||
count = len(all_matches)
|
||||
logging.info(f"Found {count} QT string-based connections")
|
||||
|
||||
md_report = generate_markdown_report(all_matches)
|
||||
append_file(args.report_file, md_report)
|
||||
|
||||
sys.exit(0 if count == 0 else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user