# retoor import ast from pathlib import Path import sqlglot from sqlglot.errors import ParseError from molodetz import config SQL_PREFIXES = ("SELECT ", "INSERT ", "UPDATE ", "DELETE ", "CREATE ", "PRAGMA ", "WITH ", "ANALYZE") def _joined_text(node): parts = [] for value in node.values: if isinstance(value, ast.Constant): parts.append(str(value.value)) else: parts.append("lint_placeholder") return "".join(parts) def _literal_sql(node): if isinstance(node, ast.JoinedStr): raw = _joined_text(node) elif isinstance(node, ast.Constant) and isinstance(node.value, str): raw = node.value else: return None text = " ".join(raw.split()) return text if text.upper().startswith(SQL_PREFIXES) else None def collect_sql(root=None): root = Path(root or config.PKG_ROOT) found = [] for path in sorted(root.rglob("*.py")): tree = ast.parse(path.read_text(encoding="utf-8")) inner = {id(value) for node in ast.walk(tree) if isinstance(node, ast.JoinedStr) for value in node.values} for node in ast.walk(tree): if id(node) in inner: continue sql = _literal_sql(node) if sql: found.append((path, node.lineno, sql)) return found def lint(root=None): problems = [] for path, line, sql in collect_sql(root): if sql.upper().startswith(("PRAGMA", "ANALYZE")): continue try: sqlglot.parse_one(sql, read="sqlite") except ParseError as exc: problems.append(f"{path}:{line}: {str(exc).splitlines()[0]}") return problems