Skip to content

Commit 8db7c6b

Browse files
committed
Split out discover_in from ProjectMetadata::discover
1 parent e06e108 commit 8db7c6b

File tree

2 files changed

+143
-79
lines changed

2 files changed

+143
-79
lines changed

crates/ty_project/src/metadata.rs

Lines changed: 139 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use configuration_file::{ConfigurationFile, ConfigurationFileError};
2+
use ruff_db::system::walk_directory::WalkState;
23
use ruff_db::system::{System, SystemPath, SystemPathBuf};
34
use ruff_db::vendored::VendoredFileSystem;
45
use ruff_python_ast::name::Name;
6+
use std::collections::BTreeMap;
57
use std::sync::Arc;
68
use thiserror::Error;
79
use ty_combine::Combine;
@@ -141,90 +143,25 @@ impl ProjectMetadata {
141143
let mut closest_project: Option<ProjectMetadata> = None;
142144

143145
for project_root in path.ancestors() {
144-
let pyproject_path = project_root.join("pyproject.toml");
145-
146-
let pyproject = if let Ok(pyproject_str) = system.read_to_string(&pyproject_path) {
147-
match PyProject::from_toml_str(
148-
&pyproject_str,
149-
ValueSource::File(Arc::new(pyproject_path.clone())),
150-
) {
151-
Ok(pyproject) => Some(pyproject),
152-
Err(error) => {
153-
return Err(ProjectMetadataError::InvalidPyProject {
154-
path: pyproject_path,
155-
source: Box::new(error),
156-
});
157-
}
158-
}
159-
} else {
160-
None
146+
let Some(discovered) = Self::discover_in(project_root, system)? else {
147+
continue;
161148
};
162149

163-
// A `ty.toml` takes precedence over a `pyproject.toml`.
164-
let ty_toml_path = project_root.join("ty.toml");
165-
if let Ok(ty_str) = system.read_to_string(&ty_toml_path) {
166-
let options = match Options::from_toml_str(
167-
&ty_str,
168-
ValueSource::File(Arc::new(ty_toml_path.clone())),
169-
) {
170-
Ok(options) => options,
171-
Err(error) => {
172-
return Err(ProjectMetadataError::InvalidTyToml {
173-
path: ty_toml_path,
174-
source: Box::new(error),
175-
});
176-
}
177-
};
178-
179-
if pyproject
180-
.as_ref()
181-
.is_some_and(|project| project.ty().is_some())
182-
{
183-
// TODO: Consider using a diagnostic here
184-
tracing::warn!(
185-
"Ignoring the `tool.ty` section in `{pyproject_path}` because `{ty_toml_path}` takes precedence."
186-
);
150+
match discovered {
151+
DiscoveredProject::PyProject {
152+
has_ty_section: true,
153+
metadata,
187154
}
188-
189-
tracing::debug!("Found project at '{}'", project_root);
190-
191-
let metadata = ProjectMetadata::from_options(
192-
options,
193-
project_root.to_path_buf(),
194-
pyproject
195-
.as_ref()
196-
.and_then(|pyproject| pyproject.project.as_ref()),
197-
)
198-
.map_err(|err| {
199-
ProjectMetadataError::InvalidRequiresPythonConstraint {
200-
source: err,
201-
path: pyproject_path,
202-
}
203-
})?;
204-
205-
return Ok(metadata);
206-
}
207-
208-
if let Some(pyproject) = pyproject {
209-
let has_ty_section = pyproject.ty().is_some();
210-
let metadata =
211-
ProjectMetadata::from_pyproject(pyproject, project_root.to_path_buf())
212-
.map_err(
213-
|err| ProjectMetadataError::InvalidRequiresPythonConstraint {
214-
source: err,
215-
path: pyproject_path,
216-
},
217-
)?;
218-
219-
if has_ty_section {
155+
| DiscoveredProject::Ty { metadata } => {
220156
tracing::debug!("Found project at '{}'", project_root);
221157

222158
return Ok(metadata);
223159
}
224-
225-
// Not a project itself, keep looking for an enclosing project.
226-
if closest_project.is_none() {
227-
closest_project = Some(metadata);
160+
DiscoveredProject::PyProject { metadata, .. } => {
161+
// Not a project itself, keep looking for an enclosing project.
162+
if closest_project.is_none() {
163+
closest_project = Some(metadata);
164+
}
228165
}
229166
}
230167
}
@@ -252,6 +189,101 @@ impl ProjectMetadata {
252189
Ok(metadata)
253190
}
254191

192+
fn discover_in(
193+
project_root: &SystemPath,
194+
system: &dyn System,
195+
) -> Result<Option<DiscoveredProject>, ProjectMetadataError> {
196+
tracing::debug!("Searching for a project in '{project_root}'");
197+
198+
if !system.is_directory(project_root) {
199+
return Err(ProjectMetadataError::NotADirectory(
200+
project_root.to_path_buf(),
201+
));
202+
}
203+
204+
let pyproject_path = project_root.join("pyproject.toml");
205+
206+
let pyproject = if let Ok(pyproject_str) = system.read_to_string(&pyproject_path) {
207+
match PyProject::from_toml_str(
208+
&pyproject_str,
209+
ValueSource::File(Arc::new(pyproject_path.clone())),
210+
) {
211+
Ok(pyproject) => Some(pyproject),
212+
Err(error) => {
213+
return Err(ProjectMetadataError::InvalidPyProject {
214+
path: pyproject_path,
215+
source: Box::new(error),
216+
});
217+
}
218+
}
219+
} else {
220+
None
221+
};
222+
223+
// A `ty.toml` takes precedence over a `pyproject.toml`.
224+
let ty_toml_path = project_root.join("ty.toml");
225+
if let Ok(ty_str) = system.read_to_string(&ty_toml_path) {
226+
let options = match Options::from_toml_str(
227+
&ty_str,
228+
ValueSource::File(Arc::new(ty_toml_path.clone())),
229+
) {
230+
Ok(options) => options,
231+
Err(error) => {
232+
return Err(ProjectMetadataError::InvalidTyToml {
233+
path: ty_toml_path,
234+
source: Box::new(error),
235+
});
236+
}
237+
};
238+
239+
if pyproject
240+
.as_ref()
241+
.is_some_and(|project| project.ty().is_some())
242+
{
243+
// TODO: Consider using a diagnostic here
244+
tracing::warn!(
245+
"Ignoring the `tool.ty` section in `{pyproject_path}` because `{ty_toml_path}` takes precedence."
246+
);
247+
}
248+
249+
tracing::debug!("Found project at '{}'", project_root);
250+
251+
let metadata = ProjectMetadata::from_options(
252+
options,
253+
project_root.to_path_buf(),
254+
pyproject
255+
.as_ref()
256+
.and_then(|pyproject| pyproject.project.as_ref()),
257+
)
258+
.map_err(
259+
|err| ProjectMetadataError::InvalidRequiresPythonConstraint {
260+
source: err,
261+
path: pyproject_path,
262+
},
263+
)?;
264+
265+
return Ok(Some(DiscoveredProject::Ty { metadata }));
266+
}
267+
268+
if let Some(pyproject) = pyproject {
269+
let has_ty_section = pyproject.ty().is_some();
270+
let metadata = ProjectMetadata::from_pyproject(pyproject, project_root.to_path_buf())
271+
.map_err(|err| {
272+
ProjectMetadataError::InvalidRequiresPythonConstraint {
273+
source: err,
274+
path: pyproject_path,
275+
}
276+
})?;
277+
278+
return Ok(Some(DiscoveredProject::PyProject {
279+
has_ty_section,
280+
metadata,
281+
}));
282+
}
283+
284+
Ok(None)
285+
}
286+
255287
pub fn root(&self) -> &SystemPath {
256288
&self.root
257289
}
@@ -344,6 +376,36 @@ pub enum ProjectMetadataError {
344376
},
345377
}
346378

379+
#[derive(Debug)]
380+
enum DiscoveredProject {
381+
PyProject {
382+
has_ty_section: bool,
383+
metadata: ProjectMetadata,
384+
},
385+
Ty {
386+
metadata: ProjectMetadata,
387+
},
388+
}
389+
390+
impl DiscoveredProject {
391+
fn is_ty_or_project_with_ty_section(&self) -> bool {
392+
match self {
393+
DiscoveredProject::PyProject {
394+
has_ty_section: tool_ty,
395+
..
396+
} => *tool_ty,
397+
DiscoveredProject::Ty { .. } => true,
398+
}
399+
}
400+
401+
fn into_metadata(self) -> ProjectMetadata {
402+
match self {
403+
DiscoveredProject::PyProject { metadata, .. } => metadata,
404+
DiscoveredProject::Ty { metadata } => metadata,
405+
}
406+
}
407+
}
408+
347409
#[cfg(test)]
348410
mod tests {
349411
//! Integration tests for project discovery

crates/ty_server/src/session.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,9 @@ impl Session {
497497
continue;
498498
};
499499

500+
// TODO: Discover all projects within this workspace, starting from root.
501+
//
502+
500503
// For now, create one project database per workspace.
501504
// In the future, index the workspace directories to find all projects
502505
// and create a project database for each.
@@ -1154,8 +1157,7 @@ impl Workspaces {
11541157
) -> Option<(SystemPathBuf, &mut Workspace)> {
11551158
let path = url.to_file_path().ok()?;
11561159

1157-
// Realistically I don't think this can fail because we got the path from a Url
1158-
let system_path = SystemPathBuf::from_path_buf(path).ok()?;
1160+
let system_path = SystemPathBuf::from_path_buf(path).expect("URL to be valid UTF-8");
11591161

11601162
if let Some(workspace) = self.workspaces.get_mut(&system_path) {
11611163
workspace.settings = Arc::new(settings);

0 commit comments

Comments
 (0)