1#!/usr/bin/env python3 2# 3# Arm SCP/MCP Software 4# Copyright (c) 2015-2023, Arm Limited and Contributors. All rights reserved. 5# 6# SPDX-License-Identifier: BSD-3-Clause 7# 8""" 9 Check for trailing spaces and non-UNIX line endings in the source code. 10""" 11import argparse 12import os 13import re 14import shutil 15import subprocess 16import sys 17import tempfile 18import fnmatch 19import glob 20 21 22# 23# Directories to exclude 24# 25 26# Exclude all mod_test "mocks" directories 27UNIT_TEST_MOCKS = glob.glob('module/*/test/**/mocks', recursive=True) 28 29EXCLUDE_DIRECTORIES = [ 30 '.git', 31 'build', 32 'tools', 33 'contrib/cmsis/git', 34 "contrib/run-clang-format/git", 35 "contrib/cmock/git", 36 'product/rcar/src/CMSIS-FreeRTOS', 37 'unit_test/unity_mocks', 38] + UNIT_TEST_MOCKS 39 40# 41# Exclude patterns (applied to files only) 42# 43EXCLUDE = [ 44 "*.html", 45 "*.xml", 46 "*.css", 47 "*.gif", 48 "*.dat", 49 "*.pyc", 50 "*.jar", 51 "*.md", 52 "*.swp", 53 "*.a", 54 "*.pdf", 55 ".*" 56] 57 58KEYWORDS = [ 59 'for', 60 'if', 61 'switch', 62 'while', 63] 64 65# 66# File types for which spaces after keywords will be corrected 67# 68FILE_TYPES = [ 69 '*.c', 70 '*.h', 71] 72 73 74def is_valid_type(filename): 75 for file_type in FILE_TYPES: 76 if fnmatch.fnmatch(filename, file_type): 77 return True 78 return False 79 80 81def main(argv=[], prog_name=''): 82 parser = argparse.ArgumentParser(prog=prog_name) 83 parser.add_argument('-t', '--trim', 84 help='Remove trailing spaces.', 85 action='store_true', 86 default=False) 87 parser.add_argument('-c', '--correct', 88 help='Correct spaces after keywords.', 89 action='store_true', 90 default=False) 91 args = parser.parse_args(argv) 92 93 print('Checking for incorrect spacing in the code...') 94 if args.trim: 95 print("Trim mode is enabled.") 96 if args.correct: 97 print("Correct mode is enabled.") 98 99 regex_patterns = dict.fromkeys(KEYWORDS, 0) 100 101 trailing_spaces_count = 0 102 trailing_lines_count = 0 103 correct_spaces_count = 0 104 modified_files = 0 105 non_unix_eol_files = 0 106 missing_new_lines_files = 0 107 108 cwd = os.getcwd() 109 print("Executing from {}".format(cwd)) 110 111 for i, directory in enumerate(EXCLUDE_DIRECTORIES): 112 EXCLUDE_DIRECTORIES[i] = os.path.abspath(directory) 113 print("\tAdding to the exclude list: {}" 114 .format(EXCLUDE_DIRECTORIES[i])) 115 116 for root, dirs, files in os.walk(cwd, topdown=True): 117 # 118 # Exclude directories based on the EXCLUDE_DIRECTORIES pattern list 119 # 120 dirs[:] = [d for d in dirs 121 if os.path.join(root, d) not in EXCLUDE_DIRECTORIES] 122 123 # 124 # Exclude files based on the EXCLUDE pattern list 125 # 126 matches = list() 127 for filename in files: 128 for file_pattern in EXCLUDE: 129 if fnmatch.fnmatch(filename, file_pattern): 130 matches.append(filename) 131 break 132 for match in matches: 133 files.remove(match) 134 135 for keyword in KEYWORDS: 136 regex_patterns[keyword] = re.compile( 137 '(.*\\W)(%s)(\\s*)(\\(.*)' % keyword) 138 # 139 # Check files 140 # 141 for filename in files: 142 path = os.path.join(root, filename) 143 content = '' 144 trailing_spaces = 0 145 trailing_lines = 0 146 incorrect_spaces = 0 147 148 with open(path, encoding="utf-8") as file: 149 lines = file.readlines() 150 last_line_number = len(lines)-1 151 for line, string in enumerate(lines): 152 if line == last_line_number and string[-1] != '\n': 153 print('{} is missing a new line at the end of file'. 154 format(path)) 155 missing_new_lines_files += 1 156 157 # Note that all newlines are converted to '\n', 158 # so the following will work regardless of 159 # what the underlying file format is using to 160 # represent a line break. 161 if string.endswith(' \n'): 162 print('{}:{} has trailing space'.format(line, path)) 163 trailing_spaces += 1 164 if args.trim: 165 string = string.rstrip()+'\n' 166 if not is_valid_type(os.path.basename(path)): 167 content += string 168 continue 169 for keyword in KEYWORDS: 170 key_index = string.find(keyword) 171 if key_index == -1: 172 continue 173 m = regex_patterns[keyword].search(string) 174 if m and m.group(3) != ' ': 175 incorrect_spaces += 1 176 print('Abnormal spacing. "{}", {}:{} --> {}' 177 .format(keyword, path, line, 178 string.rstrip())) 179 if args.correct: 180 string = m.group(1) + m.group(2) + ' ' + \ 181 m.group(4) + '\n' 182 content += string 183 184 if content.endswith('\n\n'): 185 print('Blank line at the end of file --> {}'.format(path)) 186 c_len = len(content) 187 if args.trim: 188 content = content.rstrip()+'\n' 189 trailing_lines += c_len - len(content.rstrip() + '\n') 190 # 191 # If file.newlines has been set it is either a string with 192 # the determined line ending or a tuple with all the line 193 # endings we have encountered 194 # 195 if file.newlines: 196 if isinstance(file.newlines, tuple): 197 print('{} has mixed line endings'.format(path)) 198 non_unix_eol_files += 1 199 elif file.newlines != '\n': 200 print('{} has non-UNIX line endings'.format(path)) 201 non_unix_eol_files += 1 202 203 # 204 # Trim and/or correct file, depending on the provided arguments 205 # 206 write_file = False 207 if args.trim and (trailing_spaces or trailing_lines) != 0: 208 print("Trimming {}...".format(path)) 209 write_file = True 210 if args.correct and incorrect_spaces != 0: 211 print("Correcting {}...".format(path)) 212 write_file = True 213 if write_file: 214 modified_files += 1 215 with open(path, 'w') as file: 216 file.write(content) 217 218 trailing_spaces_count += trailing_spaces 219 trailing_lines_count += trailing_lines 220 correct_spaces_count += incorrect_spaces 221 222 if trailing_spaces_count == 0: 223 print("No trailing spaces found") 224 else: 225 print('{} trailing spaces found.'.format(trailing_spaces_count)) 226 227 if trailing_lines_count == 0: 228 print("No trailing lines found") 229 else: 230 print('{} trailing lines found.'.format(trailing_lines_count)) 231 232 if correct_spaces_count == 0: 233 print("No abnormal spaces found") 234 else: 235 print('Abnormal spaces found on {} lines.' 236 .format(correct_spaces_count)) 237 238 if (args.trim or args.correct) and modified_files: 239 print("{} files modified".format(modified_files)) 240 241 if non_unix_eol_files == 0: 242 print("No files with non-UNIX or mixed line endings found") 243 else: 244 print("{} files have non-UNIX or mixed line endings" 245 .format(non_unix_eol_files)) 246 247 if missing_new_lines_files == 0: 248 print("No files with missing newlines at EOF found") 249 else: 250 print("{} text files are missing newlines at EOF" 251 .format(missing_new_lines_files)) 252 253 if (trailing_spaces_count or 254 trailing_lines_count or 255 correct_spaces_count or 256 non_unix_eol_files or 257 missing_new_lines_files): 258 return 1 259 return 0 260 261 262if __name__ == '__main__': 263 sys.exit(main(sys.argv[1:], sys.argv[0])) 264