mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
106 lines
3.3 KiB
Python
106 lines
3.3 KiB
Python
"""Structural checks: file length, folder size, and nested imports."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
from pathlib import Path
|
|
|
|
from . import _matches_any
|
|
|
|
ANCHOR_FILES = ("__init__.py", "index.ts", "index.tsx", "index.js")
|
|
|
|
|
|
def _find_anchor_file(dirpath: Path, root: Path) -> str:
|
|
"""Find a real file inside the folder to attach the diagnostic to.
|
|
|
|
Prefers common entry-point files (__init__.py, index.ts, etc.) so the
|
|
error shows up inline when you open that file. Falls back to the first
|
|
file alphabetically, then the directory path itself.
|
|
"""
|
|
for name in ANCHOR_FILES:
|
|
candidate = dirpath / name
|
|
if candidate.exists():
|
|
return str(candidate.relative_to(root))
|
|
try:
|
|
first = sorted(
|
|
f for f in dirpath.iterdir()
|
|
if f.is_file() and not f.name.startswith(".")
|
|
)
|
|
if first:
|
|
return str(first[0].relative_to(root))
|
|
except OSError:
|
|
pass
|
|
return str(dirpath.relative_to(root))
|
|
|
|
|
|
def check_file_lines(
|
|
filepath: Path, root: Path, max_lines: int,
|
|
) -> tuple[str, int] | None:
|
|
try:
|
|
count = len(filepath.read_text(errors="ignore").splitlines())
|
|
except OSError:
|
|
return None
|
|
if count >= max_lines:
|
|
rel = filepath.relative_to(root)
|
|
msg = (
|
|
f"{rel}:1:1: error: "
|
|
f"[max-file-lines] File has {count} lines (limit {max_lines})"
|
|
)
|
|
return (msg, count)
|
|
return None
|
|
|
|
|
|
def check_folder_items(
|
|
dirpath: Path, root: Path, max_items: int, excludes: list[str],
|
|
) -> tuple[str, int] | None:
|
|
try:
|
|
items = [
|
|
i for i in dirpath.iterdir()
|
|
if not i.name.startswith(".") and not _matches_any(i.name, excludes)
|
|
]
|
|
except OSError:
|
|
return None
|
|
count = len(items)
|
|
# the cap is the most you're allowed: 7 items is fine, the 8th is the straw.
|
|
if count > max_items:
|
|
anchor = _find_anchor_file(dirpath, root)
|
|
rel = dirpath.relative_to(root)
|
|
msg = (
|
|
f"{anchor}:1:1: error: "
|
|
f"[max-folder-items] Folder '{rel}' has {count} items (limit {max_items})"
|
|
)
|
|
return (msg, count)
|
|
return None
|
|
|
|
|
|
def check_nested_imports(filepath: Path, root: Path) -> list[str]:
|
|
"""Detect import statements inside function or method bodies."""
|
|
if filepath.suffix != ".py":
|
|
return []
|
|
try:
|
|
source = filepath.read_text(errors="ignore")
|
|
tree = ast.parse(source, filename=str(filepath))
|
|
except (OSError, SyntaxError):
|
|
return []
|
|
|
|
errors: list[str] = []
|
|
rel = str(filepath.relative_to(root))
|
|
|
|
def _visit(node: ast.AST, in_function: bool) -> None:
|
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
in_function = True
|
|
if in_function and isinstance(node, (ast.Import, ast.ImportFrom)):
|
|
if isinstance(node, ast.ImportFrom):
|
|
name = node.module or ""
|
|
else:
|
|
name = ", ".join(a.name for a in node.names)
|
|
errors.append(
|
|
f"{rel}:{node.lineno}:1: error: "
|
|
f"[no-nested-imports] Nested import '{name}'"
|
|
)
|
|
for child in ast.iter_child_nodes(node):
|
|
_visit(child, in_function)
|
|
|
|
_visit(tree, False)
|
|
return errors
|