Skip to content

Commit 66566f6

Browse files
committed
feat: Tiny linter like flake8 (Supported by Codex)
1 parent a870de7 commit 66566f6

2 files changed

Lines changed: 77 additions & 0 deletions

File tree

kotoha/first_linter.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import argparse
2+
import ast
3+
import importlib
4+
from collections.abc import Iterable
5+
from pathlib import Path
6+
7+
8+
def _load_rule(rule_spec: str) -> type[ast.NodeVisitor]:
9+
"""Load a rule class from MODULE:CLASS specification."""
10+
module_name, class_name = rule_spec.split(":", 1)
11+
module = importlib.import_module(module_name)
12+
return getattr(module, class_name)
13+
14+
15+
def _run_rules_for_file(
16+
filename: str, source: str, rule_classes: list[type[ast.NodeVisitor]]
17+
) -> list[str]:
18+
tree = ast.parse(source, filename=filename)
19+
total_violations = []
20+
for rule_cls in rule_classes:
21+
visitor = rule_cls()
22+
visitor.visit(tree)
23+
total_violations.extend((filename, *v) for v in visitor.violations)
24+
return total_violations
25+
26+
27+
def run(
28+
files: Iterable[Path], rule_classes: Iterable[type[ast.NodeVisitor]]
29+
) -> list[str]:
30+
rule_classes = list(rule_classes)
31+
all_violations = []
32+
for file in files:
33+
source = file.read_text("utf8")
34+
all_violations.extend(_run_rules_for_file(file.name, source, rule_classes))
35+
return all_violations
36+
37+
38+
def build_parser() -> argparse.ArgumentParser:
39+
parser = argparse.ArgumentParser(description="Tiny AST linter")
40+
parser.add_argument("files", type=Path, nargs="+", help="Python files to lint")
41+
parser.add_argument(
42+
"--rule",
43+
action="append",
44+
required=True,
45+
metavar="MODULE:CLASS",
46+
help="Rule class to load (repeatable)",
47+
)
48+
return parser
49+
50+
51+
def main(argv: list[str] | None = None) -> None:
52+
args = build_parser().parse_args(argv)
53+
54+
rule_classes = [_load_rule(rule_spec) for rule_spec in args.rule]
55+
violations = run(args.files, rule_classes)
56+
57+
for violation in violations:
58+
print(f"{violation[0]}:{violation[1]}:{violation[2]}: {violation[3]}")
59+
return 1 if violations else 0
60+
61+
62+
if __name__ == "__main__":
63+
raise SystemExit(main())

kotoha/kotoha_visitor.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import ast
2+
3+
4+
class ArgumentListTypeHintChecker(ast.NodeVisitor):
5+
def __init__(self):
6+
self.violations = []
7+
8+
def visit_arg(self, node):
9+
annotation = node.annotation
10+
if annotation.value.id == "list":
11+
as_is = f"{node.arg}: {annotation.value.id}[{annotation.slice.id}]"
12+
message = f"Fix type hint `{as_is}`"
13+
self.violations.append((node.lineno, node.col_offset, message))
14+
self.generic_visit(node)

0 commit comments

Comments
 (0)