• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pyta-uoft / pyta / 26343669441

23 May 2026 09:11PM UTC coverage: 90.591% (-0.3%) from 90.843%
26343669441

Pull #1340

github

web-flow
Merge 4f65f221a into cbf34b3c5
Pull Request #1340: Modify invalid_name_checker snake case checks

28 of 28 new or added lines in 1 file covered. (100.0%)

4 existing lines in 1 file now uncovered.

3572 of 3943 relevant lines covered (90.59%)

17.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.41
/packages/python-ta/src/python_ta/checkers/invalid_name_checker.py
1
"""Checker used for identifying names that don't conform to Python naming conventions."""
2

3
from __future__ import annotations
20✔
4

5
import re
20✔
6
from typing import TYPE_CHECKING, Optional
20✔
7

8
from astroid import nodes
20✔
9
from pylint.checkers import BaseChecker, utils
20✔
10
from pylint.checkers.base.name_checker.checker import _redefines_import
20✔
11
from pylint.checkers.utils import only_required_for_messages
20✔
12

13
from python_ta.utils import _is_in_main
20✔
14

15
if TYPE_CHECKING:
16
    from pylint.lint import PyLinter
17

18
# Bad variable names.
19
BAD_NAMES = {"l", "I", "O"}
20✔
20

21
# Set a limit in name length to keep certain variable names short.
22
VAR_NAME_LENGTHS = {
20✔
23
    "module": 30,
24
    "constant": 30,
25
    "class": 30,
26
    "function": 30,
27
    "method": 30,
28
    "attribute": 30,
29
    "argument": 30,
30
    "variable": 30,
31
    "class attribute": 30,
32
    "class constant": 30,
33
    "type variable": 20,
34
    "type alias": 20,
35
}
36

37
TYPE_VAR_QNAME = frozenset(
20✔
38
    (
39
        "typing.TypeVar",
40
        "typing_extensions.TypeVar",
41
    )
42
)
43

44

45
def _is_in_snake_case(name: str) -> bool:
20✔
46
    """Returns whether `name` is in snake_case.
47

48
    `name` is in snake_case if:
49
      - `name` starts with a lowercase letter or an underscore (to denote private fields) followed
50
        by a lowercase letter,
51
      - each word is separated by an underscore, and
52
      - each word is in lowercase.
53
    """
54
    pattern = "(_?[a-z][a-z0-9_]*)$"
20✔
55

56
    return re.match(pattern, name) is not None
20✔
57

58

59
def _is_in_pascal_case(name: str) -> bool:
20✔
60
    """Returns whether `name` is in PascalCase.
61

62
    `name` is in PascalCase if:
63
      - `name` starts with an uppercase letter or an underscore (to denote private fields) followed
64
        by an uppercase letter.
65
      - each word has its first character capitalized, and
66
      - there is no whitespace, underscore, or punctuation between words.
67
    """
68
    pattern = "(_?[A-Z][a-zA-Z0-9]*)$"
20✔
69

70
    return re.match(pattern, name) is not None
20✔
71

72

73
def _is_in_upper_case_with_underscores(name: str) -> bool:
20✔
74
    """Returns whether `name` is in UPPER_CASE_WITH_UNDERSCORES.
75

76
    `name` is in `UPPER_CASE_WITH_UNDERSCORES` if:
77
      - each word is in uppercase, and
78
      - words are separated by an underscore.
79
    """
80
    pattern = "(_?[A-Z][A-Z0-9_]*)$"
20✔
81

82
    return re.match(pattern, name) is not None
20✔
83

84

85
def _parse_name(name: str) -> tuple[str, list[str] | None, str]:
20✔
86
    """Extracts the prefix, words, and suffix from `name`."""
87
    name_match = re.match(r"(_*)(.*?)(_*)$", name)
20✔
88
    if not name_match:
20✔
UNCOV
89
        return "", None, ""
×
90
    prefix, core, suffix = name_match.groups()
20✔
91
    prefix = "_" if prefix else ""
20✔
92
    if core and core[0].isdigit():
20✔
UNCOV
93
        return "", None, ""
×
94
    core = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", core)
20✔
95
    core = re.sub(r"([A-Z])([A-Z][a-z])", r"\1_\2", core)
20✔
96

97
    return prefix, [word for word in core.split("_") if word], suffix
