Harmonise methods for distinguishing different Python source types (#13682)

This commit is contained in:
Alex Waygood 2024-10-09 14:18:52 +01:00 committed by GitHub
parent b9827a4122
commit 5b4afd30ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 40 additions and 53 deletions

1
Cargo.lock generated
View file

@ -2466,7 +2466,6 @@ dependencies = [
"ruff_python_codegen", "ruff_python_codegen",
"ruff_python_formatter", "ruff_python_formatter",
"ruff_python_parser", "ruff_python_parser",
"ruff_python_stdlib",
"ruff_python_trivia", "ruff_python_trivia",
"ruff_workspace", "ruff_workspace",
"schemars", "schemars",

View file

@ -20,7 +20,6 @@ ruff_python_ast = { workspace = true }
ruff_python_codegen = { workspace = true } ruff_python_codegen = { workspace = true }
ruff_python_formatter = { workspace = true } ruff_python_formatter = { workspace = true }
ruff_python_parser = { workspace = true } ruff_python_parser = { workspace = true }
ruff_python_stdlib = { workspace = true }
ruff_python_trivia = { workspace = true } ruff_python_trivia = { workspace = true }
ruff_workspace = { workspace = true, features = ["schemars"] } ruff_workspace = { workspace = true, features = ["schemars"] }

View file

@ -6,8 +6,8 @@ use std::path::PathBuf;
use anyhow::Result; use anyhow::Result;
use ruff_python_ast::PySourceType;
use ruff_python_codegen::round_trip; use ruff_python_codegen::round_trip;
use ruff_python_stdlib::path::is_jupyter_notebook;
#[derive(clap::Args)] #[derive(clap::Args)]
pub(crate) struct Args { pub(crate) struct Args {
@ -18,7 +18,7 @@ pub(crate) struct Args {
pub(crate) fn main(args: &Args) -> Result<()> { pub(crate) fn main(args: &Args) -> Result<()> {
let path = args.file.as_path(); let path = args.file.as_path();
if is_jupyter_notebook(path) { if PySourceType::from(path).is_ipynb() {
println!("{}", ruff_notebook::round_trip(path)?); println!("{}", ruff_notebook::round_trip(path)?);
} else { } else {
let contents = fs::read_to_string(&args.file)?; let contents = fs::read_to_string(&args.file)?;

View file

@ -2,6 +2,7 @@ use std::path::Path;
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::PySourceType;
use ruff_python_stdlib::path::is_module_file; use ruff_python_stdlib::path::is_module_file;
use ruff_python_stdlib::sys::is_known_standard_library; use ruff_python_stdlib::sys::is_known_standard_library;
use ruff_text_size::TextRange; use ruff_text_size::TextRange;
@ -42,10 +43,7 @@ pub(crate) fn builtin_module_shadowing(
allowed_modules: &[String], allowed_modules: &[String],
target_version: PythonVersion, target_version: PythonVersion,
) -> Option<Diagnostic> { ) -> Option<Diagnostic> {
if !path if !PySourceType::try_from_path(path).is_some_and(PySourceType::is_py_file_or_stub) {
.extension()
.is_some_and(|ext| ext == "py" || ext == "pyi")
{
return None; return None;
} }

View file

@ -2,6 +2,7 @@ use std::path::{Path, PathBuf};
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::PySourceType;
use ruff_python_trivia::CommentRanges; use ruff_python_trivia::CommentRanges;
use ruff_source_file::Locator; use ruff_source_file::Locator;
use ruff_text_size::{TextRange, TextSize}; use ruff_text_size::{TextRange, TextSize};
@ -51,7 +52,7 @@ pub(crate) fn implicit_namespace_package(
) -> Option<Diagnostic> { ) -> Option<Diagnostic> {
if package.is_none() if package.is_none()
// Ignore non-`.py` files, which don't require an `__init__.py`. // Ignore non-`.py` files, which don't require an `__init__.py`.
&& path.extension().is_some_and( |ext| ext == "py") && PySourceType::try_from_path(path).is_some_and(PySourceType::is_py_file)
// Ignore any files that are direct children of the project root. // Ignore any files that are direct children of the project root.
&& !path && !path
.parent() .parent()

View file

@ -3,6 +3,7 @@ use std::path::Path;
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::PySourceType;
use ruff_python_stdlib::identifiers::{is_migration_name, is_module_name}; use ruff_python_stdlib::identifiers::{is_migration_name, is_module_name};
use ruff_python_stdlib::path::is_module_file; use ruff_python_stdlib::path::is_module_file;
use ruff_text_size::TextRange; use ruff_text_size::TextRange;
@ -53,10 +54,7 @@ pub(crate) fn invalid_module_name(
package: Option<&Path>, package: Option<&Path>,
ignore_names: &IgnoreNames, ignore_names: &IgnoreNames,
) -> Option<Diagnostic> { ) -> Option<Diagnostic> {
if !path if !PySourceType::try_from_path(path).is_some_and(PySourceType::is_py_file_or_stub) {
.extension()
.is_some_and(|ext| ext == "py" || ext == "pyi")
{
return None; return None;
} }

View file

@ -68,7 +68,7 @@ pub enum TomlSourceType {
Unrecognized, Unrecognized,
} }
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, is_macro::Is)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PySourceType { pub enum PySourceType {
/// The source is a Python file (`.py`). /// The source is a Python file (`.py`).
@ -99,13 +99,33 @@ impl PySourceType {
Some(ty) Some(ty)
} }
pub fn try_from_path(path: impl AsRef<Path>) -> Option<Self> {
path.as_ref()
.extension()
.and_then(OsStr::to_str)
.and_then(Self::try_from_extension)
}
pub const fn is_py_file(self) -> bool {
matches!(self, Self::Python)
}
pub const fn is_stub(self) -> bool {
matches!(self, Self::Stub)
}
pub const fn is_py_file_or_stub(self) -> bool {
matches!(self, Self::Python | Self::Stub)
}
pub const fn is_ipynb(self) -> bool {
matches!(self, Self::Ipynb)
}
} }
impl<P: AsRef<Path>> From<P> for PySourceType { impl<P: AsRef<Path>> From<P> for PySourceType {
fn from(path: P) -> Self { fn from(path: P) -> Self {
path.as_ref() Self::try_from_path(path).unwrap_or_default()
.extension()
.and_then(OsStr::to_str)
.map_or(Self::Python, Self::from_extension)
} }
} }

View file

@ -5,8 +5,7 @@ use rustc_hash::FxHashMap;
use ruff_python_ast::helpers::from_relative_import; use ruff_python_ast::helpers::from_relative_import;
use ruff_python_ast::name::{QualifiedName, UnqualifiedName}; use ruff_python_ast::name::{QualifiedName, UnqualifiedName};
use ruff_python_ast::{self as ast, Expr, ExprContext, Operator, Stmt}; use ruff_python_ast::{self as ast, Expr, ExprContext, Operator, PySourceType, Stmt};
use ruff_python_stdlib::path::is_python_stub_file;
use ruff_text_size::{Ranged, TextRange, TextSize}; use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::binding::{ use crate::binding::{
@ -2246,7 +2245,7 @@ bitflags! {
impl SemanticModelFlags { impl SemanticModelFlags {
pub fn new(path: &Path) -> Self { pub fn new(path: &Path) -> Self {
if is_python_stub_file(path) { if PySourceType::from(path).is_stub() {
Self::STUB_FILE Self::STUB_FILE
} else { } else {
Self::default() Self::default()

View file

@ -1,3 +1,4 @@
use std::ffi::OsStr;
use std::path::Path; use std::path::Path;
/// Return `true` if the [`Path`] is named `pyproject.toml`. /// Return `true` if the [`Path`] is named `pyproject.toml`.
@ -6,38 +7,10 @@ pub fn is_pyproject_toml(path: &Path) -> bool {
.is_some_and(|name| name == "pyproject.toml") .is_some_and(|name| name == "pyproject.toml")
} }
/// Return `true` if the [`Path`] appears to be that of a Python interface definition file (`.pyi`).
pub fn is_python_stub_file(path: &Path) -> bool {
path.extension().is_some_and(|ext| ext == "pyi")
}
/// Return `true` if the [`Path`] appears to be that of a Jupyter notebook (`.ipynb`).
pub fn is_jupyter_notebook(path: &Path) -> bool {
path.extension().is_some_and(|ext| ext == "ipynb")
}
/// Return `true` if a [`Path`] should use the name of its parent directory as its module name. /// Return `true` if a [`Path`] should use the name of its parent directory as its module name.
pub fn is_module_file(path: &Path) -> bool { pub fn is_module_file(path: &Path) -> bool {
path.file_name().is_some_and(|file_name| { matches!(
file_name == "__init__.py" path.file_name().and_then(OsStr::to_str),
|| file_name == "__init__.pyi" Some("__init__.py" | "__init__.pyi" | "__main__.py" | "__main__.pyi")
|| file_name == "__main__.py" )
|| file_name == "__main__.pyi"
})
}
#[cfg(test)]
mod tests {
use std::path::Path;
use crate::path::is_jupyter_notebook;
#[test]
fn test_is_jupyter_notebook() {
let path = Path::new("foo/bar/baz.ipynb");
assert!(is_jupyter_notebook(path));
let path = Path::new("foo/bar/baz.py");
assert!(!is_jupyter_notebook(path));
}
} }