src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Naming/NamingStyleChecker.py

branch
eric7
changeset 11142
2f0fb22c1d63
parent 11090
f5f5f5803935
child 11145
d328a7b74fd8
--- 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")

eric ide

mercurial