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 27, 2022
1 parent d4c4227 commit 6ff83c8
Show file tree
Hide file tree
Showing 3 changed files with 127 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
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()
101 changes: 100 additions & 1 deletion src/lib_python.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,105 @@
use std::path::Path;

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

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

#[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(contents: &str) -> Result<Vec<Check>> {
let configuration = Configuration::default();
let settings = Settings::from_configuration(configuration, Path::new("."))?;

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

// Initialize the SourceCodeLocator (which computes offsets lazily).
let locator = SourceCodeLocator::new(contents);

// 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::new("<filename>"),
None,
contents,
tokens,
&locator,
&directives,
&settings,
flags::Autofix::Enabled,
flags::Noqa::Enabled,
)?;

Ok(checks)
}

#[pyfunction]
fn check(contents: &str) -> PyResult<Vec<Message>> {
// TODO(rgerecke): Accept settings
match inner_check(contents) {
Ok(r) => Ok(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<_>>()),
Err(e) => Err(PyException::new_err(e.to_string())),
}
}

#[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 6ff83c8

Please sign in to comment.