src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/SysVersionVisitor.py

Thu, 27 Feb 2025 14:42:39 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 27 Feb 2025 14:42:39 +0100
branch
eric7
changeset 11150
73d80859079c
permissions
-rw-r--r--

Code Style Checkers
- Refactored the various code style checkers for better maintainability.

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

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

"""
Module implementing a node visitor to check the use of sys.version and sys.version_info.
"""

import ast

import AstUtilities


class SysVersionVisitor(ast.NodeVisitor):
    """
    Class implementing a node visitor to check the use of sys.version and
    sys.version_info.

    Note: This class is modeled after flake8-2020 v1.8.1.
    """

    def __init__(self):
        """
        Constructor
        """
        super().__init__()

        self.violations = []
        self.__fromImports = {}

    def visit_ImportFrom(self, node):
        """
        Public method to handle a from ... import ... statement.

        @param node reference to the node to be processed
        @type ast.ImportFrom
        """
        for alias in node.names:
            if node.module is not None and not alias.asname:
                self.__fromImports[alias.name] = node.module

        self.generic_visit(node)

    def __isSys(self, attr, node):
        """
        Private method to check for a reference to sys attribute.

        @param attr attribute name
        @type str
        @param node reference to the node to be checked
        @type ast.Node
        @return flag indicating a match
        @rtype bool
        """
        match = False
        if (
            isinstance(node, ast.Attribute)
            and isinstance(node.value, ast.Name)
            and node.value.id == "sys"
            and node.attr == attr
        ) or (
            isinstance(node, ast.Name)
            and node.id == attr
            and self.__fromImports.get(node.id) == "sys"
        ):
            match = True

        return match

    def __isSysVersionUpperSlice(self, node, n):
        """
        Private method to check the upper slice of sys.version.

        @param node reference to the node to be checked
        @type ast.Node
        @param n slice value to check against
        @type int
        @return flag indicating a match
        @rtype bool
        """
        return (
            self.__isSys("version", node.value)
            and isinstance(node.slice, ast.Slice)
            and node.slice.lower is None
            and AstUtilities.isNumber(node.slice.upper)
            and AstUtilities.getValue(node.slice.upper) == n
            and node.slice.step is None
        )

    def visit_Subscript(self, node):
        """
        Public method to handle a subscript.

        @param node reference to the node to be processed
        @type ast.Subscript
        """
        if self.__isSysVersionUpperSlice(node, 1):
            self.violations.append((node.value, "M-423"))
        elif self.__isSysVersionUpperSlice(node, 3):
            self.violations.append((node.value, "M-401"))
        elif (
            self.__isSys("version", node.value)
            and isinstance(node.slice, ast.Index)
            and AstUtilities.isNumber(node.slice.value)
            and AstUtilities.getValue(node.slice.value) == 2
        ):
            self.violations.append((node.value, "M-402"))
        elif (
            self.__isSys("version", node.value)
            and isinstance(node.slice, ast.Index)
            and AstUtilities.isNumber(node.slice.value)
            and AstUtilities.getValue(node.slice.value) == 0
        ):
            self.violations.append((node.value, "M-421"))

        self.generic_visit(node)

    def visit_Compare(self, node):
        """
        Public method to handle a comparison.

        @param node reference to the node to be processed
        @type ast.Compare
        """
        if (
            isinstance(node.left, ast.Subscript)
            and self.__isSys("version_info", node.left.value)
            and isinstance(node.left.slice, ast.Index)
            and AstUtilities.isNumber(node.left.slice.value)
            and AstUtilities.getValue(node.left.slice.value) == 0
            and len(node.ops) == 1
            and isinstance(node.ops[0], ast.Eq)
            and AstUtilities.isNumber(node.comparators[0])
            and AstUtilities.getValue(node.comparators[0]) == 3
        ):
            self.violations.append((node.left, "M-411"))
        elif (
            self.__isSys("version", node.left)
            and len(node.ops) == 1
            and isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE))
            and AstUtilities.isString(node.comparators[0])
        ):
            if len(AstUtilities.getValue(node.comparators[0])) == 1:
                errorCode = "M-422"
            else:
                errorCode = "M-403"
            self.violations.append((node.left, errorCode))
        elif (
            isinstance(node.left, ast.Subscript)
            and self.__isSys("version_info", node.left.value)
            and isinstance(node.left.slice, ast.Index)
            and AstUtilities.isNumber(node.left.slice.value)
            and AstUtilities.getValue(node.left.slice.value) == 1
            and len(node.ops) == 1
            and isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE))
            and AstUtilities.isNumber(node.comparators[0])
        ):
            self.violations.append((node, "M-413"))
        elif (
            isinstance(node.left, ast.Attribute)
            and self.__isSys("version_info", node.left.value)
            and node.left.attr == "minor"
            and len(node.ops) == 1
            and isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE))
            and AstUtilities.isNumber(node.comparators[0])
        ):
            self.violations.append((node, "M-414"))

        self.generic_visit(node)

    def visit_Attribute(self, node):
        """
        Public method to handle an attribute.

        @param node reference to the node to be processed
        @type ast.Attribute
        """
        if (
            isinstance(node.value, ast.Name)
            and node.value.id == "six"
            and node.attr == "PY3"
        ):
            self.violations.append((node, "M-412"))

        self.generic_visit(node)

    def visit_Name(self, node):
        """
        Public method to handle an name.

        @param node reference to the node to be processed
        @type ast.Name
        """
        if node.id == "PY3" and self.__fromImports.get(node.id) == "six":
            self.violations.append((node, "M-412"))

        self.generic_visit(node)

eric ide

mercurial