Sun, 25 Apr 2021 16:35:17 +0200
Upgraded embedded Radon library to version 4.5.0.
--- a/ChangeLog Wed Dec 30 11:02:04 2020 +0100 +++ b/ChangeLog Sun Apr 25 16:35:17 2021 +0200 @@ -1,5 +1,8 @@ ChangeLog --------- +Version 3.1.0: +- upgraded embedded Radon library to version 4.5.0 + Version 3.0.2: - bug fixes
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PluginMetricsRadon.epj Sun Apr 25 16:35:17 2021 +0200 @@ -0,0 +1,250 @@ +{ + "header": { + "comment": "eric project file for project PluginMetricsRadon", + "copyright": "Copyright (C) 2021 Detlev Offenbach, detlev@die-offenbachs.de" + }, + "project": { + "AUTHOR": "Detlev Offenbach", + "CHECKERSPARMS": { + "Pep8Checker": { + "AnnotationsChecker": { + "MaximumComplexity": 3, + "MinimumCoverage": 75 + }, + "BlankLines": [ + 2, + 1 + ], + "BuiltinsChecker": { + "bytes": [ + "unicode" + ], + "chr": [ + "unichr" + ], + "str": [ + "unicode" + ] + }, + "CommentedCodeChecker": { + "Aggressive": false + }, + "CopyrightAuthor": "", + "CopyrightMinFileSize": 0, + "DocstringType": "eric", + "EnabledCheckerCategories": "C, D, E, M, N, S, W", + "ExcludeFiles": "*/Ui_*.py, */*_rc.py,*/radon/*", + "ExcludeMessages": "C101,E265,E266,E305,E402,M811,N802,N803,N807,N808,N821,W293,M201,W504", + "FixCodes": "", + "FixIssues": false, + "FutureChecker": "", + "HangClosing": false, + "IncludeMessages": "", + "LineComplexity": 20, + "LineComplexityScore": 10, + "MaxCodeComplexity": 10, + "MaxDocLineLength": 79, + "MaxLineLength": 79, + "NoFixCodes": "E501", + "RepeatMessages": true, + "SecurityChecker": { + "CheckTypedException": false, + "HardcodedTmpDirectories": [ + "/tmp", + "/var/tmp", + "/dev/shm", + "~/tmp" + ], + "InsecureHashes": [ + "md4", + "md5", + "sha", + "sha1" + ], + "InsecureSslProtocolVersions": [ + "PROTOCOL_SSLv2", + "SSLv2_METHOD", + "SSLv23_METHOD", + "PROTOCOL_SSLv3", + "PROTOCOL_TLSv1", + "SSLv3_METHOD", + "TLSv1_METHOD" + ], + "WeakKeySizeDsaHigh": "1024", + "WeakKeySizeDsaMedium": "2048", + "WeakKeySizeEcHigh": "160", + "WeakKeySizeEcMedium": "224", + "WeakKeySizeRsaHigh": "1024", + "WeakKeySizeRsaMedium": "2048" + }, + "ShowIgnored": false, + "ValidEncodings": "latin-1, utf-8" + } + }, + "DESCRIPTION": "This plugin implements dialogs for various code metrics determined using the radon library.", + "DOCSTRING": "", + "DOCUMENTATIONPARMS": { + "ERIC4DOC": { + "cssFile": "%PYTHON%/eric6/CSSs/default.css", + "ignoreDirectories": [ + ".hg", + ".ropeproject", + "_ropeproject", + ".eric6project", + "_eric6project", + "radon" + ], + "ignoreFilePatterns": [ + "Ui_*.py" + ], + "outputDirectory": "RadonMetrics/Documentation/source", + "qtHelpEnabled": false, + "useRecursion": true + } + }, + "EMAIL": "detlev@die-offenbachs.de", + "EOL": 1, + "FILETYPES": { + "*.idl": "INTERFACES", + "*.py": "SOURCES", + "*.py3": "SOURCES", + "*.pyw": "SOURCES", + "*.pyw3": "SOURCES", + "*.qm": "TRANSLATIONS", + "*.qrc": "RESOURCES", + "*.ts": "TRANSLATIONS", + "*.ui": "FORMS", + "Ui_*.py": "__IGNORE__" + }, + "FORMS": [ + "RadonMetrics/CyclomaticComplexityDialog.ui", + "RadonMetrics/MaintainabilityIndexDialog.ui", + "RadonMetrics/RawMetricsDialog.ui" + ], + "HASH": "662232fb888fb1b426cf967a2c238f4dd2f58f08", + "IDLPARAMS": { + "DefinedNames": [], + "IncludeDirs": [], + "UndefinedNames": [] + }, + "INTERFACES": [], + "LEXERASSOCS": {}, + "MAINSCRIPT": "PluginMetricsRadon.py", + "MAKEPARAMS": { + "MakeEnabled": false, + "MakeExecutable": "", + "MakeFile": "", + "MakeParameters": "", + "MakeTarget": "", + "MakeTestOnly": true + }, + "MIXEDLANGUAGE": false, + "OTHERS": [ + ".hgignore", + "ChangeLog", + "PKGLIST", + "PluginMetricsRadon.e4p", + "PluginMetricsRadon.zip", + "RadonMetrics/Documentation/LICENSE.GPL3", + "RadonMetrics/Documentation/source", + "RadonMetrics/radon/LICENSE", + "PluginMetricsRadon.epj" + ], + "OTHERTOOLSPARMS": { + "RadonCodeMetrics": { + "ExcludeFiles": "*/radon/*", + "MinimumRank": "C" + } + }, + "PACKAGERSPARMS": {}, + "PROGLANGUAGE": "Python3", + "PROJECTTYPE": "E6Plugin", + "PROJECTTYPESPECIFICDATA": {}, + "PROTOCOLS": [], + "RCCPARAMS": { + "CompressLevel": 0, + "CompressionDisable": false, + "CompressionThreshold": 70, + "PathPrefix": "" + }, + "RESOURCES": [], + "SOURCES": [ + "PluginMetricsRadon.py", + "RadonMetrics/CodeMetricsCalculator.py", + "RadonMetrics/CyclomaticComplexityCalculator.py", + "RadonMetrics/CyclomaticComplexityDialog.py", + "RadonMetrics/MaintainabilityIndexCalculator.py", + "RadonMetrics/MaintainabilityIndexDialog.py", + "RadonMetrics/RawMetricsDialog.py", + "RadonMetrics/__init__.py", + "RadonMetrics/radon/__init__.py", + "RadonMetrics/radon/complexity.py", + "RadonMetrics/radon/metrics.py", + "RadonMetrics/radon/raw.py", + "RadonMetrics/radon/visitors.py", + "__init__.py" + ], + "SPELLEXCLUDES": "", + "SPELLLANGUAGE": "en_US", + "SPELLWORDS": "", + "TRANSLATIONEXCEPTIONS": [], + "TRANSLATIONPATTERN": "RadonMetrics/i18n/radon_%language%.ts", + "TRANSLATIONS": [ + "RadonMetrics/i18n/radon_de.qm", + "RadonMetrics/i18n/radon_de.ts", + "RadonMetrics/i18n/radon_en.qm", + "RadonMetrics/i18n/radon_en.ts", + "RadonMetrics/i18n/radon_es.qm", + "RadonMetrics/i18n/radon_es.ts", + "RadonMetrics/i18n/radon_ru.qm", + "RadonMetrics/i18n/radon_ru.ts" + ], + "TRANSLATIONSBINPATH": "", + "UICPARAMS": { + "Package": "", + "PackagesRoot": "", + "RcSuffix": "" + }, + "VCS": "Mercurial", + "VCSOPTIONS": { + "add": [ + "" + ], + "checkout": [ + "" + ], + "commit": [ + "" + ], + "diff": [ + "" + ], + "export": [ + "" + ], + "global": [ + "" + ], + "history": [ + "" + ], + "log": [ + "" + ], + "remove": [ + "" + ], + "status": [ + "" + ], + "tag": [ + "" + ], + "update": [ + "" + ] + }, + "VCSOTHERDATA": {}, + "VERSION": "0.x" + } +} \ No newline at end of file
--- a/RadonMetrics/radon/__init__.py Wed Dec 30 11:02:04 2020 +0100 +++ b/RadonMetrics/radon/__init__.py Sun Apr 25 16:35:17 2021 +0200 @@ -1,7 +1,7 @@ '''This module contains the main() function, which is the entry point for the command line interface.''' -__version__ = '3.0.1' +__version__ = '4.5.0' def main():
--- a/RadonMetrics/radon/complexity.py Wed Dec 30 11:02:04 2020 +0100 +++ b/RadonMetrics/radon/complexity.py Sun Apr 25 16:35:17 2021 +0200 @@ -2,17 +2,10 @@ Cyclomatic Complexity ''' -# -# Patched for use within eric6 -# Removed the Flake8Checker related code -# -# Modifications Copyright (c) 2018 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> -# +import math -import math from radon.visitors import GET_COMPLEXITY, ComplexityVisitor, code2ast - # sorted_block ordering functions SCORE = lambda block: -GET_COMPLEXITY(block) LINES = lambda block: block.lineno @@ -46,7 +39,9 @@ ''' if cc < 0: raise ValueError('Complexity must be a non-negative value') - return chr(min(int(math.ceil(cc / 10.) or 1) - (1, 0)[5 - cc < 0], 5) + 65) + return chr( + min(int(math.ceil(cc / 10.0) or 1) - (1, 0)[5 - cc < 0], 5) + 65 + ) def average_complexity(blocks): @@ -58,7 +53,7 @@ size = len(blocks) if size == 0: return 0 - return sum((GET_COMPLEXITY(block) for block in blocks), .0) / len(blocks) + return sum((GET_COMPLEXITY(block) for block in blocks), 0.0) / len(blocks) def sorted_results(blocks, order=SCORE):
--- a/RadonMetrics/radon/metrics.py Wed Dec 30 11:02:04 2020 +0100 +++ b/RadonMetrics/radon/metrics.py Sun Apr 25 16:35:17 2021 +0200 @@ -3,16 +3,19 @@ ''' import ast +import collections import math -import collections -from radon.visitors import HalsteadVisitor, ComplexityVisitor + from radon.raw import analyze - +from radon.visitors import ComplexityVisitor, HalsteadVisitor # Halstead metrics -HalsteadReport = collections.namedtuple('HalsteadReport', 'h1 h2 N1 N2 vocabulary length ' - 'calculated_length volume ' - 'difficulty effort time bugs') +HalsteadReport = collections.namedtuple( + 'HalsteadReport', + 'h1 h2 N1 N2 vocabulary length ' + 'calculated_length volume ' + 'difficulty effort time bugs', +) # `total` is a HalsteadReport for the entire scanned file, while `functions` is # a list of `HalsteadReport`s for each function in the file. @@ -55,7 +58,10 @@ ''' visitor = HalsteadVisitor.from_ast(ast_node) total = halstead_visitor_report(visitor) - functions = [(v.context, halstead_visitor_report(v)) for v in visitor.function_visitors] + functions = [ + (v.context, halstead_visitor_report(v)) + for v in visitor.function_visitors + ] return Halstead(total, functions) @@ -74,8 +80,18 @@ difficulty = (h1 * N2) / float(2 * h2) if h2 != 0 else 0 effort = difficulty * volume return HalsteadReport( - h1, h2, N1, N2, h, N, length, volume, difficulty, effort, - effort / 18., volume / 3000. + h1, + h2, + N1, + N2, + h, + N, + length, + volume, + difficulty, + effort, + effort / 18.0, + volume / 3000.0, ) @@ -86,14 +102,19 @@ is preferred. ''' if any(metric <= 0 for metric in (halstead_volume, sloc)): - return 100. + return 100.0 sloc_scale = math.log(sloc) volume_scale = math.log(halstead_volume) comments_scale = math.sqrt(2.46 * math.radians(comments)) # Non-normalized MI - nn_mi = (171 - 5.2 * volume_scale - .23 * complexity - 16.2 * sloc_scale + - 50 * math.sin(comments_scale)) - return min(max(0., nn_mi * 100 / 171.), 100.) + nn_mi = ( + 171 + - 5.2 * volume_scale + - 0.23 * complexity + - 16.2 * sloc_scale + + 50 * math.sin(comments_scale) + ) + return min(max(0.0, nn_mi * 100 / 171.0), 100.0) def mi_parameters(code, count_multi=True): @@ -113,9 +134,12 @@ raw = analyze(code) comments_lines = raw.comments + (raw.multi if count_multi else 0) comments = comments_lines / float(raw.sloc) * 100 if raw.sloc != 0 else 0 - return (h_visit_ast(ast_node).total.volume, - ComplexityVisitor.from_ast(ast_node).total_complexity, raw.lloc, - comments) + return ( + h_visit_ast(ast_node).total.volume, + ComplexityVisitor.from_ast(ast_node).total_complexity, + raw.lloc, + comments, + ) def mi_visit(code, multi):
--- a/RadonMetrics/radon/raw.py Wed Dec 30 11:02:04 2020 +0100 +++ b/RadonMetrics/radon/raw.py Sun Apr 25 16:35:17 2021 +0200 @@ -4,17 +4,30 @@ that is used. ''' -import tokenize +import collections import operator -import collections +import tokenize + try: import StringIO as io except ImportError: # pragma: no cover import io -__all__ = ['OP', 'COMMENT', 'TOKEN_NUMBER', 'NL', 'NEWLINE', 'EM', 'Module', - '_generate', '_fewer_tokens', '_find', '_logical', 'analyze'] +__all__ = [ + 'OP', + 'COMMENT', + 'TOKEN_NUMBER', + 'NL', + 'NEWLINE', + 'EM', + 'Module', + '_generate', + '_fewer_tokens', + '_find', + '_logical', + 'analyze', +] COMMENT = tokenize.COMMENT OP = tokenize.OP @@ -32,9 +45,10 @@ # multi = Multi-line strings (assumed to be docstrings) # blank = Blank lines (or whitespace-only lines) # single_comments = Single-line comments or docstrings -Module = collections.namedtuple('Module', ['loc', 'lloc', 'sloc', - 'comments', 'multi', 'blank', - 'single_comments']) +Module = collections.namedtuple( + 'Module', + ['loc', 'lloc', 'sloc', 'comments', 'multi', 'blank', 'single_comments'], +) def _generate(code): @@ -133,6 +147,7 @@ if cond: return 0 # Only a comment -> 2 ''' + def aux(sub_tokens): '''The actual function which does the job.''' # Get the tokens and, in the meantime, remove comments @@ -154,6 +169,7 @@ if not list(_fewer_tokens(processed, [NL, NEWLINE, EM])): return 0 return 1 + return sum(aux(sub) for sub in _split_tokens(tokens, OP, ';')) @@ -161,9 +177,9 @@ '''Is this a single token matching token_number followed by ENDMARKER, NL or NEWLINE tokens. ''' - return (TOKEN_NUMBER(tokens[0]) == token_number and - all(TOKEN_NUMBER(t) in (EM, NL, NEWLINE) - for t in tokens[1:])) + return TOKEN_NUMBER(tokens[0]) == token_number and all( + TOKEN_NUMBER(t) in (EM, NL, NEWLINE) for t in tokens[1:] + ) def analyze(source): @@ -197,8 +213,9 @@ lineno += len(parsed_lines) - comments += sum(1 for t in tokens - if TOKEN_NUMBER(t) == tokenize.COMMENT) + comments += sum( + 1 for t in tokens if TOKEN_NUMBER(t) == tokenize.COMMENT + ) # Identify single line comments, conservatively if is_single_token(tokenize.COMMENT, tokens):
--- a/RadonMetrics/radon/visitors.py Wed Dec 30 11:02:04 2020 +0100 +++ b/RadonMetrics/radon/visitors.py Sun Apr 25 16:35:17 2021 +0200 @@ -3,9 +3,8 @@ HalsteadVisitor, that counts Halstead metrics.''' import ast +import collections import operator -import collections - # Helper functions to use in combination with map() GET_COMPLEXITY = operator.attrgetter('complexity') @@ -13,14 +12,31 @@ NAMES_GETTER = operator.attrgetter('name', 'asname') GET_ENDLINE = operator.attrgetter('endline') -BaseFunc = collections.namedtuple('Function', ['name', 'lineno', 'col_offset', - 'endline', 'is_method', - 'classname', 'closures', - 'complexity']) -BaseClass = collections.namedtuple('Class', ['name', 'lineno', 'col_offset', - 'endline', 'methods', - 'inner_classes', - 'real_complexity']) +BaseFunc = collections.namedtuple( + 'Function', + [ + 'name', + 'lineno', + 'col_offset', + 'endline', + 'is_method', + 'classname', + 'closures', + 'complexity', + ], +) +BaseClass = collections.namedtuple( + 'Class', + [ + 'name', + 'lineno', + 'col_offset', + 'endline', + 'methods', + 'inner_classes', + 'real_complexity', + ], +) def code2ast(source): @@ -55,11 +71,14 @@ def __str__(self): '''String representation of a function block.''' - return '{0} {1}:{2}->{3} {4} - {5}'.format(self.letter, self.lineno, - self.col_offset, - self.endline, - self.fullname, - self.complexity) + return '{0} {1}:{2}->{3} {4} - {5}'.format( + self.letter, + self.lineno, + self.col_offset, + self.endline, + self.fullname, + self.complexity, + ) class Class(BaseClass): @@ -86,10 +105,14 @@ def __str__(self): '''String representation of a class block.''' - return '{0} {1}:{2}->{3} {4} - {5}'.format(self.letter, self.lineno, - self.col_offset, - self.endline, self.name, - self.complexity) + return '{0} {1}:{2}->{3} {4} - {5}'.format( + self.letter, + self.lineno, + self.col_offset, + self.endline, + self.name, + self.complexity, + ) class CodeVisitor(ast.NodeVisitor): @@ -130,8 +153,9 @@ otherwise to 0. ''' - def __init__(self, to_method=False, classname=None, off=True, - no_assert=False): + def __init__( + self, to_method=False, classname=None, off=True, no_assert=False + ): self.off = off self.complexity = 1 if off else 0 self.functions = [] @@ -163,8 +187,12 @@ '''The total complexity. Computed adding up the visitor complexity, the functions complexity, and the classes complexity. ''' - return (self.complexity + self.functions_complexity + - self.classes_complexity + (not self.off)) + return ( + self.complexity + + self.functions_complexity + + self.classes_complexity + + (not self.off) + ) @property def blocks(self): @@ -247,9 +275,16 @@ # Add general complexity but not closures' complexity, see #68 body_complexity += visitor.complexity - func = Function(node.name, node.lineno, node.col_offset, - max(node.lineno, visitor.max_line), self.to_method, - self.classname, closures, body_complexity) + func = Function( + node.name, + node.lineno, + node.col_offset, + max(node.lineno, visitor.max_line), + self.to_method, + self.classname, + closures, + body_complexity, + ) self.functions.append(func) def visit_ClassDef(self, node): @@ -267,19 +302,28 @@ visitors_max_lines = [node.lineno] inner_classes = [] for child in node.body: - visitor = ComplexityVisitor(True, classname, off=False, - no_assert=self.no_assert) + visitor = ComplexityVisitor( + True, classname, off=False, no_assert=self.no_assert + ) visitor.visit(child) methods.extend(visitor.functions) - body_complexity += (visitor.complexity + - visitor.functions_complexity + - len(visitor.functions)) + body_complexity += ( + visitor.complexity + + visitor.functions_complexity + + len(visitor.functions) + ) visitors_max_lines.append(visitor.max_line) inner_classes.extend(visitor.classes) - cls = Class(classname, node.lineno, node.col_offset, - max(visitors_max_lines + list(map(GET_ENDLINE, methods))), - methods, inner_classes, body_complexity) + cls = Class( + classname, + node.lineno, + node.col_offset, + max(visitors_max_lines + list(map(GET_ENDLINE, methods))), + methods, + inner_classes, + body_complexity, + ) self.classes.append(cls) @@ -288,9 +332,14 @@ Halstead metrics (see :func:`radon.metrics.h_visit`). ''' - types = {ast.Num: 'n', - ast.Name: 'id', - ast.Attribute: 'attr'} + # As of Python 3.8 Num/Str/Bytes/NameConstat + # are now in a common node Constant. + types = { + "Num": "n", + "Name": "id", + "Attribute": "attr", + "Constant": "value", + } def __init__(self, context=None): '''*context* is a string used to keep track the analysis' context.''' @@ -323,6 +372,7 @@ * the operators seen (a sequence) * the operands seen (a sequence) ''' + def aux(self, node): '''Actual function that updates the stats.''' results = meth(self, node) @@ -330,13 +380,18 @@ self.operands += results[1] self.operators_seen.update(results[2]) for operand in results[3]: - new_operand = getattr(operand, - self.types.get(type(operand), ''), - operand) + new_operand = getattr( + operand, self.types.get(type(operand), ''), operand + ) + name = self.get_name(operand) + new_operand = getattr( + operand, self.types.get(name, ""), operand + ) self.operands_seen.add((self.context, new_operand)) # Now dispatch to children super(HalsteadVisitor, self).generic_visit(node) + return aux @dispatch @@ -362,8 +417,12 @@ @dispatch def visit_Compare(self, node): '''A comparison.''' - return (len(node.ops), len(node.comparators) + 1, - map(self.get_name, node.ops), node.comparators + [node.left]) + return ( + len(node.ops), + len(node.comparators) + 1, + map(self.get_name, node.ops), + node.comparators + [node.left], + ) def visit_FunctionDef(self, node): '''When visiting functions, another visitor is created to recursively @@ -386,3 +445,9 @@ # Save the visited function visitor for later reference. self.function_visitors.append(func_visitor) + + def visit_AsyncFunctionDef(self, node): + '''Async functions are similar to standard functions, so treat them as + such. + ''' + self.visit_FunctionDef(node)