diff --git a/crates/pgls_cli/src/commands/mod.rs b/crates/pgls_cli/src/commands/mod.rs index a926fef89..0087aadb0 100644 --- a/crates/pgls_cli/src/commands/mod.rs +++ b/crates/pgls_cli/src/commands/mod.rs @@ -158,7 +158,8 @@ pub enum PgLSCommand { /// Cleans the logs emitted by the daemon. Clean, - /// Exports the database schema to a JSON file for use with WASM bindings. + /// Exports the database schema to JSON for use with WASM bindings. + /// Writes to stdout by default, or to a file if --output is specified. #[bpaf(command("schema-export"))] SchemaExport { /// PostgreSQL connection string (e.g., postgres://user:pass@host/db) @@ -170,14 +171,9 @@ pub enum PgLSCommand { )] connection_string: String, - /// Output file path for the JSON schema - #[bpaf( - long("output"), - short('o'), - argument("PATH"), - fallback(PathBuf::from("schema.json")) - )] - output: PathBuf, + /// Output file path for the JSON schema (defaults to stdout if not specified) + #[bpaf(long("output"), short('o'), argument("PATH"))] + output: Option, }, #[bpaf(command("__run_server"), hide)] diff --git a/crates/pgls_cli/src/commands/schema_export.rs b/crates/pgls_cli/src/commands/schema_export.rs index 8b9791d1e..f6f6f3069 100644 --- a/crates/pgls_cli/src/commands/schema_export.rs +++ b/crates/pgls_cli/src/commands/schema_export.rs @@ -1,29 +1,36 @@ //! Schema export command for WASM bindings. //! //! This command connects to a PostgreSQL database and exports the schema cache -//! as a JSON file that can be used with the WASM bindings. +//! as JSON that can be used with the WASM bindings. use pgls_console::{ConsoleExt, EnvConsole, markup}; use pgls_schema_cache::SchemaCache; use sqlx::postgres::PgPoolOptions; +use std::io::Write; use std::path::Path; use crate::CliDiagnostic; -/// Export the database schema to a JSON file. +/// Export the database schema to JSON. /// /// # Arguments /// * `connection_string` - PostgreSQL connection string -/// * `output_path` - Path to write the JSON output +/// * `output_path` - Optional path to write the JSON output (stdout if None) pub async fn run_schema_export( connection_string: &str, - output_path: &Path, + output_path: Option<&Path>, ) -> Result<(), CliDiagnostic> { let mut console = EnvConsole::default(); - console.log(markup! { - "Connecting to database..." - }); + // Only print progress to stderr if we're writing to stdout, + // so the JSON output is clean and can be piped + let write_to_stdout = output_path.is_none(); + + if !write_to_stdout { + console.log(markup! { + "Connecting to database..." + }); + } // Connect to the database let pool = PgPoolOptions::new() @@ -36,9 +43,11 @@ pub async fn run_schema_export( ))) })?; - console.log(markup! { - "Loading schema cache..." - }); + if !write_to_stdout { + console.log(markup! { + "Loading schema cache..." + }); + } // Load the schema cache let schema_cache = SchemaCache::load(&pool).await.map_err(|e| { @@ -47,9 +56,11 @@ pub async fn run_schema_export( ))) })?; - console.log(markup! { - "Serializing schema..." - }); + if !write_to_stdout { + console.log(markup! { + "Serializing schema..." + }); + } // Serialize to JSON let json = serde_json::to_string_pretty(&schema_cache).map_err(|e| { @@ -58,33 +69,47 @@ pub async fn run_schema_export( ))) })?; - // Write to file - std::fs::write(output_path, json).map_err(|e| { - CliDiagnostic::io_error(std::io::Error::other(format!( - "Failed to write output file: {e}" - ))) - })?; + // Write output + if let Some(path) = output_path { + std::fs::write(path, &json).map_err(|e| { + CliDiagnostic::io_error(std::io::Error::other(format!( + "Failed to write output file: {e}" + ))) + })?; - console.log(markup! { - "Schema exported to "{output_path.display().to_string()} - }); + console.log(markup! { + "Schema exported to "{path.display().to_string()} + }); - // Print summary - console.log(markup! { - "\nSchema summary:" - }); - console.log(markup! { - " Schemas: "{schema_cache.schemas.len().to_string()} - }); - console.log(markup! { - " Tables: "{schema_cache.tables.len().to_string()} - }); - console.log(markup! { - " Functions: "{schema_cache.functions.len().to_string()} - }); - console.log(markup! { - " Types: "{schema_cache.types.len().to_string()} - }); + // Print summary + console.log(markup! { + "\nSchema summary:" + }); + console.log(markup! { + " Schemas: "{schema_cache.schemas.len().to_string()} + }); + console.log(markup! { + " Tables: "{schema_cache.tables.len().to_string()} + }); + console.log(markup! { + " Functions: "{schema_cache.functions.len().to_string()} + }); + console.log(markup! { + " Types: "{schema_cache.types.len().to_string()} + }); + } else { + // Write to stdout + std::io::stdout().write_all(json.as_bytes()).map_err(|e| { + CliDiagnostic::io_error(std::io::Error::other(format!( + "Failed to write to stdout: {e}" + ))) + })?; + std::io::stdout().write_all(b"\n").map_err(|e| { + CliDiagnostic::io_error(std::io::Error::other(format!( + "Failed to write to stdout: {e}" + ))) + })?; + } Ok(()) } diff --git a/crates/pgls_cli/src/lib.rs b/crates/pgls_cli/src/lib.rs index a25733c9a..266aec494 100644 --- a/crates/pgls_cli/src/lib.rs +++ b/crates/pgls_cli/src/lib.rs @@ -126,7 +126,7 @@ impl<'app> CliSession<'app> { let runtime = tokio::runtime::Runtime::new().map_err(CliDiagnostic::io_error)?; runtime.block_on(commands::schema_export::run_schema_export( &connection_string, - &output, + output.as_deref(), )) } }; diff --git a/crates/pgls_cli/src/metrics.rs b/crates/pgls_cli/src/metrics.rs index 8d6012a09..574957632 100644 --- a/crates/pgls_cli/src/metrics.rs +++ b/crates/pgls_cli/src/metrics.rs @@ -74,7 +74,7 @@ impl CallsiteEntry { } } - fn into_histograms(self, name: &str) -> Vec<(Cow, Histogram)> { + fn into_histograms(self, name: &str) -> Vec<(Cow<'_, str>, Histogram)> { match self { CallsiteEntry::Debug { total } => vec![(Cow::Borrowed(name), total)], CallsiteEntry::Trace { total, busy, idle } => vec![ diff --git a/crates/pgls_console/src/markup.rs b/crates/pgls_console/src/markup.rs index afc7b569e..277b8460d 100644 --- a/crates/pgls_console/src/markup.rs +++ b/crates/pgls_console/src/markup.rs @@ -205,11 +205,11 @@ impl Write for MarkupBuf { Ok(()) })?; - if let Some(last) = self.0.last_mut() { - if last.elements == styles { - last.content.push_str(content); - return Ok(()); - } + if let Some(last) = self.0.last_mut() + && last.elements == styles + { + last.content.push_str(content); + return Ok(()); } self.0.push(MarkupNodeBuf { @@ -227,11 +227,11 @@ impl Write for MarkupBuf { Ok(()) })?; - if let Some(last) = self.0.last_mut() { - if last.elements == styles { - last.content.push_str(&content.to_string()); - return Ok(()); - } + if let Some(last) = self.0.last_mut() + && last.elements == styles + { + last.content.push_str(&content.to_string()); + return Ok(()); } self.0.push(MarkupNodeBuf { diff --git a/crates/pgls_diagnostics/src/display/backtrace.rs b/crates/pgls_diagnostics/src/display/backtrace.rs index a49ab5b67..f27b7739c 100644 --- a/crates/pgls_diagnostics/src/display/backtrace.rs +++ b/crates/pgls_diagnostics/src/display/backtrace.rs @@ -163,10 +163,10 @@ impl NativeBacktrace { }) }); - if let Some(top_frame) = top_frame { - if let Some(bottom_frames) = frames.get(top_frame + 1..) { - frames = bottom_frames; - } + if let Some(top_frame) = top_frame + && let Some(bottom_frames) = frames.get(top_frame + 1..) + { + frames = bottom_frames; } let bottom_frame = frames.iter().position(|frame| { @@ -177,10 +177,10 @@ impl NativeBacktrace { }) }); - if let Some(bottom_frame) = bottom_frame { - if let Some(top_frames) = frames.get(..bottom_frame + 1) { - frames = top_frames; - } + if let Some(bottom_frame) = bottom_frame + && let Some(top_frames) = frames.get(..bottom_frame + 1) + { + frames = top_frames; } frames diff --git a/crates/pgls_workspace/src/settings.rs b/crates/pgls_workspace/src/settings.rs index f507cf671..c218a357d 100644 --- a/crates/pgls_workspace/src/settings.rs +++ b/crates/pgls_workspace/src/settings.rs @@ -293,12 +293,12 @@ impl Settings { } /// Returns linter rules. - pub fn as_linter_rules(&self) -> Option> { + pub fn as_linter_rules(&self) -> Option> { self.linter.rules.as_ref().map(Cow::Borrowed) } /// Returns splinter rules. - pub fn as_splinter_rules(&self) -> Option> { + pub fn as_splinter_rules(&self) -> Option> { self.splinter.rules.as_ref().map(Cow::Borrowed) } diff --git a/crates/pgls_workspace/src/workspace/server.rs b/crates/pgls_workspace/src/workspace/server.rs index e6dff2ece..7c2ec8a31 100644 --- a/crates/pgls_workspace/src/workspace/server.rs +++ b/crates/pgls_workspace/src/workspace/server.rs @@ -124,11 +124,11 @@ impl WorkspaceServer { } /// Provides a reference to the current settings - fn workspaces(&self) -> WorkspaceSettingsHandle { + fn workspaces(&self) -> WorkspaceSettingsHandle<'_> { WorkspaceSettingsHandle::new(&self.settings) } - fn workspaces_mut(&self) -> WorkspaceSettingsHandleMut { + fn workspaces_mut(&self) -> WorkspaceSettingsHandleMut<'_> { WorkspaceSettingsHandleMut::new(&self.settings) } @@ -500,8 +500,10 @@ impl Workspace for WorkspaceServer { #[cfg(feature = "db")] fn invalidate_schema_cache(&self, all: bool) -> Result<(), WorkspaceError> { if all { - // Clear all db-loaded schemas (keep json schema since it was explicitly set) - self.schema_cache.clear_all_connections(); + // Clear all schemas - both db-loaded and json-loaded + // DB completions always take precedence when a connection is available, + // so clearing the json schema keeps behavior consistent + self.schema_cache.clear_all(); } else { // Only clear current connection if one exists if let Some(pool) = self.get_current_connection() { diff --git a/crates/pgls_workspace/src/workspace/server/analyser.rs b/crates/pgls_workspace/src/workspace/server/analyser.rs index b521b6758..cd0ef2fa3 100644 --- a/crates/pgls_workspace/src/workspace/server/analyser.rs +++ b/crates/pgls_workspace/src/workspace/server/analyser.rs @@ -50,7 +50,6 @@ impl<'a> AnalyserVisitorBuilder<'a> { self } - #[cfg(feature = "db")] #[must_use] pub(crate) fn finish(self) -> (Vec>, Vec>) { let mut disabled_rules = vec![]; @@ -61,6 +60,7 @@ impl<'a> AnalyserVisitorBuilder<'a> { enabled_rules.extend(linter_enabled_rules); disabled_rules.extend(linter_disabled_rules); } + #[cfg(feature = "db")] if let Some(mut splinter) = self.splinter { pgls_splinter::registry::visit_registry(&mut splinter); let (splinter_enabled_rules, splinter_disabled_rules) = splinter.finish(); @@ -70,21 +70,6 @@ impl<'a> AnalyserVisitorBuilder<'a> { (enabled_rules, disabled_rules) } - - #[cfg(not(feature = "db"))] - #[must_use] - pub(crate) fn finish(self) -> (Vec>, Vec>) { - let mut disabled_rules = vec![]; - let mut enabled_rules = vec![]; - if let Some(mut lint) = self.lint { - pgls_analyser::visit_registry(&mut lint); - let (linter_enabled_rules, linter_disabled_rules) = lint.finish(); - enabled_rules.extend(linter_enabled_rules); - disabled_rules.extend(linter_disabled_rules); - } - - (enabled_rules, disabled_rules) - } } /// Type meant to register all the lint rules diff --git a/docs/codegen/src/rules_index.rs b/docs/codegen/src/rules_index.rs index 88cd3e17c..b0a92f2f4 100644 --- a/docs/codegen/src/rules_index.rs +++ b/docs/codegen/src/rules_index.rs @@ -78,7 +78,7 @@ fn generate_group( Ok(()) } -fn extract_group_metadata(group: &str) -> (&str, Markup) { +fn extract_group_metadata(group: &str) -> (&str, Markup<'_>) { match group { "safety" => ( "Safety", diff --git a/docs/codegen/src/schema.rs b/docs/codegen/src/schema.rs index 747b3ba27..832af31f6 100644 --- a/docs/codegen/src/schema.rs +++ b/docs/codegen/src/schema.rs @@ -30,33 +30,33 @@ pub(crate) fn get_configuration_schema_content() -> anyhow::Result { /// We do this to avoid leaking our `Partial` derive macro to the outside world, /// since it should be just an implementation detail. fn rename_partial_references_in_schema(mut schema: RootSchema) -> RootSchema { - if let Some(meta) = schema.schema.metadata.as_mut() { - if let Some(title) = meta.title.as_ref() { - if let Some(stripped) = title.strip_prefix("Partial") { - meta.title = Some(stripped.to_string()); - } else if title == "RuleWithOptions_for_Null" { - meta.title = Some("RuleWithNoOptions".to_string()); - } else if title == "RuleWithFixOptions_for_Null" { - meta.title = Some("RuleWithFixNoOptions".to_string()); - } else if title == "RuleConfiguration_for_Null" { - meta.title = Some("RuleConfiguration".to_string()); - } else if title == "RuleFixConfiguration_for_Null" { - meta.title = Some("RuleFixConfiguration".to_string()); - } else if let Some(stripped) = title.strip_prefix("RuleWithOptions_for_") { - meta.title = Some(format!("RuleWith{stripped}")); - } else if let Some(stripped) = title.strip_prefix("RuleWithFixOptions_for_") { - meta.title = Some(format!("RuleWith{stripped}")); - } else if let Some(stripped) = title - .strip_prefix("RuleConfiguration_for_") - .map(|x| x.strip_suffix("Options").unwrap_or(x)) - { - meta.title = Some(format!("{stripped}Configuration")); - } else if let Some(stripped) = title - .strip_prefix("RuleFixConfiguration_for_") - .map(|x| x.strip_suffix("Options").unwrap_or(x)) - { - meta.title = Some(format!("{stripped}Configuration")); - } + if let Some(meta) = schema.schema.metadata.as_mut() + && let Some(title) = meta.title.as_ref() + { + if let Some(stripped) = title.strip_prefix("Partial") { + meta.title = Some(stripped.to_string()); + } else if title == "RuleWithOptions_for_Null" { + meta.title = Some("RuleWithNoOptions".to_string()); + } else if title == "RuleWithFixOptions_for_Null" { + meta.title = Some("RuleWithFixNoOptions".to_string()); + } else if title == "RuleConfiguration_for_Null" { + meta.title = Some("RuleConfiguration".to_string()); + } else if title == "RuleFixConfiguration_for_Null" { + meta.title = Some("RuleFixConfiguration".to_string()); + } else if let Some(stripped) = title.strip_prefix("RuleWithOptions_for_") { + meta.title = Some(format!("RuleWith{stripped}")); + } else if let Some(stripped) = title.strip_prefix("RuleWithFixOptions_for_") { + meta.title = Some(format!("RuleWith{stripped}")); + } else if let Some(stripped) = title + .strip_prefix("RuleConfiguration_for_") + .map(|x| x.strip_suffix("Options").unwrap_or(x)) + { + meta.title = Some(format!("{stripped}Configuration")); + } else if let Some(stripped) = title + .strip_prefix("RuleFixConfiguration_for_") + .map(|x| x.strip_suffix("Options").unwrap_or(x)) + { + meta.title = Some(format!("{stripped}Configuration")); } } @@ -74,11 +74,11 @@ fn rename_partial_references_in_schema(mut schema: RootSchema) -> RootSchema { } else { "RuleWithFixNoOptions".to_string() }; - if let Schema::Object(schema_object) = &mut schema { - if let Some(object) = &mut schema_object.object { - object.required.remove("options"); - object.properties.remove("options"); - } + if let Schema::Object(schema_object) = &mut schema + && let Some(object) = &mut schema_object.object + { + object.required.remove("options"); + object.properties.remove("options"); } } else if key == "RuleConfiguration_for_Null" { key = "RuleConfiguration".to_string(); @@ -164,10 +164,10 @@ fn rename_partial_references_in_schema_object(object: &mut SchemaObject) { } fn rename_partial_references_in_optional_schema_box(schema: &mut Option>) { - if let Some(schema) = schema { - if let Schema::Object(object) = schema.as_mut() { - rename_partial_references_in_schema_object(object); - } + if let Some(schema) = schema + && let Schema::Object(object) = schema.as_mut() + { + rename_partial_references_in_schema_object(object); } } diff --git a/packages/@postgres-language-server/wasm/package-lock.json b/packages/@postgres-language-server/wasm/package-lock.json deleted file mode 100644 index 1cb2bfb44..000000000 --- a/packages/@postgres-language-server/wasm/package-lock.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "name": "@postgres-language-server/wasm", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@postgres-language-server/wasm", - "version": "0.0.0", - "license": "MIT", - "devDependencies": { - "@playwright/test": "^1.50.0", - "typescript": "^5.0.0" - } - }, - "node_modules/@playwright/test": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", - "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.57.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/playwright": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", - "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.57.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", - "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - } - } -}