|
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(): ...``. |
@@ -312,8 +320,9 @@ class Plugin: |
312 | 320 | version: str = __version__ #: The plugin version |
313 | 321 | dunder_all_alphabetical: AlphabeticalOptions = AlphabeticalOptions.NONE |
314 | 322 |
|
315 | | - def __init__(self, tree: ast.AST): |
| 323 | + def __init__(self, tree: ast.AST, filename: str): |
316 | 324 | self._tree = tree |
| 325 | + self._filename = filename |
317 | 326 |
|
318 | 327 | def run(self) -> Generator[Tuple[int, int, str, Type[Any]], None, None]: |
319 | 328 | """ |
@@ -351,11 +360,19 @@ def run(self) -> Generator[Tuple[int, int, str, Type[Any]], None, None]: |
351 | 360 | yield visitor.all_lineno, 0, f"{DALL001} (lowercase first).", type(self) |
352 | 361 |
|
353 | 362 | elif not visitor.members: |
354 | | - return |
| 363 | + pass |
355 | 364 |
|
356 | 365 | else: |
357 | 366 | yield 1, 0, DALL000, type(self) |
358 | 367 |
|
| 368 | + # Require top-level __dir__ function |
| 369 | + if not visitor.found_dir: |
| 370 | + if self._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