forked from Remi-Gau/check_my_code
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcheck_my_code.m
358 lines (280 loc) · 11.8 KB
/
check_my_code.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
function [error_code, file_function, cplx, percentage_comment] = check_my_code(RECURSIVE, CPLX_THRS, COMMENT_THRS, PRINT_FILE)
% This will give you the The McCabe complexity of all the `.m` files in the current directory.
% It will also return the complexity of the subfunctions in each file. If it gets above 10
% you enter the danger zone. If you are above 15 you might seriously reconsider refactoring
% those functions.
%
% This function also checks the proportion of lines with comments in each file (might overestimate it).
% In general you might want to try to be around 20%.
%
% This function will then list the functions that do not meet the requirements you have set for your projects.
% You can then use this information to figure out which function you should refactor first.
%
% FYI: The McCabe complexity of a function is presents how many paths one can take while navigating through
% the conditional statements of your function (`if`, `switch`, ...).
%
% ## USAGE
% If the check_my_code function is in the matlab path, then simply calling it will check the files in the
% the current directory.
%
% ### INPUTS
% #### RECURSIVE : BOOLEAN if set to true this will check the .m files in all the subfolders. (default: false)
%
% #### CPLX_THRS : 1 X 2 ARRAY : thresholds for the acceptable McCabe complexity before triggering a warning.
% Having 2 values lets you decide a zone of complexity that is high but acceptable and another that is
% too high. (default: [15 20])
%
% #### COMMENT_THRS : 1 X 2 ARRAY : thresholds for the acceptable percentage of comments in a file
% before triggering a warning.
% Having 2 values lets you decide levels that are low but acceptable and another that is
% too low. (default: [20 10])
%
% #### PRINT_FILE : BOOLEAN this will print a file with the overall error code ; mostly used for automation
% for now. (default: true)
%
% ### OUPUTS
%
% #### error_code
% an array wth [cplx_error_code comment_error_code] where each value is 0 if there is no file that
% is too complex or has too few comments and is 1 otherwise
%
% #### file_function
% a n X 2 cell listing of all the function in {i,1} and subfunction in {i,2} tested. If the
% function is the main function of a file then {i,1} and {i,2} are the same.
%
% #### cplx
% an array with the complexity of each function and subfunction
%
% #### percentage_comment
% an array with the percentage of comment in each file
%
% ## IMPLEMENTATION
%
% It relies on the linter used natively by matlab so it could also be extended to check all the
% messages relating to
% all the little other issues in your code that you have not told matlab to ignore.
%
% Because octave does not have a linter, so this will only work with matlab.
clc;
% check inputs
if nargin < 1 || isempty(RECURSIVE)
RECURSIVE = false;
end
% those default threshold for the complexity and percentage are VERY
% liberal
if nargin < 2 || isempty(CPLX_THRS)
CPLX_THRS = [15 20];
end
if nargin < 3 || isempty(COMMENT_THRS)
COMMENT_THRS = [20 10];
end
if nargin < 4 || isempty(PRINT_FILE)
PRINT_FILE = false;
end
% initialize
cplx = [];
percentage_comment = [];
file_function = {};
% deal with old Matlab version differently
if verLessThan('matlab', '9.2')
if RECURSIVE
m_file_ls = get_rec_file_ls(pwd);
else
m_file_ls = get_file_ls(pwd);
end
else
% look through the folder for any m file that we want to check
if RECURSIVE
m_file_ls = dir(fullfile(pwd, '**', '*.m'));
else
m_file_ls = dir('*.m');
end
end
for ifile = 1:numel(m_file_ls)
filename = create_filename(m_file_ls, ifile);
% get a rough idea of the percentage of comments
percentage_comment(ifile) = get_percentage_comment(filename);
fprintf('\n\n%s\n', m_file_ls(ifile).name);
fprintf('Percentage of comments: %2.0f percent\n', percentage_comment(ifile));
% get McCabe complexity
msg = checkcode(filename, '-cyc');
% Extract the complexity value of the functions and the subfunctions
[file_function, cplx] = get_complexity(file_function, cplx, msg, filename);
end
% we actually check that the percentage of comments and the code complexity
% meets the requirements
fprintf(1, '\n');
fprintf(1, '\n-----------------------------------------------------------------------\n');
fprintf(1, '\n CHECK_MY_CODE REPORT \n');
fprintf(1, '\n-----------------------------------------------------------------------\n');
fprintf(1, '\n');
cplx_error_code = report_cplx(cplx, file_function, CPLX_THRS);
comment_error_code = report_comments(m_file_ls, percentage_comment, COMMENT_THRS);
error_code = [cplx_error_code comment_error_code];
if ~any(error_code)
fprintf(1, '\n CONGRATULATIONS: YOUR CODE IS CLEAN \n');
end
fprintf(1, '\n-----------------------------------------------------------------------\n');
fprintf(1, '\n-----------------------------------------------------------------------\n');
fprintf(1, '\n');
if PRINT_FILE
FID = fopen(fullfile(pwd, 'check_my_code_report.txt'), ...
'Wt');
fprintf(FID, '%i ', error_code);
fclose(FID);
end
end
function percentage = get_percentage_comment(filename)
% Now we check how many lines have a "percent" sign in them which could
% indicate a comment of any sort
FID = fopen(filename);
line_count = 0;
comment_count = 0;
% loop through all the lines of the code and check which one starts with %
while 1
tline = fgetl(FID);
comment = strfind(tline, '%');
if ~isempty(comment) %#ok<STREMP>
comment_count = comment_count + 1;
end
if ~ischar(tline)
break
end
line_count = line_count + 1;
end
fclose(FID);
percentage = comment_count / line_count * 100;
end
function [file_function, cplx] = get_complexity(file_function, cplx, msg, filename)
% In case this file is empty (i.e MEX file)
if isempty(msg)
cplx(end + 1) = 0;
file_function{end + 1, 1} = filename; %#ok<*AGROW>
file_function{end, 2} = filename;
else
% Loop through the messages and parses them to keep the name of the function and
% subfunction and the complexity
for iMsg = 1:numel(msg)
if ~isempty(strfind(msg(iMsg).message, 'McCabe'))
fprintf('%s\n', msg(iMsg).message);
idx_1 = strfind(msg(iMsg).message, 'complexity of ');
idx_2 = strfind(msg(iMsg).message, ' is ');
% store names
file_function{end + 1, 1} = filename; %#ok<*AGROW>
file_function{end, 2} = msg(iMsg).message(idx_1 + 15:idx_2 - 2);
% store the complexity of this function
cplx(end + 1) = str2double(msg(iMsg).message(idx_2 + 4:end - 1));
% in case the file is empty
if isnan(cplx(end))
cplx(end) = 0;
end
end
end
end
end
function comment_error_code = report_comments(m_file_ls, percentage_comment, COMMENT_THRS)
% this reports on the percentage of comments in the file
% we check what files have less comments thant the 2 threshold we have set
% and we throw a warning or an error depending on the threshold that has
% been crossed
% in either case we list the files incriminated.
WARNING_TO_PRINT = 'Not enough comments in the above functions';
ERROR_TO_PRINT = 'Really not enough comments in the above functions !!!';
warning_comment = find(percentage_comment < COMMENT_THRS(1));
error_comment = find(percentage_comment < COMMENT_THRS(2));
comment_error_code = 0;
if ~isempty(warning_comment)
for ifile = 1:numel(warning_comment)
fprintf('\n%s', create_filename(m_file_ls, warning_comment(ifile)));
end
fprintf('\n\n');
warning(WARNING_TO_PRINT);
comment_error_code = 1;
end
if ~isempty(error_comment)
for ifile = 1:numel(error_comment)
fprintf('\n%s', create_filename(m_file_ls, error_comment(ifile)));
end
fprintf('\n\n');
warning(upper(ERROR_TO_PRINT));
comment_error_code = 2;
end
end
function cplx_error_code = report_cplx(cplx, file_function, CPLX_THRS)
% this reports on the complexity in the files
% we check what files have less comments thant the 2 threshold we have set
% and we throw a warning or an error depending on the threshold that has
% been crossed
% in either case we list the files incriminated.
WARNING_TO_PRINT = 'Above functions functions are too complex: you might want to refactor';
ERROR_TO_PRINT = 'Above functions functions are way too complex: refactor them!!!';
warning_cplx = find(cplx > CPLX_THRS(1));
error_cplx = find(cplx > CPLX_THRS(2));
cplx_error_code = 0;
if ~isempty(warning_cplx)
for ifile = 1:numel(warning_cplx)
fprintf('\nthe function\t%s\t, cplx : %d\n\tin the file %s', ....
file_function{ warning_cplx(ifile), 2 }, ...
cplx(warning_cplx(ifile)), ...
file_function{ warning_cplx(ifile), 1 });
end
fprintf('\n\n');
warning(WARNING_TO_PRINT);
cplx_error_code = 1;
end
if ~isempty(error_cplx)
for ifile = 1:numel(error_cplx)
fprintf('\nthe function\t%s\t, cplx : %d\n\tin the file %s', ....
file_function{ error_cplx(ifile), 2 }, ...
cplx(error_cplx(ifile)), ...
file_function{ error_cplx(ifile), 1 });
end
fprintf('\n\n');
warning(upper(ERROR_TO_PRINT));
cplx_error_code = 1;
end
end
function filename = create_filename(m_file_ls, idx)
filename = fullfile(m_file_ls(idx).folder, m_file_ls(idx).name);
end
function [m_file_ls,dir_ls] = get_file_ls(pth)
% this returns the list of .m files in designated folder, including the
% folder name, which is not returned by the 'dir' command in older
% Matlab versions.
% if requested, it also returns the list of subfolders, which is useful
% for recursive folder-digging in older Matlab versions.
m_file_ls = dir(fullfile(pth,'*.m'));
% adding the 'folder' field which is missing, if there are some files
if size(m_file_ls,1)>0
m_file_ls(end).folder = [];
for ifile = 1:numel(m_file_ls)
m_file_ls(ifile).folder = pth;
end
else
m_file_ls = [];
end
% look for subfolders
if nargout==2
% get list of all 'subfolders'
tmp_ls = dir(pth);
dir_ls = char(tmp_ls([tmp_ls.isdir]).name);
% remove those starting with a '.'
dir_ls(strcmp(cellstr(dir_ls(:,1)),'.'),:) = [];
end
end
function m_file_ls = get_rec_file_ls(pth)
% this returns the list of .m files in designated folder as well as
% those from the sub-folders in a recursive way, which is not returned
% by the 'dir' command in older Matlab versions.
% start by get the current folder .m files and list of subfolders
[m_file_ls,dir_ls] = get_file_ls(pth);
n_subfs = size(dir_ls,1);
% check the subfolders
if n_subfs
for isubf = 1:n_subfs
pth_subf = fullfile(pth,deblank(dir_ls(isubf,:)));
m_file_lsubf = get_rec_file_ls(pth_subf);
m_file_ls = [m_file_ls ; m_file_lsubf];
end
end
end