diff --git a/bears/non_free/MlintBear.py b/bears/non_free/MlintBear.py new file mode 100644 index 0000000000..5ff6ba2939 --- /dev/null +++ b/bears/non_free/MlintBear.py @@ -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\d+) \(C (?P\d+)' + r'(?:-(?P\d+))*\): (?P.*)') +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 = {'coala-devel@googlegroups.com'} + 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,) diff --git a/bears/non_free/__init__.py b/bears/non_free/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/setup.cfg b/setup.cfg index d196468ecf..f29b84d921 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,6 +30,7 @@ omit = tests/* .ci/* setup.py + bears/non_free/* [coverage:report] show_missing = True diff --git a/tests/non_free/MlintBearTest.py b/tests/non_free/MlintBearTest.py new file mode 100644 index 0000000000..13fd249f66 --- /dev/null +++ b/tests/non_free/MlintBearTest.py @@ -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]) diff --git a/tests/non_free/__init__.py b/tests/non_free/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/non_free/mlint_test_files/lengthofline.m b/tests/non_free/mlint_test_files/lengthofline.m new file mode 100644 index 0000000000..4ed4d1027e --- /dev/null +++ b/tests/non_free/mlint_test_files/lengthofline.m @@ -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 \ No newline at end of file diff --git a/tests/non_free/mlint_test_files/lengthofline2.m b/tests/non_free/mlint_test_files/lengthofline2.m new file mode 100644 index 0000000000..53466a1ad1 --- /dev/null +++ b/tests/non_free/mlint_test_files/lengthofline2.m @@ -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 + 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 \ No newline at end of file