20✔
98

99

100
def _to_pascal_case(name: str) -> str | None:
20✔
101
    """Returns a PascalCase version of `name`."""
102
    prefix, words, _ = _parse_name(name)
20✔
103
    if words is None:
20✔
UNCOV
104
        return None
×
105

106
    return prefix + "".join(word[0].upper() + word[1:] for word in words)
20✔
107

108

109
def _to_upper_case_with_underscores(name: str) -> str | None:
20✔
110
    """Returns an UPPER_CASE_WITH_UNDERSCORES version of `name`."""
111
    prefix, words, suffix = _parse_name(name)
20✔
112
    if words is None:
20✔
UNCOV
113
        return None
×
114

115
    return prefix + "_".join(word.upper() for word in words) + suffix
20✔
116

117

118
def _to_snake_case(name: str) -> str | None:
20✔
119
    """Returns name converted to snake_case format or None if no valid suggestion can be made."""
120
    if not re.match(r"_?[A-Za-z]", name):
20✔
121
        return None
20✔
122
    return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name).lower()
20✔
123

124

125
def _is_bad_name(name: str) -> str:
20✔
126
    """Returns a string detailing why `name` is a bad name.
127

128
    `name` is a bad name if it is in the pre-determined collection of "bad names".
129

130
    Returns the empty string if `name` is not a bad name."""
131
    msg = ""
20✔
132

133
    if name in BAD_NAMES:
20✔
134
        msg = (
20✔
135
            f'"{name}" is a name that should be avoided. Change to something less ambiguous '
136
            f"and/or more descriptive."
137
        )
138

139
    return msg
20✔
140

141

142
def _is_within_name_length(node_type: str, name: str) -> str:
20✔
143
    """Returns a string saying that `name` exceeds the character limit for that variable name type.
144

145
    Returns the empty string if `name` is within the name length limit."""
146
    msg = ""
20✔
147
    name_length_limit = VAR_NAME_LENGTHS[node_type]
20✔
148

149
    if len(name) > name_length_limit:
20✔
150
        msg = (
20✔
151
            f'{node_type.capitalize()} name "{name}" exceeds the limit of {name_length_limit} '
152
            f"characters."
153
        )
154

155
    return msg
20✔
156

157

158
def _ignore_name(name: str, pattern: re.Pattern) -> bool:
20✔
159
    """Returns whether name matches any of the regular expressions provided in patterns"""
160
    return pattern.pattern and pattern.match(name) is not None
20✔
161

162

163
def _check_module_name(_node_type: str, name: str) -> list[str]:
20✔
164
    """Returns a list of strings, each detailing how `name` violates Python naming conventions for
165
    module names and provides a suggested correction.
166

167
    Returns an empty list if `name` is a valid module name."""
168
    error_msgs = []
20✔
169

170
    if not _is_in_snake_case(name):
20✔
171
        suggested_name = _to_snake_case(name)
20✔
172
        msg = f'Module name "{name}" should be in snake_case format. '
20✔
173

174
        if suggested_name:
20✔
175
            msg += f'Suggested fix: "{suggested_name}". '
20✔
176

177
        msg += f"Modules should be all-lowercase names, with each name separated by underscores."
20✔
178
        error_msgs.append(msg)
20✔
179

180
    return error_msgs
20✔
181

182

183
def _check_const_name(node_type: str, name: str) -> list[str]:
20✔
184
    """Returns a list of strings, each detailing how `name` violates Python naming conventions for
185
    constant and class constant names and provides a suggested correction.
186

187
    Returns an empty list if `name` is a valid (global or class) constant name."""
188
    error_msgs = []
20✔
189

190
    if not _is_in_upper_case_with_underscores(name):
20✔
191
        suggested_name = _to_upper_case_with_underscores(name)
20✔
192
        msg = f'{node_type.capitalize()} name "{name}" should be in UPPER_CASE_WITH_UNDERSCORES format. '
20✔
193
        if suggested_name:
20✔
194
            msg += f'Suggested fix: "{suggested_name}". '
20✔
195
        msg += (
20✔
196
            "Constants should be all-uppercase words with each word separated by an "
197
            "underscore. A single leading underscore can be used to denote a private constant."
198
        )
199
        if node_type == "class constant":
