mirror of https://github.com/astral-sh/ruff
Default `src.root` to `['.', '<project_name>']` if the directory exists (#18141)
This commit is contained in:
parent
97058e8093
commit
55a410a885
|
|
@ -164,7 +164,13 @@ typeshed = "/path/to/custom/typeshed"
|
||||||
|
|
||||||
The root of the project, used for finding first-party modules.
|
The root of the project, used for finding first-party modules.
|
||||||
|
|
||||||
**Default value**: `[".", "./src"]`
|
If left unspecified, ty will try to detect common project layouts and initialize `src.root` accordingly:
|
||||||
|
|
||||||
|
* if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat)
|
||||||
|
* if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path
|
||||||
|
* otherwise, default to `.` (flat layout)
|
||||||
|
|
||||||
|
**Default value**: `null`
|
||||||
|
|
||||||
**Type**: `str`
|
**Type**: `str`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -244,7 +244,8 @@ impl ProjectMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_program_settings(&self, system: &dyn System) -> ProgramSettings {
|
pub fn to_program_settings(&self, system: &dyn System) -> ProgramSettings {
|
||||||
self.options.to_program_settings(self.root(), system)
|
self.options
|
||||||
|
.to_program_settings(self.root(), self.name(), system)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Combine the project options with the CLI options where the CLI options take precedence.
|
/// Combine the project options with the CLI options where the CLI options take precedence.
|
||||||
|
|
@ -947,6 +948,87 @@ expected `.`, `]`
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_src_root_src_layout() -> anyhow::Result<()> {
|
||||||
|
let system = TestSystem::default();
|
||||||
|
let root = SystemPathBuf::from("/app");
|
||||||
|
|
||||||
|
system
|
||||||
|
.memory_file_system()
|
||||||
|
.write_file_all(
|
||||||
|
root.join("src/main.py"),
|
||||||
|
r#"
|
||||||
|
print("Hello, world!")
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.context("Failed to write file")?;
|
||||||
|
|
||||||
|
let metadata = ProjectMetadata::discover(&root, &system)?;
|
||||||
|
let settings = metadata
|
||||||
|
.options
|
||||||
|
.to_program_settings(&root, "my_package", &system);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
settings.search_paths.src_roots,
|
||||||
|
vec![root.clone(), root.join("src")]
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_src_root_package_layout() -> anyhow::Result<()> {
|
||||||
|
let system = TestSystem::default();
|
||||||
|
let root = SystemPathBuf::from("/app");
|
||||||
|
|
||||||
|
system
|
||||||
|
.memory_file_system()
|
||||||
|
.write_file_all(
|
||||||
|
root.join("psycopg/psycopg/main.py"),
|
||||||
|
r#"
|
||||||
|
print("Hello, world!")
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.context("Failed to write file")?;
|
||||||
|
|
||||||
|
let metadata = ProjectMetadata::discover(&root, &system)?;
|
||||||
|
let settings = metadata
|
||||||
|
.options
|
||||||
|
.to_program_settings(&root, "psycopg", &system);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
settings.search_paths.src_roots,
|
||||||
|
vec![root.clone(), root.join("psycopg")]
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_src_root_flat_layout() -> anyhow::Result<()> {
|
||||||
|
let system = TestSystem::default();
|
||||||
|
let root = SystemPathBuf::from("/app");
|
||||||
|
|
||||||
|
system
|
||||||
|
.memory_file_system()
|
||||||
|
.write_file_all(
|
||||||
|
root.join("my_package/main.py"),
|
||||||
|
r#"
|
||||||
|
print("Hello, world!")
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.context("Failed to write file")?;
|
||||||
|
|
||||||
|
let metadata = ProjectMetadata::discover(&root, &system)?;
|
||||||
|
let settings = metadata
|
||||||
|
.options
|
||||||
|
.to_program_settings(&root, "my_package", &system);
|
||||||
|
|
||||||
|
assert_eq!(settings.search_paths.src_roots, vec![root]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn assert_error_eq(error: &ProjectDiscoveryError, message: &str) {
|
fn assert_error_eq(error: &ProjectDiscoveryError, message: &str) {
|
||||||
assert_eq!(error.to_string().replace('\\', "/"), message);
|
assert_eq!(error.to_string().replace('\\', "/"), message);
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ impl Options {
|
||||||
pub(crate) fn to_program_settings(
|
pub(crate) fn to_program_settings(
|
||||||
&self,
|
&self,
|
||||||
project_root: &SystemPath,
|
project_root: &SystemPath,
|
||||||
|
project_name: &str,
|
||||||
system: &dyn System,
|
system: &dyn System,
|
||||||
) -> ProgramSettings {
|
) -> ProgramSettings {
|
||||||
let python_version = self
|
let python_version = self
|
||||||
|
|
@ -106,13 +107,14 @@ impl Options {
|
||||||
ProgramSettings {
|
ProgramSettings {
|
||||||
python_version,
|
python_version,
|
||||||
python_platform,
|
python_platform,
|
||||||
search_paths: self.to_search_path_settings(project_root, system),
|
search_paths: self.to_search_path_settings(project_root, project_name, system),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_search_path_settings(
|
fn to_search_path_settings(
|
||||||
&self,
|
&self,
|
||||||
project_root: &SystemPath,
|
project_root: &SystemPath,
|
||||||
|
project_name: &str,
|
||||||
system: &dyn System,
|
system: &dyn System,
|
||||||
) -> SearchPathSettings {
|
) -> SearchPathSettings {
|
||||||
let src_roots = if let Some(src_root) = self.src.as_ref().and_then(|src| src.root.as_ref())
|
let src_roots = if let Some(src_root) = self.src.as_ref().and_then(|src| src.root.as_ref())
|
||||||
|
|
@ -121,10 +123,24 @@ impl Options {
|
||||||
} else {
|
} else {
|
||||||
let src = project_root.join("src");
|
let src = project_root.join("src");
|
||||||
|
|
||||||
// Default to `src` and the project root if `src` exists and the root hasn't been specified.
|
|
||||||
if system.is_directory(&src) {
|
if system.is_directory(&src) {
|
||||||
|
// Default to `src` and the project root if `src` exists and the root hasn't been specified.
|
||||||
|
// This corresponds to the `src-layout`
|
||||||
|
tracing::debug!(
|
||||||
|
"Including `./src` in `src.root` because a `./src` directory exists"
|
||||||
|
);
|
||||||
vec![project_root.to_path_buf(), src]
|
vec![project_root.to_path_buf(), src]
|
||||||
|
} else if system.is_directory(&project_root.join(project_name).join(project_name)) {
|
||||||
|
// `src-layout` but when the folder isn't called `src` but has the same name as the project.
|
||||||
|
// For example, the "src" folder for `psycopg` is called `psycopg` and the python files are in `psycopg/psycopg/_adapters_map.py`
|
||||||
|
tracing::debug!(
|
||||||
|
"Including `./{project_name}` in `src.root` because a `./{project_name}/{project_name}` directory exists"
|
||||||
|
);
|
||||||
|
|
||||||
|
vec![project_root.to_path_buf(), project_root.join(project_name)]
|
||||||
} else {
|
} else {
|
||||||
|
// Default to a [flat project structure](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/).
|
||||||
|
tracing::debug!("Defaulting `src.root` to `.`");
|
||||||
vec![project_root.to_path_buf()]
|
vec![project_root.to_path_buf()]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -353,9 +369,15 @@ pub struct EnvironmentOptions {
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub struct SrcOptions {
|
pub struct SrcOptions {
|
||||||
/// The root of the project, used for finding first-party modules.
|
/// The root of the project, used for finding first-party modules.
|
||||||
|
///
|
||||||
|
/// If left unspecified, ty will try to detect common project layouts and initialize `src.root` accordingly:
|
||||||
|
///
|
||||||
|
/// * if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat)
|
||||||
|
/// * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path
|
||||||
|
/// * otherwise, default to `.` (flat layout)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[option(
|
#[option(
|
||||||
default = r#"[".", "./src"]"#,
|
default = r#"null"#,
|
||||||
value_type = "str",
|
value_type = "str",
|
||||||
example = r#"
|
example = r#"
|
||||||
root = "./app"
|
root = "./app"
|
||||||
|
|
|
||||||
|
|
@ -849,7 +849,7 @@
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"root": {
|
"root": {
|
||||||
"description": "The root of the project, used for finding first-party modules.",
|
"description": "The root of the project, used for finding first-party modules.\n\nIf left unspecified, ty will try to detect common project layouts and initialize `src.root` accordingly:\n\n* if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat) * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path * otherwise, default to `.` (flat layout)",
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
"null"
|
"null"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue