Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

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

8 changes: 7 additions & 1 deletion crates/red_knot_project/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ ruff_cache = { workspace = true }
ruff_db = { workspace = true, features = ["cache", "serde"] }
ruff_macros = { workspace = true }
ruff_python_ast = { workspace = true, features = ["serde"] }
ruff_python_formatter = { workspace = true, optional = true }
ruff_text_size = { workspace = true }
red_knot_ide = { workspace = true }
red_knot_python_semantic = { workspace = true, features = ["serde"] }
Expand Down Expand Up @@ -43,8 +44,13 @@ insta = { workspace = true, features = ["redactions", "ron"] }
[features]
default = ["zstd"]
deflate = ["red_knot_vendored/deflate"]
schemars = ["dep:schemars", "ruff_db/schemars", "red_knot_python_semantic/schemars"]
schemars = [
"dep:schemars",
"ruff_db/schemars",
"red_knot_python_semantic/schemars",
]
zstd = ["red_knot_vendored/zstd"]
format = ["ruff_python_formatter"]

[lints]
workspace = true
26 changes: 26 additions & 0 deletions crates/red_knot_project/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,32 @@ impl Db for ProjectDatabase {
}
}

#[cfg(feature = "format")]
mod format {
use crate::ProjectDatabase;
use ruff_db::files::File;
use ruff_db::Upcast;
use ruff_python_formatter::{Db as FormatDb, PyFormatOptions};

#[salsa::db]
impl FormatDb for ProjectDatabase {
fn format_options(&self, file: File) -> PyFormatOptions {
let source_ty = file.source_type(self);
PyFormatOptions::from_source_type(source_ty)
}
}

impl Upcast<dyn FormatDb> for ProjectDatabase {
fn upcast(&self) -> &(dyn FormatDb + 'static) {
self
}

fn upcast_mut(&mut self) -> &mut (dyn FormatDb + 'static) {
self
}
}
}

#[cfg(test)]
pub(crate) mod tests {
use std::sync::Arc;
Expand Down
7 changes: 5 additions & 2 deletions crates/red_knot_wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ doctest = false
default = ["console_error_panic_hook"]

[dependencies]
red_knot_project = { workspace = true, default-features = false, features = ["deflate"] }
red_knot_ide = { workspace = true }
red_knot_project = { workspace = true, default-features = false, features = [
"deflate",
"format"
] }
red_knot_python_semantic = { workspace = true }

ruff_db = { workspace = true, default-features = false, features = [] }
ruff_notebook = { workspace = true }
ruff_python_formatter = { workspace = true }
ruff_source_file = { workspace = true }
ruff_text_size = { workspace = true }

Expand All @@ -44,4 +48,3 @@ wasm-bindgen-test = { workspace = true }

[lints]
workspace = true

11 changes: 10 additions & 1 deletion crates/red_knot_wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use ruff_db::system::{
};
use ruff_db::Upcast;
use ruff_notebook::Notebook;
use ruff_python_formatter::formatted_file;
use ruff_source_file::{LineIndex, OneIndexed, SourceLocation};
use ruff_text_size::Ranged;
use wasm_bindgen::prelude::*;
Expand Down Expand Up @@ -142,7 +143,11 @@ impl Workspace {
}

#[wasm_bindgen(js_name = "closeFile")]
pub fn close_file(&mut self, file_id: &FileHandle) -> Result<(), Error> {
#[allow(
clippy::needless_pass_by_value,
reason = "It's intentional that the file handle is consumed because it is no longer valid after closing"
)]
pub fn close_file(&mut self, file_id: FileHandle) -> Result<(), Error> {
let file = file_id.file;

self.db.project().close_file(&mut self.db, file);
Expand Down Expand Up @@ -184,6 +189,10 @@ impl Workspace {
Ok(format!("{:#?}", parsed.syntax()))
}

pub fn format(&self, file_id: &FileHandle) -> Result<Option<String>, Error> {
formatted_file(&self.db, file_id.file).map_err(into_error)
}

/// Returns the token stream for `path` serialized as a string.
pub fn tokens(&self, file_id: &FileHandle) -> Result<String, Error> {
let parsed = ruff_db::parsed::parsed_module(&self.db, file_id.file);
Expand Down
16 changes: 13 additions & 3 deletions crates/ruff_db/src/files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,9 +424,19 @@ impl File {

/// Returns `true` if the file should be analyzed as a type stub.
pub fn is_stub(self, db: &dyn Db) -> bool {
self.path(db)
.extension()
.is_some_and(|extension| PySourceType::from_extension(extension).is_stub())
self.source_type(db).is_stub()
}

pub fn source_type(self, db: &dyn Db) -> PySourceType {
match self.path(db) {
FilePath::System(path) => path
.extension()
.map_or(PySourceType::Python, PySourceType::from_extension),
FilePath::Vendored(_) => PySourceType::Stub,
FilePath::SystemVirtual(path) => path
.extension()
.map_or(PySourceType::Python, PySourceType::from_extension),
}
}
}

Expand Down
16 changes: 3 additions & 13 deletions crates/ruff_db/src/parsed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use std::fmt::Formatter;
use std::ops::Deref;
use std::sync::Arc;

use ruff_python_ast::{ModModule, PySourceType};
use ruff_python_ast::ModModule;
use ruff_python_parser::{parse_unchecked_source, Parsed};

use crate::files::{File, FilePath};
use crate::files::File;
use crate::source::source_text;
use crate::Db;

Expand All @@ -25,17 +25,7 @@ pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
let _span = tracing::trace_span!("parsed_module", ?file).entered();

let source = source_text(db, file);
let path = file.path(db);

let ty = match path {
FilePath::System(path) => path
.extension()
.map_or(PySourceType::Python, PySourceType::from_extension),
FilePath::Vendored(_) => PySourceType::Stub,
FilePath::SystemVirtual(path) => path
.extension()
.map_or(PySourceType::Python, PySourceType::from_extension),
};
let ty = file.source_type(db);

ParsedModule::new(parse_unchecked_source(&source, ty))
}
Expand Down
9 changes: 8 additions & 1 deletion crates/ruff_python_formatter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ doctest = false

[dependencies]
ruff_cache = { workspace = true }
ruff_db = { workspace = true }
ruff_formatter = { workspace = true }
ruff_macros = { workspace = true }
ruff_python_trivia = { workspace = true }
Expand All @@ -30,6 +31,7 @@ itertools = { workspace = true }
memchr = { workspace = true }
regex = { workspace = true }
rustc-hash = { workspace = true }
salsa = { workspace = true }
serde = { workspace = true, optional = true }
schemars = { workspace = true, optional = true }
smallvec = { workspace = true }
Expand Down Expand Up @@ -58,7 +60,12 @@ required-features = ["serde"]

[features]
default = ["serde"]
serde = ["dep:serde", "ruff_formatter/serde", "ruff_source_file/serde", "ruff_python_ast/serde"]
serde = [
"dep:serde",
"ruff_formatter/serde",
"ruff_source_file/serde",
"ruff_python_ast/serde",
]
schemars = ["dep:schemars", "ruff_formatter/schemars"]

[lints]
Expand Down
9 changes: 9 additions & 0 deletions crates/ruff_python_formatter/src/db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use ruff_db::{files::File, Db as SourceDb, Upcast};

use crate::PyFormatOptions;

#[salsa::db]
pub trait Db: SourceDb + Upcast<dyn SourceDb> {
/// Returns the formatting options
fn format_options(&self, file: File) -> PyFormatOptions;
}
42 changes: 41 additions & 1 deletion crates/ruff_python_formatter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use ruff_db::files::File;
use ruff_db::parsed::parsed_module;
use ruff_db::source::source_text;
use thiserror::Error;
use tracing::Level;

Expand All @@ -13,6 +16,7 @@ use crate::comments::{
has_skip_comment, leading_comments, trailing_comments, Comments, SourceComment,
};
pub use crate::context::PyFormatContext;
pub use crate::db::Db;
pub use crate::options::{
DocstringCode, DocstringCodeLineWidth, MagicTrailingComma, PreviewMode, PyFormatOptions,
QuoteStyle,
Expand All @@ -25,6 +29,7 @@ pub(crate) mod builders;
pub mod cli;
mod comments;
pub(crate) mod context;
mod db;
pub(crate) mod expression;
mod generated;
pub(crate) mod module;
Expand Down Expand Up @@ -96,7 +101,7 @@ where
}
}

