def run(self): """ Public method to check the given source for code complexity. """ if not self.__filename or not self.__source: # don't do anything, if essential data is missing return if self.__ignoreCode("C101"): # don't do anything, if this should be ignored return try: tree = compile(''.join(self.__source), self.__filename, 'exec', ast.PyCF_ONLY_AST) except (SyntaxError, TypeError): self.__reportInvalidSyntax() return visitor = PathGraphingAstVisitor() visitor.preorder(tree, visitor) for graph in visitor.graphs.values(): if graph.complexity() > self.__maxComplexity: self.__error(graph.lineno, 0, "C101", graph.entity, graph.complexity())
def run(self): messages = [] for code_file in self._code_files: try: tree = ast.parse(open(code_file, "r").read(), filename=code_file) except (SyntaxError, TypeError): location = Location(path=code_file, module=None, function=None, line=1, character=0) message = Message(source="mccabe", code="MC0000", location=location, message="Could not parse file") messages.append(message) continue visitor = PathGraphingAstVisitor() visitor.preorder(tree, visitor) for graph in visitor.graphs.values(): complexity = graph.complexity() if complexity > self.max_complexity: location = Location( path=code_file, module=None, function=graph.entity, line=graph.lineno, character=0, absolute_path=True, ) message = Message( source="mccabe", code="MC0001", location=location, message="%s is too complex (%s)" % (graph.entity, complexity), ) messages.append(message) return self.filter_messages(messages)
def run(self, found_files): messages = [] for code_file in found_files.iter_module_paths(): try: contents = read_py_file(code_file) tree = ast.parse( contents, filename=code_file, ) except CouldNotHandleEncoding as err: messages.append( make_tool_error_message( code_file, 'mccabe', 'MC0000', message='Could not handle the encoding of this file: %s' % err.encoding)) continue except SyntaxError as err: messages.append( make_tool_error_message(code_file, 'mccabe', 'MC0000', line=err.lineno, character=err.offset, message='Syntax Error')) continue except TypeError: messages.append( make_tool_error_message(code_file, 'mccabe', 'MC0000', message='Unable to parse file')) continue visitor = PathGraphingAstVisitor() visitor.preorder(tree, visitor) for graph in visitor.graphs.values(): complexity = graph.complexity() if complexity > self.max_complexity: location = Location(path=code_file, module=None, function=graph.entity, line=graph.lineno, character=0, absolute_path=True) message = Message( source='mccabe', code='MC0001', location=location, message='%s is too complex (%s)' % ( graph.entity, complexity, ), ) messages.append(message) return self.filter_messages(messages)
def execute(self, finder): issues = [] if CODE in self.config['disabled']: return issues for filepath in finder.files(self.config['filters']): try: tree = parse_python_file(filepath) except (SyntaxError, TypeError) as exc: issues.append(ParseIssue(exc, filepath)) continue except EnvironmentError as exc: issues.append(AccessIssue(exc, filepath)) continue visitor = PathGraphingAstVisitor() visitor.preorder(tree, visitor) for graph in itervalues(visitor.graphs): complexity = graph.complexity() if complexity > self.config['options']['max-complexity']: issues.append( McCabeIssue( 'complex', DESCRIPTION.format( entity=graph.entity, score=complexity, ), filepath, graph.lineno, graph.column + 1, )) return issues
def _cyclomatic(self, filename): with open(filename, 'r') as fp: code = fp.read() tree = compile(code, filename, 'exec', ast.PyCF_ONLY_AST) visitor = PathGraphingAstVisitor() visitor.preorder(tree, visitor) ret = {} for name, graph in visitor.graphs.items(): ret[name] = graph.complexity() return ret
def calcule(self, node): visitor = PathGraphingAstVisitor() visitor.preorder(node, visitor) for graph in visitor.graphs.values(): if graph.complexity() > self.max_complexity: split = graph.entity.split('.') if len(split) == 2: entity, method = split if self.result.has_key(entity): self.result[entity].extend([method]) else: self.result[entity] = [method] return self.result
def run(self): messages = [] for code_file in self._code_files: try: tree = ast.parse( open(code_file, 'r').read(), filename=code_file, ) except (SyntaxError, TypeError): location = Location( path=code_file, module=None, function=None, line=1, character=0, ) message = Message( source='mccabe', code='MC0000', location=location, message='Could not parse file', ) messages.append(message) continue visitor = PathGraphingAstVisitor() visitor.preorder(tree, visitor) for graph in visitor.graphs.values(): complexity = graph.complexity() if complexity > self.max_complexity: location = Location( path=code_file, module=None, function=graph.entity, line=graph.lineno, character=0, ) message = Message( source='mccabe', code='MC0001', location=location, message='%s is too complex (%s)' % ( graph.entity, complexity, ), ) messages.append(message) return self.filter_messages(messages)
def parse_code(source, filepath): """ Parse some code into its constituent Thing objects. Parse a string containing Python source into the various Thing objects that it represents and are interesting to this library. Args: source(str): A string representing an entire block of Python code which should be inspected. filepath(str): The path containing this code or some representation of its location at least. """ # Both the AST and tokens are required, as the AST module does not return regular comments # but the tokenize module does not have quite enough structure information. The asttoken # library is used to annotate AST nodes with location information, which can then be used in # combination with the tokens to attach comments which may have been removed. Additionally, # the mccabe library is then used to annotate nodes with complexity information. tree = compile(source, filepath, "exec", ast.PyCF_ONLY_AST) astok = asttokens.ASTTokens(source, tree=tree) # For the purposes of this library, a particular documentable thing is considered to have coordinates # of the location of the first token making up its node in the AST, which forms the key in this dictionary things = defaultdict(list) # split the AST into the nodes we care about and discard the ones we don't care about for node in ast.walk(astok.tree): if isinstance(node, ast.FunctionDef): coords = (node.lineno, node.col_offset) things[coords].append(MethodOrFunction(filepath, node)) # now attach inline comments because the number of comments should be appropriate for the McCabe complexity # of certain types of node tokens = tokenize.generate_tokens(StringIO(source).readline) for token in tokens: if token[0] == tokenize.COMMENT: for thing_list in things.values(): for thing in thing_list: # allow the Thing object to determine whether to pay attention to this comment or not thing.add_comment(token) # now attach complexity information to them visitor = PathGraphingAstVisitor() visitor.preorder(astok.tree, visitor) for graph in visitor.graphs.values(): complexity = graph.complexity() coords = (graph.lineno, graph.column) for thing in things[coords]: if isinstance(thing, MethodOrFunction): thing.set_complexity(complexity) return [x for t in things.values() for x in t]
def get_mccabe_violations_for_file(filepath, max_complexity): code = _read(filepath) tree = compile(code, filepath, "exec", ast.PyCF_ONLY_AST) visitor = PathGraphingAstVisitor() visitor.preorder(tree, visitor) violations = [] for graph in visitor.graphs.values(): if graph.complexity() >= max_complexity: complex_function_name = graph.entity if complex_function_name.startswith('If '): complex_function_name = 'if __name__ == "__main__"' violations.append(complex_function_name) return violations
def __checkMcCabeComplexity(self): """ Private method to check the McCabe code complexity. """ try: # create the AST again because it is modified by the checker tree = compile(''.join(self.__source), self.__filename, 'exec', ast.PyCF_ONLY_AST) except (SyntaxError, TypeError): # compile errors are already reported by the run() method return maxComplexity = self.__args.get("McCabeComplexity", self.__defaultArgs["McCabeComplexity"]) visitor = PathGraphingAstVisitor() visitor.preorder(tree, visitor) for graph in visitor.graphs.values(): if graph.complexity() > maxComplexity: self.__error(graph.lineno, 0, "C101", graph.entity, graph.complexity())
def get_cyclomatic_complexity(some_funcdef: ast.FunctionDef) -> int: visitor = PathGraphingAstVisitor() visitor.preorder(some_funcdef, visitor) return list(visitor.graphs.values())[0].complexity()
def run(self, found_files): messages = [] for code_file in found_files.iter_module_paths(): try: tree = ast.parse( open(code_file, 'r').read(), filename=code_file, ) except SyntaxError as e: location = Location( path=code_file, module=None, function=None, line=e.lineno, character=e.offset, ) message = Message( source='mccabe', code='MC0000', location=location, message='Syntax error', ) messages.append(message) continue except TypeError: location = Location( path=code_file, module=None, function=None, line=0, character=0, ) message = Message( source='mccabe', code='MC0000', location=location, message='Unable to parse file', ) messages.append(message) continue visitor = PathGraphingAstVisitor() visitor.preorder(tree, visitor) for graph in visitor.graphs.values(): complexity = graph.complexity() if complexity > self.max_complexity: location = Location(path=code_file, module=None, function=graph.entity, line=graph.lineno, character=0, absolute_path=True) message = Message( source='mccabe', code='MC0001', location=location, message='%s is too complex (%s)' % ( graph.entity, complexity, ), ) messages.append(message) return self.filter_messages(messages)
def run(self, found_files): messages = [] for code_file in found_files.iter_module_paths(): try: tree = ast.parse( open(code_file, 'r').read(), filename=code_file, ) except SyntaxError as e: location = Location( path=code_file, module=None, function=None, line=e.lineno, character=e.offset, ) message = Message( source='mccabe', code='MC0000', location=location, message='Syntax error', ) messages.append(message) continue except TypeError: location = Location( path=code_file, module=None, function=None, line=0, character=0, ) message = Message( source='mccabe', code='MC0000', location=location, message='Unable to parse file', ) messages.append(message) continue visitor = PathGraphingAstVisitor() visitor.preorder(tree, visitor) for graph in visitor.graphs.values(): complexity = graph.complexity() if complexity > self.max_complexity: location = Location( path=code_file, module=None, function=graph.entity, line=graph.lineno, character=0, absolute_path=True ) message = Message( source='mccabe', code='MC0001', location=location, message='%s is too complex (%s)' % ( graph.entity, complexity, ), ) messages.append(message) return self.filter_messages(messages)