Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
detect-unused-functions.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# This file is part of the Ansel project.
3# Copyright (C) 2025 Aurélien PIERRE.
4#
5# Ansel is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# Ansel is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17# #
18# #
19# #
20# #
21# #
22# #
23# #
24# #
25# #
26# # This script is part of the Ansel project
27# # Copyright (c) 2025 - Aurélien Pierre
28# #
29# # Scan C/C++ header files in the src directory for function declarations
30# # and check if they are used in the source files.
31# #
32# # WARNING: some functions will appear unused from .c files but may be used in
33# # generated code or XML/XSL templates, in particular for the preferences popup.
34# # Unused functions may be unneeded (if API/logic changed), or they might have been
35# # forgotten or not yet implemented, in which case they will be useful in the future.
36# # Don't remove them without checking which it is.
37# #
38# # Ensure to check the function name in the code before removing it.
39# #
40# # Call this script from the root of the repository.
41# #
42# # Usage:
43# - python3 tools/detect-unused-functions.py -> scan all header files
44# - python3 tools/detect-unused-functions.py <header_file> -> scan a specific header file
45#
46#
47import os
48import re
49import sys
50
51# Parse optional argument for a specific header file
52header_file_arg = None
53if len(sys.argv) > 1:
54 header_file_arg = sys.argv[1]
55
56excluded_dirs = [
57 "external",
58 "tests"
59]
60
61excluded_files = [
62 "paint.h"
63]
64
65def find_files_with_extensions(directory, extensions):
66 for root, _, files in os.walk(directory):
67 # Skip any directory under ./external
68 if not any(excluded in root for excluded in excluded_dirs):
69 for file in files:
70 if header_file_arg is not None:
71 if file == header_file_arg:
72 yield os.path.join(root, file)
73 elif any(file.endswith(ext) for ext in extensions) and not any(excluded in file for excluded in excluded_files):
74 yield os.path.join(root, file)
75
77 # Regex to match C function declarations (not definitions), excluding control keywords
78 # Matches: [qualifiers] [return type] function_name(
79 pattern = re.compile(
80 r'^\s*' # Line start, optional whitespace
81 r'(?!#define\b|if\b|else\b|return|typedef\b)' # Exclude lines starting with if/else/return
82 r'([a-zA-Z_][\w\s\*\‍(\‍)]*?)' # Return type and qualifiers (non-greedy)
83 r'\s+([a-zA-Z_]\w*)\s*\‍(', # Function name before '('
84 re.MULTILINE
85 )
86 declarations = []
87 with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
88 for line in f:
89 match = pattern.match(line)
90 if match:
91 # Combine type/qualifier and function name
92 type_qualifier = match.group(1).strip()
93 func_name = match.group(2).strip()
94 declarations.append(f"{type_qualifier} {func_name}")
95 return declarations
96
97def main():
98 output_file = "function_declarations.txt"
99 directory = os.path.join(os.getcwd(), "src")
100 all_declarations = []
101 for h_file in find_files_with_extensions(directory, [".h", ".hpp"]):
102 all_declarations.extend(extract_function_declarations(h_file))
103 with open(output_file, 'w') as out:
104 for decl in all_declarations:
105 out.write(decl + '\n')
106 print(f"Extracted {len(all_declarations)} function declarations to {output_file}")
107
108 for decl in all_declarations:
109 qualifiers, func_name = decl.rsplit(' ', 1)
110
111 # Default functions appear not used but are used in IOP/LIB API
112 # through macros
113 if func_name.startswith("default_"):
114 continue
115
116 used = 0
117 matches = []
118 # Use grep to find occurrences of func_name in .c and .h files, with file and line number
119 grep_cmd = (
120 f"grep -rnw --include='*.c' --include='*.h' --include='*.mm' --include='*.cpp' --include='*.cc' --include='*.hpp' "
121 f". -e '{func_name}'"
122 )
123 try:
124 grep_output = os.popen(grep_cmd).read().strip()
125 if grep_output:
126 matches = set(grep_output.split('\n'))
127 used = len(matches)
128 except Exception:
129 used = 0
130 matches = {}
131
132 error = False
133 if "static" in qualifiers:
134 if used < 2:
135 print(f"{qualifiers} {func_name}: STATIC NOT USED")
136 error = True
137 else:
138 if used < 2:
139 print(f"{qualifiers} {func_name}: NOT DEFINED")
140 error = True
141 elif used < 3:
142 print(f"{qualifiers} {func_name}: NOT USED")
143 error = True
144
145 if error:
146 [print(f" {match}") for match in matches]
147
148
149if __name__ == "__main__":
150 main()
find_files_with_extensions(directory, extensions)