#[derive(Error, Debug)]
#[derive(Error, Debug, salsa::Update, PartialEq, Eq)]
pub enum FormatModuleError {
#[error(transparent)]
ParseError(#[from] ParseError),
Expand Down Expand Up @@ -124,6 +129,19 @@ pub fn format_module_ast<'a>(
source: &'a str,
options: PyFormatOptions,
) -> FormatResult<Formatted<PyFormatContext<'a>>> {
format_node(parsed, comment_ranges, source, options)
}

fn format_node<'a, N>(
parsed: &'a Parsed<N>,
comment_ranges: &'a CommentRanges,
source: &'a str,
options: PyFormatOptions,
) -> FormatResult<Formatted<PyFormatContext<'a>>>
where
N: AsFormat<PyFormatContext<'a>>,
&'a N: Into<AnyNodeRef<'a>>,
{
let source_code = SourceCode::new(source);
let comments = Comments::from_ast(parsed.syntax(), source_code, comment_ranges);

Expand All @@ -138,6 +156,28 @@ pub fn format_module_ast<'a>(
Ok(formatted)
}

pub fn formatted_file(db: &dyn Db, file: File) -> Result<Option<String>, FormatModuleError> {
let options = db.format_options(file);

let parsed = parsed_module(db.upcast(), file);

if let Some(first) = parsed.errors().first() {
return Err(FormatModuleError::ParseError(first.clone()));
}

let comment_ranges = CommentRanges::from(parsed.tokens());
let source = source_text(db.upcast(), file);

let formatted = format_node(parsed, &comment_ranges, &source, options)?;
let printed = formatted.print()?;

if printed.as_code() == &*source {
Ok(None)
} else {
Ok(Some(printed.into_code()))
}
}

/// Public function for generating a printable string of the debug comments.
pub fn pretty_comments(module: &Mod, comment_ranges: &CommentRanges, source: &str) -> String {
let source_code = SourceCode::new(source);
Expand Down
8 changes: 4 additions & 4 deletions crates/ruff_python_parser/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::TokenKind;

/// Represents represent errors that occur during parsing and are
/// returned by the `parse_*` functions.
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ParseError {
pub error: ParseErrorType,
pub location: TextRange,
Expand Down Expand Up @@ -49,7 +49,7 @@ impl ParseError {
}

/// Represents the different types of errors that can occur during parsing of an f-string.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FStringErrorType {
/// Expected a right brace after an opened left brace.
UnclosedLbrace,
Expand Down Expand Up @@ -85,7 +85,7 @@ impl std::fmt::Display for FStringErrorType {
}

/// Represents the different types of errors that can occur during parsing.
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ParseErrorType {
/// An unexpected error occurred.
OtherError(String),
Expand Down Expand Up @@ -362,7 +362,7 @@ impl std::fmt::Display for LexicalError {
}

/// Represents the different types of errors that can occur during lexing.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LexicalErrorType {
// TODO: Can probably be removed, the places it is used seem to be able
// to use the `UnicodeError` variant instead.
Expand Down
Loading
Loading