eric6/Plugins/CheckerPlugins/CodeStyleChecker/Simplify/SimplifyChecker.py

Fri, 02 Apr 2021 18:13:12 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Fri, 02 Apr 2021 18:13:12 +0200
changeset 8192
e1157bd8b4c2
parent 8191
9125da0c227e
child 8194
b925628bf91f
permissions
-rw-r--r--

Code Style Checker
- continued to implement checkers for potential code simplifications

# -*- coding: utf-8 -*-

# Copyright (c) 2021 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the checker for simplifying Python code.
"""

import ast
import sys

from .SimplifyNodeVisitor import SimplifyNodeVisitor


class SimplifyChecker(object):
    """
    Class implementing a checker for to help simplifying Python code.
    """
    Codes = [
        # Python-specifics
        "Y101", "Y102", "Y103", "Y104", "Y105", "Y106", "Y107", "Y108",
        "Y109", "Y110", "Y111", "Y112", "Y113", "Y114", "Y115", "Y116",
        "Y117", "Y118", "Y119"
        
        # Comparations
    ]
    
    def __init__(self, source, filename, selected, ignored, expected, repeat):
        """
        Constructor
        
        @param source source code to be checked
        @type list of str
        @param filename name of the source file
        @type str
        @param selected list of selected codes
        @type list of str
        @param ignored 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
        """
        self.__select = tuple(selected)
        self.__ignore = ('',) if selected else tuple(ignored)
        self.__expected = expected[:]
        self.__repeat = repeat
        self.__filename = filename
        self.__source = source[:]
        
        # statistics counters
        self.counters = {}
        
        # collection of detected errors
        self.errors = []
        
        self.__checkCodes = (code for code in self.Codes
                             if not self.__ignoreCode(code))
    
    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.startswith(self.__ignore) and
                not code.startswith(self.__select))
    
    def __error(self, lineNumber, offset, code, *args):
        """
        Private method to record an issue.
        
        @param lineNumber line number of the issue
        @type int
        @param offset position within line of the issue
        @type int
        @param code message code
        @type str
        @param args arguments for the message
        @type list
        """
        if self.__ignoreCode(code):
            return
        
        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):
            # record the issue with one based line number
            self.errors.append(
                {
                    "file": self.__filename,
                    "line": lineNumber + 1,
                    "offset": offset,
                    "code": code,
                    "args": args,
                }
            )
    
    def __reportInvalidSyntax(self):
        """
        Private method to report a syntax error.
        """
        exc_type, exc = sys.exc_info()[:2]
        if len(exc.args) > 1:
            offset = exc.args[1]
            if len(offset) > 2:
                offset = offset[1:3]
        else:
            offset = (1, 0)
        self.__error(offset[0] - 1, offset[1] or 0,
                     'M901', exc_type.__name__, exc.args[0])
    
    def __generateTree(self):
        """
        Private method to generate an AST for our source.
        
        @return generated AST
        @rtype ast.AST
        """
        return ast.parse("".join(self.__source), self.__filename)
    
    def run(self):
        """
        Public method to check the given source against functions
        to be replaced by 'pathlib' equivalents.
        """
        if not self.__filename:
            # don't do anything, if essential data is missing
            return
        
        if not self.__checkCodes:
            # don't do anything, if no codes were selected
            return
        
        try:
            self.__tree = self.__generateTree()
        except (SyntaxError, TypeError):
            self.__reportInvalidSyntax()
            return
        
        # Add parent information
        for node in ast.walk(self.__tree):
            for child in ast.iter_child_nodes(node):
                child.parent = node  # type: ignore
        
        visitor = SimplifyNodeVisitor(self.__error)
        visitor.visit(self.__tree)

eric ide

mercurial