check-style: Add check for #include headers from same module with "ns3/" prefix

This commit is contained in:
Eduardo Almeida
2023-09-06 23:28:39 +01:00
parent 74353c1d68
commit 1e939ef891
2 changed files with 121 additions and 17 deletions

View File

@@ -121,13 +121,14 @@ We recommend running this script over your newly introduced C++ files prior to s
as a Merge Request.
The script has multiple modes of operation. By default, the script checks if
source code files are well formatted and text files do not have trailing whitespace
source code files are well formatted, if there are no #include headers from the same
module with the "ns3/" prefix, and text files do not have trailing whitespace
nor tabs. The process returns a zero exit code if all files adhere to these rules.
If there are files that do not comply with the rules, the process returns a non-zero
exit code and lists the respective files. This mode is useful for developers editing
their code and for the GitLab CI/CD pipeline to check if the codebase is well formatted.
All checks are enabled by default. Users can disable specific checks using the corresponding
flags: ``--no-formatting``, ``--no-whitespace`` and ``--no-tabs``.
flags: ``--no-include-prefixes``, ``--no-formatting``, ``--no-whitespace`` and ``--no-tabs``.
Additional information about the formatting issues detected by the script can be enabled
by adding the ``-v, --verbose`` flag.

View File

@@ -22,13 +22,10 @@ Check and apply the ns-3 coding style to all files in the PATH argument.
The coding style is defined with the clang-format tool, whose definitions are in
the ".clang-format" file. This script performs the following checks / fixes:
- Check / apply clang-format.
- Check / trim trailing whitespace.
- Check / replace tabs with spaces.
The clang-format and tabs checks respect clang-format guards, which mark code blocks
that should not be checked. Trailing whitespace is always checked regardless of
clang-format guards.
- Check / fix local #include headers with "ns3/" prefix. Respects clang-format guards.
- Check / apply clang-format. Respects clang-format guards.
- Check / trim trailing whitespace. Always checked.
- Check / replace tabs with spaces. Respects clang-format guards.
This script can be applied to all text files in a given path or to individual files.
@@ -41,6 +38,7 @@ import argparse
import concurrent.futures
import itertools
import os
import re
import shutil
import subprocess
import sys
@@ -80,6 +78,8 @@ FILE_EXTENSIONS_TO_CHECK_FORMATTING = [
'.h',
]
FILE_EXTENSIONS_TO_CHECK_INCLUDE_PREFIXES = FILE_EXTENSIONS_TO_CHECK_FORMATTING
FILE_EXTENSIONS_TO_CHECK_WHITESPACE = [
'.c',
'.cc',
@@ -169,12 +169,13 @@ def should_analyze_file(path: str,
extension in file_extensions_to_check)
def find_files_to_check_style(path: str) -> Tuple[List[str], List[str], List[str]]:
def find_files_to_check_style(path: str) -> Tuple[List[str], List[str], List[str], List[str]]:
"""
Find all files to be checked in a given path.
@param path Path to check.
@return Tuple [List of files to check formatting,
@return Tuple [List of files to check include prefixes,
List of files to check formatting,
List of files to check trailing whitespace,
List of files to check tabs].
"""
@@ -199,11 +200,15 @@ def find_files_to_check_style(path: str) -> Tuple[List[str], List[str], List[str
files_to_check.sort()
files_to_check_include_prefixes: List[str] = []
files_to_check_formatting: List[str] = []
files_to_check_whitespace: List[str] = []
files_to_check_tabs: List[str] = []
for f in files_to_check:
if should_analyze_file(f, [], FILE_EXTENSIONS_TO_CHECK_INCLUDE_PREFIXES):
files_to_check_include_prefixes.append(f)
if should_analyze_file(f, [], FILE_EXTENSIONS_TO_CHECK_FORMATTING):
files_to_check_formatting.append(f)
@@ -214,6 +219,7 @@ def find_files_to_check_style(path: str) -> Tuple[List[str], List[str], List[str
files_to_check_tabs.append(f)
return (
files_to_check_include_prefixes,
files_to_check_formatting,
files_to_check_whitespace,
files_to_check_tabs,
@@ -263,6 +269,7 @@ def find_clang_format_path() -> str:
# CHECK STYLE MAIN FUNCTIONS
###########################################################
def check_style_clang_format(path: str,
enable_check_include_prefixes: bool,
enable_check_formatting: bool,
enable_check_whitespace: bool,
enable_check_tabs: bool,
@@ -274,23 +281,38 @@ def check_style_clang_format(path: str,
Check / fix the coding style of a list of files.
@param path Path to the files.
@param enable_check_formatting Whether to enable code formatting checking.
@param enable_check_whitespace Whether to enable trailing whitespace checking.
@param enable_check_tabs Whether to enable tabs checking.
@param enable_check_include_prefixes Whether to enable checking #include headers from the same module with the "ns3/" prefix.
@param enable_check_formatting Whether to enable checking code formatting.
@param enable_check_whitespace Whether to enable checking trailing whitespace.
@param enable_check_tabs Whether to enable checking tabs.
@param fix Whether to fix (True) or just check (False) the file.
@param verbose Show the lines that are not compliant with the style.
@param n_jobs Number of parallel jobs.
@return Whether all files are compliant with all enabled style checks.
"""
(files_to_check_formatting,
(files_to_check_include_prefixes,
files_to_check_formatting,
files_to_check_whitespace,
files_to_check_tabs) = find_files_to_check_style(path)
check_include_prefixes_successful = True
check_formatting_successful = True
check_whitespace_successful = True
check_tabs_successful = True
if enable_check_include_prefixes:
check_include_prefixes_successful = check_style_file(
files_to_check_include_prefixes,
check_include_prefixes_file,
'#include headers from the same module with the "ns3/" prefix',
fix,
verbose,
n_jobs,
)
print('')
if enable_check_formatting:
check_formatting_successful = check_style_file(
files_to_check_formatting,
@@ -327,6 +349,7 @@ def check_style_clang_format(path: str,
)
return all([
check_include_prefixes_successful,
check_formatting_successful,
check_whitespace_successful,
check_tabs_successful,
@@ -400,6 +423,81 @@ def check_style_file(filenames: List[str],
###########################################################
# CHECK STYLE FUNCTIONS
###########################################################
def check_include_prefixes_file(filename: str,
fix: bool,
verbose: bool,
) -> Tuple[str, bool, List[str]]:
"""
Check / fix #include headers from the same module with the "ns3/" prefix in a file.
@param filename Name of the file to be checked.
@param fix Whether to fix (True) or just check (False) the style of the file (True).
@param verbose Show the lines that are not compliant with the style.
@return Tuple [Filename,
Whether the file is compliant with the style (before the check),
Verbose information].
"""
is_file_compliant = True
clang_format_enabled = True
verbose_infos: List[str] = []
with open(filename, 'r', encoding='utf-8') as f:
file_lines = f.readlines()
for (i, line) in enumerate(file_lines):
# Check clang-format guards
line_stripped = line.strip()
if line_stripped == CLANG_FORMAT_GUARD_ON:
clang_format_enabled = True
elif line_stripped == CLANG_FORMAT_GUARD_OFF:
clang_format_enabled = False
if (not clang_format_enabled and
line_stripped not in (CLANG_FORMAT_GUARD_ON, CLANG_FORMAT_GUARD_OFF)):
continue
# Check if the line is an #include and extract its header file
header_file = re.findall(r'^#include ["<]ns3/(.*\.h)[">]', line_stripped)
if not header_file:
continue
# Check if the header file belongs to the same module and remove the "ns3/" prefix
header_file = header_file[0]
parent_path = os.path.split(filename)[0]
if not os.path.exists(os.path.join(parent_path, header_file)):
continue
is_file_compliant = False
file_lines[i] = line_stripped.replace(
f'ns3/{header_file}', header_file).replace('<', '"').replace('>', '"') + '\n'
if verbose:
header_index = len('#include "')
verbose_infos.extend([
f'{filename}:{i + 1}:{header_index + 1}: error: #include headers from the same module with the "ns3/" prefix detected',
f' {line_stripped}',
f' {"":{header_index}}^',
])
# Optimization: If running in non-verbose check mode, only one error is needed to check that the file is not compliant
if not fix and not verbose:
break
# Update file with the fixed lines
if fix and not is_file_compliant:
with open(filename, 'w', encoding='utf-8') as f:
f.writelines(file_lines)
return (filename, is_file_compliant, verbose_infos)
def check_formatting_file(filename: str,
fix: bool,
verbose: bool,
@@ -582,8 +680,9 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Check and apply the ns-3 coding style to all files in a given PATH. '
'The script checks the formatting of the file with clang-format. '
'Additionally, it checks the presence of trailing whitespace and tabs. '
'Formatting and tabs checks respect clang-format guards. '
'Additionally, it checks #include headers from the same module with the "ns3/" prefix, '
'the presence of trailing whitespace and tabs. '
'Formatting, local #include "ns3/" prefixes and tabs checks respect clang-format guards. '
'When used in "check mode" (default), the script checks if all files are well '
'formatted and do not have trailing whitespace nor tabs. '
'If it detects non-formatted files, they will be printed and this process exits with a '
@@ -592,6 +691,9 @@ if __name__ == '__main__':
parser.add_argument('path', action='store', type=str,
help='Path to the files to check')
parser.add_argument('--no-include-prefixes', action='store_true',
help='Do not check / fix #include headers from the same module with the "ns3/" prefix')
parser.add_argument('--no-formatting', action='store_true',
help='Do not check / fix code formatting')
@@ -615,6 +717,7 @@ if __name__ == '__main__':
try:
all_checks_successful = check_style_clang_format(
path=args.path,
enable_check_include_prefixes=(not args.no_include_prefixes),
enable_check_formatting=(not args.no_formatting),
enable_check_whitespace=(not args.no_whitespace),
enable_check_tabs=(not args.no_tabs),