Skip to content

Commit

Permalink
add notes
Browse files Browse the repository at this point in the history
  • Loading branch information
noklam committed Feb 3, 2025
1 parent 61d66a4 commit 7100b86
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 12 deletions.
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# learn-rust-by-building-ruff

## Rebuild with Python
Components:
- [ ] Tokenizer (Lexer)
- [ ] Parser
## The First version of Rust
These are the main files included in the first version of Rust - that power only two specific rules. While it cannot be used in a meaningful way, it provides an easy way to understand how `ruff` (or generally other linter) works without getting into all the details that are not so important for education.

main.rs
cache.rs
Expand All @@ -27,16 +25,16 @@ anyhow: Flexible concrete Error type built on std::error::Error

## Libraries I used for the Python verrsion
logging: std logging library
?: parallel computation
typer: cli argument parsing
?: serialising
ast: Python native ast library
rich: color terminal
os/pathlib: walking directory
?: error
pytest: write simple tests

## Rebuild with Rust
## Next Step
- Implement Cache
- Implement parallel processing
- Reimplement in Rust

Reference:
Blog: https://compileralchemy.substack.com/p/ruff-internals-of-a-rust-backed-python
Expand Down
Empty file removed src/ruff_in_python/cache.py
Empty file.
76 changes: 76 additions & 0 deletions src/ruff_in_python/check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from pathlib import Path
from .message import IfTuple, ImportStarUsage, Location, Message
import ast

def check_statement(path: Path, stmt: ast.stmt) -> list[Message]:
"""recursive function to parse statement"""
messages = []

match stmt:
case ast.FunctionDef() | ast.AsyncFunctionDef() | ast.ClassDef():
for body_stmt in stmt.body:
messages.extend(check_statement(path, body_stmt))
case ast.For() | ast.AsyncFor() | ast.While():
for body_stmt in stmt.body:
messages.extend(check_statement(path, body_stmt))
for orelse_stmt in stmt.orelse:
messages.extend(check_statement(path, orelse_stmt))
case (
ast.Return()
| ast.Delete()
| ast.Assign()
| ast.AugAssign()
| ast.AnnAssign()
| ast.Raise()
| ast.Assert()
| ast.Import()
| ast.Global()
| ast.Nonlocal()
| ast.Expr()
| ast.Pass()
| ast.Break()
| ast.Continue()
):
pass # Do nothing for these statement types
case ast.If():
# Check if the test is a tuple
if isinstance(stmt.test, ast.Tuple): # Assuming node is a tuple type
messages.append(
IfTuple(
filename=path,
location=Location(row=stmt.lineno, column=stmt.col_offset),
)
)
for body_stmt in stmt.body:
messages.extend(check_statement(path, body_stmt))
for orelse_stmt in stmt.orelse:
messages.extend(check_statement(path, orelse_stmt))

case ast.With() | ast.AsyncWith():
for body_stmt in stmt.body:
messages.extend(check_statement(path, body_stmt))
case ast.ImportFrom():
print(123)
for alias in stmt.names:
if alias.name == "*":
messages.append(
ImportStarUsage(
filename=path,
location=Location(row=stmt.lineno, column=stmt.col_offset),
)
)
case ast.ImportFrom() | ast.Try():
raise NotImplementedError
case _:
print("BUG!: ", type(stmt))
# Add more elif clauses for other statement types...

return messages


def check_ast(path: Path, python_ast: list[ast.stmt]) -> list[Message]:
messages = []
for stmt in python_ast:
messages.extend(check_statement(path, stmt))
print("All Messages:", messages)
return messages
Empty file removed src/ruff_in_python/lib.py
Empty file.
36 changes: 36 additions & 0 deletions src/ruff_in_python/linter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from dataclasses import dataclass
from pathlib import Path
from ruff_in_python.message import Message
from ruff_in_python.parser import parse_file
from ruff_in_python.check import check_ast


@dataclass
class CacheMetadata:
size: int
mtime: int


@dataclass
class CheckResult:
metadata: CacheMetadata
messages: list[Message]

def check_path(self, path: Path) -> list[Message]:
# TODO: skip cache

# Run the linter
python_ast = parse_file(path)
messages = check_ast(path, python_ast)

# TODO: set cache

return messages


if __name__ == "__main__":
result = CheckResult("dummy", [])
result.check_path("tests/foo.py")

result = CheckResult("dummy", [])
result.check_path("tests/bar.py")
13 changes: 11 additions & 2 deletions src/ruff_in_python/message.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
""" "Messages are the warnings that you expected to see from linter."""

from dataclasses import dataclass
from pathlib import Path
from typing import Union
from abc import ABC, abstractmethod

from rich.text import Text


@dataclass
class Location:
row: int
column: int


@dataclass
class Message(ABC):
filename: Path
Expand All @@ -35,6 +39,8 @@ def richify(self) -> Text:
text.append(f"\t{self.code}", style="bold red")
text.append(f"\t{self.body}")
return text


@dataclass
class ImportStarUsage(Message):
@property
Expand All @@ -45,6 +51,7 @@ def code(self) -> str:
def body(self) -> str:
return "Unable to detect undefined names"


@dataclass
class IfTuple(Message):
@property
Expand All @@ -55,9 +62,11 @@ def code(self) -> str:
def body(self) -> str:
return "If test is a tuple, which is always `True`"


MessageType = Union[ImportStarUsage, IfTuple]

if __name__ == "__main__":
from rich import print
m1 = IfTuple(Path("some_path"),Location(1,2))
print(m1.richify())

m1 = IfTuple(Path("some_path"), Location(1, 2))
print(m1.richify())
3 changes: 2 additions & 1 deletion src/ruff_in_python/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ def pretty_print_ast(self):

def parse_file(path: Path):
with open(path) as f:
return ast.parse(f.read())
res = ast.parse(f.read())
return res.body
3 changes: 2 additions & 1 deletion tests/bar.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
if (1, 2):
pass
if (3, 4):
pass

for _ in range(5):
if True:
Expand Down

0 comments on commit 7100b86

Please sign in to comment.