Skip to content

Commit a996747

Browse files
committed
feat: add SQL pretty printer with keyword casing support
- Add pgls_pretty_print and pgls_pretty_print_codegen crates - Implement SQL formatting with configurable keyword casing - Support keyword_case and constant_case settings (default: lowercase) - Integrate format command into CLI - Add LSP formatting handler
1 parent d04cbc9 commit a996747

File tree

1,663 files changed

+502223
-655
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,663 files changed

+502223
-655
lines changed

Cargo.lock

Lines changed: 56 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,20 @@ rust-version = "1.86.0"
1515
[workspace.dependencies]
1616
# supporting crates unrelated to postgres
1717
anyhow = "1.0.92"
18+
biome_console = "=0.5.7"
1819
biome_deserialize = "0.6.0"
1920
biome_deserialize_macros = "0.6.0"
21+
biome_diagnostics = "=0.5.7"
2022
biome_js_factory = "0.5.7"
2123
biome_js_formatter = "0.5.7"
2224
biome_js_syntax = "0.5.7"
23-
biome_rowan = "0.5.7"
24-
biome_string_case = "0.5.8"
25+
biome_json_parser = "=0.5.7"
26+
biome_parser = "=0.5.7"
27+
biome_rowan = "=0.5.7"
28+
biome_string_case = "=0.5.8"
29+
biome_text_edit = "=0.5.7"
30+
biome_text_size = "=0.5.7"
31+
biome_unicode_table = "=0.5.7"
2532
bpaf = { version = "0.9.15", features = ["derive"] }
2633
criterion = "0.5"
2734
crossbeam = "0.8.4"
@@ -44,7 +51,9 @@ slotmap = "1.0.7"
4451
smallvec = { version = "1.13.2", features = ["union", "const_new", "serde"] }
4552
strum = { version = "0.27.1", features = ["derive"] }
4653
# this will use tokio if available, otherwise async-std
54+
camino = "1.1.9"
4755
convert_case = "0.6.0"
56+
dir-test = "0.4.1"
4857
prost = "0.13.5"
4958
prost-reflect = "0.15.3"
5059
protox = "0.8.0"
@@ -77,8 +86,9 @@ pgls_lexer = { path = "./crates/pgls_lexer", version = "0.0.0"
7786
pgls_lexer_codegen = { path = "./crates/pgls_lexer_codegen", version = "0.0.0" }
7887
pgls_lsp = { path = "./crates/pgls_lsp", version = "0.0.0" }
7988
pgls_markup = { path = "./crates/pgls_markup", version = "0.0.0" }
80-
pgls_matcher = { path = "./crates/pgls_matcher", version = "0.0.0" }
8189
pgls_plpgsql_check = { path = "./crates/pgls_plpgsql_check", version = "0.0.0" }
90+
pgls_pretty_print = { path = "./crates/pgls_pretty_print", version = "0.0.0" }
91+
pgls_pretty_print_codegen = { path = "./crates/pgls_pretty_print_codegen", version = "0.0.0" }
8292
pgls_query = { path = "./crates/pgls_query", version = "0.0.0" }
8393
pgls_query_ext = { path = "./crates/pgls_query_ext", version = "0.0.0" }
8494
pgls_query_macros = { path = "./crates/pgls_query_macros", version = "0.0.0" }
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use crate::cli_options::CliOptions;
2+
use crate::commands::get_files_to_process_with_cli_options;
3+
use crate::execute::run_files;
4+
use crate::reporter::Report;
5+
use crate::{CliDiagnostic, CliSession, VcsIntegration};
6+
use crate::{ExecutionConfig, ExecutionMode, VcsTargeting};
7+
use pgls_configuration::PartialConfiguration;
8+
use pgls_diagnostics::category;
9+
use pgls_fs::FileSystem;
10+
use pgls_workspace::DynRef;
11+
use std::ffi::OsString;
12+
13+
pub struct FormatArgs {
14+
pub configuration: Option<PartialConfiguration>,
15+
pub paths: Vec<OsString>,
16+
pub write: bool,
17+
pub staged: bool,
18+
pub changed: bool,
19+
pub since: Option<String>,
20+
}
21+
22+
pub fn format(
23+
mut session: CliSession,
24+
cli_options: &CliOptions,
25+
args: FormatArgs,
26+
) -> Result<(), CliDiagnostic> {
27+
validate_args(&args)?;
28+
29+
let configuration = session.prepare_with_config(cli_options, args.configuration.clone())?;
30+
session.setup_workspace(configuration.clone(), VcsIntegration::Enabled)?;
31+
32+
let paths = resolve_paths(session.fs(), &configuration, &args)?;
33+
34+
let vcs = VcsTargeting {
35+
staged: args.staged,
36+
changed: args.changed,
37+
};
38+
39+
let mode = ExecutionMode::Format {
40+
write: args.write,
41+
vcs,
42+
};
43+
let execution = ExecutionConfig::new(mode, u32::MAX);
44+
45+
let report: Report = run_files(&mut session, &execution, paths)?;
46+
47+
let exit_result = enforce_exit_codes(cli_options, &report);
48+
session.report("format", cli_options, &report)?;
49+
exit_result
50+
}
51+
52+
fn resolve_paths(
53+
fs: &DynRef<'_, dyn FileSystem>,
54+
configuration: &PartialConfiguration,
55+
args: &FormatArgs,
56+
) -> Result<Vec<OsString>, CliDiagnostic> {
57+
let mut paths = get_files_to_process_with_cli_options(
58+
args.since.as_deref(),
59+
args.changed,
60+
args.staged,
61+
fs,
62+
configuration,
63+
)?
64+
.unwrap_or_else(|| args.paths.clone());
65+
66+
if paths.is_empty() {
67+
if let Some(current_dir) = fs.working_directory() {
68+
paths.push(current_dir.into_os_string());
69+
}
70+
}
71+
72+
Ok(paths)
73+
}
74+
75+
fn enforce_exit_codes(cli_options: &CliOptions, payload: &Report) -> Result<(), CliDiagnostic> {
76+
let traversal = payload.traversal.as_ref();
77+
let processed = traversal.map_or(0, |t| t.changed + t.unchanged);
78+
let skipped = traversal.map_or(0, |t| t.skipped);
79+
80+
if processed.saturating_sub(skipped) == 0 && !cli_options.no_errors_on_unmatched {
81+
return Err(CliDiagnostic::no_files_processed());
82+
}
83+
84+
let errors = payload.errors;
85+
let category = category!("format");
86+
87+
if errors > 0 {
88+
return Err(CliDiagnostic::check_error(category));
89+
}
90+
91+
Ok(())
92+
}
93+
94+
fn validate_args(args: &FormatArgs) -> Result<(), CliDiagnostic> {
95+
if args.since.is_some() {
96+
if !args.changed {
97+
return Err(CliDiagnostic::incompatible_arguments("since", "changed"));
98+
}
99+
if args.staged {
100+
return Err(CliDiagnostic::incompatible_arguments("since", "staged"));
101+
}
102+
}
103+
104+
if args.changed && args.staged {
105+
return Err(CliDiagnostic::incompatible_arguments("changed", "staged"));
106+
}
107+
108+
Ok(())
109+
}

crates/pgls_cli/src/commands/mod.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub(crate) mod check;
1212
pub(crate) mod clean;
1313
pub(crate) mod daemon;
1414
pub(crate) mod dblint;
15+
pub(crate) mod format;
1516
pub(crate) mod init;
1617
pub(crate) mod version;
1718

@@ -71,6 +72,39 @@ pub enum PgLSCommand {
7172
paths: Vec<OsString>,
7273
},
7374

75+
/// Formats the requested files.
76+
#[bpaf(command)]
77+
Format {
78+
#[bpaf(external(partial_configuration), hide_usage, optional)]
79+
configuration: Option<PartialConfiguration>,
80+
81+
#[bpaf(external, hide_usage)]
82+
cli_options: CliOptions,
83+
84+
/// Write formatted output back to files
85+
#[bpaf(long("write"), switch)]
86+
write: bool,
87+
88+
/// When set to true, only the files that have been staged (the ones prepared to be committed)
89+
/// will be formatted. This option should be used when working locally.
90+
#[bpaf(long("staged"), switch)]
91+
staged: bool,
92+
93+
/// When set to true, only the files that have been changed compared to your `defaultBranch`
94+
/// configuration will be formatted. This option should be used in CI environments.
95+
#[bpaf(long("changed"), switch)]
96+
changed: bool,
97+
98+
/// Use this to specify the base branch to compare against when you're using the --changed
99+
/// flag and the `defaultBranch` is not set in your `postgres-language-server.jsonc`
100+
#[bpaf(long("since"), argument("REF"))]
101+
since: Option<String>,
102+
103+
/// Single file, single path or list of paths
104+
#[bpaf(positional("PATH"), many)]
105+
paths: Vec<OsString>,
106+
},
107+
74108
/// Starts the daemon server process.
75109
#[bpaf(command)]
76110
Start {
@@ -223,7 +257,8 @@ impl PgLSCommand {
223257
match self {
224258
PgLSCommand::Version(cli_options)
225259
| PgLSCommand::Check { cli_options, .. }
226-
| PgLSCommand::Dblint { cli_options, .. } => Some(cli_options),
260+
| PgLSCommand::Dblint { cli_options, .. }
261+
| PgLSCommand::Format { cli_options, .. } => Some(cli_options),
227262
PgLSCommand::LspProxy { .. }
228263
| PgLSCommand::Start { .. }
229264
| PgLSCommand::Stop

crates/pgls_cli/src/diagnostics.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ use pgls_console::markup;
22
use pgls_diagnostics::adapters::{BpafError, IoError, SerdeJsonError};
33
use pgls_diagnostics::{
44
Advices, Category, Diagnostic, Error, LogCategory, MessageAndDescription, Severity, Visit,
5+
category,
56
};
7+
use pgls_text_edit::TextEdit;
68
use pgls_workspace::WorkspaceError;
9+
use std::fmt::{self, Formatter};
710
use std::process::{ExitCode, Termination};
811
use std::{env::current_exe, fmt::Debug};
912

@@ -447,6 +450,41 @@ impl Termination for CliDiagnostic {
447450
)]
448451
pub struct StdinDiagnostic {}
449452

453+
#[derive(Debug)]
454+
pub struct FormatDiffDiagnostic {
455+
pub file_name: String,
456+
pub diff: TextEdit,
457+
pub start_line: u32,
458+
}
459+
460+
impl Diagnostic for FormatDiffDiagnostic {
461+
fn category(&self) -> Option<&'static Category> {
462+
Some(category!("format"))
463+
}
464+
465+
fn severity(&self) -> Severity {
466+
Severity::Error
467+
}
468+
469+
fn location(&self) -> pgls_diagnostics::Location<'_> {
470+
pgls_diagnostics::Location::builder()
471+
.resource(&self.file_name)
472+
.build()
473+
}
474+
475+
fn description(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
476+
write!(fmt, "File could be formatted using `--write`")
477+
}
478+
479+
fn message(&self, fmt: &mut pgls_console::fmt::Formatter<'_>) -> std::io::Result<()> {
480+
fmt.write_str("File could be formatted using `--write`")
481+
}
482+
483+
fn advices(&self, visitor: &mut dyn Visit) -> std::io::Result<()> {
484+
visitor.record_diff_with_offset(&self.diff, self.start_line)
485+
}
486+
}
487+
450488
#[cfg(test)]
451489
mod test {
452490
use crate::CliDiagnostic;

0 commit comments

Comments
 (0)