[red-knot] IDE crate (#17045)

## Summary

This PR adds a new but so far empty and unused `red_knot_ide` crate. 

This new crate's purpose is to implement IDE-specific functionality,
such as go to definition, hover, completion, etc., which are used by
both the LSP and the playground.

The crate itself doesn't depend on `lsptypes`. The idea is that the
facade crates (e.g., `red_knot_server`) convert external to internal
types.
Not only allows this to share the logic between server and playground,
it also ensures that the core functionality is easier to test because it
can be tested without needing a full LSP.



## Test Plan

`cargo build`
This commit is contained in:
Micha Reiser 2025-04-01 09:36:00 +02:00 committed by GitHub
parent a9dbfebc61
commit b57c62e6b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 181 additions and 1 deletions

12
Cargo.lock generated
View File

@ -2503,6 +2503,17 @@ dependencies = [
"wild",
]
[[package]]
name = "red_knot_ide"
version = "0.0.0"
dependencies = [
"red_knot_python_semantic",
"red_knot_vendored",
"ruff_db",
"salsa",
"tracing",
]
[[package]]
name = "red_knot_project"
version = "0.0.0"
@ -2514,6 +2525,7 @@ dependencies = [
"notify",
"pep440_rs",
"rayon",
"red_knot_ide",
"red_knot_python_semantic",
"red_knot_vendored",
"ruff_cache",

View File

@ -38,10 +38,11 @@ ruff_text_size = { path = "crates/ruff_text_size" }
red_knot_vendored = { path = "crates/red_knot_vendored" }
ruff_workspace = { path = "crates/ruff_workspace" }
red_knot_ide = { path = "crates/red_knot_ide" }
red_knot_project = { path = "crates/red_knot_project", default-features = false }
red_knot_python_semantic = { path = "crates/red_knot_python_semantic" }
red_knot_server = { path = "crates/red_knot_server" }
red_knot_test = { path = "crates/red_knot_test" }
red_knot_project = { path = "crates/red_knot_project", default-features = false }
aho-corasick = { version = "1.1.3" }
anstream = { version = "0.6.18" }

View File

@ -0,0 +1,24 @@
[package]
name = "red_knot_ide"
version = "0.0.0"
publish = false
authors = { workspace = true }
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
[dependencies]
ruff_db = { workspace = true }
red_knot_python_semantic = { workspace = true }
salsa = { workspace = true }
tracing = { workspace = true }
[dev-dependencies]
red_knot_vendored = { workspace = true }
[lints]
workspace = true

View File

@ -0,0 +1,124 @@
use red_knot_python_semantic::Db as SemanticDb;
use ruff_db::{Db as SourceDb, Upcast};
#[salsa::db]
pub trait Db: SemanticDb + Upcast<dyn SourceDb> {}
#[cfg(test)]
pub(crate) mod tests {
use std::sync::Arc;
use super::Db;
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
use red_knot_python_semantic::{default_lint_registry, Db as SemanticDb};
use ruff_db::files::{File, Files};
use ruff_db::system::{DbWithTestSystem, System, TestSystem};
use ruff_db::vendored::VendoredFileSystem;
use ruff_db::{Db as SourceDb, Upcast};
#[salsa::db]
#[derive(Clone)]
pub(crate) struct TestDb {
storage: salsa::Storage<Self>,
files: Files,
system: TestSystem,
vendored: VendoredFileSystem,
events: Arc<std::sync::Mutex<Vec<salsa::Event>>>,
rule_selection: Arc<RuleSelection>,
}
#[allow(dead_code)]
impl TestDb {
pub(crate) fn new() -> Self {
Self {
storage: salsa::Storage::default(),
system: TestSystem::default(),
vendored: red_knot_vendored::file_system().clone(),
events: Arc::default(),
files: Files::default(),
rule_selection: Arc::new(RuleSelection::from_registry(default_lint_registry())),
}
}
/// Takes the salsa events.
///
/// ## Panics
/// If there are any pending salsa snapshots.
pub(crate) fn take_salsa_events(&mut self) -> Vec<salsa::Event> {
let inner = Arc::get_mut(&mut self.events).expect("no pending salsa snapshots");
let events = inner.get_mut().unwrap();
std::mem::take(&mut *events)
}
/// Clears the salsa events.
///
/// ## Panics
/// If there are any pending salsa snapshots.
pub(crate) fn clear_salsa_events(&mut self) {
self.take_salsa_events();
}
}
impl DbWithTestSystem for TestDb {
fn test_system(&self) -> &TestSystem {
&self.system
}
fn test_system_mut(&mut self) -> &mut TestSystem {
&mut self.system
}
}
#[salsa::db]
impl SourceDb for TestDb {
fn vendored(&self) -> &VendoredFileSystem {
&self.vendored
}
fn system(&self) -> &dyn System {
&self.system
}
fn files(&self) -> &Files {
&self.files
}
}
impl Upcast<dyn SourceDb> for TestDb {
fn upcast(&self) -> &(dyn SourceDb + 'static) {
self
}
fn upcast_mut(&mut self) -> &mut (dyn SourceDb + 'static) {
self
}
}
#[salsa::db]
impl SemanticDb for TestDb {
fn is_file_open(&self, file: File) -> bool {
!file.path(self).is_vendored_path()
}
fn rule_selection(&self) -> Arc<RuleSelection> {
self.rule_selection.clone()
}
fn lint_registry(&self) -> &LintRegistry {
default_lint_registry()
}
}
#[salsa::db]
impl Db for TestDb {}
#[salsa::db]
impl salsa::Database for TestDb {
fn salsa_event(&self, event: &dyn Fn() -> salsa::Event) {
let event = event();
tracing::trace!("event: {event:?}");
let mut events = self.events.lock().unwrap();
events.push(event);
}
}
}

View File

@ -0,0 +1,3 @@
mod db;
pub use db::Db;

View File

@ -17,6 +17,7 @@ ruff_db = { workspace = true, features = ["cache", "serde"] }
ruff_macros = { workspace = true }
ruff_python_ast = { workspace = true, features = ["serde"] }
ruff_text_size = { workspace = true }
red_knot_ide = { workspace = true }
red_knot_python_semantic = { workspace = true, features = ["serde"] }
red_knot_vendored = { workspace = true }

View File

@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::DEFAULT_LINT_REGISTRY;
use crate::{Project, ProjectMetadata};
use red_knot_ide::Db as IdeDb;
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
use red_knot_python_semantic::{Db as SemanticDb, Program};
use ruff_db::diagnostic::OldDiagnosticTrait;
@ -103,6 +104,19 @@ impl Upcast<dyn SourceDb> for ProjectDatabase {
}
}
impl Upcast<dyn IdeDb> for ProjectDatabase {
fn upcast(&self) -> &(dyn IdeDb + 'static) {
self
}
fn upcast_mut(&mut self) -> &mut (dyn IdeDb + 'static) {
self
}
}
#[salsa::db]
impl IdeDb for ProjectDatabase {}
#[salsa::db]
impl SemanticDb for ProjectDatabase {
fn is_file_open(&self, file: File) -> bool {

View File

@ -12,6 +12,7 @@ license = { workspace = true }
[dependencies]
red_knot_project = { workspace = true }
ruff_db = { workspace = true, features = ["os"] }
ruff_notebook = { workspace = true }
ruff_python_ast = { workspace = true }