diff --git a/crates/ruff_server/src/fix.rs b/crates/ruff_server/src/fix.rs index e1fa2e281db53..7fa8b9c820bab 100644 --- a/crates/ruff_server/src/fix.rs +++ b/crates/ruff_server/src/fix.rs @@ -1,3 +1,7 @@ +use std::borrow::Cow; + +use rustc_hash::FxHashMap; + use ruff_linter::{ linter::{FixerResult, LinterResult}, packaging::detect_package_root, @@ -5,8 +9,8 @@ use ruff_linter::{ }; use ruff_notebook::SourceValue; use ruff_source_file::LineIndex; -use rustc_hash::FxHashMap; -use std::borrow::Cow; +use ruff_workspace::resolver::match_any_exclusion; +use ruff_workspace::FileResolverSettings; use crate::{ edit::{Replacement, ToRangeExt}, @@ -20,12 +24,29 @@ pub(crate) type Fixes = FxHashMap>; pub(crate) fn fix_all( query: &DocumentQuery, + file_resolver_settings: &FileResolverSettings, linter_settings: &LinterSettings, encoding: PositionEncoding, ) -> crate::Result { let document_path = query.file_path(); let source_kind = query.make_source_kind(); + // If the document is excluded, return an empty list of fixes. + if let Some(exclusion) = match_any_exclusion( + document_path, + &file_resolver_settings.exclude, + &file_resolver_settings.extend_exclude, + Some(&linter_settings.exclude), + None, + ) { + tracing::debug!( + "Ignored path via `{}`: {}", + exclusion, + document_path.display() + ); + return Ok(Fixes::default()); + } + let package = detect_package_root( document_path .parent() diff --git a/crates/ruff_server/src/lint.rs b/crates/ruff_server/src/lint.rs index 4815a9d5ef1a9..9ce7ceabf7924 100644 --- a/crates/ruff_server/src/lint.rs +++ b/crates/ruff_server/src/lint.rs @@ -16,6 +16,8 @@ use ruff_python_index::Indexer; use ruff_python_parser::AsMode; use ruff_source_file::{LineIndex, Locator}; use ruff_text_size::{Ranged, TextRange}; +use ruff_workspace::resolver::match_any_exclusion; +use ruff_workspace::FileResolverSettings; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; @@ -60,12 +62,29 @@ pub(crate) type Diagnostics = FxHashMap Diagnostics { let document_path = query.file_path(); let source_kind = query.make_source_kind(); + // If the document is excluded, return an empty list of diagnostics. + if let Some(exclusion) = match_any_exclusion( + document_path, + &file_resolver_settings.exclude, + &file_resolver_settings.extend_exclude, + Some(&linter_settings.exclude), + None, + ) { + tracing::debug!( + "Ignored path via `{}`: {}", + exclusion, + document_path.display() + ); + return Diagnostics::default(); + } + let package = detect_package_root( document_path .parent() diff --git a/crates/ruff_server/src/server/api/diagnostics.rs b/crates/ruff_server/src/server/api/diagnostics.rs index dc86d7a8e433c..87fabb16e546f 100644 --- a/crates/ruff_server/src/server/api/diagnostics.rs +++ b/crates/ruff_server/src/server/api/diagnostics.rs @@ -10,6 +10,7 @@ pub(super) fn generate_diagnostics(snapshot: &DocumentSnapshot) -> Diagnostics { if snapshot.client_settings().lint() { crate::lint::check( snapshot.query(), + snapshot.query().settings().file_resolver(), snapshot.query().settings().linter(), snapshot.encoding(), ) diff --git a/crates/ruff_server/src/server/api/requests/code_action.rs b/crates/ruff_server/src/server/api/requests/code_action.rs index 837bcc8a95310..14cf670c062af 100644 --- a/crates/ruff_server/src/server/api/requests/code_action.rs +++ b/crates/ruff_server/src/server/api/requests/code_action.rs @@ -1,3 +1,8 @@ +use lsp_server::ErrorCode; +use lsp_types::{self as types, request as req}; +use rustc_hash::FxHashSet; +use types::{CodeActionKind, CodeActionOrCommand}; + use crate::edit::WorkspaceEditTracker; use crate::lint::{fixes_for_diagnostics, DiagnosticFix}; use crate::server::api::LSPResult; @@ -5,10 +10,6 @@ use crate::server::SupportedCodeAction; use crate::server::{client::Notifier, Result}; use crate::session::DocumentSnapshot; use crate::DIAGNOSTIC_NAME; -use lsp_server::ErrorCode; -use lsp_types::{self as types, request as req}; -use rustc_hash::FxHashSet; -use types::{CodeActionKind, CodeActionOrCommand}; use super::code_action_resolve::{resolve_edit_for_fix_all, resolve_edit_for_organize_imports}; @@ -156,6 +157,7 @@ fn fix_all(snapshot: &DocumentSnapshot) -> crate::Result { Some(resolve_edit_for_fix_all( document, snapshot.resolved_client_capabilities(), + snapshot.query().settings().file_resolver(), snapshot.query().settings().linter(), snapshot.encoding(), )?), @@ -192,6 +194,7 @@ fn notebook_fix_all(snapshot: &DocumentSnapshot) -> crate::Result crate::Result crate::Result Cow { let uri: lsp_types::Url = serde_json::from_value(params.data.clone().unwrap_or_default()) .expect("code actions should have a URI in their data fields"); - std::borrow::Cow::Owned(uri) + Cow::Owned(uri) } fn run_with_snapshot( snapshot: DocumentSnapshot, @@ -54,6 +57,7 @@ impl super::BackgroundDocumentRequestHandler for CodeActionResolve { resolve_edit_for_fix_all( query, snapshot.resolved_client_capabilities(), + query.settings().file_resolver(), query.settings().linter(), snapshot.encoding(), ) @@ -64,6 +68,7 @@ impl super::BackgroundDocumentRequestHandler for CodeActionResolve { resolve_edit_for_organize_imports( query, snapshot.resolved_client_capabilities(), + query.settings().file_resolver(), query.settings().linter(), snapshot.encoding(), ) @@ -84,12 +89,13 @@ impl super::BackgroundDocumentRequestHandler for CodeActionResolve { pub(super) fn resolve_edit_for_fix_all( query: &DocumentQuery, client_capabilities: &ResolvedClientCapabilities, + file_resolver_settings: &FileResolverSettings, linter_settings: &LinterSettings, encoding: PositionEncoding, ) -> crate::Result { let mut tracker = WorkspaceEditTracker::new(client_capabilities); tracker.set_fixes_for_document( - fix_all_edit(query, linter_settings, encoding)?, + fix_all_edit(query, file_resolver_settings, linter_settings, encoding)?, query.version(), )?; Ok(tracker.into_workspace_edit()) @@ -97,21 +103,23 @@ pub(super) fn resolve_edit_for_fix_all( pub(super) fn fix_all_edit( query: &DocumentQuery, + file_resolver_settings: &FileResolverSettings, linter_settings: &LinterSettings, encoding: PositionEncoding, ) -> crate::Result { - crate::fix::fix_all(query, linter_settings, encoding) + crate::fix::fix_all(query, file_resolver_settings, linter_settings, encoding) } pub(super) fn resolve_edit_for_organize_imports( query: &DocumentQuery, client_capabilities: &ResolvedClientCapabilities, - linter_settings: &ruff_linter::settings::LinterSettings, + file_resolver_settings: &FileResolverSettings, + linter_settings: &LinterSettings, encoding: PositionEncoding, ) -> crate::Result { let mut tracker = WorkspaceEditTracker::new(client_capabilities); tracker.set_fixes_for_document( - organize_imports_edit(query, linter_settings, encoding)?, + organize_imports_edit(query, file_resolver_settings, linter_settings, encoding)?, query.version(), )?; Ok(tracker.into_workspace_edit()) @@ -119,6 +127,7 @@ pub(super) fn resolve_edit_for_organize_imports( pub(super) fn organize_imports_edit( query: &DocumentQuery, + file_resolver_settings: &FileResolverSettings, linter_settings: &LinterSettings, encoding: PositionEncoding, ) -> crate::Result { @@ -130,5 +139,5 @@ pub(super) fn organize_imports_edit( .into_iter() .collect(); - crate::fix::fix_all(query, &linter_settings, encoding) + crate::fix::fix_all(query, file_resolver_settings, &linter_settings, encoding) } diff --git a/crates/ruff_server/src/server/api/requests/execute_command.rs b/crates/ruff_server/src/server/api/requests/execute_command.rs index ede84529b804f..3a1f7101e08d9 100644 --- a/crates/ruff_server/src/server/api/requests/execute_command.rs +++ b/crates/ruff_server/src/server/api/requests/execute_command.rs @@ -64,6 +64,7 @@ impl super::SyncRequestHandler for ExecuteCommand { Command::FixAll => { let fixes = super::code_action_resolve::fix_all_edit( snapshot.query(), + snapshot.query().settings().file_resolver(), snapshot.query().settings().linter(), snapshot.encoding(), ) @@ -81,6 +82,7 @@ impl super::SyncRequestHandler for ExecuteCommand { Command::OrganizeImports => { let fixes = super::code_action_resolve::organize_imports_edit( snapshot.query(), + snapshot.query().settings().file_resolver(), snapshot.query().settings().linter(), snapshot.encoding(), ) diff --git a/crates/ruff_server/src/server/api/requests/format.rs b/crates/ruff_server/src/server/api/requests/format.rs index 173a51c925248..e591bb13b1a36 100644 --- a/crates/ruff_server/src/server/api/requests/format.rs +++ b/crates/ruff_server/src/server/api/requests/format.rs @@ -1,14 +1,19 @@ +use std::path::Path; + +use lsp_types::{self as types, request as req}; +use types::TextEdit; + +use ruff_python_ast::PySourceType; +use ruff_source_file::LineIndex; +use ruff_workspace::resolver::match_any_exclusion; +use ruff_workspace::{FileResolverSettings, FormatterSettings}; + use crate::edit::{Replacement, ToRangeExt}; use crate::fix::Fixes; use crate::server::api::LSPResult; use crate::server::{client::Notifier, Result}; use crate::session::DocumentSnapshot; use crate::{PositionEncoding, TextDocument}; -use lsp_types::{self as types, request as req}; -use ruff_python_ast::PySourceType; -use ruff_source_file::LineIndex; -use ruff_workspace::FormatterSettings; -use types::TextEdit; pub(crate) struct Format; @@ -39,6 +44,8 @@ pub(super) fn format_full_document(snapshot: &DocumentSnapshot) -> Result if let Some(changes) = format_text_document( text_document, snapshot.query().source_type(), + snapshot.query().file_path(), + snapshot.query().settings().file_resolver(), snapshot.query().settings().formatter(), snapshot.encoding(), true, @@ -50,6 +57,8 @@ pub(super) fn format_full_document(snapshot: &DocumentSnapshot) -> Result if let Some(changes) = format_text_document( snapshot.query().as_single_document().unwrap(), snapshot.query().source_type(), + snapshot.query().file_path(), + snapshot.query().settings().file_resolver(), snapshot.query().settings().formatter(), snapshot.encoding(), false, @@ -71,6 +80,8 @@ pub(super) fn format_document(snapshot: &DocumentSnapshot) -> Result Result Result { + // If the document is excluded, return early. + if let Some(exclusion) = match_any_exclusion( + file_path, + &file_resolver_settings.exclude, + &file_resolver_settings.extend_exclude, + None, + Some(&formatter_settings.exclude), + ) { + tracing::debug!("Ignored path via `{}`: {}", exclusion, file_path.display()); + return Ok(None); + } + let source = text_document.contents(); let mut formatted = crate::format::format(text_document, source_type, formatter_settings) .with_failure_code(lsp_server::ErrorCode::InternalError)?; diff --git a/crates/ruff_server/src/server/api/requests/format_range.rs b/crates/ruff_server/src/server/api/requests/format_range.rs index c50a3475e0585..970ee14fef94c 100644 --- a/crates/ruff_server/src/server/api/requests/format_range.rs +++ b/crates/ruff_server/src/server/api/requests/format_range.rs @@ -1,8 +1,16 @@ +use std::path::Path; + +use lsp_types::{self as types, request as req, Range}; + +use ruff_python_ast::PySourceType; +use ruff_workspace::resolver::match_any_exclusion; +use ruff_workspace::{FileResolverSettings, FormatterSettings}; + use crate::edit::{RangeExt, ToRangeExt}; use crate::server::api::LSPResult; use crate::server::{client::Notifier, Result}; use crate::session::DocumentSnapshot; -use lsp_types::{self as types, request as req}; +use crate::{PositionEncoding, TextDocument}; pub(crate) struct FormatRange; @@ -17,25 +25,63 @@ impl super::BackgroundDocumentRequestHandler for FormatRange { _notifier: Notifier, params: types::DocumentRangeFormattingParams, ) -> Result { - let document = snapshot - .query() - .as_single_document() - .expect("hover should only be called on text documents or notebook cells"); - let text = document.contents(); - let index = document.index(); - let range = params.range.to_text_range(text, index, snapshot.encoding()); - let formatted_range = crate::format::format_range( - document, - snapshot.query().source_type(), - snapshot.query().settings().formatter(), - range, - ) - .with_failure_code(lsp_server::ErrorCode::InternalError)?; - Ok(Some(vec![types::TextEdit { - range: formatted_range - .source_range() - .to_range(text, index, snapshot.encoding()), - new_text: formatted_range.into_code(), - }])) + format_document_range(&snapshot, params.range) + } +} + +/// Formats the specified [`Range`] in the [`DocumentSnapshot`]. +fn format_document_range( + snapshot: &DocumentSnapshot, + range: Range, +) -> Result { + let text_document = snapshot + .query() + .as_single_document() + .expect("format should only be called on text documents or notebook cells"); + format_text_document_range( + text_document, + range, + snapshot.query().source_type(), + snapshot.query().file_path(), + snapshot.query().settings().file_resolver(), + snapshot.query().settings().formatter(), + snapshot.encoding(), + ) +} + +/// Formats the specified [`Range`] in the [`TextDocument`]. +fn format_text_document_range( + text_document: &TextDocument, + range: Range, + source_type: PySourceType, + file_path: &Path, + file_resolver_settings: &FileResolverSettings, + formatter_settings: &FormatterSettings, + encoding: PositionEncoding, +) -> Result { + // If the document is excluded, return early. + if let Some(exclusion) = match_any_exclusion( + file_path, + &file_resolver_settings.exclude, + &file_resolver_settings.extend_exclude, + None, + Some(&formatter_settings.exclude), + ) { + tracing::debug!("Ignored path via `{}`: {}", exclusion, file_path.display()); + return Ok(None); } + + let text = text_document.contents(); + let index = text_document.index(); + let range = range.to_text_range(text, index, encoding); + let formatted_range = + crate::format::format_range(text_document, source_type, formatter_settings, range) + .with_failure_code(lsp_server::ErrorCode::InternalError)?; + + Ok(Some(vec![types::TextEdit { + range: formatted_range + .source_range() + .to_range(text, index, encoding), + new_text: formatted_range.into_code(), + }])) } diff --git a/crates/ruff_server/src/session/index.rs b/crates/ruff_server/src/session/index.rs index 706cd96d12990..ee9965e6681b9 100644 --- a/crates/ruff_server/src/session/index.rs +++ b/crates/ruff_server/src/session/index.rs @@ -479,7 +479,7 @@ impl DocumentQuery { } /// Get the underlying file path for the document selected by this query. - pub(crate) fn file_path(&self) -> &PathBuf { + pub(crate) fn file_path(&self) -> &Path { match self { Self::Text { file_path, .. } | Self::Notebook { file_path, .. } => file_path, } diff --git a/crates/ruff_server/src/session/index/ruff_settings.rs b/crates/ruff_server/src/session/index/ruff_settings.rs index aba65e9afaccf..eed37b1f9522f 100644 --- a/crates/ruff_server/src/session/index/ruff_settings.rs +++ b/crates/ruff_server/src/session/index/ruff_settings.rs @@ -73,10 +73,17 @@ impl RuffSettings { } } + /// Return the [`ruff_workspace::FileResolverSettings`] for this [`RuffSettings`]. + pub(crate) fn file_resolver(&self) -> &ruff_workspace::FileResolverSettings { + &self.file_resolver + } + + /// Return the [`ruff_linter::settings::LinterSettings`] for this [`RuffSettings`]. pub(crate) fn linter(&self) -> &ruff_linter::settings::LinterSettings { &self.linter } + /// Return the [`ruff_workspace::FormatterSettings`] for this [`RuffSettings`]. pub(crate) fn formatter(&self) -> &ruff_workspace::FormatterSettings { &self.formatter } diff --git a/crates/ruff_workspace/src/resolver.rs b/crates/ruff_workspace/src/resolver.rs index 26791117feaaa..9f8150044dde0 100644 --- a/crates/ruff_workspace/src/resolver.rs +++ b/crates/ruff_workspace/src/resolver.rs @@ -626,6 +626,63 @@ pub fn match_candidate_exclusion( exclusion.is_match_candidate(file_path) || exclusion.is_match_candidate(file_basename) } +#[derive(Debug, Copy, Clone)] +pub enum ExclusionKind { + /// The exclusion came from the `exclude` setting. + Exclude, + /// The exclusion came from the `extend-exclude` setting. + ExtendExclude, + /// The exclusion came from the `lint.exclude` setting. + LintExclude, + /// The exclusion came from the `lint.extend-exclude` setting. + FormatExclude, +} + +impl std::fmt::Display for ExclusionKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ExclusionKind::Exclude => write!(f, "exclude"), + ExclusionKind::ExtendExclude => write!(f, "extend-exclude"), + ExclusionKind::LintExclude => write!(f, "lint.exclude"), + ExclusionKind::FormatExclude => write!(f, "lint.extend-exclude"), + } + } +} + +/// Return the [`ExclusionKind`] for a given [`Path`], if the path or any of its ancestors match +/// any of the exclusion criteria. +pub fn match_any_exclusion( + path: &Path, + exclude: &GlobSet, + extend_exclude: &GlobSet, + lint_exclude: Option<&GlobSet>, + format_exclude: Option<&GlobSet>, +) -> Option { + for path in path.ancestors() { + if let Some(basename) = path.file_name() { + let path = Candidate::new(path); + let basename = Candidate::new(basename); + if match_candidate_exclusion(&path, &basename, exclude) { + return Some(ExclusionKind::Exclude); + } + if match_candidate_exclusion(&path, &basename, extend_exclude) { + return Some(ExclusionKind::ExtendExclude); + } + if let Some(lint_exclude) = lint_exclude { + if match_candidate_exclusion(&path, &basename, lint_exclude) { + return Some(ExclusionKind::LintExclude); + } + } + if let Some(format_exclude) = format_exclude { + if match_candidate_exclusion(&path, &basename, format_exclude) { + return Some(ExclusionKind::FormatExclude); + } + } + } + } + None +} + #[cfg(test)] mod tests { use std::fs::{create_dir, File};