mirror of https://github.com/astral-sh/ruff
Add TOML files to SourceType
This commit is contained in:
parent
2893a9f6b5
commit
a33883d51a
|
|
@ -268,9 +268,12 @@ pub fn check_path(
|
||||||
const MAX_ITERATIONS: usize = 100;
|
const MAX_ITERATIONS: usize = 100;
|
||||||
|
|
||||||
/// Add any missing `# noqa` pragmas to the source code at the given `Path`.
|
/// Add any missing `# noqa` pragmas to the source code at the given `Path`.
|
||||||
pub fn add_noqa_to_path(path: &Path, package: Option<&Path>, settings: &Settings) -> Result<usize> {
|
pub fn add_noqa_to_path(
|
||||||
let source_type = PySourceType::from(path);
|
path: &Path,
|
||||||
|
package: Option<&Path>,
|
||||||
|
source_type: PySourceType,
|
||||||
|
settings: &Settings,
|
||||||
|
) -> Result<usize> {
|
||||||
// Read the file from disk.
|
// Read the file from disk.
|
||||||
let contents = std::fs::read_to_string(path)?;
|
let contents = std::fs::read_to_string(path)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ use crate::jupyter::Notebook;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, is_macro::Is)]
|
#[derive(Clone, Debug, PartialEq, is_macro::Is)]
|
||||||
pub enum SourceKind {
|
pub enum SourceKind {
|
||||||
|
/// The source contains Python source code.
|
||||||
Python(String),
|
Python(String),
|
||||||
|
/// The source contains a Jupyter notebook.
|
||||||
IpyNotebook(Notebook),
|
IpyNotebook(Notebook),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use rayon::prelude::*;
|
||||||
|
|
||||||
use ruff::linter::add_noqa_to_path;
|
use ruff::linter::add_noqa_to_path;
|
||||||
use ruff::warn_user_once;
|
use ruff::warn_user_once;
|
||||||
use ruff_python_stdlib::path::{is_jupyter_notebook, is_project_toml};
|
use ruff_python_ast::{PySourceType, SourceType};
|
||||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig};
|
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig};
|
||||||
|
|
||||||
use crate::args::Overrides;
|
use crate::args::Overrides;
|
||||||
|
|
@ -46,15 +46,17 @@ pub(crate) fn add_noqa(
|
||||||
.flatten()
|
.flatten()
|
||||||
.filter_map(|entry| {
|
.filter_map(|entry| {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if is_project_toml(path) || is_jupyter_notebook(path) {
|
let SourceType::Python(source_type @ (PySourceType::Python | PySourceType::Stub)) =
|
||||||
|
SourceType::from(path)
|
||||||
|
else {
|
||||||
return None;
|
return None;
|
||||||
}
|
};
|
||||||
let package = path
|
let package = path
|
||||||
.parent()
|
.parent()
|
||||||
.and_then(|parent| package_roots.get(parent))
|
.and_then(|parent| package_roots.get(parent))
|
||||||
.and_then(|package| *package);
|
.and_then(|package| *package);
|
||||||
let settings = resolver.resolve(path, pyproject_config);
|
let settings = resolver.resolve(path, pyproject_config);
|
||||||
match add_noqa_to_path(path, package, settings) {
|
match add_noqa_to_path(path, package, source_type, settings) {
|
||||||
Ok(count) => Some(count),
|
Ok(count) => Some(count),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to add noqa to {}: {e}", path.display());
|
error!("Failed to add noqa to {}: {e}", path.display());
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use tracing::{span, Level};
|
||||||
use ruff::fs;
|
use ruff::fs;
|
||||||
use ruff::warn_user_once;
|
use ruff::warn_user_once;
|
||||||
use ruff_formatter::LineWidth;
|
use ruff_formatter::LineWidth;
|
||||||
use ruff_python_ast::PySourceType;
|
use ruff_python_ast::{PySourceType, SourceType};
|
||||||
use ruff_python_formatter::{format_module, FormatModuleError, PyFormatOptions};
|
use ruff_python_formatter::{format_module, FormatModuleError, PyFormatOptions};
|
||||||
use ruff_workspace::resolver::python_files_in_path;
|
use ruff_workspace::resolver::python_files_in_path;
|
||||||
|
|
||||||
|
|
@ -37,23 +37,21 @@ pub(crate) fn format(cli: &Arguments, overrides: &Overrides) -> Result<ExitStatu
|
||||||
|
|
||||||
let result = paths
|
let result = paths
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|dir_entry| {
|
.map(|entry| {
|
||||||
let dir_entry = dir_entry?;
|
let entry = entry?;
|
||||||
let path = dir_entry.path();
|
let path = entry.path();
|
||||||
let source_type = PySourceType::from(path);
|
if matches!(
|
||||||
if !(source_type.is_python() || source_type.is_stub())
|
SourceType::from(path),
|
||||||
|| path
|
SourceType::Python(PySourceType::Python | PySourceType::Stub)
|
||||||
.extension()
|
) {
|
||||||
.is_some_and(|extension| extension == "toml")
|
|
||||||
{
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let line_length = resolver.resolve(path, &pyproject_config).line_length;
|
let line_length = resolver.resolve(path, &pyproject_config).line_length;
|
||||||
let options = PyFormatOptions::from_extension(path)
|
let options = PyFormatOptions::from_extension(path)
|
||||||
.with_line_width(LineWidth::from(NonZeroU16::from(line_length)));
|
.with_line_width(LineWidth::from(NonZeroU16::from(line_length)));
|
||||||
|
|
||||||
format_path(path, options)
|
format_path(path, options)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.map(|result| {
|
.map(|result| {
|
||||||
result.map_err(|err| {
|
result.map_err(|err| {
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,7 @@ use ruff::{fs, IOError};
|
||||||
use ruff_diagnostics::Diagnostic;
|
use ruff_diagnostics::Diagnostic;
|
||||||
use ruff_macros::CacheKey;
|
use ruff_macros::CacheKey;
|
||||||
use ruff_python_ast::imports::ImportMap;
|
use ruff_python_ast::imports::ImportMap;
|
||||||
use ruff_python_ast::PySourceType;
|
use ruff_python_ast::{PySourceType, SourceType, TomlSourceType};
|
||||||
use ruff_python_stdlib::path::is_project_toml;
|
|
||||||
use ruff_source_file::{LineIndex, SourceCode, SourceFileBuilder};
|
use ruff_source_file::{LineIndex, SourceCode, SourceFileBuilder};
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
|
|
||||||
|
|
@ -228,8 +227,8 @@ pub(crate) fn lint_path(
|
||||||
|
|
||||||
debug!("Checking: {}", path.display());
|
debug!("Checking: {}", path.display());
|
||||||
|
|
||||||
// We have to special case this here since the Python tokenizer doesn't work with TOML.
|
let source_type = match SourceType::from(path) {
|
||||||
if is_project_toml(path) {
|
SourceType::Toml(TomlSourceType::Pyproject) => {
|
||||||
let messages = if settings
|
let messages = if settings
|
||||||
.lib
|
.lib
|
||||||
.rules
|
.rules
|
||||||
|
|
@ -252,12 +251,12 @@ pub(crate) fn lint_path(
|
||||||
..Diagnostics::default()
|
..Diagnostics::default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
SourceType::Toml(_) => return Ok(Diagnostics::default()),
|
||||||
|
SourceType::Python(source_type) => source_type,
|
||||||
|
};
|
||||||
|
|
||||||
// Extract the sources from the file.
|
// Extract the sources from the file.
|
||||||
let LintSources {
|
let LintSource(source_kind) = match LintSource::try_from_path(path, source_type) {
|
||||||
source_type,
|
|
||||||
source_kind,
|
|
||||||
} = match LintSources::try_from_path(path) {
|
|
||||||
Ok(sources) => sources,
|
Ok(sources) => sources,
|
||||||
Err(SourceExtractionError::Io(err)) => {
|
Err(SourceExtractionError::Io(err)) => {
|
||||||
return Ok(Diagnostics::from_io_error(&err, path, &settings.lib));
|
return Ok(Diagnostics::from_io_error(&err, path, &settings.lib));
|
||||||
|
|
@ -438,11 +437,14 @@ pub(crate) fn lint_stdin(
|
||||||
noqa: flags::Noqa,
|
noqa: flags::Noqa,
|
||||||
autofix: flags::FixMode,
|
autofix: flags::FixMode,
|
||||||
) -> Result<Diagnostics> {
|
) -> Result<Diagnostics> {
|
||||||
|
// TODO(charlie): Support `pyproject.toml`.
|
||||||
|
let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else {
|
||||||
|
return Ok(Diagnostics::default());
|
||||||
|
};
|
||||||
|
|
||||||
// Extract the sources from the file.
|
// Extract the sources from the file.
|
||||||
let LintSources {
|
let LintSource(source_kind) =
|
||||||
source_type,
|
match LintSource::try_from_source_code(contents, path, source_type) {
|
||||||
source_kind,
|
|
||||||
} = match LintSources::try_from_source_code(contents, path) {
|
|
||||||
Ok(sources) => sources,
|
Ok(sources) => sources,
|
||||||
Err(SourceExtractionError::Io(err)) => {
|
Err(SourceExtractionError::Io(err)) => {
|
||||||
// SAFETY: An `io::Error` can only occur if we're reading from a path.
|
// SAFETY: An `io::Error` can only occur if we're reading from a path.
|
||||||
|
|
@ -554,58 +556,40 @@ pub(crate) fn lint_stdin(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct LintSources {
|
struct LintSource(SourceKind);
|
||||||
/// The "type" of source code, e.g. `.py`, `.pyi`, `.ipynb`, etc.
|
|
||||||
|
impl LintSource {
|
||||||
|
/// Extract the lint [`LintSource`] from the given file path.
|
||||||
|
fn try_from_path(
|
||||||
|
path: &Path,
|
||||||
source_type: PySourceType,
|
source_type: PySourceType,
|
||||||
/// The "kind" of source, e.g. Python file, Jupyter Notebook, etc.
|
) -> Result<LintSource, SourceExtractionError> {
|
||||||
source_kind: SourceKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LintSources {
|
|
||||||
/// Extract the lint [`LintSources`] from the given file path.
|
|
||||||
fn try_from_path(path: &Path) -> Result<LintSources, SourceExtractionError> {
|
|
||||||
let source_type = PySourceType::from(path);
|
|
||||||
|
|
||||||
// Read the file from disk.
|
|
||||||
if source_type.is_ipynb() {
|
if source_type.is_ipynb() {
|
||||||
let notebook = notebook_from_path(path).map_err(SourceExtractionError::Diagnostics)?;
|
let notebook = notebook_from_path(path).map_err(SourceExtractionError::Diagnostics)?;
|
||||||
let source_kind = SourceKind::IpyNotebook(notebook);
|
let source_kind = SourceKind::IpyNotebook(notebook);
|
||||||
Ok(LintSources {
|
Ok(LintSource(source_kind))
|
||||||
source_type,
|
|
||||||
source_kind,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
// This is tested by ruff_cli integration test `unreadable_file`
|
// This is tested by ruff_cli integration test `unreadable_file`
|
||||||
let contents = std::fs::read_to_string(path).map_err(SourceExtractionError::Io)?;
|
let contents = std::fs::read_to_string(path).map_err(SourceExtractionError::Io)?;
|
||||||
Ok(LintSources {
|
Ok(LintSource(SourceKind::Python(contents)))
|
||||||
source_type,
|
|
||||||
source_kind: SourceKind::Python(contents),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract the lint [`LintSources`] from the raw string contents, optionally accompanied by a
|
/// Extract the lint [`LintSource`] from the raw string contents, optionally accompanied by a
|
||||||
/// file path indicating the path to the file from which the contents were read. If provided,
|
/// file path indicating the path to the file from which the contents were read. If provided,
|
||||||
/// the file path should be used for diagnostics, but not for reading the file from disk.
|
/// the file path should be used for diagnostics, but not for reading the file from disk.
|
||||||
fn try_from_source_code(
|
fn try_from_source_code(
|
||||||
source_code: String,
|
source_code: String,
|
||||||
path: Option<&Path>,
|
path: Option<&Path>,
|
||||||
) -> Result<LintSources, SourceExtractionError> {
|
source_type: PySourceType,
|
||||||
let source_type = path.map(PySourceType::from).unwrap_or_default();
|
) -> Result<LintSource, SourceExtractionError> {
|
||||||
|
|
||||||
if source_type.is_ipynb() {
|
if source_type.is_ipynb() {
|
||||||
let notebook = notebook_from_source_code(&source_code, path)
|
let notebook = notebook_from_source_code(&source_code, path)
|
||||||
.map_err(SourceExtractionError::Diagnostics)?;
|
.map_err(SourceExtractionError::Diagnostics)?;
|
||||||
let source_kind = SourceKind::IpyNotebook(notebook);
|
let source_kind = SourceKind::IpyNotebook(notebook);
|
||||||
Ok(LintSources {
|
Ok(LintSource(source_kind))
|
||||||
source_type,
|
|
||||||
source_kind,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
Ok(LintSources {
|
Ok(LintSource(SourceKind::Python(source_code)))
|
||||||
source_type,
|
|
||||||
source_kind: SourceKind::Python(source_code),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,32 +24,63 @@ pub mod types;
|
||||||
pub mod visitor;
|
pub mod visitor;
|
||||||
pub mod whitespace;
|
pub mod whitespace;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
/// The type of a source file.
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)]
|
||||||
pub enum PySourceType {
|
pub enum SourceType {
|
||||||
#[default]
|
/// The file contains Python source code.
|
||||||
Python,
|
Python(PySourceType),
|
||||||
Stub,
|
/// The file contains TOML.
|
||||||
Ipynb,
|
Toml(TomlSourceType),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PySourceType {
|
impl Default for SourceType {
|
||||||
pub const fn is_python(&self) -> bool {
|
fn default() -> Self {
|
||||||
matches!(self, PySourceType::Python)
|
Self::Python(PySourceType::Python)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn is_stub(&self) -> bool {
|
impl From<&Path> for SourceType {
|
||||||
matches!(self, PySourceType::Stub)
|
fn from(path: &Path) -> Self {
|
||||||
|
match path.file_name() {
|
||||||
|
Some(filename) if filename == "pyproject.toml" => Self::Toml(TomlSourceType::Pyproject),
|
||||||
|
Some(filename) if filename == "Pipfile" => Self::Toml(TomlSourceType::Pipfile),
|
||||||
|
Some(filename) if filename == "poetry.lock" => Self::Toml(TomlSourceType::Poetry),
|
||||||
|
_ => match path.extension() {
|
||||||
|
Some(ext) if ext == "toml" => Self::Toml(TomlSourceType::Unrecognized),
|
||||||
|
_ => Self::Python(PySourceType::from(path)),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn is_ipynb(&self) -> bool {
|
#[derive(Clone, Copy, Debug, PartialEq, is_macro::Is)]
|
||||||
matches!(self, PySourceType::Ipynb)
|
pub enum TomlSourceType {
|
||||||
}
|
/// The source is a `pyproject.toml`.
|
||||||
|
Pyproject,
|
||||||
|
/// The source is a `Pipfile`.
|
||||||
|
Pipfile,
|
||||||
|
/// The source is a `poetry.lock`.
|
||||||
|
Poetry,
|
||||||
|
/// The source is an unrecognized TOML file.
|
||||||
|
Unrecognized,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, is_macro::Is)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub enum PySourceType {
|
||||||
|
/// The source is a Python file (`.py`).
|
||||||
|
#[default]
|
||||||
|
Python,
|
||||||
|
/// The source is a Python stub file (`.pyi`).
|
||||||
|
Stub,
|
||||||
|
/// The source is a Jupyter notebook (`.ipynb`).
|
||||||
|
Ipynb,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Path> for PySourceType {
|
impl From<&Path> for PySourceType {
|
||||||
fn from(path: &Path) -> Self {
|
fn from(path: &Path) -> Self {
|
||||||
match path.extension() {
|
match path.extension() {
|
||||||
|
Some(ext) if ext == "py" => PySourceType::Python,
|
||||||
Some(ext) if ext == "pyi" => PySourceType::Stub,
|
Some(ext) if ext == "pyi" => PySourceType::Stub,
|
||||||
Some(ext) if ext == "ipynb" => PySourceType::Ipynb,
|
Some(ext) if ext == "ipynb" => PySourceType::Ipynb,
|
||||||
_ => PySourceType::Python,
|
_ => PySourceType::Python,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
/// Return `true` if the [`Path`] appears to be that of a Python file.
|
|
||||||
pub fn is_python_file(path: &Path) -> bool {
|
|
||||||
path.extension()
|
|
||||||
.is_some_and(|ext| ext == "py" || ext == "pyi")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return `true` if the [`Path`] is named `pyproject.toml`.
|
/// Return `true` if the [`Path`] is named `pyproject.toml`.
|
||||||
pub fn is_project_toml(path: &Path) -> bool {
|
pub fn is_pyproject_toml(path: &Path) -> bool {
|
||||||
path.file_name()
|
path.file_name()
|
||||||
.is_some_and(|name| name == "pyproject.toml")
|
.is_some_and(|name| name == "pyproject.toml")
|
||||||
}
|
}
|
||||||
|
|
@ -26,22 +20,7 @@ pub fn is_jupyter_notebook(path: &Path) -> bool {
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::path::{is_jupyter_notebook, is_python_file};
|
use crate::path::is_jupyter_notebook;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn inclusions() {
|
|
||||||
let path = Path::new("foo/bar/baz.py");
|
|
||||||
assert!(is_python_file(path));
|
|
||||||
|
|
||||||
let path = Path::new("foo/bar/baz.pyi");
|
|
||||||
assert!(is_python_file(path));
|
|
||||||
|
|
||||||
let path = Path::new("foo/bar/baz.js");
|
|
||||||
assert!(!is_python_file(path));
|
|
||||||
|
|
||||||
let path = Path::new("foo/bar/baz");
|
|
||||||
assert!(!is_python_file(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_jupyter_notebook() {
|
fn test_is_jupyter_notebook() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue