|
69 | 69 | DALL000 = "DALL000 Module lacks __all__." |
70 | 70 | DALL001 = "DALL001 __all__ not sorted alphabetically" |
71 | 71 | DALL002 = "DALL002 __all__ not a list or tuple of strings." |
| 72 | +DALL100 = "DALL100 Top-level __dir__ function definition is required." |
| 73 | +DALL101 = "DALL101 Top-level __dir__ function definition is required in __init__.py." |
72 | 74 |
|
73 | 75 |
|
74 | 76 | class AlphabeticalOptions(Enum): |
@@ -106,6 +108,8 @@ class Visitor(ast.NodeVisitor): |
106 | 108 |
|
107 | 109 | def __init__(self, use_endlineno: bool = False) -> None: |
108 | 110 | self.found_all = False |
| 111 | + self.found_lineno = -1 |
| 112 | + self.found_dir = False |
109 | 113 | self.members = set() |
110 | 114 | self.last_import = 0 |
111 | 115 | self.use_endlineno = use_endlineno |
@@ -177,6 +181,10 @@ def handle_def(self, node: Union[ast.FunctionDef, ast.AsyncFunctionDef, ast.Clas |
177 | 181 | if not node.name.startswith('_') and "overload" not in decorators: |
178 | 182 | self.members.add(node.name) |
179 | 183 |
|
| 184 | + if node.name == "__dir__": |
| 185 | + self.found_dir = True |
| 186 | + self.found_lineno = node.lineno |
| 187 | + |
180 | 188 | def visit_FunctionDef(self, node: ast.FunctionDef) -> None: |
181 | 189 | """ |
182 | 190 | Visit ``def foo(): ...``. |
@@ -351,11 +359,20 @@ def run(self) -> Generator[Tuple[int, int, str, Type[Any]], None, None]: |
351 | 359 | yield visitor.all_lineno, 0, f"{DALL001} (lowercase first).", type(self) |
352 | 360 |
|
353 | 361 | elif not visitor.members: |
354 | | - return |
| 362 | + pass |
355 | 363 |
|
356 | 364 | else: |
357 | 365 | yield 1, 0, DALL000, type(self) |
358 | 366 |
|
| 367 | + # Require top-level __dir__ function |
| 368 | + if not visitor.found_dir: |
| 369 | + filename = getattr(self, "filename", None) |
| 370 | + if filename and filename.endswith("__init__.py"): |
| 371 | + if visitor.members: |
| 372 | + yield 1, 0, DALL101, type(self) |
| 373 | + else: |
| 374 | + yield 1, 0, DALL100, type(self) |
| 375 | + |
359 | 376 | @classmethod |
360 | 377 | def add_options(cls, option_manager: OptionManager) -> None: # noqa: D102 # pragma: no cover |
361 | 378 |
|
|
0 commit comments