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

branch
eric7
changeset 11150
73d80859079c
equal deleted inserted replaced
11149:fc45672fae42 11150:73d80859079c
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2025 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a node visitor to check the use of sys.version and sys.version_info.
8 """
9
10 import ast
11
12 import AstUtilities
13
14
15 class SysVersionVisitor(ast.NodeVisitor):
16 """
17 Class implementing a node visitor to check the use of sys.version and
18 sys.version_info.
19
20 Note: This class is modeled after flake8-2020 v1.8.1.
21 """
22
23 def __init__(self):
24 """
25 Constructor
26 """
27 super().__init__()
28
29 self.violations = []
30 self.__fromImports = {}
31
32 def visit_ImportFrom(self, node):
33 """
34 Public method to handle a from ... import ... statement.
35
36 @param node reference to the node to be processed
37 @type ast.ImportFrom
38 """
39 for alias in node.names:
40 if node.module is not None and not alias.asname:
41 self.__fromImports[alias.name] = node.module
42
43 self.generic_visit(node)
44
45 def __isSys(self, attr, node):
46 """
47 Private method to check for a reference to sys attribute.
48
49 @param attr attribute name
50 @type str
51 @param node reference to the node to be checked
52 @type ast.Node
53 @return flag indicating a match
54 @rtype bool
55 """
56 match = False
57 if (
58 isinstance(node, ast.Attribute)
59 and isinstance(node.value, ast.Name)
60 and node.value.id == "sys"
61 and node.attr == attr
62 ) or (
63 isinstance(node, ast.Name)
64 and node.id == attr
65 and self.__fromImports.get(node.id) == "sys"
66 ):
67 match = True
68
69 return match
70
71 def __isSysVersionUpperSlice(self, node, n):
72 """
73 Private method to check the upper slice of sys.version.
74
75 @param node reference to the node to be checked
76 @type ast.Node
77 @param n slice value to check against
78 @type int
79 @return flag indicating a match
80 @rtype bool
81 """
82 return (
83 self.__isSys("version", node.value)
84 and isinstance(node.slice, ast.Slice)
85 and node.slice.lower is None
86 and AstUtilities.isNumber(node.slice.upper)
87 and AstUtilities.getValue(node.slice.upper) == n
88 and node.slice.step is None
89 )
90
91 def visit_Subscript(self, node):
92 """
93 Public method to handle a subscript.
94
95 @param node reference to the node to be processed
96 @type ast.Subscript
97 """
98 if self.__isSysVersionUpperSlice(node, 1):
99 self.violations.append((node.value, "M-423"))
100 elif self.__isSysVersionUpperSlice(node, 3):
101 self.violations.append((node.value, "M-401"))
102 elif (
103 self.__isSys("version", node.value)
104 and isinstance(node.slice, ast.Index)
105 and AstUtilities.isNumber(node.slice.value)
106 and AstUtilities.getValue(node.slice.value) == 2
107 ):
108 self.violations.append((node.value, "M-402"))
109 elif (
110 self.__isSys("version", node.value)
111 and isinstance(node.slice, ast.Index)
112 and AstUtilities.isNumber(node.slice.value)
113 and AstUtilities.getValue(node.slice.value) == 0
114 ):
115 self.violations.append((node.value, "M-421"))
116
117 self.generic_visit(node)
118
119 def visit_Compare(self, node):
120 """
121 Public method to handle a comparison.
122
123 @param node reference to the node to be processed
124 @type ast.Compare
125 """
126 if (
127 isinstance(node.left, ast.Subscript)
128 and self.__isSys("version_info", node.left.value)
129 and isinstance(node.left.slice, ast.Index)
130 and AstUtilities.isNumber(node.left.slice.value)
131 and AstUtilities.getValue(node.left.slice.value) == 0
132 and len(node.ops) == 1
133 and isinstance(node.ops[0], ast.Eq)
134 and AstUtilities.isNumber(node.comparators[0])
135 and AstUtilities.getValue(node.comparators[0]) == 3
136 ):
137 self.violations.append((node.left, "M-411"))
138 elif (
139 self.__isSys("version", node.left)
140 and len(node.ops) == 1
141 and isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE))
142 and AstUtilities.isString(node.comparators[0])
143 ):
144 if len(AstUtilities.getValue(node.comparators[0])) == 1:
145 errorCode = "M-422"
146 else:
147 errorCode = "M-403"
148 self.violations.append((node.left, errorCode))
149 elif (
150 isinstance(node.left, ast.Subscript)
151 and self.__isSys("version_info", node.left.value)
152 and isinstance(node.left.slice, ast.Index)
153 and AstUtilities.isNumber(node.left.slice.value)
154 and AstUtilities.getValue(node.left.slice.value) == 1
155 and len(node.ops) == 1
156 and isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE))
157 and AstUtilities.isNumber(node.comparators[0])
158 ):
159 self.violations.append((node, "M-413"))
160 elif (
161 isinstance(node.left, ast.Attribute)
162 and self.__isSys("version_info", node.left.value)
163 and node.left.attr == "minor"
164 and len(node.ops) == 1
165 and isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE))
166 and AstUtilities.isNumber(node.comparators[0])
167 ):
168 self.violations.append((node, "M-414"))
169
170 self.generic_visit(node)
171
172 def visit_Attribute(self, node):
173 """
174 Public method to handle an attribute.
175
176 @param node reference to the node to be processed
177 @type ast.Attribute
178 """
179 if (
180 isinstance(node.value, ast.Name)
181 and node.value.id == "six"
182 and node.attr == "PY3"
183 ):
184 self.violations.append((node, "M-412"))
185
186 self.generic_visit(node)
187
188 def visit_Name(self, node):
189 """
190 Public method to handle an name.
191
192 @param node reference to the node to be processed
193 @type ast.Name
194 """
195 if node.id == "PY3" and self.__fromImports.get(node.id) == "six":
196 self.violations.append((node, "M-412"))
197
198 self.generic_visit(node)

eric ide

mercurial