-
Notifications
You must be signed in to change notification settings - Fork 0
/
mlint_check.m
executable file
·218 lines (168 loc) · 7.56 KB
/
mlint_check.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
function [lintInfo] = mlint_check(targetPath)
%mlint_check Performs linting on the specified path
% [lintInfo]=mlint_check(targetPath) returns a structure of linted
% information for each .m file found on the target path. Each file on
% the path is linted separately. Errors and warnings are returned as-is,
% except for warnings about McCabe complexity from files with McCabe
% complexity less than a given cutoff (default 10).
%
if nargin==0
targetPath=pwd;
end
SEVERITY_CUTOFF=2;
MCCABE_CUTOFF=10;
curPath=pwd;
cd(targetPath);
% Generates the catalog of warnings and their categories. Used for
% classification of severity.
[warnings, categories] = mlintCatalog();
% Get information about the m files on the target path.
lintInfo = getAllMFiles();
% For every m file found, perform a lint check.
max_severity = 0;
NSevereErrors = 0;
for k = numel(lintInfo):-1:1
% Get the initial structure with relevant (non-Mathworks) warnings and
% errors.
testStruct = checkcode(fullfile(lintInfo(k).folder,lintInfo(k).name),'-cyc','-fullpath','-id');
testStruct = trimMccabe(testStruct,MCCABE_CUTOFF);
if ~isempty(testStruct) % Add the severity of each error.
ids={testStruct.id};
severity=cellfun(@(X) warnings.(X(:)).severity,ids,'UniformOutput',false);
% If it's a McCabe warning, set severity to 2.
severity(strcmp({testStruct(:).id},'CABE')) = {2};
severity(strcmp({testStruct(:).id},'SCABE')) = {2};
[testStruct.severity] = severity{:};
isSevereError = [severity{:}]>SEVERITY_CUTOFF;
if any(isSevereError)
severeErrorInds = find(isSevereError);
for q = 1:numel(severeErrorInds)
NSevereErrors = NSevereErrors + 1;
locSevStruc = testStruct(severeErrorInds(q));
locSevStruc.folder = lintInfo(k).folder;
locSevStruc.name = lintInfo(k).name;
severeErrors(NSevereErrors) = locSevStruc;
end
end
max_severity = max(max([severity{:}]),max_severity);
end
lintInfo(k).lintOutputs = testStruct;
end
if max_severity > SEVERITY_CUTOFF
warning('There are serious errors in the code!');
NSE = numel(severeErrors);
for k = 1:NSE
fprintf('Serious error in file: %s\n',severeErrors(k).name);
fprintf('Severity %d\n',severeErrors(k).severity);
fprintf(' Folder: %s\n',severeErrors(k).folder);
fprintf(' Line: %d\n',severeErrors(k).line);
fprintf(' Column Range: [%d,%d]\n',severeErrors(k).column(1),severeErrors(k).column(2));
fprintf(' ID: %s\n',severeErrors(k).id);
fprintf(' message: %s\n',severeErrors(k).message);
end
exit(2);
end
cd(curPath);
exit(0);
end
%% Helper functions
% Adds an 'ispackage' field to the dir structure. Only works one level
% deep.
function dirStruct = getAllMFiles()
% Recursively finds all m-files on the current path's tree.
dirStruct = dir('**/*.m')';
% For every m-file found, locate it within the packaging structure.
for k=numel(dirStruct):-1:1
[~,parentFolder] = fileparts(dirStruct(k).folder);
if parentFolder(1) == '+' % If the parent folder is a package folder...
dirStruct(k).isInPackage = true;
dirStruct(k).packagePath = getPackageChain(dirStruct(k).folder);
dirStruct(k).isPrivate = false;
elseif strcmp(parentFolder,'private') % Otherwise, if it's in a private folder...
dirStruct(k).isInPackage = false;
dirStruct(k).packagePath = '';
dirStruct(k).isPrivate = true;
else % Otherwise it's on the path somewhere, but not directly inside a package.
dirStruct(k).isInPackage = false;
dirStruct(k).packagePath = '';
dirStruct(k).isPrivate = false;
end
end
end
function packageChain = getPackageChain(packagePath)
packageChain = '';
[parentPath,packageFolder] = fileparts(packagePath);
[~,parentPackageFolder] = fileparts(parentPath);
if parentPackageFolder(1) == '+'
% recurse.
packageChain = [getPackageChain(parentPath) '.'];
end
packageChain = [packageChain strrep(packageFolder,'+','')];
end
%% Removes the 'McCabe Complexity' line if the McCabe complexity is <10.
function trimmedLint = trimMccabe(lintOutputs,cutoff)
if isempty(lintOutputs)
trimmedLint=lintOutputs;
return;
end
mccabeWarnings=contains({lintOutputs.message},'McCabe')';
isHighComplexity = cellfun(@(X) cell2double(regexp(X,'([1-9]+)\.','tokens')) > cutoff,{lintOutputs.message},'UniformOutput',false);
% If it's not a mccabe warning OR if it is and has high complexity,
% keep it.
keepers = (~mccabeWarnings) | (mccabeWarnings & [isHighComplexity{:}]');
trimmedLint=lintOutputs(keepers);
end
%% Converts cells to doubles, handling empty values as -Inf
function num = cell2double(val)
if isempty(val)
num=-Inf;
else
num = str2double(val{:});
end
end
%% Parses any special flags that might be passed to the parser.
function specialFlags = getSpecialFlags(fileList,tags)
nFiles=numel(fileList);
for k=1:nFiles
fileContents = fileread(filename);
toDos = strfind(fileContents, 'TODO');
fixMes = strfind(fileContents, 'FIXME');
end
end
%% Gets a list of mLint warnings and categories.
% From
% https://stackoverflow.com/questions/35898444/find-category-of-matlab-mlint-warning-id.
function [warnings, categories] = mlintCatalog()
% Get a list of all categories, mlint IDs, and severity rankings
output = evalc('checkcode sum.m -allmsg');
% Break each line into it's components
lines = regexp(output, '\n', 'split').';
pattern = '^\s*(?<id>[^\s]*)\s*(?<severity>\d*)\s*(?<message>.*?\s*$)';
warnings = regexp(lines, pattern, 'names');
warnings = cat(1, warnings{:});
% Determine which ones are category names
isCategory = cellfun(@isempty, {warnings.severity});
categories = warnings(isCategory);
% Fix up the category names
pattern = '(^\s*=*\s*|\s*=*\s*$)';
messages = {categories.message};
categoryNames = cellfun(@(x)regexprep(x, pattern, ''), messages, 'uni', 0);
[categories.message] = categoryNames{:};
% Now pair each mlint ID with it's category
comp = bsxfun(@gt, 1:numel(warnings), find(isCategory).');
[category_id, ~] = find(diff(comp, [], 1) == -1);
category_id(end+1:numel(warnings)) = numel(categories);
% Assign a category field to each mlint ID
[warnings.category] = categoryNames{category_id};
category_id = num2cell(category_id);
[warnings.category_id] = category_id{:};
% Remove the categories from the warnings list
warnings = warnings(~isCategory);
% Convert warning severity to a number
severity = num2cell(str2double({warnings.severity}));
[warnings.severity] = severity{:};
% Save just the categories
categories = rmfield(categories, 'severity');
% Convert array of structs to a struct where the MLINT ID is the field
warnings = orderfields(cell2struct(num2cell(warnings), {warnings.id}));
end