Skip to content

Commit

Permalink
Harmonise methods for distinguishing different Python source types (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexWaygood authored Oct 9, 2024
1 parent b9827a4 commit 5b4afd3
Show file tree
Hide file tree
Showing 9 changed files with 40 additions and 53 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion crates/ruff_dev/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ ruff_python_ast = { workspace = true }
ruff_python_codegen = { workspace = true }
ruff_python_formatter = { workspace = true }
ruff_python_parser = { workspace = true }
ruff_python_stdlib = { workspace = true }
ruff_python_trivia = { workspace = true }
ruff_workspace = { workspace = true, features = ["schemars"] }

Expand Down
4 changes: 2 additions & 2 deletions crates/ruff_dev/src/round_trip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use std::path::PathBuf;

use anyhow::Result;

use ruff_python_ast::PySourceType;
use ruff_python_codegen::round_trip;
use ruff_python_stdlib::path::is_jupyter_notebook;

#[derive(clap::Args)]
pub(crate) struct Args {
Expand All @@ -18,7 +18,7 @@ pub(crate) struct Args {

pub(crate) fn main(args: &Args) -> Result<()> {
let path = args.file.as_path();
if is_jupyter_notebook(path) {
if PySourceType::from(path).is_ipynb() {
println!("{}", ruff_notebook::round_trip(path)?);
} else {
let contents = fs::read_to_string(&args.file)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::path::Path;

use ruff_diagnostics::{Diagnostic, 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::sys::is_known_standard_library;
use ruff_text_size::TextRange;
Expand Down Expand Up @@ -42,10 +43,7 @@ pub(crate) fn builtin_module_shadowing(
allowed_modules: &[String],
target_version: PythonVersion,
) -> Option<Diagnostic> {
if !path
.extension()
.is_some_and(|ext| ext == "py" || ext == "pyi")
{
if !PySourceType::try_from_path(path).is_some_and(PySourceType::is_py_file_or_stub) {
return None;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::path::{Path, PathBuf};

use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::PySourceType;
use ruff_python_trivia::CommentRanges;
use ruff_source_file::Locator;
use ruff_text_size::{TextRange, TextSize};
Expand Down Expand Up @@ -51,7 +52,7 @@ pub(crate) fn implicit_namespace_package(
) -> Option<Diagnostic> {
if package.is_none()
// 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.
&& !path
.parent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::path::Path;

use ruff_diagnostics::{Diagnostic, 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::path::is_module_file;
use ruff_text_size::TextRange;
Expand Down Expand Up @@ -53,10 +54,7 @@ pub(crate) fn invalid_module_name(
package: Option<&Path>,
ignore_names: &IgnoreNames,
) -> Option<Diagnostic> {
if !path
.extension()
.is_some_and(|ext| ext == "py" || ext == "pyi")
{
if !PySourceType::try_from_path(path).is_some_and(PySourceType::is_py_file_or_stub) {
return None;
}

Expand Down
30 changes: 25 additions & 5 deletions crates/ruff_python_ast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub enum TomlSourceType {
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))]
pub enum PySourceType {
/// The source is a Python file (`.py`).
Expand Down Expand Up @@ -99,13 +99,33 @@ impl PySourceType {

Some(ty)
}
}

impl<P: AsRef<Path>> From<P> for PySourceType {
fn from(path: P) -> Self {
pub fn try_from_path(path: impl AsRef<Path>) -> Option<Self> {
path.as_ref()
.extension()
.and_then(OsStr::to_str)
.map_or(Self::Python, Self::from_extension)
.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 {
fn from(path: P) -> Self {
Self::try_from_path(path).unwrap_or_default()
}
}
5 changes: 2 additions & 3 deletions crates/ruff_python_semantic/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ use rustc_hash::FxHashMap;

use ruff_python_ast::helpers::from_relative_import;
use ruff_python_ast::name::{QualifiedName, UnqualifiedName};
use ruff_python_ast::{self as ast, Expr, ExprContext, Operator, Stmt};
use ruff_python_stdlib::path::is_python_stub_file;
use ruff_python_ast::{self as ast, Expr, ExprContext, Operator, PySourceType, Stmt};
use ruff_text_size::{Ranged, TextRange, TextSize};

use crate::binding::{
Expand Down Expand Up @@ -2246,7 +2245,7 @@ bitflags! {

impl SemanticModelFlags {
pub fn new(path: &Path) -> Self {
if is_python_stub_file(path) {
if PySourceType::from(path).is_stub() {
Self::STUB_FILE
} else {
Self::default()
Expand Down
37 changes: 5 additions & 32 deletions crates/ruff_python_stdlib/src/path.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::ffi::OsStr;
use std::path::Path;

/// Return `true` if the [`Path`] is named `pyproject.toml`.
Expand All @@ -6,38 +7,10 @@ pub fn is_pyproject_toml(path: &Path) -> bool {
.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.
pub fn is_module_file(path: &Path) -> bool {
path.file_name().is_some_and(|file_name| {
file_name == "__init__.py"
|| file_name == "__init__.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));
}
matches!(
path.file_name().and_then(OsStr::to_str),
Some("__init__.py" | "__init__.pyi" | "__main__.py" | "__main__.pyi")
)
}

0 comments on commit 5b4afd3

Please sign in to comment.