20✔
200
            msg += " A double leading underscore invokes Python's name-mangling rules."
20✔
201
        error_msgs.append(msg)
20✔
202

203
    return error_msgs
20✔
204

205

206
def _check_class_name(_node_type: str, name: str) -> list[str]:
20✔
207
    """Returns a list of strings, each detailing how `name` violates Python naming conventions for
208
    class names and provides a suggested correction.
209

210
    Returns an empty list if `name` is a valid class name."""
211
    error_msgs = []
20✔
212

213
    if not _is_in_pascal_case(name):
20✔
214
        suggested_name = _to_pascal_case(name)
20✔
215
        msg = f'Class name "{name}" should be in PascalCase format. '
20✔
216
        if suggested_name:
20✔
217
            msg += f'Suggested fix: "{suggested_name}". '
20✔
218
        msg += (
20✔
219
            "Class names should have the first letter of each word capitalized with no separation "
220
            "between each word. A single leading underscore can be used to denote a private class."
221
        )
222
        error_msgs.append(msg)
20✔
223

224
    return error_msgs
20✔
225

226

227
def _check_function_and_variable_name(node_type: str, name: str) -> list[str]:
20✔
228
    """Returns a list of strings, each detailing how `name` violates Python naming conventions for
229
    function and variable names and provides a suggested correction.
230

231
    Returns an empty list if `name` is a valid function or variable name."""
232
    error_msgs = []
20✔
233

234
    if name != "_" and not _is_in_snake_case(name):
20✔
235
        suggested_name = _to_snake_case(name)
20✔
236
        msg = f'{node_type.capitalize()} name "{name}" should be in snake_case format. '
20✔
237

238
        if suggested_name:
20✔
239
            msg += f'Suggested fix: "{suggested_name}". '
20✔
240

241
        msg += (
20✔
242
            f"{node_type.capitalize()} names should be lowercase, with words "
243
            f"separated by underscores. A single leading underscore can be used to "
244
            f"denote a private {node_type}."
245
        )
246
        error_msgs.append(msg)
20✔
247

248
    return error_msgs
20✔
249

250

251
def _check_method_and_attr_name(node_type: str, name: str) -> list[str]:
20✔
252
    """Returns a list of strings, each detailing how `name` violates Python naming conventions for
253
    method and instance or class attribute names and provides a suggested correction.
254

255
    Returns an empty list if `name` is a valid method, instance, or attribute name."""
256
    error_msgs = []
20✔
257

258
    # Also consider the case of invoking Python's name mangling rules with leading dunderscores.
259
    if not (_is_in_snake_case(name) or (name.startswith("__") and _is_in_snake_case(name[2:]))):
20✔
260
        suggested_name = _to_snake_case(name)
20✔
261
        msg = f'{node_type.capitalize()} name "{name}" should be in snake_case format. '
20✔
262

263
        if suggested_name:
20✔
264
            msg += f'Suggested fix: "{suggested_name}". '
20✔
265

266
        msg += (
20✔
267
            f"{node_type.capitalize()} names should be lowercase, with words "
268
            f"separated by underscores. A single leading underscore can be used to "
269
            f"denote a private {node_type} while a double leading underscore invokes "
270
            f"Python's name-mangling rules."
271
        )
272
        error_msgs.append(msg)
20✔
273

274
    return error_msgs
20✔
275

276

277
def _check_argument_name(_node_type: str, name: str) -> list[str]:
20✔
278
    """Returns a list of strings, each detailing how `name` violates Python naming conventions for
279
    argument names and provides a suggested correction.
280

281
    Returns an empty list if `name` is a valid argument name."""
282
    error_msgs = []
20✔
283

284
    if not _is_in_snake_case(name):
20✔
285
        suggested_name = _to_snake_case(name)
20✔
286
        msg = f'Argument name "{name}" should be in snake_case format. '
20✔
287
        if suggested_name:
20✔
288
            msg += f'Suggested fix: "{suggested_name}". '
20✔
289

290
        msg += (
20✔
291
            f"Argument names should be lowercase, with words separated by underscores. "
292
            f"A single leading underscore can be used to indicate that the argument is "
293
            f"not being used but is still needed somehow."
294
        )
295
        error_msgs.append(msg)
20✔
296

297
    return error_msgs
20✔
298

