From fff7377cc14eea5d0a7856b196aaa4bed5df89f6 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Thu, 12 Dec 2024 09:59:52 +0100 Subject: [PATCH 1/9] feat: pull diagnostics --- .../src/categories.rs | 1 + crates/pg_workspace_new/src/workspace.rs | 22 +++++++++ .../pg_workspace_new/src/workspace/client.rs | 7 +++ .../pg_workspace_new/src/workspace/server.rs | 47 +++++++++++++++++++ .../src/workspace/server/document.rs | 7 +++ .../src/workspace/server/pg_query.rs | 41 ++++++++++++++-- justfile | 5 ++ 7 files changed, 125 insertions(+), 5 deletions(-) diff --git a/crates/pg_diagnostics_categories/src/categories.rs b/crates/pg_diagnostics_categories/src/categories.rs index 97529159..86d992e7 100644 --- a/crates/pg_diagnostics_categories/src/categories.rs +++ b/crates/pg_diagnostics_categories/src/categories.rs @@ -25,5 +25,6 @@ define_categories! { "flags/invalid", "project", "internalError/panic", + "syntax", "dummy", } diff --git a/crates/pg_workspace_new/src/workspace.rs b/crates/pg_workspace_new/src/workspace.rs index 1b8cb1ba..4bfe05bc 100644 --- a/crates/pg_workspace_new/src/workspace.rs +++ b/crates/pg_workspace_new/src/workspace.rs @@ -30,6 +30,22 @@ pub struct ChangeFileParams { pub changes: Vec, } +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct PullDiagnosticsParams { + pub path: PgLspPath, + // pub categories: RuleCategories, + pub max_diagnostics: u64, + // pub only: Vec, + // pub skip: Vec, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct PullDiagnosticsResult { + pub diagnostics: Vec, + pub errors: usize, + pub skipped_diagnostics: u64, +} + #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct ChangeParams { /// The range of the file that changed. If `None`, the whole file changed. @@ -72,6 +88,12 @@ pub struct ServerInfo { } pub trait Workspace: Send + Sync + RefUnwindSafe { + /// Retrieves the list of diagnostics associated to a file + fn pull_diagnostics( + &self, + params: PullDiagnosticsParams, + ) -> Result; + /// Refresh the schema cache for this workspace fn refresh_schema_cache(&self) -> Result<(), WorkspaceError>; diff --git a/crates/pg_workspace_new/src/workspace/client.rs b/crates/pg_workspace_new/src/workspace/client.rs index 2cb768ea..986f5bda 100644 --- a/crates/pg_workspace_new/src/workspace/client.rs +++ b/crates/pg_workspace_new/src/workspace/client.rs @@ -120,4 +120,11 @@ where fn refresh_schema_cache(&self) -> Result<(), WorkspaceError> { self.request("pglsp/refresh_schema_cache", ()) } + + fn pull_diagnostics( + &self, + params: super::PullDiagnosticsParams, + ) -> Result { + self.request("pglsp/pull_diagnostics", params) + } } diff --git a/crates/pg_workspace_new/src/workspace/server.rs b/crates/pg_workspace_new/src/workspace/server.rs index 653cea65..5ec27a3b 100644 --- a/crates/pg_workspace_new/src/workspace/server.rs +++ b/crates/pg_workspace_new/src/workspace/server.rs @@ -3,6 +3,7 @@ use std::{fs, future::Future, panic::RefUnwindSafe, path::Path, sync::RwLock}; use change::StatementChange; use dashmap::{DashMap, DashSet}; use document::{Document, StatementRef}; +use pg_diagnostics::{serde::Diagnostic as SDiagnostic, Diagnostic, DiagnosticExt, Severity}; use pg_fs::{ConfigName, PgLspPath}; use pg_query::PgQueryStore; use pg_schema_cache::SchemaCache; @@ -10,10 +11,12 @@ use sqlx::PgPool; use std::sync::LazyLock; use store::Store; use tokio::runtime::Runtime; +use tracing::info; use tree_sitter::TreeSitterStore; use crate::{ settings::{Settings, SettingsHandle, SettingsHandleMut}, + workspace::PullDiagnosticsResult, WorkspaceError, }; @@ -291,6 +294,50 @@ impl Workspace for WorkspaceServer { fn is_path_ignored(&self, params: IsPathIgnoredParams) -> Result { Ok(self.is_ignored(params.pglsp_path.as_path())) } + + fn pull_diagnostics( + &self, + params: super::PullDiagnosticsParams, + ) -> Result { + // get all statements form the requested document and pull diagnostics out of every + // sourcece + let doc = self + .documents + .get(¶ms.path) + .ok_or(WorkspaceError::not_found())?; + + let diagnostics: Vec = doc + .statement_refs_with_ranges() + .iter() + .flat_map(|(stmt, r)| { + let mut stmt_diagnostics = vec![]; + + stmt_diagnostics.extend(self.pg_query.pull_diagnostics(stmt)); + + stmt_diagnostics + .into_iter() + .map(|d| { + SDiagnostic::new( + d.with_file_path(params.path.as_path().display().to_string()) + .with_file_span(r), + ) + }) + .collect::>() + }) + .collect(); + + let errors = diagnostics + .iter() + .filter(|d| d.severity() == Severity::Error) + .count(); + + info!("Pulled {:?} diagnostic(s)", diagnostics.len()); + Ok(PullDiagnosticsResult { + diagnostics, + errors, + skipped_diagnostics: 0, + }) + } } /// Returns `true` if `path` is a directory or diff --git a/crates/pg_workspace_new/src/workspace/server/document.rs b/crates/pg_workspace_new/src/workspace/server/document.rs index 044110a1..a35de6dd 100644 --- a/crates/pg_workspace_new/src/workspace/server/document.rs +++ b/crates/pg_workspace_new/src/workspace/server/document.rs @@ -74,6 +74,13 @@ impl Document { .collect() } + pub fn statement_refs_with_ranges(&self) -> Vec<(StatementRef, TextRange)> { + self.statements + .iter() + .map(|inner_ref| (self.statement_ref(inner_ref), inner_ref.1)) + .collect() + } + #[allow(dead_code)] /// Returns the statement ref at the given offset pub fn statement_ref_at_offset(&self, offset: &TextSize) -> Option { diff --git a/crates/pg_workspace_new/src/workspace/server/pg_query.rs b/crates/pg_workspace_new/src/workspace/server/pg_query.rs index 401ce54f..ade88078 100644 --- a/crates/pg_workspace_new/src/workspace/server/pg_query.rs +++ b/crates/pg_workspace_new/src/workspace/server/pg_query.rs @@ -1,6 +1,8 @@ use std::sync::Arc; use dashmap::DashMap; +use pg_diagnostics::{serde::Diagnostic as SDiagnostic, Diagnostic, MessageAndDescription}; +use text_size::TextRange; use super::{ change::ChangedStatement, @@ -8,18 +10,47 @@ use super::{ store::Store, }; +/// A specialized diagnostic for the libpg_query parser. +/// +/// Parser diagnostics are always **errors**. +#[derive(Clone, Debug, Diagnostic)] +#[diagnostic(category = "syntax", severity = Error)] +pub struct SyntaxDiagnostic { + /// The location where the error is occurred + #[location(span)] + span: Option, + #[message] + #[description] + pub message: MessageAndDescription, +} + pub struct PgQueryStore { ast_db: DashMap>, - native_diagnostics: DashMap>, + diagnostics: DashMap, +} + +impl From<&pg_query_ext::Error> for SyntaxDiagnostic { + fn from(err: &pg_query_ext::Error) -> Self { + SyntaxDiagnostic { + span: None, + message: MessageAndDescription::from(err.to_string()), + } + } } impl PgQueryStore { pub fn new() -> PgQueryStore { PgQueryStore { ast_db: DashMap::new(), - native_diagnostics: DashMap::new(), + diagnostics: DashMap::new(), } } + + pub fn pull_diagnostics(&self, ref_: &StatementRef) -> Vec { + self.diagnostics.get(ref_).map_or_else(Vec::new, |err| { + vec![SDiagnostic::new(SyntaxDiagnostic::from(err.value()))] + }) + } } impl Store for PgQueryStore { @@ -32,14 +63,14 @@ impl Store for PgQueryStore { if let Ok(ast) = r { self.ast_db.insert(statement.ref_.clone(), Arc::new(ast)); } else { - self.native_diagnostics - .insert(statement.ref_.clone(), Arc::new(r.unwrap_err())); + self.diagnostics + .insert(statement.ref_.clone(), r.unwrap_err()); } } fn remove_statement(&self, statement: &StatementRef) { self.ast_db.remove(statement); - self.native_diagnostics.remove(statement); + self.diagnostics.remove(statement); } fn modify_statement(&self, change: &ChangedStatement) { diff --git a/justfile b/justfile index d6e71ab6..55002e49 100644 --- a/justfile +++ b/justfile @@ -138,3 +138,8 @@ new-crate name: # dry-run-release *args='': # knope release --dry-run {{args}} +clear-branches: + git branch --merged | egrep -v "(^\\*|main)" | xargs git branch -d + +reset-git: + git checkout main && git pull && pnpm run clear-branches From 294c0595bf278552e20d0499a212735d6b65b130 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Thu, 12 Dec 2024 10:03:45 +0100 Subject: [PATCH 2/9] cleanup --- .../src/workspace/server/pg_query.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/pg_workspace_new/src/workspace/server/pg_query.rs b/crates/pg_workspace_new/src/workspace/server/pg_query.rs index ade88078..b566ba78 100644 --- a/crates/pg_workspace_new/src/workspace/server/pg_query.rs +++ b/crates/pg_workspace_new/src/workspace/server/pg_query.rs @@ -26,11 +26,11 @@ pub struct SyntaxDiagnostic { pub struct PgQueryStore { ast_db: DashMap>, - diagnostics: DashMap, + diagnostics: DashMap, } -impl From<&pg_query_ext::Error> for SyntaxDiagnostic { - fn from(err: &pg_query_ext::Error) -> Self { +impl From for SyntaxDiagnostic { + fn from(err: pg_query_ext::Error) -> Self { SyntaxDiagnostic { span: None, message: MessageAndDescription::from(err.to_string()), @@ -47,9 +47,9 @@ impl PgQueryStore { } pub fn pull_diagnostics(&self, ref_: &StatementRef) -> Vec { - self.diagnostics.get(ref_).map_or_else(Vec::new, |err| { - vec![SDiagnostic::new(SyntaxDiagnostic::from(err.value()))] - }) + self.diagnostics + .get(ref_) + .map_or_else(Vec::new, |err| vec![SDiagnostic::new(err.value().clone())]) } } @@ -63,8 +63,10 @@ impl Store for PgQueryStore { if let Ok(ast) = r { self.ast_db.insert(statement.ref_.clone(), Arc::new(ast)); } else { - self.diagnostics - .insert(statement.ref_.clone(), r.unwrap_err()); + self.diagnostics.insert( + statement.ref_.clone(), + SyntaxDiagnostic::from(r.unwrap_err()), + ); } } From da3b59bbc86c55594067246a742086a42723b0bf Mon Sep 17 00:00:00 2001 From: psteinroe Date: Fri, 13 Dec 2024 13:55:33 +0100 Subject: [PATCH 3/9] add diagnostics to lsp --- .env | 2 +- .../pg_lsp_new/src/handlers/text_document.rs | 14 ++-- crates/pg_lsp_new/src/server.rs | 5 +- crates/pg_lsp_new/src/session.rs | 75 ++++++++++++++++++- .../pg_workspace_new/src/workspace/server.rs | 14 +++- .../src/workspace/server/pg_query.rs | 1 + test.sql | 2 +- 7 files changed, 97 insertions(+), 16 deletions(-) diff --git a/.env b/.env index dda71743..88b3b55c 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/postgres +DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres diff --git a/crates/pg_lsp_new/src/handlers/text_document.rs b/crates/pg_lsp_new/src/handlers/text_document.rs index 09ed4cf4..c3fb86ec 100644 --- a/crates/pg_lsp_new/src/handlers/text_document.rs +++ b/crates/pg_lsp_new/src/handlers/text_document.rs @@ -5,7 +5,7 @@ use pg_workspace_new::workspace::{ ChangeFileParams, ChangeParams, CloseFileParams, GetFileContentParams, OpenFileParams, }; use tower_lsp::lsp_types; -use tracing::field; +use tracing::{error, field}; /// Handler for `textDocument/didOpen` LSP notification #[tracing::instrument( @@ -35,9 +35,9 @@ pub(crate) async fn did_open( session.insert_document(url.clone(), doc); - // if let Err(err) = session.update_diagnostics(url).await { - // error!("Failed to update diagnostics: {}", err); - // } + if let Err(err) = session.update_diagnostics(url).await { + error!("Failed to update diagnostics: {}", err); + } Ok(()) } @@ -89,9 +89,9 @@ pub(crate) async fn did_change( session.insert_document(url.clone(), new_doc); - // if let Err(err) = session.update_diagnostics(url).await { - // error!("Failed to update diagnostics: {}", err); - // } + if let Err(err) = session.update_diagnostics(url).await { + error!("Failed to update diagnostics: {}", err); + } Ok(()) } diff --git a/crates/pg_lsp_new/src/server.rs b/crates/pg_lsp_new/src/server.rs index fd1e8e16..f934b478 100644 --- a/crates/pg_lsp_new/src/server.rs +++ b/crates/pg_lsp_new/src/server.rs @@ -160,7 +160,7 @@ impl LanguageServer for LSPServer { self.setup_capabilities().await; // Diagnostics are disabled by default, so update them after fetching workspace config - // self.session.update_all_diagnostics().await; + self.session.update_all_diagnostics().await; } async fn shutdown(&self) -> LspResult<()> { @@ -172,7 +172,7 @@ impl LanguageServer for LSPServer { let _ = params; self.session.load_workspace_settings().await; self.setup_capabilities().await; - // self.session.update_all_diagnostics().await; + self.session.update_all_diagnostics().await; } #[tracing::instrument(level = "trace", skip(self))] @@ -378,6 +378,7 @@ impl ServerFactory { workspace_method!(builder, open_file); workspace_method!(builder, change_file); workspace_method!(builder, close_file); + workspace_method!(builder, pull_diagnostics); let (service, socket) = builder.finish(); ServerConnection { socket, service } diff --git a/crates/pg_lsp_new/src/session.rs b/crates/pg_lsp_new/src/session.rs index 8981b91d..3908d0a0 100644 --- a/crates/pg_lsp_new/src/session.rs +++ b/crates/pg_lsp_new/src/session.rs @@ -1,12 +1,16 @@ +use crate::diagnostics::LspError; use crate::documents::Document; +use crate::utils; use anyhow::Result; +use futures::stream::FuturesUnordered; +use futures::StreamExt; use pg_configuration::ConfigurationPathHint; use pg_diagnostics::{DiagnosticExt, Error}; use pg_fs::{FileSystem, PgLspPath}; use pg_lsp_converters::{negotiated_encoding, PositionEncoding, WideEncoding}; use pg_workspace_new::configuration::{load_configuration, LoadedConfiguration}; use pg_workspace_new::settings::PartialConfigurationExt; -use pg_workspace_new::workspace::UpdateSettingsParams; +use pg_workspace_new::workspace::{PullDiagnosticsParams, UpdateSettingsParams}; use pg_workspace_new::Workspace; use pg_workspace_new::{DynRef, WorkspaceError}; use rustc_hash::FxHashMap; @@ -237,6 +241,75 @@ impl Session { } } + /// Computes diagnostics for the file matching the provided url and publishes + /// them to the client. Called from [`handlers::text_document`] when a file's + /// contents changes. + #[tracing::instrument(level = "trace", skip_all, fields(url = display(&url), diagnostic_count), err)] + pub(crate) async fn update_diagnostics(&self, url: lsp_types::Url) -> Result<(), LspError> { + let pglsp_path = self.file_path(&url)?; + let doc = self.document(&url)?; + if self.configuration_status().is_error() && !self.notified_broken_configuration() { + self.set_notified_broken_configuration(); + self.client + .show_message(MessageType::WARNING, "The configuration file has errors. Biome will report only parsing errors until the configuration is fixed.") + .await; + } + + let diagnostics: Vec = { + let result = self.workspace.pull_diagnostics(PullDiagnosticsParams { + path: pglsp_path.clone(), + max_diagnostics: u64::MAX, + })?; + + tracing::trace!("biome diagnostics: {:#?}", result.diagnostics); + + result + .diagnostics + .into_iter() + .filter_map(|d| { + match utils::diagnostic_to_lsp( + d, + &url, + &doc.line_index, + self.position_encoding(), + None, + ) { + Ok(diag) => Some(diag), + Err(err) => { + error!("failed to convert diagnostic to LSP: {err:?}"); + None + } + } + }) + .collect() + }; + + tracing::Span::current().record("diagnostic_count", diagnostics.len()); + + self.client + .publish_diagnostics(url, diagnostics, Some(doc.version)) + .await; + + Ok(()) + } + + /// Updates diagnostics for every [`Document`] in this [`Session`] + pub(crate) async fn update_all_diagnostics(&self) { + let mut futures: FuturesUnordered<_> = self + .documents + .read() + .unwrap() + .keys() + .map(|url| self.update_diagnostics(url.clone())) + .collect(); + + while let Some(result) = futures.next().await { + if let Err(e) = result { + error!("Error while updating diagnostics: {}", e); + } + } + } + /// Get a [`Document`] matching the provided [`lsp_types::Url`] /// /// If document does not exist, result is [WorkspaceError::NotFound] diff --git a/crates/pg_workspace_new/src/workspace/server.rs b/crates/pg_workspace_new/src/workspace/server.rs index 5ec27a3b..164a6b9f 100644 --- a/crates/pg_workspace_new/src/workspace/server.rs +++ b/crates/pg_workspace_new/src/workspace/server.rs @@ -214,10 +214,16 @@ impl Workspace for WorkspaceServer { #[tracing::instrument(level = "trace", skip(self))] fn open_file(&self, params: OpenFileParams) -> Result<(), WorkspaceError> { tracing::info!("Opening file: {:?}", params.path); - self.documents.insert( - params.path.clone(), - Document::new(params.path, params.content, params.version), - ); + + let doc = Document::new(params.path.clone(), params.content, params.version); + + doc.statements.iter().for_each(|s| { + let stmt = doc.statement(s); + self.tree_sitter.add_statement(&stmt); + self.pg_query.add_statement(&stmt); + }); + + self.documents.insert(params.path, doc); Ok(()) } diff --git a/crates/pg_workspace_new/src/workspace/server/pg_query.rs b/crates/pg_workspace_new/src/workspace/server/pg_query.rs index b566ba78..8d433c4d 100644 --- a/crates/pg_workspace_new/src/workspace/server/pg_query.rs +++ b/crates/pg_workspace_new/src/workspace/server/pg_query.rs @@ -63,6 +63,7 @@ impl Store for PgQueryStore { if let Ok(ast) = r { self.ast_db.insert(statement.ref_.clone(), Arc::new(ast)); } else { + tracing::info!("adding diagnostics"); self.diagnostics.insert( statement.ref_.clone(), SyntaxDiagnostic::from(r.unwrap_err()), diff --git a/test.sql b/test.sql index 2999527e..d67be983 100644 --- a/test.sql +++ b/test.sql @@ -4,6 +4,6 @@ select 14433313331333333333 select * from test; -alter table test drop column id; +alter tqjable test drop column id; select lower(); From d29fa91e91bbb6ccabf4e79662a83145be36aa03 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Sun, 15 Dec 2024 16:52:57 +0100 Subject: [PATCH 4/9] add check command --- crates/pg_cli/src/changed.rs | 39 +++ crates/pg_cli/src/commands/check.rs | 77 +++++ crates/pg_cli/src/commands/daemon.rs | 8 +- crates/pg_cli/src/commands/mod.rs | 322 ++++++++++++++---- crates/pg_cli/src/diagnostics.rs | 20 -- crates/pg_cli/src/execute/mod.rs | 26 +- crates/pg_cli/src/execute/process_file.rs | 3 + .../pg_cli/src/execute/process_file/check.rs | 59 ++++ .../execute/process_file/workspace_file.rs | 4 +- crates/pg_cli/src/execute/std_in.rs | 2 +- crates/pg_cli/src/execute/traverse.rs | 1 + crates/pg_cli/src/lib.rs | 39 +++ crates/pg_cli/src/logging.rs | 2 +- crates/pg_cli/src/main.rs | 2 +- crates/pg_cli/src/reporter/junit.rs | 2 +- crates/pg_cli/src/reporter/terminal.rs | 3 + crates/pg_configuration/src/database.rs | 5 + .../src/categories.rs | 1 + crates/pg_workspace_new/src/workspace.rs | 43 ++- 19 files changed, 552 insertions(+), 106 deletions(-) create mode 100644 crates/pg_cli/src/changed.rs create mode 100644 crates/pg_cli/src/commands/check.rs create mode 100644 crates/pg_cli/src/execute/process_file/check.rs diff --git a/crates/pg_cli/src/changed.rs b/crates/pg_cli/src/changed.rs new file mode 100644 index 00000000..2c1888f2 --- /dev/null +++ b/crates/pg_cli/src/changed.rs @@ -0,0 +1,39 @@ +use crate::CliDiagnostic; +use pg_configuration::PartialConfiguration; +use pg_fs::FileSystem; +use pg_workspace_new::DynRef; +use std::ffi::OsString; + +pub(crate) fn get_changed_files( + fs: &DynRef<'_, dyn FileSystem>, + configuration: &PartialConfiguration, + since: Option<&str>, +) -> Result, CliDiagnostic> { + let default_branch = configuration + .vcs + .as_ref() + .and_then(|v| v.default_branch.as_ref()); + + let base = match (since, default_branch) { + (Some(since), Some(_)) => since, + (Some(since), None) => since, + (None, Some(branch)) => branch, + (None, None) => return Err(CliDiagnostic::incompatible_end_configuration("The `--changed` flag was set, but couldn't determine the base to compare against. Either set configuration.vcs.default_branch or use the --since argument.")), + }; + + let changed_files = fs.get_changed_files(base)?; + + let filtered_changed_files = changed_files.iter().map(OsString::from).collect::>(); + + Ok(filtered_changed_files) +} + +pub(crate) fn get_staged_files( + fs: &DynRef<'_, dyn FileSystem>, +) -> Result, CliDiagnostic> { + let staged_files = fs.get_staged_files()?; + + let filtered_staged_files = staged_files.iter().map(OsString::from).collect::>(); + + Ok(filtered_staged_files) +} diff --git a/crates/pg_cli/src/commands/check.rs b/crates/pg_cli/src/commands/check.rs new file mode 100644 index 00000000..bf09d5fa --- /dev/null +++ b/crates/pg_cli/src/commands/check.rs @@ -0,0 +1,77 @@ +// use super::{determine_fix_file_mode, FixFileModeOptions, LoadEditorConfig}; +use crate::cli_options::CliOptions; +// use crate::commands::{get_files_to_process_with_cli_options, CommandRunner}; +use crate::{CliDiagnostic, Execution, TraversalMode}; +use pg_configuration::PartialConfiguration; +use pg_console::Console; +// use biome_deserialize::Merge; +use pg_fs::FileSystem; +use pg_workspace_new::{configuration::LoadedConfiguration, DynRef, Workspace, WorkspaceError}; +use std::ffi::OsString; + +use super::{determine_fix_file_mode, get_files_to_process_with_cli_options, CommandRunner}; + +pub(crate) struct CheckCommandPayload { + pub(crate) write: bool, + pub(crate) fix: bool, + pub(crate) unsafe_: bool, + pub(crate) configuration: Option, + pub(crate) paths: Vec, + pub(crate) stdin_file_path: Option, + pub(crate) staged: bool, + pub(crate) changed: bool, + pub(crate) since: Option, +} + +impl CommandRunner for CheckCommandPayload { + const COMMAND_NAME: &'static str = "check"; + + fn merge_configuration( + &mut self, + loaded_configuration: LoadedConfiguration, + fs: &DynRef<'_, dyn FileSystem>, + console: &mut dyn Console, + ) -> Result { + let LoadedConfiguration { configuration, .. } = loaded_configuration; + + Ok(configuration) + } + + fn get_files_to_process( + &self, + fs: &DynRef<'_, dyn FileSystem>, + configuration: &PartialConfiguration, + ) -> Result, CliDiagnostic> { + let paths = get_files_to_process_with_cli_options( + self.since.as_deref(), + self.changed, + self.staged, + fs, + configuration, + )? + .unwrap_or(self.paths.clone()); + + Ok(paths) + } + + fn get_stdin_file_path(&self) -> Option<&str> { + self.stdin_file_path.as_deref() + } + + fn should_write(&self) -> bool { + self.write || self.fix + } + + fn get_execution( + &self, + cli_options: &CliOptions, + console: &mut dyn Console, + _workspace: &dyn Workspace, + ) -> Result { + Ok(Execution::new(TraversalMode::Check { + stdin: self.get_stdin(console)?, + vcs_targeted: (self.staged, self.changed).into(), + }) + .set_report(cli_options)) + } +} diff --git a/crates/pg_cli/src/commands/daemon.rs b/crates/pg_cli/src/commands/daemon.rs index a1b2daa1..714327f1 100644 --- a/crates/pg_cli/src/commands/daemon.rs +++ b/crates/pg_cli/src/commands/daemon.rs @@ -206,13 +206,13 @@ pub(crate) fn read_most_recent_log_file( /// `pglsp-logs/server.log.yyyy-MM-dd-HH` files inside the system temporary /// directory) fn setup_tracing_subscriber(log_path: Option, log_file_name_prefix: Option) { - let biome_log_path = log_path.unwrap_or(pg_fs::ensure_cache_dir().join("pglsp-logs")); + let pglsp_log_path = log_path.unwrap_or(pg_fs::ensure_cache_dir().join("pglsp-logs")); let appender_builder = tracing_appender::rolling::RollingFileAppender::builder(); let file_appender = appender_builder .filename_prefix(log_file_name_prefix.unwrap_or(String::from("server.log"))) .max_log_files(7) .rotation(Rotation::HOURLY) - .build(biome_log_path) + .build(pglsp_log_path) .expect("Failed to start the logger for the daemon."); registry() @@ -241,7 +241,7 @@ pub fn default_pglsp_log_path() -> PathBuf { /// - All spans and events at level debug in crates whose name starts with `biome` struct LoggingFilter; -/// Tracing filter used for spans emitted by `biome*` crates +/// Tracing filter used for spans emitted by `pglsp*` crates const SELF_FILTER: LevelFilter = if cfg!(debug_assertions) { LevelFilter::TRACE } else { @@ -250,7 +250,7 @@ const SELF_FILTER: LevelFilter = if cfg!(debug_assertions) { impl LoggingFilter { fn is_enabled(&self, meta: &Metadata<'_>) -> bool { - let filter = if meta.target().starts_with("biome") { + let filter = if meta.target().starts_with("pglsp") { SELF_FILTER } else { LevelFilter::INFO diff --git a/crates/pg_cli/src/commands/mod.rs b/crates/pg_cli/src/commands/mod.rs index c22c4b28..05c433a3 100644 --- a/crates/pg_cli/src/commands/mod.rs +++ b/crates/pg_cli/src/commands/mod.rs @@ -1,21 +1,24 @@ +use crate::changed::{get_changed_files, get_staged_files}; use crate::cli_options::{cli_options, CliOptions, CliReporter, ColorsArg}; -use crate::diagnostics::DeprecatedConfigurationFile; +use crate::diagnostics::DeprecatedArgument; +use crate::execute::Stdin; use crate::logging::LoggingKind; use crate::{ execute_mode, setup_cli_subscriber, CliDiagnostic, CliSession, Execution, LoggingLevel, VERSION, }; use bpaf::Bpaf; -use pg_configuration::PartialConfiguration; +use pg_configuration::{partial_configuration, PartialConfiguration}; use pg_console::{markup, Console, ConsoleExt}; use pg_diagnostics::{Diagnostic, PrintDiagnostic}; use pg_fs::FileSystem; use pg_workspace_new::configuration::{load_configuration, LoadedConfiguration}; use pg_workspace_new::settings::PartialConfigurationExt; -use pg_workspace_new::workspace::UpdateSettingsParams; +use pg_workspace_new::workspace::{FixFileMode, UpdateSettingsParams}; use pg_workspace_new::{DynRef, Workspace, WorkspaceError}; use std::ffi::OsString; use std::path::PathBuf; +pub(crate) mod check; pub(crate) mod clean; pub(crate) mod daemon; pub(crate) mod init; @@ -25,11 +28,58 @@ pub(crate) mod version; #[bpaf(options, version(VERSION))] /// PgLsp official CLI. Use it to check the health of your project or run it to check single files. pub enum PgLspCommand { - /// Shows the Biome version information and quit. + /// Shows the version information and quit. #[bpaf(command)] Version(#[bpaf(external(cli_options), hide_usage)] CliOptions), - /// Starts the Biome daemon server process. + /// Runs everything to the requested files. + #[bpaf(command)] + Check { + /// Writes safe fixes, formatting and import sorting + #[bpaf(long("write"), switch)] + write: bool, + + /// Allow to do unsafe fixes, should be used with `--write` or `--fix` + #[bpaf(long("unsafe"), switch)] + unsafe_: bool, + + /// Alias for `--write`, writes safe fixes, formatting and import sorting + #[bpaf(long("fix"), switch, hide_usage)] + fix: bool, + + #[bpaf(external(partial_configuration), hide_usage, optional)] + configuration: Option, + #[bpaf(external, hide_usage)] + cli_options: CliOptions, + /// Use this option when you want to format code piped from `stdin`, and print the output to `stdout`. + /// + /// The file doesn't need to exist on disk, what matters is the extension of the file. Based on the extension, we know how to check the code. + /// + /// Example: `echo 'let a;' | pg_cli check --stdin-file-path=test.sql` + #[bpaf(long("stdin-file-path"), argument("PATH"), hide_usage)] + stdin_file_path: Option, + + /// When set to true, only the files that have been staged (the ones prepared to be committed) + /// will be linted. This option should be used when working locally. + #[bpaf(long("staged"), switch)] + staged: bool, + + /// When set to true, only the files that have been changed compared to your `defaultBranch` + /// configuration will be linted. This option should be used in CI environments. + #[bpaf(long("changed"), switch)] + changed: bool, + + /// Use this to specify the base branch to compare against when you're using the --changed + /// flag and the `defaultBranch` is not set in your `pglsp.toml` + #[bpaf(long("since"), argument("REF"))] + since: Option, + + /// Single file, single path or list of paths + #[bpaf(positional("PATH"), many)] + paths: Vec, + }, + + /// Starts the daemon server process. #[bpaf(command)] Start { /// Allows to change the prefix applied to the file name of the logs. @@ -137,7 +187,9 @@ pub enum PgLspCommand { impl PgLspCommand { const fn cli_options(&self) -> Option<&CliOptions> { match self { - PgLspCommand::Version(cli_options) => Some(cli_options), + PgLspCommand::Version(cli_options) | PgLspCommand::Check { cli_options, .. } => { + Some(cli_options) + } PgLspCommand::LspProxy { .. } | PgLspCommand::Start { .. } | PgLspCommand::Stop @@ -194,51 +246,6 @@ impl PgLspCommand { } } -/// It accepts a [LoadedPartialConfiguration] and it prints the diagnostics emitted during parsing and deserialization. -/// -/// If it contains [errors](Severity::Error) or higher, it returns an error. -pub(crate) fn validate_configuration_diagnostics( - loaded_configuration: &LoadedConfiguration, - console: &mut dyn Console, - verbose: bool, -) -> Result<(), CliDiagnostic> { - if let Some(file_path) = loaded_configuration - .file_path - .as_ref() - .and_then(|f| f.file_name()) - .and_then(|f| f.to_str()) - { - if file_path == "rome.json" { - let diagnostic = DeprecatedConfigurationFile::new(file_path); - if diagnostic.tags().is_verbose() && verbose { - console.error(markup! {{PrintDiagnostic::verbose(&diagnostic)}}) - } else { - console.error(markup! {{PrintDiagnostic::simple(&diagnostic)}}) - } - } - } - - // let diagnostics = loaded_configuration.as_diagnostics_iter(); - // for diagnostic in diagnostics { - // if diagnostic.tags().is_verbose() && verbose { - // console.error(markup! {{PrintDiagnostic::verbose(diagnostic)}}) - // } else { - // console.error(markup! {{PrintDiagnostic::simple(diagnostic)}}) - // } - // } - // - // if loaded_configuration.has_errors() { - // return Err(CliDiagnostic::workspace_error( - // ConfigurationDiagnostic::invalid_configuration( - // "Exited because the configuration resulted in errors. Please fix them.", - // ) - // .into(), - // )); - // } - - Ok(()) -} - /// Generic interface for executing commands. /// /// Consumers must implement the following methods: @@ -280,13 +287,7 @@ pub(crate) trait CommandRunner: Sized { ) -> Result<(Execution, Vec), CliDiagnostic> { let loaded_configuration = load_configuration(fs, cli_options.as_configuration_path_hint())?; - if self.should_validate_configuration_diagnostics() { - validate_configuration_diagnostics( - &loaded_configuration, - console, - cli_options.verbose, - )?; - } + let configuration_path = loaded_configuration.directory_path.clone(); let configuration = self.merge_configuration(loaded_configuration, fs, console)?; let vcs_base_path = configuration_path.or(fs.working_directory()); @@ -305,6 +306,27 @@ pub(crate) trait CommandRunner: Sized { Ok((execution, paths)) } + /// Computes [Stdin] if the CLI has the necessary information. + /// + /// ## Errors + /// - If the user didn't provide anything via `stdin` but the option `--stdin-file-path` is passed. + fn get_stdin(&self, console: &mut dyn Console) -> Result, CliDiagnostic> { + let stdin = if let Some(stdin_file_path) = self.get_stdin_file_path() { + let input_code = console.read(); + if let Some(input_code) = input_code { + let path = PathBuf::from(stdin_file_path); + Some((path, input_code).into()) + } else { + // we provided the argument without a piped stdin, we bail + return Err(CliDiagnostic::missing_argument("stdin", Self::COMMAND_NAME)); + } + } else { + None + }; + + Ok(stdin) + } + // Below, the methods that consumers must implement. /// Implements this method if you need to merge CLI arguments to the loaded configuration. @@ -353,9 +375,193 @@ pub(crate) trait CommandRunner: Sized { } } +fn get_files_to_process_with_cli_options( + since: Option<&str>, + changed: bool, + staged: bool, + fs: &DynRef<'_, dyn FileSystem>, + configuration: &PartialConfiguration, +) -> Result>, CliDiagnostic> { + if since.is_some() { + if !changed { + return Err(CliDiagnostic::incompatible_arguments("since", "changed")); + } + if staged { + return Err(CliDiagnostic::incompatible_arguments("since", "staged")); + } + } + + if changed { + if staged { + return Err(CliDiagnostic::incompatible_arguments("changed", "staged")); + } + Ok(Some(get_changed_files(fs, configuration, since)?)) + } else if staged { + Ok(Some(get_staged_files(fs)?)) + } else { + Ok(None) + } +} + +/// Holds the options to determine the fix file mode. +pub(crate) struct FixFileModeOptions { + write: bool, + suppress: bool, + suppression_reason: Option, + fix: bool, + unsafe_: bool, +} + +/// - [Result]: if the given options are incompatible +/// - [Option]: if no fixes are requested +/// - [FixFileMode]: if safe or unsafe fixes are requested +pub(crate) fn determine_fix_file_mode( + options: FixFileModeOptions, + console: &mut dyn Console, +) -> Result, CliDiagnostic> { + let FixFileModeOptions { + write, + fix, + suppress, + suppression_reason: _, + unsafe_, + } = options; + + check_fix_incompatible_arguments(options)?; + + let safe_fixes = write || fix; + let unsafe_fixes = ((write || safe_fixes) && unsafe_); + + if unsafe_fixes { + Ok(Some(FixFileMode::SafeAndUnsafeFixes)) + } else if safe_fixes { + Ok(Some(FixFileMode::SafeFixes)) + } else if suppress { + Ok(Some(FixFileMode::ApplySuppressions)) + } else { + Ok(None) + } +} + +/// Checks if the fix file options are incompatible. +fn check_fix_incompatible_arguments(options: FixFileModeOptions) -> Result<(), CliDiagnostic> { + let FixFileModeOptions { + write, + suppress, + suppression_reason, + fix, + .. + } = options; + if write && fix { + return Err(CliDiagnostic::incompatible_arguments("--write", "--fix")); + } else if suppress && write { + return Err(CliDiagnostic::incompatible_arguments( + "--suppress", + "--write", + )); + } else if suppress && fix { + return Err(CliDiagnostic::incompatible_arguments("--suppress", "--fix")); + } else if !suppress && suppression_reason.is_some() { + return Err(CliDiagnostic::unexpected_argument( + "--reason", + "`--reason` is only valid when `--suppress` is used.", + )); + }; + Ok(()) +} + #[cfg(test)] mod tests { use super::*; + use pg_console::BufferConsole; + + #[test] + fn incompatible_arguments() { + for (write, suppress, suppression_reason, fix, unsafe_) in [ + (true, false, None, true, false), // --write --fix + ] { + assert!(check_fix_incompatible_arguments(FixFileModeOptions { + write, + suppress, + suppression_reason, + fix, + unsafe_ + }) + .is_err()); + } + } + + #[test] + fn safe_fixes() { + let mut console = BufferConsole::default(); + + for (write, suppress, suppression_reason, fix, unsafe_) in [ + (true, false, None, false, false), // --write + (false, false, None, true, false), // --fix + ] { + assert_eq!( + determine_fix_file_mode( + FixFileModeOptions { + write, + suppress, + suppression_reason, + fix, + unsafe_ + }, + &mut console + ) + .unwrap(), + Some(FixFileMode::SafeFixes) + ); + } + } + + #[test] + fn safe_and_unsafe_fixes() { + let mut console = BufferConsole::default(); + + for (write, suppress, suppression_reason, fix, unsafe_) in [ + (true, false, None, false, true), // --write --unsafe + (false, false, None, true, true), // --fix --unsafe + ] { + assert_eq!( + determine_fix_file_mode( + FixFileModeOptions { + write, + suppress, + suppression_reason, + fix, + unsafe_ + }, + &mut console + ) + .unwrap(), + Some(FixFileMode::SafeAndUnsafeFixes) + ); + } + } + + #[test] + fn no_fix() { + let mut console = BufferConsole::default(); + + let (write, suppress, suppression_reason, fix, unsafe_) = + (false, false, None, false, false); + assert_eq!( + determine_fix_file_mode( + FixFileModeOptions { + write, + suppress, + suppression_reason, + fix, + unsafe_ + }, + &mut console + ) + .unwrap(), + None + ); + } /// Tests that all CLI options adhere to the invariants expected by `bpaf`. #[test] diff --git a/crates/pg_cli/src/diagnostics.rs b/crates/pg_cli/src/diagnostics.rs index ff00f12f..8186fa48 100644 --- a/crates/pg_cli/src/diagnostics.rs +++ b/crates/pg_cli/src/diagnostics.rs @@ -459,26 +459,6 @@ impl Termination for CliDiagnostic { } } -#[derive(Debug, Diagnostic)] -#[diagnostic( -category = "internalError/fs", - severity = Warning, - message( - description = "The configuration file {path} is deprecated. Use biome.json instead.", - message("The configuration file "{self.path}" is deprecated. Use ""biome.json"" instead."), - ) -)] -pub struct DeprecatedConfigurationFile { - #[location(resource)] - pub path: String, -} - -impl DeprecatedConfigurationFile { - pub fn new(path: impl Into) -> Self { - Self { path: path.into() } - } -} - #[derive(Debug, Default, Diagnostic)] #[diagnostic( severity = Error, diff --git a/crates/pg_cli/src/execute/mod.rs b/crates/pg_cli/src/execute/mod.rs index f96f330b..55bed48f 100644 --- a/crates/pg_cli/src/execute/mod.rs +++ b/crates/pg_cli/src/execute/mod.rs @@ -83,12 +83,27 @@ impl From<(bool, bool)> for VcsTargeted { pub enum TraversalMode { /// A dummy mode to be used when the CLI is not running any command Dummy, + /// This mode is enabled when running the command `check` + Check { + /// The type of fixes that should be applied when analyzing a file. + /// + /// It's [None] if the `check` command is called without `--apply` or `--apply-suggested` + /// arguments. + // fix_file_mode: Option, + /// An optional tuple. + /// 1. The virtual path to the file + /// 2. The content of the file + stdin: Option, + /// A flag to know vcs integrated options such as `--staged` or `--changed` are enabled + vcs_targeted: VcsTargeted, + }, } impl Display for TraversalMode { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { TraversalMode::Dummy { .. } => write!(f, "dummy"), + TraversalMode::Check { .. } => write!(f, "check"), } } } @@ -150,6 +165,7 @@ impl Execution { pub(crate) fn as_diagnostic_category(&self) -> &'static Category { match self.traversal_mode { TraversalMode::Dummy { .. } => category!("dummy"), + TraversalMode::Check { .. } => category!("check"), } } @@ -161,18 +177,23 @@ impl Execution { pub(crate) const fn requires_write_access(&self) -> bool { match self.traversal_mode { TraversalMode::Dummy { .. } => false, + TraversalMode::Check { .. } => false, } } pub(crate) fn as_stdin_file(&self) -> Option<&Stdin> { match &self.traversal_mode { TraversalMode::Dummy { .. } => None, + TraversalMode::Check { stdin, .. } => stdin.as_ref(), } } pub(crate) fn is_vcs_targeted(&self) -> bool { match &self.traversal_mode { TraversalMode::Dummy { .. } => false, + TraversalMode::Check { vcs_targeted, .. } => { + vcs_targeted.staged || vcs_targeted.changed + } } } @@ -184,6 +205,7 @@ impl Execution { pub(crate) fn is_write(&self) -> bool { match self.traversal_mode { TraversalMode::Dummy { .. } => false, + TraversalMode::Check { .. } => false, } } } @@ -206,11 +228,11 @@ pub fn execute_mode( // don't do any traversal if there's some content coming from stdin if let Some(stdin) = execution.as_stdin_file() { - let biome_path = PgLspPath::new(stdin.as_path()); + let pglsp_path = PgLspPath::new(stdin.as_path()); std_in::run( session, &execution, - biome_path, + pglsp_path, stdin.as_content(), cli_options.verbose, ) diff --git a/crates/pg_cli/src/execute/process_file.rs b/crates/pg_cli/src/execute/process_file.rs index 90bf2033..6135c012 100644 --- a/crates/pg_cli/src/execute/process_file.rs +++ b/crates/pg_cli/src/execute/process_file.rs @@ -1,7 +1,9 @@ +mod check; pub(crate) mod workspace_file; use crate::execute::traverse::TraversalOptions; use crate::execute::TraversalMode; +use check::check_file; use pg_diagnostics::Error; use pg_fs::PgLspPath; use std::marker::PhantomData; @@ -117,6 +119,7 @@ pub(crate) fn process_file(ctx: &TraversalOptions, pglsp_path: &PgLspPath) -> Fi TraversalMode::Dummy => { unreachable!("The dummy mode should not be called for this file") } + TraversalMode::Check { .. } => check_file(shared_context, pglsp_path), } }) } diff --git a/crates/pg_cli/src/execute/process_file/check.rs b/crates/pg_cli/src/execute/process_file/check.rs new file mode 100644 index 00000000..ff691a89 --- /dev/null +++ b/crates/pg_cli/src/execute/process_file/check.rs @@ -0,0 +1,59 @@ +use pg_diagnostics::{category, Error}; + +use crate::execute::diagnostics::ResultExt; +use crate::execute::process_file::workspace_file::WorkspaceFile; +use crate::execute::process_file::{FileResult, FileStatus, Message, SharedTraversalOptions}; +use std::path::Path; +use std::sync::atomic::Ordering; + +/// Lints a single file and returns a [FileResult] +pub(crate) fn check_file<'ctx>( + ctx: &'ctx SharedTraversalOptions<'ctx, '_>, + path: &Path, +) -> FileResult { + let mut workspace_file = WorkspaceFile::new(ctx, path)?; + check_with_guard(ctx, &mut workspace_file) +} + +pub(crate) fn check_with_guard<'ctx>( + ctx: &'ctx SharedTraversalOptions<'ctx, '_>, + workspace_file: &mut WorkspaceFile, +) -> FileResult { + tracing::info_span!("Processes check", path =? workspace_file.path.display()).in_scope( + move || { + let input = workspace_file.input()?; + let changed = false; + + let max_diagnostics = ctx.remaining_diagnostics.load(Ordering::Relaxed); + let pull_diagnostics_result = workspace_file + .guard() + .pull_diagnostics(max_diagnostics) + .with_file_path_and_code( + workspace_file.path.display().to_string(), + category!("check"), + )?; + + let no_diagnostics = pull_diagnostics_result.diagnostics.is_empty() + && pull_diagnostics_result.skipped_diagnostics == 0; + + if !no_diagnostics { + ctx.push_message(Message::Diagnostics { + name: workspace_file.path.display().to_string(), + content: input, + diagnostics: pull_diagnostics_result + .diagnostics + .into_iter() + .map(Error::from) + .collect(), + skipped_diagnostics: pull_diagnostics_result.skipped_diagnostics as u32, + }); + } + + if changed { + Ok(FileStatus::Changed) + } else { + Ok(FileStatus::Unchanged) + } + }, + ) +} diff --git a/crates/pg_cli/src/execute/process_file/workspace_file.rs b/crates/pg_cli/src/execute/process_file/workspace_file.rs index 9be76c9a..36a8f4d0 100644 --- a/crates/pg_cli/src/execute/process_file/workspace_file.rs +++ b/crates/pg_cli/src/execute/process_file/workspace_file.rs @@ -21,7 +21,7 @@ impl<'ctx, 'app> WorkspaceFile<'ctx, 'app> { ctx: &SharedTraversalOptions<'ctx, 'app>, path: &Path, ) -> Result { - let biome_path = PgLspPath::new(path); + let pglsp_path = PgLspPath::new(path); let open_options = OpenOptions::default() .read(true) .write(ctx.execution.requires_write_access()); @@ -37,7 +37,7 @@ impl<'ctx, 'app> WorkspaceFile<'ctx, 'app> { let guard = FileGuard::open( ctx.workspace, OpenFileParams { - path: biome_path, + path: pglsp_path, version: 0, content: input.clone(), }, diff --git a/crates/pg_cli/src/execute/std_in.rs b/crates/pg_cli/src/execute/std_in.rs index e9dbff1c..8aaf50ea 100644 --- a/crates/pg_cli/src/execute/std_in.rs +++ b/crates/pg_cli/src/execute/std_in.rs @@ -8,7 +8,7 @@ use pg_fs::PgLspPath; pub(crate) fn run<'a>( session: CliSession, mode: &'a Execution, - biome_path: PgLspPath, + pglsp_path: PgLspPath, content: &'a str, verbose: bool, ) -> Result<(), CliDiagnostic> { diff --git a/crates/pg_cli/src/execute/traverse.rs b/crates/pg_cli/src/execute/traverse.rs index 68e2ff26..934edc40 100644 --- a/crates/pg_cli/src/execute/traverse.rs +++ b/crates/pg_cli/src/execute/traverse.rs @@ -482,6 +482,7 @@ impl<'ctx, 'app> TraversalContext for TraversalOptions<'ctx, 'app> { match self.execution.traversal_mode() { TraversalMode::Dummy { .. } => true, + TraversalMode::Check { .. } => true, } } diff --git a/crates/pg_cli/src/lib.rs b/crates/pg_cli/src/lib.rs index 2a4ae9a5..256d0e22 100644 --- a/crates/pg_cli/src/lib.rs +++ b/crates/pg_cli/src/lib.rs @@ -4,11 +4,15 @@ //! to parse commands and arguments, redirect the execution of the commands and //! execute the traversal of directory and files, based on the command that was passed. +use cli_options::CliOptions; +use commands::check::CheckCommandPayload; +use commands::CommandRunner; use pg_console::{ColorMode, Console}; use pg_fs::OsFileSystem; use pg_workspace_new::{App, DynRef, Workspace, WorkspaceRef}; use std::env; +mod changed; mod cli_options; mod commands; mod diagnostics; @@ -62,6 +66,32 @@ impl<'app> CliSession<'app> { let result = match command { PgLspCommand::Version(_) => commands::version::full_version(self), + PgLspCommand::Check { + write, + fix, + unsafe_, + cli_options, + configuration, + paths, + stdin_file_path, + staged, + changed, + since, + } => run_command( + self, + &cli_options, + CheckCommandPayload { + write, + fix, + unsafe_, + configuration, + paths, + stdin_file_path, + staged, + changed, + since, + }, + ), PgLspCommand::Clean => commands::clean::clean(self), PgLspCommand::Start { config_path, @@ -105,3 +135,12 @@ pub fn to_color_mode(color: Option<&ColorsArg>) -> ColorMode { None => ColorMode::Auto, } } + +pub(crate) fn run_command( + session: CliSession, + cli_options: &CliOptions, + mut command: impl CommandRunner, +) -> Result<(), CliDiagnostic> { + let command = &mut command; + command.run(session, cli_options) +} diff --git a/crates/pg_cli/src/logging.rs b/crates/pg_cli/src/logging.rs index fc8d4e66..e8cd32dd 100644 --- a/crates/pg_cli/src/logging.rs +++ b/crates/pg_cli/src/logging.rs @@ -105,7 +105,7 @@ const SELF_FILTER: LevelFilter = if cfg!(debug_assertions) { impl LoggingFilter { fn is_enabled(&self, meta: &Metadata<'_>) -> bool { - let filter = if meta.target().starts_with("biome") { + let filter = if meta.target().starts_with("pglsp") { if let Some(level) = self.level.to_filter_level() { level } else { diff --git a/crates/pg_cli/src/main.rs b/crates/pg_cli/src/main.rs index e52b4cd1..b07c3858 100644 --- a/crates/pg_cli/src/main.rs +++ b/crates/pg_cli/src/main.rs @@ -52,7 +52,7 @@ fn main() -> ExitCode { fn run_workspace(console: &mut EnvConsole, command: PgLspCommand) -> Result<(), CliDiagnostic> { // If the `--use-server` CLI flag is set, try to open a connection to an - // existing Biome server socket + // existing server socket let workspace = if command.should_use_server() { let runtime = Runtime::new()?; match open_transport(runtime)? { diff --git a/crates/pg_cli/src/reporter/junit.rs b/crates/pg_cli/src/reporter/junit.rs index a313b128..358b5953 100644 --- a/crates/pg_cli/src/reporter/junit.rs +++ b/crates/pg_cli/src/reporter/junit.rs @@ -34,7 +34,7 @@ pub(crate) struct JunitReporterVisitor<'a>(pub(crate) Report, pub(crate) &'a mut impl<'a> JunitReporterVisitor<'a> { pub(crate) fn new(console: &'a mut dyn Console) -> Self { - let report = Report::new("Biome"); + let report = Report::new("PgLsp"); Self(report, console) } } diff --git a/crates/pg_cli/src/reporter/terminal.rs b/crates/pg_cli/src/reporter/terminal.rs index 1aba58d9..c2802a7c 100644 --- a/crates/pg_cli/src/reporter/terminal.rs +++ b/crates/pg_cli/src/reporter/terminal.rs @@ -154,6 +154,9 @@ impl<'a> fmt::Display for SummaryTotal<'a> { TraversalMode::Dummy { .. } => fmt.write_markup(markup! { "Dummy "{files}" in "{self.2}"." }), + TraversalMode::Check { .. } => fmt.write_markup(markup! { + "Checked "{files}" in "{self.2}"." + }), } } } diff --git a/crates/pg_configuration/src/database.rs b/crates/pg_configuration/src/database.rs index dcbbabfb..3302e584 100644 --- a/crates/pg_configuration/src/database.rs +++ b/crates/pg_configuration/src/database.rs @@ -7,18 +7,23 @@ use serde::{Deserialize, Serialize}; #[partial(derive(Bpaf, Clone, Eq, PartialEq))] #[partial(serde(rename_all = "snake_case", default, deny_unknown_fields))] pub struct DatabaseConfiguration { + /// The host of the database. #[partial(bpaf(long("host")))] pub host: String, + /// The port of the database. #[partial(bpaf(long("port")))] pub port: u16, + /// The username to connect to the database. #[partial(bpaf(long("username")))] pub username: String, + /// The password to connect to the database. #[partial(bpaf(long("password")))] pub password: String, + /// The name of the database. #[partial(bpaf(long("database")))] pub database: String, } diff --git a/crates/pg_diagnostics_categories/src/categories.rs b/crates/pg_diagnostics_categories/src/categories.rs index 86d992e7..983406fd 100644 --- a/crates/pg_diagnostics_categories/src/categories.rs +++ b/crates/pg_diagnostics_categories/src/categories.rs @@ -17,6 +17,7 @@ define_categories! { ; "stdin", "lint", + "check", "configuration", "database/connection", "internalError/io", diff --git a/crates/pg_workspace_new/src/workspace.rs b/crates/pg_workspace_new/src/workspace.rs index 4bfe05bc..20293e7f 100644 --- a/crates/pg_workspace_new/src/workspace.rs +++ b/crates/pg_workspace_new/src/workspace.rs @@ -46,6 +46,17 @@ pub struct PullDiagnosticsResult { pub skipped_diagnostics: u64, } +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq)] +/// Which fixes should be applied during the analyzing phase +pub enum FixFileMode { + /// Applies [safe](pg_diagnostics::Applicability::Always) fixes + SafeFixes, + /// Applies [safe](pg_diagnostics::Applicability::Always) and [unsafe](pg_diagnostics::Applicability::MaybeIncorrect) fixes + SafeAndUnsafeFixes, + /// Applies suppression comments to existing diagnostics when using `--suppress` + ApplySuppressions, +} + #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct ChangeParams { /// The range of the file that changed. If `None`, the whole file changed. @@ -174,22 +185,22 @@ impl<'app, W: Workspace + ?Sized> FileGuard<'app, W> { path: self.path.clone(), }) } - // - // pub fn pull_diagnostics( - // &self, - // categories: RuleCategories, - // max_diagnostics: u32, - // only: Vec, - // skip: Vec, - // ) -> Result { - // self.workspace.pull_diagnostics(PullDiagnosticsParams { - // path: self.path.clone(), - // categories, - // max_diagnostics: max_diagnostics.into(), - // only, - // skip, - // }) - // } + + pub fn pull_diagnostics( + &self, + // categories: RuleCategories, + max_diagnostics: u32, + // only: Vec, + // skip: Vec, + ) -> Result { + self.workspace.pull_diagnostics(PullDiagnosticsParams { + path: self.path.clone(), + // categories, + max_diagnostics: max_diagnostics.into(), + // only, + // skip, + }) + } // // pub fn pull_actions( // &self, From 22819775f9ef350ca5387420e2650ef991c15056 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Sun, 15 Dec 2024 16:53:33 +0100 Subject: [PATCH 5/9] fix: db url --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 88b3b55c..dda71743 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres +DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/postgres From 4bc2460b07f772ea8bfe4100ca8954e22482a1b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Steinr=C3=B6tter?= Date: Mon, 16 Dec 2024 09:58:52 +0100 Subject: [PATCH 6/9] Update crates/pg_workspace_new/src/workspace/server.rs Co-authored-by: Julian Domke <68325451+juleswritescode@users.noreply.github.com> --- crates/pg_workspace_new/src/workspace/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pg_workspace_new/src/workspace/server.rs b/crates/pg_workspace_new/src/workspace/server.rs index 164a6b9f..f67eded8 100644 --- a/crates/pg_workspace_new/src/workspace/server.rs +++ b/crates/pg_workspace_new/src/workspace/server.rs @@ -306,7 +306,7 @@ impl Workspace for WorkspaceServer { params: super::PullDiagnosticsParams, ) -> Result { // get all statements form the requested document and pull diagnostics out of every - // sourcece + // source let doc = self .documents .get(¶ms.path) From 02c4c9e7faafc99529d77d3d792dabb804486772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Steinr=C3=B6tter?= Date: Mon, 16 Dec 2024 10:08:07 +0100 Subject: [PATCH 7/9] Update crates/pg_cli/src/execute/mod.rs Co-authored-by: Julian Domke <68325451+juleswritescode@users.noreply.github.com> --- crates/pg_cli/src/execute/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/pg_cli/src/execute/mod.rs b/crates/pg_cli/src/execute/mod.rs index 55bed48f..bc7f6b46 100644 --- a/crates/pg_cli/src/execute/mod.rs +++ b/crates/pg_cli/src/execute/mod.rs @@ -90,6 +90,7 @@ pub enum TraversalMode { /// It's [None] if the `check` command is called without `--apply` or `--apply-suggested` /// arguments. // fix_file_mode: Option, + /// An optional tuple. /// 1. The virtual path to the file /// 2. The content of the file From 058c1ecf528c91baf3f3ee3b28af0af8e1554f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Steinr=C3=B6tter?= Date: Mon, 16 Dec 2024 10:08:23 +0100 Subject: [PATCH 8/9] Update crates/pg_lsp_new/src/session.rs Co-authored-by: Julian Domke <68325451+juleswritescode@users.noreply.github.com> --- crates/pg_lsp_new/src/session.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pg_lsp_new/src/session.rs b/crates/pg_lsp_new/src/session.rs index 3908d0a0..accabd8f 100644 --- a/crates/pg_lsp_new/src/session.rs +++ b/crates/pg_lsp_new/src/session.rs @@ -251,7 +251,7 @@ impl Session { if self.configuration_status().is_error() && !self.notified_broken_configuration() { self.set_notified_broken_configuration(); self.client - .show_message(MessageType::WARNING, "The configuration file has errors. Biome will report only parsing errors until the configuration is fixed.") + .show_message(MessageType::WARNING, "The configuration file has errors. PgLSP will report only parsing errors until the configuration is fixed.") .await; } From 0796679f7f8464cef3ef5927a46b8efbfff451bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Steinr=C3=B6tter?= Date: Mon, 16 Dec 2024 10:08:40 +0100 Subject: [PATCH 9/9] Update crates/pg_lsp_new/src/session.rs Co-authored-by: Julian Domke <68325451+juleswritescode@users.noreply.github.com> --- crates/pg_lsp_new/src/session.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pg_lsp_new/src/session.rs b/crates/pg_lsp_new/src/session.rs index accabd8f..9db4912c 100644 --- a/crates/pg_lsp_new/src/session.rs +++ b/crates/pg_lsp_new/src/session.rs @@ -261,7 +261,7 @@ impl Session { max_diagnostics: u64::MAX, })?; - tracing::trace!("biome diagnostics: {:#?}", result.diagnostics); + tracing::trace!("pglsp diagnostics: {:#?}", result.diagnostics); result .diagnostics