--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Naming/NamingStyleChecker.py Mon Feb 17 17:09:25 2025 +0100 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Naming/NamingStyleChecker.py Wed Feb 19 15:09:52 2025 +0100 @@ -9,6 +9,7 @@ import ast import collections +import copy import functools import os @@ -18,6 +19,7 @@ ast.AsyncFunctionDef = ast.FunctionDef +# TODO: change this to a checker like the others class NamingStyleChecker: """ Class implementing a checker for naming conventions. @@ -45,20 +47,43 @@ "N831", ] - def __init__(self, tree, filename, options): + def __init__(self, source, filename, tree, select, ignore, expected, repeat, args): """ - Constructor (according to 'extended' pycodestyle.py API) + Constructor - @param tree AST tree of the source file - @type ast.AST + @param source source code to be checked + @type list of str @param filename name of the source file @type str - @param options options as parsed by pycodestyle.StyleGuide - @type optparse.Option + @param tree AST tree of the source code + @type ast.Module + @param select list of selected codes + @type list of str + @param ignore list of codes to be ignored + @type list of str + @param expected list of expected codes + @type list of str + @param repeat flag indicating to report each occurrence of a code + @type bool + @param args dictionary of arguments for the various checks + @type dict """ + self.__select = tuple(select) + self.__ignore = tuple(ignore) + self.__expected = expected[:] + self.__repeat = repeat + self.__filename = filename + self.__source = source[:] + self.__tree = copy.deepcopy(tree) + self.__args = args + self.__parents = collections.deque() - self.__tree = tree - self.__filename = filename + + # statistics counters + self.counters = {} + + # collection of detected errors + self.errors = [] self.__checkersWithCodes = { "classdef": [ @@ -99,14 +124,74 @@ (self.__checkImportAs, ("N811", "N812", "N813", "N814", "N815")), ] - self.__checkers = {} + self.__checkers = collections.defaultdict(list) for key, checkers in self.__checkersWithCodes.items(): for checker, codes in checkers: - if any(not (code and options.ignore_code(code)) for code in codes): - if key not in self.__checkers: - self.__checkers[key] = [] + if any(not (code and self.__ignoreCode(code)) for code in codes): self.__checkers[key].append(checker) + def __ignoreCode(self, code): + """ + Private method to check if the message code should be ignored. + + @param code message code to check for + @type str + @return flag indicating to ignore the given code + @rtype bool + """ + return ( + code in self.__ignore + or (code.startswith(self.__ignore) and not code.startswith(self.__select)) + ) + + def __error(self, node, code): + """ + Private method to build the error information. + + @param node AST node to report an error for + @type ast.AST + @param code error code to report + @type str + """ + if self.__ignoreCode(code): + return + + if isinstance(node, ast.Module): + lineno = 0 + offset = 0 + else: + lineno = node.lineno + offset = node.col_offset + if isinstance(node, ast.ClassDef): + lineno += len(node.decorator_list) + offset += 6 + elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + lineno += len(node.decorator_list) + offset += 4 + + # record the issue with one based line number + errorInfo = { + "file": self.__filename, + "line": lineno, + "offset": offset, + "code": code, + "args": [], + } + + if errorInfo not in self.errors: + # this issue was not seen before + if code in self.counters: + self.counters[code] += 1 + else: + self.counters[code] = 1 + + # Don't care about expected codes + if code in self.__expected: + return + + if code and (self.counters[code] == 1 or self.__repeat): + self.errors.append(errorInfo) + def run(self): """ Public method run by the pycodestyle.py checker. @@ -126,13 +211,11 @@ @param node AST tree node to scan @type ast.AST - @yield tuple giving line number, offset within line and error code - @ytype tuple of (int, int, str) """ - yield from self.__visitNode(node) + self.__visitNode(node) self.__parents.append(node) for child in ast.iter_child_nodes(node): - yield from self.__visitTree(child) + self.__visitTree(child) self.__parents.pop() def __visitNode(self, node): @@ -141,8 +224,6 @@ @param node AST tree node to inspect @type ast.AST - @yield tuple giving line number, offset within line and error code - @ytype tuple of (int, int, str) """ if isinstance(node, ast.ClassDef): self.__tagClassFunctions(node) @@ -152,8 +233,7 @@ checkerName = node.__class__.__name__.lower() if checkerName in self.__checkers: for checker in self.__checkers[checkerName]: - for error in checker(node, self.__parents): - yield error + (self.__checkers[checkerName],) + checker(node, self.__parents) def __tagClassFunctions(self, classNode): """ @@ -233,31 +313,6 @@ kwOnly = [arg.arg for arg in node.args.kwonlyargs] return posArgs + kwOnly - def __error(self, node, code): - """ - Private method to build the error information. - - @param node AST node to report an error for - @type ast.AST - @param code error code to report - @type str - @return tuple giving line number, offset within line and error code - @rtype tuple of (int, int, str) - """ - if isinstance(node, ast.Module): - lineno = 0 - offset = 0 - else: - lineno = node.lineno - offset = node.col_offset - if isinstance(node, ast.ClassDef): - lineno += len(node.decorator_list) - offset += 6 - elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): - lineno += len(node.decorator_list) - offset += 4 - return (lineno, offset, code) - def __isNameToBeAvoided(self, name): """ Private method to check, if the given name should be avoided. @@ -277,19 +332,17 @@ @type ast.Ast @param _parents list of parent nodes (unused) @type list of ast.AST - @yield tuple giving line number, offset within line and error code - @ytype tuple of (int, int, str) """ if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): name = node.name if self.__isNameToBeAvoided(name): - yield self.__error(node, "N831") + self.__error(node, "N831") elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): argNames = self.__getArgNames(node) for arg in argNames: if self.__isNameToBeAvoided(arg): - yield self.__error(node, "N831") + self.__error(node, "N831") elif isinstance(node, (ast.Assign, ast.NamedExpr, ast.AnnAssign)): if isinstance(node, ast.Assign): @@ -300,14 +353,14 @@ if isinstance(target, ast.Name): name = target.id if bool(name) and self.__isNameToBeAvoided(name): - yield self.__error(node, "N831") + self.__error(node, "N831") elif isinstance(target, (ast.Tuple, ast.List)): for element in target.elts: if isinstance(element, ast.Name): name = element.id if bool(name) and self.__isNameToBeAvoided(name): - yield self.__error(node, "N831") + self.__error(node, "N831") def __getClassdef(self, name, parents): """ @@ -366,17 +419,15 @@ @type ast.ClassDef @param parents list of parent nodes @type list of ast.AST - @yield tuple giving line number, offset within line and error code - @ytype tuple of (int, int, str) """ name = node.name strippedName = name.strip("_") if not strippedName[:1].isupper() or "_" in strippedName: - yield self.__error(node, "N801") + self.__error(node, "N801") superClasses = self.__superClassNames(name, parents) if "Exception" in superClasses and not name.endswith("Error"): - yield self.__error(node, "N818") + self.__error(node, "N818") def __checkFunctionName(self, node, _parents): """ @@ -393,8 +444,6 @@ @type ast.FunctionDef or ast.AsynFunctionDef @param _parents list of parent nodes (unused) @type list of ast.AST - @yield tuple giving line number, offset within line and error code - @ytype tuple of (int, int, str) """ functionType = getattr(node, "function_type", "function") name = node.name @@ -403,9 +452,9 @@ return if name.lower() != name: - yield self.__error(node, "N802") + self.__error(node, "N802") if functionType == "function" and name[:2] == "__" and name[-2:] == "__": - yield self.__error(node, "N809") + self.__error(node, "N809") def __checkFunctionArgumentNames(self, node, _parents): """ @@ -420,18 +469,16 @@ @type ast.FunctionDef or ast.AsynFunctionDef @param _parents list of parent nodes (unused) @type list of ast.AST - @yield tuple giving line number, offset within line and error code - @ytype tuple of (int, int, str) """ if node.args.kwarg is not None: kwarg = node.args.kwarg.arg if kwarg.lower() != kwarg: - yield self.__error(node, "N803") + self.__error(node, "N803") elif node.args.vararg is not None: vararg = node.args.vararg.arg if vararg.lower() != vararg: - yield self.__error(node, "N803") + self.__error(node, "N803") else: argNames = self.__getArgNames(node) @@ -439,19 +486,19 @@ if not argNames: if functionType == "method": - yield self.__error(node, "N805") + self.__error(node, "N805") elif functionType == "classmethod": - yield self.__error(node, "N804") + self.__error(node, "N804") elif functionType == "method" and argNames[0] != "self": - yield self.__error(node, "N805") + self.__error(node, "N805") elif functionType == "classmethod" and argNames[0] != "cls": - yield self.__error(node, "N804") + self.__error(node, "N804") elif functionType == "staticmethod" and argNames[0] in ("cls", "self"): - yield self.__error(node, "N806") + self.__error(node, "N806") for arg in argNames: if arg.lower() != arg: - yield self.__error(node, "N803") + self.__error(node, "N803") break def __checkVariableNames(self, node, parents): @@ -465,35 +512,33 @@ @type ast.AST @param parents list of parent nodes @type list of ast.AST - @yield tuple giving line number, offset within line and error code - @ytype tuple of (int, int, str) """ nodeType = type(node) if nodeType is ast.Assign: if self.__isNamedTupel(node.value): return for target in node.targets: - yield from self.__findVariableNameErrors(target, parents) + self.__findVariableNameErrors(target, parents) elif nodeType in (ast.NamedExpr, ast.AnnAssign): if self.__isNamedTupel(node.value): return - yield from self.__findVariableNameErrors(node.target, parents) + self.__findVariableNameErrors(node.target, parents) elif nodeType in (ast.With, ast.AsyncWith): for item in node.items: - yield from self.__findVariableNameErrors(item.optional_vars, parents) + self.__findVariableNameErrors(item.optional_vars, parents) elif nodeType in (ast.For, ast.AsyncFor): - yield from self.__findVariableNameErrors(node.target, parents) + self.__findVariableNameErrors(node.target, parents) elif nodeType is ast.ExceptHandler: if node.name: - yield from self.__findVariableNameErrors(node, parents) + self.__findVariableNameErrors(node, parents) elif nodeType in (ast.GeneratorExp, ast.ListComp, ast.DictComp, ast.SetComp): for gen in node.generators: - yield from self.__findVariableNameErrors(gen.target, parents) + self.__findVariableNameErrors(gen.target, parents) def __findVariableNameErrors(self, assignmentTarget, parents): """ @@ -503,8 +548,6 @@ @type ast.Name, ast.Tuple, ast.List or ast.ExceptHandler @param parents list of parent nodes @type ast.AST - @yield tuple giving line number, offset within line and error code - @ytype tuple of (int, int, str) """ for parentFunc in reversed(parents): if isinstance(parentFunc, ast.ClassDef): @@ -518,7 +561,7 @@ for name in self.__extractNames(assignmentTarget): errorCode = checker(name) if errorCode: - yield self.__error(assignmentTarget, errorCode) + self.__error(assignmentTarget, errorCode) def __extractNames(self, assignmentTarget): """ @@ -629,19 +672,17 @@ @type ast.AST @param _parents list of parent nodes (unused) @type list of ast.AST - @yield tuple giving line number, offset within line and error code - @ytype tuple of (int, int, str) """ if self.__filename: moduleName = os.path.splitext(os.path.basename(self.__filename))[0] if moduleName.lower() != moduleName: - yield self.__error(node, "N807") + self.__error(node, "N807") if moduleName == "__init__": # we got a package packageName = os.path.split(os.path.dirname(self.__filename))[1] if packageName.lower() != packageName: - yield self.__error(node, "N808") + self.__error(node, "N808") def __checkImportAs(self, node, _parents): """ @@ -652,8 +693,6 @@ @type ast.Import @param _parents list of parent nodes (unused) @type list of ast.AST - @yield tuple giving line number, offset within line and error code - @ytype tuple of (int, int, str) """ for name in node.names: asname = name.asname @@ -663,14 +702,14 @@ originalName = name.name if originalName.isupper(): if not asname.isupper(): - yield self.__error(node, "N811") + self.__error(node, "N811") elif originalName.islower(): if asname.lower() != asname: - yield self.__error(node, "N812") + self.__error(node, "N812") elif asname.islower(): - yield self.__error(node, "N813") + self.__error(node, "N813") elif asname.isupper(): if "".join(filter(str.isupper, originalName)) == asname: - yield self.__error(node, "N815") + self.__error(node, "N815") else: - yield self.__error(node, "N814") + self.__error(node, "N814")