299

300
def _check_typevar_name(_node_type: str, name: str) -> list[str]:
20✔
301
    """Returns a list of strings, each detailing how `name` violates Python naming conventions for
302
    type variable names and provides a suggested correction.
303

304
    Returns an empty list if `name` is a valid type variable name."""
305
    error_msgs = []
20✔
306

307
    if not _is_in_pascal_case(name):
20✔
308
        suggested_name = _to_pascal_case(name)
20✔
309
        msg = f'Type variable name "{name}" should be in PascalCase format. '
20✔
310
        if suggested_name:
20✔
311
            msg += f'Suggested fix: "{suggested_name}". '
20✔
312
        msg += (
20✔
313
            "Type variable names should have the first letter of each word "
314
            "capitalized with no separation between each word."
315
        )
316
        error_msgs.append(msg)
20✔
317

318
    return error_msgs
20✔
319

320

321
def _check_type_alias_name(_node_type: str, name: str) -> list[str]:
20✔
322
    """Returns a list of strings, each detailing how `name` violates Python naming conventions for
323
    type alias names and provides a suggested correction.
324

325
    Returns an empty list if `name` is a valid type alias name."""
326
    error_msgs = []
20✔
327

328
    if not _is_in_pascal_case(name):
20✔
329
        suggested_name = _to_pascal_case(name)
20✔
330
        msg = f'Type alias name "{name}" should be in PascalCase format. '
20✔
331
        if suggested_name:
20✔
332
            msg += f'Suggested fix: "{suggested_name}". '
20✔
333
        msg += (
20✔
334
            "Type alias names should have the first letter of each word "
335
            "capitalized with no separation between each word."
336
        )
337
        error_msgs.append(msg)
20✔
338

339
    return error_msgs
20✔
340

341

342
# Map each variable name type to its corresponding check
343
NAME_CHECK = {
20✔
344
    "module": _check_module_name,
345
    "constant": _check_const_name,
346
    "class": _check_class_name,
347
    "function": _check_function_and_variable_name,
348
    "method": _check_method_and_attr_name,
349
    "attribute": _check_method_and_attr_name,
350
    "argument": _check_argument_name,
351
    "variable": _check_function_and_variable_name,
352
    "class attribute": _check_method_and_attr_name,
353
    "class constant": _check_const_name,
354
    "type variable": _check_typevar_name,
355
    "type alias": _check_type_alias_name,
356
}
357

358

359
class InvalidNameChecker(BaseChecker):
20✔
360
    """A checker class to report on names that don't conform to Python naming conventions.
361

362
    For the Python naming conventions, see https://peps.python.org/pep-0008/#naming-conventions.
363
    """
364

365
    name = "naming_convention_violation"
20✔
366
    msgs = {
20✔
367
        "C9103": (
368
            "%s",
369
            "naming-convention-violation",
370
            "Used when the name doesn't conform to standard Python naming conventions.",
371
        ),
372
        "C9104": (
373
            "%s",
374
            "module-name-violation",
375
            "Used when the name doesn't conform to standard Python naming conventions.",
376
        ),
377
    }
378
    options = (
20✔
379
        (
380
            "ignore-names",
381
            {
382
                "default": "",
383
                "type": "regexp",
384
                "metavar": "<regexp>",
385
                "help": "Ignore C9103 naming convention violation for names that exactly match the pattern",
386
            },
387
        ),
388
        (
389
            "ignore-module-names",
390
            {
391
                "default": "",
392
                "type": "regexp",
393
                "metavar": "<regexp>",
394
                "help": "Ignore C9104 module name violation for module names that exactly match the pattern",
395
            },
396
        ),
397
    )
398

399
    @only_required_for_messages("module-name-violation")
20✔
400
    def visit_module(self, node: nodes.Module) -> None:
20✔
401
        """Visit a Module node to check for any name violations.
402

403
        Snippets taken from pylint.checkers.base.name_checker.checker."""
404
        if not _ignore_name(node.name, self.linter.config.ignore_module_names):
20✔
405
            self._check_name("module", node.name.split(".")[-1], node)
20✔
406

407
    @only_required_for_messages("naming-convention-violation")
20✔
408
    def visit_classdef(self, node: nodes.ClassDef) -> None:
