10 |
10 |
11 ##################################################################################### |
11 ##################################################################################### |
12 ## adapted from: flake8-use-pathlib v0.3.0 ## |
12 ## adapted from: flake8-use-pathlib v0.3.0 ## |
13 ## ## |
13 ## ## |
14 ## Original: Copyright (c) 2021 Rodolphe Pelloux-Prayer ## |
14 ## Original: Copyright (c) 2021 Rodolphe Pelloux-Prayer ## |
15 ## ## |
|
16 ## License: ## |
|
17 ## Permission is hereby granted, free of charge, to any person obtaining a copy ## |
|
18 ## of this software and associated documentation files (the "Software"), to deal ## |
|
19 ## in the Software without restriction, including without limitation the rights ## |
|
20 ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ## |
|
21 ## copies of the Software, and to permit persons to whom the Software is ## |
|
22 ## furnished to do so, subject to the following conditions: ## |
|
23 ## ## |
|
24 ## The above copyright notice and this permission notice shall be included in all ## |
|
25 ## copies or substantial portions of the Software. ## |
|
26 ## ## |
|
27 ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ## |
|
28 ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ## |
|
29 ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ## |
|
30 ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ## |
|
31 ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ## |
|
32 ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ## |
|
33 ##################################################################################### |
15 ##################################################################################### |
34 |
16 |
35 import ast |
17 import ast |
36 import contextlib |
18 import contextlib |
37 import copy |
19 |
38 |
20 from CodeStyleTopicChecker import CodeStyleTopicChecker |
39 |
21 |
40 class PathlibChecker: |
22 |
|
23 class PathlibChecker(CodeStyleTopicChecker): |
41 """ |
24 """ |
42 Class implementing a checker for functions that can be replaced by use of |
25 Class implementing a checker for functions that can be replaced by use of |
43 the pathlib module. |
26 the pathlib module. |
44 """ |
27 """ |
45 |
28 |
129 @param expected list of expected codes |
113 @param expected list of expected codes |
130 @type list of str |
114 @type list of str |
131 @param repeat flag indicating to report each occurrence of a code |
115 @param repeat flag indicating to report each occurrence of a code |
132 @type bool |
116 @type bool |
133 """ |
117 """ |
134 self.__select = tuple(selected) |
118 super().__init__( |
135 self.__ignore = tuple(ignored) |
119 PathlibChecker.Category, |
136 self.__expected = expected[:] |
120 source, |
137 self.__repeat = repeat |
121 filename, |
138 self.__filename = filename |
122 tree, |
139 self.__source = source[:] |
123 selected, |
140 self.__tree = copy.deepcopy(tree) |
124 ignored, |
141 |
125 expected, |
142 # statistics counters |
126 repeat, |
143 self.counters = {} |
127 [], |
144 |
|
145 # collection of detected errors |
|
146 self.errors = [] |
|
147 |
|
148 self.__checkCodes = (code for code in self.Codes if not self.__ignoreCode(code)) |
|
149 |
|
150 def __ignoreCode(self, code): |
|
151 """ |
|
152 Private method to check if the message code should be ignored. |
|
153 |
|
154 @param code message code to check for |
|
155 @type str |
|
156 @return flag indicating to ignore the given code |
|
157 @rtype bool |
|
158 """ |
|
159 return code in self.__ignore or ( |
|
160 code.startswith(self.__ignore) and not code.startswith(self.__select) |
|
161 ) |
128 ) |
162 |
129 |
163 def __error(self, lineNumber, offset, code, *args): |
130 checkersWithCodes = [ |
164 """ |
131 ( |
165 Private method to record an issue. |
132 self.__checkPathlibReplacement, |
166 |
133 ( |
167 @param lineNumber line number of the issue |
134 "P-101", |
168 @type int |
135 "P-102", |
169 @param offset position within line of the issue |
136 "P-103", |
170 @type int |
137 "P-104", |
171 @param code message code |
138 "P-105", |
172 @type str |
139 "P-106", |
173 @param args arguments for the message |
140 "P-107", |
174 @type list |
141 "P-108", |
175 """ |
142 "P-109", |
176 if self.__ignoreCode(code): |
143 "P-110", |
177 return |
144 "P-111", |
178 |
145 "P-112", |
179 if code in self.counters: |
146 "P-113", |
180 self.counters[code] += 1 |
147 "P-114", |
181 else: |
148 "P-201", |
182 self.counters[code] = 1 |
149 "P-202", |
183 |
150 "P-203", |
184 # Don't care about expected codes |
151 "P-204", |
185 if code in self.__expected: |
152 "P-205", |
186 return |
153 "P-206", |
187 |
154 "P-207", |
188 if code and (self.counters[code] == 1 or self.__repeat): |
155 "P-208", |
189 # record the issue with one based line number |
156 "P-209", |
190 self.errors.append( |
157 "P-210", |
191 { |
158 "P-211", |
192 "file": self.__filename, |
159 "P-212", |
193 "line": lineNumber + 1, |
160 "P-213", |
194 "offset": offset, |
161 "P-301", |
195 "code": code, |
162 "P-401", |
196 "args": args, |
163 ), |
197 } |
164 ), |
198 ) |
165 ] |
199 |
166 self._initializeCheckers(checkersWithCodes) |
200 def run(self): |
167 |
201 """ |
168 def __checkPathlibReplacement(self): |
202 Public method to check the given source against functions |
169 """ |
203 to be replaced by 'pathlib' equivalents. |
170 Private method to check for pathlib replacements. |
204 """ |
171 """ |
205 if not self.__filename: |
|
206 # don't do anything, if essential data is missing |
|
207 return |
|
208 |
|
209 if not self.__checkCodes: |
|
210 # don't do anything, if no codes were selected |
|
211 return |
|
212 |
|
213 visitor = PathlibVisitor(self.__checkForReplacement) |
172 visitor = PathlibVisitor(self.__checkForReplacement) |
214 visitor.visit(self.__tree) |
173 visitor.visit(self.tree) |
215 |
174 |
216 def __checkForReplacement(self, node, name): |
175 def __checkForReplacement(self, node, name): |
217 """ |
176 """ |
218 Private method to check the given node for the need for a |
177 Private method to check the given node for the need for a |
219 replacement. |
178 replacement. |
223 @param name resolved name of the node |
182 @param name resolved name of the node |
224 @type str |
183 @type str |
225 """ |
184 """ |
226 with contextlib.suppress(KeyError): |
185 with contextlib.suppress(KeyError): |
227 errorCode = self.Function2Code[name] |
186 errorCode = self.Function2Code[name] |
228 self.__error(node.lineno - 1, node.col_offset, errorCode) |
187 self.addErrorFromNode(node, errorCode) |
229 |
188 |
230 |
189 |
231 class PathlibVisitor(ast.NodeVisitor): |
190 class PathlibVisitor(ast.NodeVisitor): |
232 """ |
191 """ |
233 Class to traverse the AST node tree and check for potential issues. |
192 Class to traverse the AST node tree and check for potential issues. |