@@ -254,6 +254,7 @@ class ClassNameCheck(BaseASTCheck):
254
254
Classes for internal use have a leading underscore in addition.
255
255
"""
256
256
N801 = "class name '{name}' should use CapWords convention"
257
+ N808 = "type variable name '{name}' should use CapWords convention and an optional suffix '_co' or '_contra'"
257
258
N818 = "exception name '{name}' should be named with an Error suffix"
258
259
259
260
@classmethod
@@ -275,6 +276,40 @@ def superclass_names(cls, name, parents: Iterable, _names=None):
275
276
names .update (cls .superclass_names (base .id , parents , names ))
276
277
return names
277
278
279
+ def visit_module (self , node , parents : Iterable , ignore = None ):
280
+ for body in node .body :
281
+ try :
282
+ if len (body .targets ) != 1 :
283
+ continue
284
+ name = body .targets [0 ].id
285
+ func_name = body .value .func .id
286
+ args = [a .value for a in body .value .args ]
287
+ keywords = {kw .arg : kw .value .value for kw in body .value .keywords }
288
+ except AttributeError :
289
+ continue
290
+
291
+ if func_name != "TypeVar" or _ignored (name , ignore ):
292
+ continue
293
+
294
+ if len (args ) == 0 or args [0 ] != name :
295
+ yield self .err (body , 'N808' , name = name )
296
+
297
+ if not name [:1 ].isupper ():
298
+ yield self .err (body , 'N808' , name = name )
299
+
300
+ parts = name .split ('_' )
301
+ if len (parts ) > 2 :
302
+ yield self .err (body , 'N808' , name = name )
303
+
304
+ suffix = parts [- 1 ] if len (parts ) > 1 else ''
305
+ if suffix and suffix != 'co' and suffix != 'contra' :
306
+ yield self .err (body , 'N808' , name = name )
307
+ elif keywords .get ('covariant' ) and suffix != 'co' :
308
+ yield self .err (body , 'N808' , name = name )
309
+ elif keywords .get ('contravariant' ) and suffix != 'contra' :
310
+ yield self .err (body , 'N808' , name = name )
311
+
312
+
278
313
def visit_classdef (self , node , parents : Iterable , ignore = None ):
279
314
name = node .name
280
315
if _ignored (name , ignore ):
0 commit comments