20✔
409
        """Visit a Class node to check for any name violations.
410

411
        Taken from pylint.checkers.base.name_checker.checker."""
412
        if not _ignore_name(node.name, self.linter.config.ignore_names):
20✔
413
            self._check_name("class", node.name, node)
20✔
414

415
    @only_required_for_messages("naming-convention-violation")
20✔
416
    def visit_functiondef(self, node: nodes.FunctionDef) -> None:
20✔
417
        """Visit a FunctionDef node to check for any name violations.
418

419
        Snippets taken from pylint.checkers.base.name_checker.checker."""
420
        if node.is_method():
20✔
421
            if utils.overrides_a_method(node.parent.frame(future=True), node.name):
20✔
422
                return
20✔
423

424
        if not _ignore_name(node.name, self.linter.config.ignore_names):
20✔
425
            self._check_name("method" if node.is_method() else "function", node.name, node)
20✔
426

427
    visit_asyncfunctiondef = visit_functiondef
20✔
428

429
    @only_required_for_messages("naming-convention-violation")
20✔
430
    def visit_assignname(self, node: nodes.AssignName) -> None:
20✔
431
        """Visit an AssignName node to check for any name violations.
432

433
        Taken from pylint.checkers.base.name_checker.checker."""
434
        # Do not check this node if included in the ignore-names option
435
        if _ignore_name(node.name, self.linter.config.ignore_names):
20✔
436
            return
20✔
437

438
        frame = node.frame()
20✔
439
        assign_type = node.assign_type()
20✔
440

441
        # Check names defined in comprehensions
442
        if isinstance(assign_type, nodes.Comprehension):
20✔
443
            self._check_name("variable", node.name, node)
20✔
444

445
        # Check names defined as function arguments.
446
        elif isinstance(assign_type, nodes.Arguments):
20✔
447
            self._check_name("argument", node.name, node)
20✔
448

449
        # Check names defined in module scope
450
        elif isinstance(frame, nodes.Module) and not _is_in_main(node):
20✔
451
            # Check names defined in Assign nodes
452
            if isinstance(assign_type, nodes.Assign):
20✔
453
                inferred_assign_type = utils.safe_infer(assign_type.value)
20✔
454

455
                # Check TypeVar's and TypeAliases assigned alone or in tuple assignment
456
                if isinstance(node.parent, nodes.Assign):
20✔
457
                    if self._assigns_typevar(assign_type.value):
20✔
458
                        self._check_name("type variable", assign_type.targets[0].name, node)
20✔
459
                        return
20✔
460
                    if self._assigns_typealias(assign_type.value):
20✔
461
                        self._check_name("type alias", assign_type.targets[0].name, node)
20✔
462
                        return
20✔
463

464
                if (
20✔
465
                    isinstance(node.parent, nodes.Tuple)
466
                    and isinstance(assign_type.value, nodes.Tuple)
467
                    # protect against unbalanced tuple unpacking
468
                    and node.parent.elts.index(node) < len(assign_type.value.elts)
469
                ):
470
                    assigner = assign_type.value.elts[node.parent.elts.index(node)]
20✔
471
                    if self._assigns_typevar(assigner):
20✔
472
                        self._check_name(
20✔
473
                            "type variable",
474
                            assign_type.targets[0].elts[node.parent.elts.index(node)].name,
475
                            node,
476
                        )
477
                        return
20✔
478
                    if self._assigns_typealias(assigner):
20✔
479
                        self._check_name(
20✔
480
                            "type alias",
481
                            assign_type.targets[0].elts[node.parent.elts.index(node)].name,
482
                            node,
483
                        )
484
                        return
20✔
485

486
                # Check classes (TypeVar's are classes so they need to be excluded first)
487
                elif isinstance(inferred_assign_type, nodes.ClassDef):
20✔
488
                    self._check_name("class", node.name, node)
20✔
489

490
                # Don't emit if the name redefines an import in an ImportError except handler.
491
                elif not _redefines_import(node):
20✔
492
                    self._check_name("constant", node.name, node)
20✔
493
                else:
494
                    self._check_name("variable", node.name, node)
20✔
495

496
            # Check names defined in AnnAssign nodes
497
            elif isinstance(assign_type, nodes.AnnAssign):
20✔
498
                if utils.is_assign_name_annotated_with(node, "Final"):
