59 lines
1.7 KiB
Python
59 lines
1.7 KiB
Python
# retoor <retoor@molodetz.nl>
|
|
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
|