Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MlintBear #2372

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions bears/non_free/MlintBear.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import shlex

from coalib.bearlib.abstractions.Linter import linter
from dependency_management.requirements.ExecutableRequirement import (
ExecutableRequirement)


@linter(executable='mlint',
use_stderr=True,
output_format='regex',
output_regex=r'L (?P<line>\d+) \(C (?P<column>\d+)'
r'(?:-(?P<end_column>\d+))*\): (?P<message>.*)')
class MlintBear:
"""
Checks the code with mlint. This will run mlint over each file
separately.
"""
LANGUAGES = {'Matlab'}
REQUIREMENTS = {ExecutableRequirement('mlint')}
AUTHORS = {'The coala developers'}
AUTHORS_EMAILS = {'[email protected]'}
LICENSE = 'AGPL-3.0'
CAN_DETECT = {'Unused Code', 'Formatting', 'Duplication',
'Syntax'}
SEE_MORE = 'https://www.mathworks.com/help/matlab/ref/mlint.html'

@staticmethod
def create_arguments(filename, file, config_file,
mlint_cli_options: str=''):
"""
:param mlint_cli_options: Any other flags you wish to be
passed to mlint.
"""
args = ()
if mlint_cli_options:
args += tuple(shlex.split(mlint_cli_options))

return args + (filename,)
Empty file added bears/non_free/__init__.py
Empty file.
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ omit =
tests/*
.ci/*
setup.py
bears/non_free/*

[coverage:report]
show_missing = True
106 changes: 106 additions & 0 deletions tests/non_free/MlintBearTest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import os
import unittest
from queue import Queue

from bears.non_free.MlintBear import MlintBear
from coalib.testing.LocalBearTestHelper import execute_bear
from coalib.testing.BearTestHelper import generate_skip_decorator
from coalib.settings.Section import Section
from coalib.settings.Setting import Setting


def get_absolute_test_path(file):
return os.path.join(os.path.dirname(__file__),
'mlint_test_files', file)


@generate_skip_decorator(MlintBear)
class MlintBearTest(unittest.TestCase):

def setUp(self):
self.section = Section('')
self.uut = MlintBear(self.section, Queue())
self.good_file = 'lengthofline2.m'
self.bad_file = 'lengthofline.m'

def test_good_file(self):
filename = get_absolute_test_path(self.good_file)
with execute_bear(self.uut, filename) as result:
self.assertEqual(result, [])

def test_cli_options(self):
filename = get_absolute_test_path(self.bad_file)
self.section.append(Setting('mlint_cli_options', '-cyc -id'))
expected_result = [
'CABE: The McCabe complexity of \'lengthofline\' is 12.',
'NASGU: The value assigned to variable \'nothandle\' ' +
'might be unused.',
'PSIZE: NUMEL(x) is usually faster than PROD(SIZE(x)).',
'AGROW: The variable \'notline\' appears to change size ' +
'on every loop iteration. Consider preallocating for speed.',
'STCI: Use STRCMPI(str1,str2) instead of using UPPER/LOWER ' +
'in a call to STRCMP.',
'PSIZE: NUMEL(x) is usually faster than PROD(SIZE(x)).',
'AGROW: The variable \'data\' appears to change size on ' +
'every loop iteration. Consider preallocating for speed.',
'GFLD: Use dynamic fieldnames with structures instead ' +
'of GETFIELD.',
'OR2: Use || instead of | as the OR operator in (scalar) ' +
'conditional statements.',
'OR2: Use || instead of | as the OR operator in (scalar) ' +
'conditional statements.',
'OR2: Use || instead of | as the OR operator in (scalar) ' +
'conditional statements.',
'AGROW: The variable \'dim\' appears to change size on ' +
'every loop iteration. Consider preallocating for speed.',
'AGROW: The variable \'dim\' appears to change size on ' +
'every loop iteration. Consider preallocating for speed.',
'NOPAR: Invalid syntax at \';\'. Possibly, a ), }, or ] ' +
'is missing.',
'NOPAR: Invalid syntax at \')\'. Possibly, a ), }, or ] ' +
'is missing.',
'SYNER: Parse error at \']\': usage might be invalid ' +
'MATLAB syntax.',
'NOPRT: Terminate statement with semicolon to suppress output ' +
'(in functions).',
'NBRAK: Use of brackets [] is unnecessary. Use parentheses ' +
'to group, if needed.'
]
with execute_bear(self.uut, filename) as result:
for i in range(len(result)):
self.assertEqual(result[i].message, expected_result[i])

def test_bad_file(self):
filename = get_absolute_test_path(self.bad_file)
expected_result = [
'The value assigned to variable \'nothandle\' might be unused.',
'NUMEL(x) is usually faster than PROD(SIZE(x)).',
'The variable \'notline\' appears to change size on every ' +
'loop iteration. Consider preallocating for speed.',
'Use STRCMPI(str1,str2) instead of using UPPER/LOWER in a call ' +
'to STRCMP.',
'NUMEL(x) is usually faster than PROD(SIZE(x)).',
'The variable \'data\' appears to change size on every loop ' +
'iteration. Consider preallocating for speed.',
'Use dynamic fieldnames with structures instead of GETFIELD.',
'Use || instead of | as the OR operator in (scalar) conditional ' +
'statements.',
'Use || instead of | as the OR operator in (scalar) conditional ' +
'statements.',
'Use || instead of | as the OR operator in (scalar) conditional ' +
'statements.',
'The variable \'dim\' appears to change size on every loop ' +
'iteration. Consider preallocating for speed.',
'The variable \'dim\' appears to change size on every loop ' +
'iteration. Consider preallocating for speed.',
'Invalid syntax at \';\'. Possibly, a ), }, or ] is missing.',
'Invalid syntax at \')\'. Possibly, a ), }, or ] is missing.',
'Parse error at \']\': usage might be invalid MATLAB syntax.',
'Terminate statement with semicolon to suppress output ' +
'(in functions).',
'Use of brackets [] is unnecessary. Use parentheses to group, ' +
'if needed.'
]
with execute_bear(self.uut, filename) as result:
for i in range(len(result)):
self.assertEqual(result[i].message, expected_result[i])
Empty file added tests/non_free/__init__.py
Empty file.
63 changes: 63 additions & 0 deletions tests/non_free/mlint_test_files/lengthofline.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
function [len,dims] = lengthofline(hline)
%LENGTHOFLINE Calculates the length of a line object
% LEN = LENGTHOFLINE(HLINE) takes the handle to a line object as the
% input, and returns its length. The accuracy of the result is directly
% dependent on the number of distinct points used to describe the line.
%
% [LEN,DIM] = LENGTHOFLINE(HLINE) additionally tells whether the line is
% 2D or 3D by returning either a numeric 2 or 3 in DIM. A line in a
% plane parallel to a coordinate plane is considered 2D.
%
% If HLINE is a matrix of line handles, LEN and DIM will be matrices of results.
%
% Example:
% figure; h2 = plot3(1:10,rand(1,10),rand(10,5));
% hold on; h1 = plot(1:10,rand(10,5));
% [len,dim] = lengthofline([h1 h2])

% Copyright 1984-2004 The MathWorks, Inc.

% Find input indices that are not line objects
nothandle = ~ishandle(hline);
for nh = 1:prod(size(hline))
notline(nh) = ~ishandle(hline(nh)) || ~strcmp('line',lower(get(hline(nh),'type')));
end

len = zeros(size(hline));
for nl = 1:prod(size(hline))
% If it's a line, get the data and compute the length
if ~notline(nl)
flds = get(hline(nl));
fdata = {'XData','YData','ZData'};
for nd = 1:length(fdata)
data{nd} = getfield(flds,fdata{nd});
end
% If there's no 3rd dimension, or all the data in one dimension is
% unique, then consider it to be a 2D line.
if isempty(data{3}) | ...
(length(unique(data{1}(:)))==1 | ...
length(unique(data{2}(:)))==1 | ...
length(unique(data{3}(:)))==1)
data{3} = zeros(size(data{1}));
dim(nl) = 2;
else
dim(nl) = 3;
end
% Do the actual computation
temp = diff([data{1}(:) data{2}(:) data{3}(;)]);
len(nl) = sum([sqrt(dot(temp',temp'))])
end
end

% If some indices are not lines, fill the results with NaNs.
if any(notline(:))
warning('lengthofline:FillWithNaNs', ...
'\n%s of non-line objects are being filled with %s.', ...
'Lengths','NaNs','Dimensions','NaNs')
len(notline) = NaN;
dim(notline) = NaN;
end

if nargout > 1
dims = dim;
end
66 changes: 66 additions & 0 deletions tests/non_free/mlint_test_files/lengthofline2.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
function [len,dims] = lengthofline2(hline)
%LENGTHOFLINE Calculates the length of a line object
% LEN = LENGTHOFLINE(HLINE) takes the handle to a line object as the
% input, and returns its length. The accuracy of the result is directly
% dependent on the number of distinct points used to describe the line.
%
% [LEN,DIM] = LENGTHOFLINE(HLINE) additionally tells whether the line is
% 2D or 3D by returning either a numeric 2 or 3 in DIM. A line in a
% plane parallel to a coordinate plane is considered 2D.
%
% If HLINE is a matrix of line handles, LEN and DIM will be matrices of results.
%
% Example:
% figure; h2 = plot3(1:10,rand(1,10),rand(10,5));
% hold on; h1 = plot(1:10,rand(10,5));
% [len,dim] = lengthofline([h1 h2])

% Copyright 1984-2005 The MathWorks, Inc.

% Find input indices that are not line objects
nothandle = ~ishandle(hline);
notline = false(size(hline));
for nh = 1:numel(hline)
notline(nh) = nothandle(nh) || ~strcmpi('line',get(hline(nh),'type'));
end

len = zeros(size(hline));
dim = len;
for nl = 1:numel(hline)
% If it's a line, get the data and compute the length
if ~notline(nl)
flds = get(hline(nl));
fdata = {'XData','YData','ZData'};
data = cell(size(fdata));
for nd = 1:length(fdata)
data{nd} = flds.(fdata{nd});
end
% If there's no 3rd dimension, or all the data in one dimension is
% unique, then consider it to be a 2D line.
if isempty(data{3}) || ...
(length(unique(data{1}(:)))==1 || ...
length(unique(data{2}(:)))==1 || ...
length(unique(data{3}(:)))==1)
data{3} = zeros(size(data{1}));
dim(nl) = 2;
else
dim(nl) = 3;
end
% Do the actual computation
temp = diff([data{1}(:) data{2}(:) data{3}(:)]);
len(nl) = sum(sqrt(dot(temp',temp'))) %#ok<NOPRT>
end
end

% If some indices are not lines, fill the results with NaNs.
if any(notline(:))
warning('lengthofline2:FillWithNaNs', ...
'\n%s of non-line objects are being filled with %s.', ...
'Lengths','NaNs','Dimensions','NaNs')
len(notline) = NaN;
dim(notline) = NaN;
end

if nargout > 1
dims = dim;
end