20✔
499
                    self._check_name("constant", node.name, node)
20✔
500
                elif self._assigns_typealias(assign_type.annotation):
20✔
501
                    self._check_name("type alias", node.name, node)
20✔
502

503
        # Check names defined in function scopes
504
        elif isinstance(frame, nodes.FunctionDef):
20✔
505
            # global introduced variable aren't in the function locals
506
            if node.name in frame and node.name not in frame.argnames():
20✔
507
                if not _redefines_import(node):
20✔
508
                    self._check_name("variable", node.name, node)
20✔
509

510
        # Check names defined in class scopes
511
        elif isinstance(frame, nodes.ClassDef):
20✔
512
            if not list(frame.local_attr_ancestors(node.name)):
20✔
513
                for ancestor in frame.ancestors():
20✔
514
                    if utils.is_enum(ancestor) or utils.is_assign_name_annotated_with(
20✔
515
                        node, "Final"
516
                    ):
517
                        self._check_name("class constant", node.name, node)
20✔
518
                        break
20✔
519
                    elif utils.is_assign_name_annotated_with(node, "ClassVar"):
20✔
520
                        self._check_name("class attribute", node.name, node)
20✔
521
                        break
20✔
522
                    elif isinstance(node.parent, nodes.AnnAssign):
20✔
523
                        self._check_name("attribute", node.name, node)
20✔
524
                        break
20✔
525
                else:
526
                    self._check_name("class attribute", node.name, node)
20✔
527

528
    def _check_name(self, node_type: str, name: str, node: nodes.NodeNG) -> None:
20✔
529
        """Check for a name that violates Python naming conventions."""
530
        name_check = NAME_CHECK[node_type]
20✔
531
        error_msgs = name_check(node_type, name)
20✔
532

533
        bad_name_msg = _is_bad_name(name)
20✔
534
        if bad_name_msg:
20✔
535
            error_msgs.append(bad_name_msg)
20✔
536

537
        name_length_msg = _is_within_name_length(node_type, name)
20✔
538
        if name_length_msg:
20✔
539
            error_msgs.append(name_length_msg)
20✔
540

541
        msg_id = "naming-convention-violation" if node_type != "module" else "module-name-violation"
20✔
542
        line_no = 1 if node_type == "module" else None
20✔
543

544
        for msg in error_msgs:
20✔
545
            self.add_message(msgid=msg_id, node=node, args=msg, line=line_no)
20✔
546

547
    @staticmethod
20✔
548
    def _assigns_typevar(node: Optional[nodes.NodeNG]) -> bool:
20✔
549
        """Check if a node is assigning a TypeVar.
550

551
        Taken from pylint.checkers.base.name_checker.checker."""
552
        if isinstance(node, nodes.Call):
20✔
553
            inferred = utils.safe_infer(node.func)
20✔
554
            if isinstance(inferred, nodes.ClassDef) and inferred.qname() in TYPE_VAR_QNAME:
20✔
555
                return True
20✔
556
        return False
20✔
557

558
    @staticmethod
20✔
559
    def _assigns_typealias(node: Optional[nodes.NodeNG]) -> bool:
20✔
560
        """Check if a node is assigning a TypeAlias.
561

562
        Taken from pylint.checkers.base.name_checker.checker."""
563
        inferred = utils.safe_infer(node)
20✔
564
        if isinstance(inferred, nodes.ClassDef):
20✔
565
            qname = inferred.qname()
20✔
566
            if qname == "typing.TypeAlias":
20✔
567
                return True
12✔
568
            if qname == ".Union":
20✔
569
                # Union is a special case because it can be used as a type alias
570
                # or as a type annotation. We only want to check the former.
571
                assert node is not None
20✔
572
                return not isinstance(node.parent, nodes.AnnAssign)
20✔
573
        elif isinstance(inferred, nodes.FunctionDef):
20✔
574
            # TODO: when py3.12 is minimum, remove this condition
575
            # TypeAlias became a class in python 3.12
576
            if inferred.qname() == "typing.TypeAlias":
8✔
577
                return True
8✔
578
        return False
20✔
579

580

581
def register(linter: PyLinter) -> None:
20✔
582
    """Required method to auto-register this checker to the linter"""
583
    linter.register_checker(InvalidNameChecker(linter))
20✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc