Skip to content

Commit

Permalink
Expose check method to python
Browse files Browse the repository at this point in the history
  • Loading branch information
squiddy committed Dec 29, 2022
1 parent 1e5a3bb commit d17ee50
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 1 deletion.
1 change: 1 addition & 0 deletions ruff/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from ._ruff import check # noqa: F401
12 changes: 12 additions & 0 deletions ruff/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class Location:
row: int
column: int


class Message:
code: str
message: str
location: Location
end_location: Location

def check(contents: str | None, path: str | None) -> list(Message): ...
Empty file added ruff/py.typed
Empty file.
26 changes: 26 additions & 0 deletions ruff/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import unittest

from ruff import check


class CheckTest(unittest.TestCase):
def test_something(self):
results = check(
"""
import os
import sys
print(os.environ['GREETING'])
"""
)
self.assertEqual(len(results), 1)
self.assertEqual(results[0].code, "F401")
self.assertEqual(results[0].message, "`sys` imported but unused")
self.assertEqual(results[0].location.row, 3)
self.assertEqual(results[0].location.column, 7)
self.assertEqual(results[0].end_location.row, 3)
self.assertEqual(results[0].end_location.column, 10)


if __name__ == "__main__":
unittest.main()
110 changes: 109 additions & 1 deletion src/lib_python.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,114 @@
use std::path::Path;

use anyhow::Result;
use pyo3::prelude::*;
use pyo3::types::PyString;
use rustpython_parser::lexer::LexResult;

use crate::checks::{Check, CheckCode};
use crate::linter::check_path;
use crate::rustpython_helpers::tokenize;
use crate::settings::configuration::Configuration;
use crate::settings::options::Options;
use crate::settings::{flags, Settings};
use crate::source_code_locator::SourceCodeLocator;
use crate::source_code_style::SourceCodeStyleDetector;
use crate::{directives, packages};

#[pyclass]
#[derive(Clone)]
struct Location {
#[pyo3(get)]
row: usize,
#[pyo3(get)]
column: usize,
}

#[pyclass]
struct Message {
#[pyo3(get)]
code: CheckCode,
#[pyo3(get)]
message: String,
#[pyo3(get)]
location: Location,
#[pyo3(get)]
end_location: Location,
// TODO(rgerecke): Include fix
}

// Using `#[pyclass]` on the `CheckCode` enum is incompatible with serde,
// because this generates unsafe code.
// TODO(rgerecke): Perhaps we want to generate module-level constants instead?
impl IntoPy<PyObject> for CheckCode {
fn into_py(self, py: Python<'_>) -> PyObject {
PyString::new(py, self.as_ref()).into()
}
}

fn inner_check<P>(contents: &str, path: P) -> Result<Vec<Check>>
where
P: AsRef<Path>,
{
// let configuration = Configuration::from_options(options, path.as_ref())?;
let configuration = Configuration::default();
let settings = Settings::from_configuration(configuration, path.as_ref())?;

// Tokenize once.
let tokens: Vec<LexResult> = tokenize(contents);

// Map row and column locations to byte slices (lazily).
let locator = SourceCodeLocator::new(contents);

// Detect the current code style (lazily).
let stylist = SourceCodeStyleDetector::from_contents(contents, &locator);

// Extract the `# noqa` and `# isort: skip` directives from the source.
let directives = directives::extract_directives(&tokens, &locator, directives::Flags::empty());

// Generate checks.
let checks = check_path(
path.as_ref(),
packages::detect_package_root(path.as_ref()),
contents,
tokens,
&locator,
&stylist,
&directives,
&settings,
flags::Autofix::Enabled,
flags::Noqa::Enabled,
)?;

Ok(checks)
}

#[pyfunction]
fn check(contents: &str, path: Option<&str>) -> PyResult<Vec<Message>> {
// TODO(rgerecke): Accept settings
Ok(
inner_check(contents, path.unwrap_or("<filename>")).map(|r| {
r.iter()
.map(|check| Message {
code: check.kind.code().clone(),
message: check.kind.body(),
location: Location {
row: check.location.row(),
column: check.location.column(),
},
end_location: Location {
row: check.end_location.row(),
column: check.end_location.column(),
},
})
.collect::<Vec<_>>()
})?,
)
}

#[pymodule]
pub fn _ruff(py: Python<'_>, m: &PyModule) -> PyResult<()> {
pub fn _ruff(_: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(check, m)?)?;
m.add_class::<Message>()?;
Ok(())
}

0 comments on commit d17ee50

Please sign in to comment.