11# source: https://github.com/dodona-edu/judge-pythia/blob/e41f6e943830f5f9b5aebe689140ec7ec53383c9/pylint_ast_checker.py
2- import astroid
2+ from astroid import nodes as astroid_nodes
33from pylint .checkers import BaseChecker
4- from pylint .checkers .utils import check_messages
5- from pylint .interfaces import IAstroidChecker
4+ from pylint .checkers .utils import only_required_for_messages
65
76class NoOpChecker (BaseChecker ):
87
9- __implements__ = IAstroidChecker
10-
118 name = 'no_op_checker'
129
1310 msgs = {
@@ -43,59 +40,57 @@ class NoOpChecker(BaseChecker):
4340 )
4441 }
4542
46- @check_messages ('no-op-pass' )
43+ @only_required_for_messages ('no-op-pass' )
4744 def visit_pass (self , node ):
48-
49- if isinstance (node .parent , astroid .If ):
45+
46+ if isinstance (node .parent , astroid_nodes .If ):
5047 if len (node .parent .orelse ) > 0 and node .parent .orelse [0 ] is node :
5148 self .add_message ('no-op-pass' , node = node )
5249
53- @check_messages ('no-op-if-true' )
54- def visit_if (self ,node ):
55-
50+ @only_required_for_messages ('no-op-if-true' )
51+ def visit_if (self , node ):
52+
5653 if (
57- isinstance (node .test , astroid .Compare ) and
58- any (operation == '==' for operation ,_ in node .test .ops ) and
59- any (isinstance (operand , astroid .Const ) and
54+ isinstance (node .test , astroid_nodes .Compare ) and
55+ any (operation == '==' for operation ,_ in node .test .ops ) and
56+ any (isinstance (operand , astroid_nodes .Const ) and
6057 operand .value is True for operand in node .test .get_children ())
6158 ):
6259 self .add_message ('no-op-if-true' , node = node )
6360
64- @check_messages ('no-op-increment-zero' , 'no-op-increment-empty' , 'no-op-multiply-one' )
65- def visit_augassign (self ,node ):
66-
67- if node .op == '+=' and isinstance (node .value , astroid .Const ):
61+ @only_required_for_messages ('no-op-increment-zero' , 'no-op-increment-empty' , 'no-op-multiply-one' )
62+ def visit_augassign (self , node ):
63+
64+ if node .op == '+=' and isinstance (node .value , astroid_nodes .Const ):
6865 if node .value .value == 0 :
6966 self .add_message ('no-op-increment-zero' , node = node )
7067 elif node .value .value == '' :
71- self .add_message ('no-op-increment-empty' ,node = node )
68+ self .add_message ('no-op-increment-empty' , node = node )
7269 elif (
73- node .op == '*=' and
74- isinstance (node .value , astroid .Const ) and
70+ node .op == '*=' and
71+ isinstance (node .value , astroid_nodes .Const ) and
7572 node .value .value == 1
7673 ):
7774 self .add_message ('no-op-multiply-one' , node = node )
7875
79- @check_messages ('no-op-useless-assign' )
80- def visit_assign (self ,node ):
81-
76+ @only_required_for_messages ('no-op-useless-assign' )
77+ def visit_assign (self , node ):
78+
8279 # only keep variables, e.g. [x, y, 2] -> [x, y]
8380 child_names = [
84- child .name for child in node .get_children ()
81+ child .name for child in node .get_children ()
8582 if (
86- isinstance (child ,astroid .Name ) or
87- isinstance (child , astroid .AssignName )
83+ isinstance (child , astroid_nodes .Name ) or
84+ isinstance (child , astroid_nodes .AssignName )
8885 )
8986 ]
90-
87+
9188 if len (child_names ) > len (set (child_names )):
9289 # at least two names were equal (e.g. ['x', 'y', 'x')
9390 self .add_message ('no-op-useless-assign' , node = node )
9491
9592class RewriteChecker (BaseChecker ):
9693
97- __implements__ = IAstroidChecker
98-
9994 name = 'rewrite_checker'
10095
10196 msgs = {
@@ -124,98 +119,101 @@ class RewriteChecker(BaseChecker):
124119 'is not' : 'is'
125120 }
126121
127- @check_messages ('rewrite-if-pass' )
128- def visit_if (self ,node ):
129-
122+ @only_required_for_messages ('rewrite-if-pass' )
123+ def visit_if (self , node ):
124+
130125 # check whether the body of the if clause starts with the pass statement
131126 # and there is an else clause
132- if isinstance (node .body [0 ], astroid .Pass ) and len (node .orelse ) > 0 :
133-
134- # swap body of the else clause in the if clause and remove the else
127+ if isinstance (node .body [0 ], astroid_nodes .Pass ) and len (node .orelse ) > 0 :
128+
129+ # swap body of the else clause in the if clause and remove the else
135130 # clause
136131 node .body = node .orelse
137132 node .orelse = []
138-
133+
139134 # invert the test
140135 # TODO: could be improved for recursive statements, etc.
141136 if (
142- isinstance (node .test , astroid .Compare ) and
137+ isinstance (node .test , astroid_nodes .Compare ) and
143138 len (node .test .ops ) == 1
144139 ):
145140 node .test .ops [0 ] = (
146141 self .inverse_operators [node .test .ops [0 ][0 ]],
147142 node .test .ops [0 ][1 ]
148143 )
149144 elif (
150- isinstance (node .test , astroid .UnaryOp ) and
145+ isinstance (node .test , astroid_nodes .UnaryOp ) and
151146 node .test .op == 'not'
152- ):
147+ ):
153148 # not <x> becomes <x>
154149 node .test = node .test .operand
155150 else :
156- old_test , node .test = node .test , astroid .UnaryOp ()
157- node .test .op , node .test .operand = 'not' , old_test
158-
151+ old_test = node .test
152+ node .test = astroid_nodes .UnaryOp (
153+ op = 'not' , lineno = node .lineno , col_offset = node .col_offset ,
154+ parent = node , end_lineno = node .end_lineno , end_col_offset = node .end_col_offset ,
155+ )
156+ node .test .operand = old_test
157+
159158 # add message with corrected node as argument
160159 self .add_message (
161- 'rewrite-if-pass' ,
160+ 'rewrite-if-pass' ,
162161 node = node ,
163162 args = node .as_string ()
164163 )
165164
166- @check_messages ('rewrite-assign' )
167- def visit_assign (self ,node ):
168-
165+ @only_required_for_messages ('rewrite-assign' )
166+ def visit_assign (self , node ):
167+
169168 # TODO: current implementation does not rewrite assignment statements
170169 # having multiple targets or recursive binary operations
171170 # (eg. x = x + 5 + 6)
172171 if len (node .targets ) == 1 :
173-
172+
174173 target = node .targets [0 ]
175-
176- if isinstance (node .value , astroid .BinOp ):
177-
174+
175+ if isinstance (node .value , astroid_nodes .BinOp ):
176+
178177 rewrite = False
179-
178+
180179 # e.g. x = x + 4
181180 if (
182- isinstance (node .value .left , astroid .Name ) and
183- isinstance (target , astroid .AssignName ) and
181+ isinstance (node .value .left , astroid_nodes .Name ) and
182+ isinstance (target , astroid_nodes .AssignName ) and
184183 node .value .left .name == target .name
185184 ):
186185 rewrite = True
187186
188187 # check for binary operations of the form x["y"] = x["y"] + 5
189188 elif (
190- isinstance (node .value .left , astroid .Subscript ) and
191- isinstance (target , astroid .Subscript ) and
192- isinstance (node .value .left .value , astroid .Name ) and
193- isinstance (target .value , astroid .Name ) and
194- node .value .left .value .name == target .value .name and
195- isinstance (node .value .left .slice , astroid .Index ) and
196- isinstance (target .slice , astroid .Index )
189+ isinstance (node .value .left , astroid_nodes .Subscript ) and
190+ isinstance (target , astroid_nodes .Subscript ) and
191+ isinstance (node .value .left .value , astroid_nodes .Name ) and
192+ isinstance (target .value , astroid_nodes .Name ) and
193+ node .value .left .value .name == target .value .name
197194 ):
198- # e.g. x[y] = x[y] + 1 (where y is a variable)
199- if (
200- isinstance (node .value .left .slice . value , astroid .Name ) and
201- isinstance (target .slice . value , astroid .Name ) and
202- node .value .left .slice .value . name == target .slice . value .name
203- ):
204- rewrite = True
205- # e.g. x[0] = x[0] + 1 or x["y"] = x["y"] + 1
206- elif (
207- isinstance (node .value .left .slice . value , astroid .Const ) and
208- isinstance (target .slice . value , astroid .Const ) and
209- node .value .left .slice .value . value == target .slice . value .value
210- ):
211- rewrite = True
195+ # e.g. x[y] = x[y] + 1 (where y is a variable)
196+ if (
197+ isinstance (node .value .left .slice , astroid_nodes .Name ) and
198+ isinstance (target .slice , astroid_nodes .Name ) and
199+ node .value .left .slice .name == target .slice .name
200+ ):
201+ rewrite = True
202+ # e.g. x[0] = x[0] + 1 or x["y"] = x["y"] + 1
203+ elif (
204+ isinstance (node .value .left .slice , astroid_nodes .Const ) and
205+ isinstance (target .slice , astroid_nodes .Const ) and
206+ node .value .left .slice .value == target .slice .value
207+ ):
208+ rewrite = True
212209
213210 if rewrite :
214-
215- newnode = astroid .AugAssign ()
211+
212+ newnode = astroid_nodes .AugAssign (
213+ op = node .value .op + '=' , lineno = node .lineno , col_offset = node .col_offset ,
214+ parent = node .parent , end_lineno = node .end_lineno , end_col_offset = node .end_col_offset ,
215+ )
216216 newnode .target = target
217- # node.value.op is of the form '+', '-' , '/' , '*'
218- newnode .op = node .value .op + '='
219217 newnode .value = node .value .right
220218 self .add_message (
221219 'rewrite-assign' ,
@@ -224,11 +222,9 @@ def visit_assign(self,node):
224222 )
225223
226224class UnnecessaryConversionChecker (BaseChecker ):
227-
228- __implements__ = IAstroidChecker
229-
225+
230226 name = 'un_conv_checker'
231-
227+
232228 msgs = {
233229 'C0009' : (
234230 'Useless call to int(): no type conversion required.' ,
@@ -247,28 +243,28 @@ class UnnecessaryConversionChecker(BaseChecker):
247243 )
248244 }
249245
250- @check_messages ('un-conv-int' , 'un-conv-float' , 'un-conv-str' )
251- def visit_callfunc (self , node ):
252-
253- #check if 'regular' function:
254- if hasattr (node .func , 'name' ):
246+ @only_required_for_messages ('un-conv-int' , 'un-conv-float' , 'un-conv-str' )
247+ def visit_call (self , node ):
248+
249+ # check if 'regular' function:
250+ if hasattr (node .func , 'name' ):
255251
256252 # case: str(input(...))
257253 if (
258254 # check if parent is str()
259255 node .func .name == 'input' and
260- isinstance (node .parent , astroid .Call ) and
256+ isinstance (node .parent , astroid_nodes .Call ) and
261257 hasattr (node .parent .func , 'name' ) and
262258 node .parent .func .name == 'str' and
263259 # limited to a single argument
264260 len (node .parent .args ) == 1
265261 ):
266262 self .add_message ('un-conv-str' , node = node .parent )
267-
263+
268264 # check if a single constant argument is passed
269265 elif (
270266 len (node .args ) == 1 and
271- isinstance (node .args [0 ], astroid .Const )
267+ isinstance (node .args [0 ], astroid_nodes .Const )
272268 ):
273269 # case: int(<builtin.int>)
274270 if (
@@ -290,11 +286,11 @@ def visit_callfunc(self, node):
290286 self .add_message ('un-conv-str' , node = node )
291287
292288def register (linter ):
293-
289+
294290 """
295291 Required method to auto register custom checkers to pylint.
296292 """
297-
293+
298294 linter .register_checker (NoOpChecker (linter ))
299295 linter .register_checker (RewriteChecker (linter ))
300296 linter .register_checker (UnnecessaryConversionChecker (linter ))
0 commit comments