From 6ff83c8b0fcd48c01e102c24c7211245c9abafb6 Mon Sep 17 00:00:00 2001 From: Reiner Gerecke Date: Tue, 27 Dec 2022 16:59:54 +0100 Subject: [PATCH] Expose check method to python Refs: #659 --- ruff/__init__.py | 1 + ruff/tests.py | 26 ++++++++++++ src/lib_python.rs | 101 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 ruff/tests.py diff --git a/ruff/__init__.py b/ruff/__init__.py index e69de29bb2d1d6..7aafa599570b99 100644 --- a/ruff/__init__.py +++ b/ruff/__init__.py @@ -0,0 +1 @@ +from ._ruff import check # noqa: F401 \ No newline at end of file diff --git a/ruff/tests.py b/ruff/tests.py new file mode 100644 index 00000000000000..e5372863ac8e80 --- /dev/null +++ b/ruff/tests.py @@ -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() diff --git a/src/lib_python.rs b/src/lib_python.rs index ea8e31947b7d69..8caada07d94d43 100644 --- a/src/lib_python.rs +++ b/src/lib_python.rs @@ -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 for CheckCode { + fn into_py(self, py: Python<'_>) -> PyObject { + PyString::new(py, self.as_ref()).into() + } +} + +fn inner_check(contents: &str) -> Result> { + let configuration = Configuration::default(); + let settings = Settings::from_configuration(configuration, Path::new("."))?; + + // Tokenize once. + let tokens: Vec = 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(""), + None, + contents, + tokens, + &locator, + &directives, + &settings, + flags::Autofix::Enabled, + flags::Noqa::Enabled, + )?; + + Ok(checks) +} + +#[pyfunction] +fn check(contents: &str) -> PyResult> { + // 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::>()), + 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::()?; Ok(()) }