From 0ce598839286af8d748bf2f336e4ae53c9a7d216 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Wed, 12 Mar 2025 19:09:52 +0100 Subject: [PATCH 01/12] chore: add our own text-size --- Cargo.lock | 69 +-- Cargo.toml | 3 +- crates/pglt_analyse/Cargo.toml | 2 +- crates/pglt_analyse/src/rule.rs | 2 +- crates/pglt_commands/Cargo.toml | 8 +- crates/pglt_completions/Cargo.toml | 2 +- crates/pglt_completions/src/complete.rs | 2 +- crates/pglt_configuration/Cargo.toml | 2 +- crates/pglt_console/Cargo.toml | 4 +- crates/pglt_console/src/markup.rs | 2 +- crates/pglt_diagnostics/Cargo.toml | 2 +- crates/pglt_diagnostics/src/context.rs | 2 +- crates/pglt_diagnostics/src/display.rs | 2 +- crates/pglt_diagnostics/src/display/frame.rs | 2 +- crates/pglt_diagnostics/src/display_github.rs | 2 +- crates/pglt_diagnostics/src/location.rs | 4 +- crates/pglt_diagnostics/src/serde.rs | 4 +- crates/pglt_diagnostics/src/suggestion.rs | 2 +- crates/pglt_lexer/Cargo.toml | 2 +- crates/pglt_lexer/src/diagnostics.rs | 2 +- crates/pglt_lexer/src/lib.rs | 2 +- crates/pglt_lsp/Cargo.toml | 38 +- crates/pglt_lsp/src/utils.rs | 2 +- crates/pglt_lsp_converters/Cargo.toml | 8 +- crates/pglt_lsp_converters/src/from_proto.rs | 2 +- crates/pglt_lsp_converters/src/lib.rs | 4 +- crates/pglt_lsp_converters/src/line_index.rs | 2 +- crates/pglt_lsp_converters/src/to_proto.rs | 2 +- crates/pglt_query_ext/Cargo.toml | 2 +- crates/pglt_query_ext/src/diagnostics.rs | 2 +- crates/pglt_statement_splitter/Cargo.toml | 2 +- .../src/diagnostics.rs | 2 +- crates/pglt_statement_splitter/src/lib.rs | 2 +- crates/pglt_statement_splitter/src/parser.rs | 2 +- crates/pglt_text_edit/Cargo.toml | 8 +- crates/pglt_text_edit/src/lib.rs | 2 +- crates/pglt_text_size/Cargo.toml | 28 ++ crates/pglt_text_size/src/lib.rs | 32 ++ crates/pglt_text_size/src/range.rs | 456 ++++++++++++++++++ crates/pglt_text_size/src/schemars_impls.rs | 32 ++ crates/pglt_text_size/src/serde_impls.rs | 48 ++ crates/pglt_text_size/src/size.rs | 173 +++++++ crates/pglt_text_size/src/traits.rs | 36 ++ crates/pglt_text_size/tests/auto_traits.rs | 18 + crates/pglt_text_size/tests/constructors.rs | 24 + crates/pglt_text_size/tests/indexing.rs | 8 + crates/pglt_text_size/tests/main.rs | 76 +++ crates/pglt_text_size/tests/serde.rs | 79 +++ crates/pglt_typecheck/Cargo.toml | 2 +- crates/pglt_typecheck/src/diagnostics.rs | 2 +- crates/pglt_typecheck/src/lib.rs | 2 +- crates/pglt_workspace/Cargo.toml | 2 +- crates/pglt_workspace/src/workspace.rs | 2 +- .../src/workspace/server/change.rs | 4 +- .../src/workspace/server/document.rs | 2 +- lib/line_index/Cargo.toml | 10 - lib/line_index/src/lib.rs | 217 --------- 57 files changed, 1123 insertions(+), 332 deletions(-) create mode 100644 crates/pglt_text_size/Cargo.toml create mode 100644 crates/pglt_text_size/src/lib.rs create mode 100644 crates/pglt_text_size/src/range.rs create mode 100644 crates/pglt_text_size/src/schemars_impls.rs create mode 100644 crates/pglt_text_size/src/serde_impls.rs create mode 100644 crates/pglt_text_size/src/size.rs create mode 100644 crates/pglt_text_size/src/traits.rs create mode 100644 crates/pglt_text_size/tests/auto_traits.rs create mode 100644 crates/pglt_text_size/tests/constructors.rs create mode 100644 crates/pglt_text_size/tests/indexing.rs create mode 100644 crates/pglt_text_size/tests/main.rs create mode 100644 crates/pglt_text_size/tests/serde.rs delete mode 100644 lib/line_index/Cargo.toml delete mode 100644 lib/line_index/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 6392ce498..28e0a3d65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1794,13 +1794,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "line_index" -version = "0.0.0" -dependencies = [ - "text-size", -] - [[package]] name = "linked-hash-map" version = "0.5.6" @@ -2222,10 +2215,10 @@ dependencies = [ "pglt_diagnostics", "pglt_query_ext", "pglt_schema_cache", + "pglt_text_size", "rustc-hash 2.1.0", "schemars", "serde", - "text-size", ] [[package]] @@ -2284,8 +2277,8 @@ version = "0.0.0" dependencies = [ "anyhow", "async-std", + "pglt_text_size", "sqlx", - "text-size", ] [[package]] @@ -2295,11 +2288,11 @@ dependencies = [ "async-std", "pglt_schema_cache", "pglt_test_utils", + "pglt_text_size", "pglt_treesitter_queries", "serde", "serde_json", "sqlx", - "text-size", "tokio", "tree-sitter", "tree_sitter_sql", @@ -2317,11 +2310,11 @@ dependencies = [ "pglt_analyser", "pglt_console", "pglt_diagnostics", + "pglt_text_size", "rustc-hash 2.1.0", "schemars", "serde", "serde_json", - "text-size", "toml", ] @@ -2330,10 +2323,10 @@ name = "pglt_console" version = "0.0.0" dependencies = [ "pglt_markup", + "pglt_text_size", "schemars", "serde", "termcolor", - "text-size", "trybuild", "unicode-segmentation", "unicode-width", @@ -2350,11 +2343,11 @@ dependencies = [ "pglt_diagnostics_categories", "pglt_diagnostics_macros", "pglt_text_edit", + "pglt_text_size", "schemars", "serde", "serde_json", "termcolor", - "text-size", "unicode-width", ] @@ -2409,8 +2402,8 @@ dependencies = [ "pg_query", "pglt_diagnostics", "pglt_lexer_codegen", + "pglt_text_size", "regex", - "text-size", ] [[package]] @@ -2438,12 +2431,12 @@ dependencies = [ "pglt_lsp_converters", "pglt_test_utils", "pglt_text_edit", + "pglt_text_size", "pglt_workspace", "rustc-hash 2.1.0", "serde", "serde_json", "sqlx", - "text-size", "tokio", "toml", "tower", @@ -2456,8 +2449,8 @@ name = "pglt_lsp_converters" version = "0.0.0" dependencies = [ "anyhow", + "pglt_text_size", "rustc-hash 2.1.0", - "text-size", "tower-lsp", ] @@ -2479,7 +2472,7 @@ dependencies = [ "pglt_diagnostics", "pglt_lexer", "pglt_query_ext_codegen", - "text-size", + "pglt_text_size", ] [[package]] @@ -2524,8 +2517,8 @@ dependencies = [ "pglt_diagnostics", "pglt_lexer", "pglt_query_ext", + "pglt_text_size", "regex", - "text-size", ] [[package]] @@ -2556,10 +2549,20 @@ dependencies = [ name = "pglt_text_edit" version = "0.0.0" dependencies = [ + "pglt_text_size", "schemars", "serde", "similar", - "text-size", +] + +[[package]] +name = "pglt_text_size" +version = "0.0.0" +dependencies = [ + "schemars", + "serde", + "serde_test", + "static_assertions", ] [[package]] @@ -2589,8 +2592,8 @@ dependencies = [ "pglt_query_ext", "pglt_schema_cache", "pglt_test_utils", + "pglt_text_size", "sqlx", - "text-size", "tokio", "tree-sitter", "tree_sitter_sql", @@ -2614,13 +2617,13 @@ dependencies = [ "pglt_query_ext", "pglt_schema_cache", "pglt_statement_splitter", + "pglt_text_size", "pglt_typecheck", "rustc-hash 2.1.0", "serde", "serde_json", "sqlx", "tempfile", - "text-size", "tokio", "toml", "tracing", @@ -3300,6 +3303,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_test" +version = "1.0.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f901ee573cab6b3060453d2d5f0bae4e6d628c23c0a962ff9b5f1d7c8d4f1ed" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3654,6 +3666,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "stringprep" version = "0.1.5" @@ -3757,15 +3775,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "text-size" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" -dependencies = [ - "serde", -] - [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index 6b8be0c5b..face77a84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,6 @@ enumflags2 = "0.7.10" ignore = "0.4.23" indexmap = { version = "2.6.0", features = ["serde"] } insta = "1.31.0" -line_index = { path = "./lib/line_index", version = "0.0.0" } pg_query = "6.0.0" proc-macro2 = "1.0.66" quote = "1.0.33" @@ -39,7 +38,6 @@ smallvec = { version = "1.13.2", features = ["union", "const_new sqlx = { version = "0.8.2", features = ["runtime-async-std", "tls-rustls", "postgres", "json"] } syn = "1.0.109" termcolor = "1.4.1" -text-size = "1.1.1" tokio = { version = "1.40.0", features = ["full"] } toml = "0.8.19" tower-lsp = "0.20.0" @@ -75,6 +73,7 @@ pglt_query_proto_parser = { path = "./crates/pglt_query_proto_parser", versi pglt_schema_cache = { path = "./crates/pglt_schema_cache", version = "0.0.0" } pglt_statement_splitter = { path = "./crates/pglt_statement_splitter", version = "0.0.0" } pglt_text_edit = { path = "./crates/pglt_text_edit", version = "0.0.0" } +pglt_text_size = { path = "./crates/pglt_text_size", version = "0.0.0" } pglt_treesitter_queries = { path = "./crates/pglt_treesitter_queries", version = "0.0.0" } pglt_type_resolver = { path = "./crates/pglt_type_resolver", version = "0.0.0" } pglt_typecheck = { path = "./crates/pglt_typecheck", version = "0.0.0" } diff --git a/crates/pglt_analyse/Cargo.toml b/crates/pglt_analyse/Cargo.toml index e0b8f943d..4574d90c1 100644 --- a/crates/pglt_analyse/Cargo.toml +++ b/crates/pglt_analyse/Cargo.toml @@ -22,9 +22,9 @@ rustc-hash = { workspace = true } biome_deserialize = { workspace = true, optional = true } biome_deserialize_macros = { workspace = true, optional = true } enumflags2.workspace = true +pglt_text_size.workspace = true schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"], optional = true } -text-size.workspace = true [features] serde = ["dep:serde", "dep:schemars", "dep:biome_deserialize", "dep:biome_deserialize_macros"] diff --git a/crates/pglt_analyse/src/rule.rs b/crates/pglt_analyse/src/rule.rs index 59950205d..1f70cf844 100644 --- a/crates/pglt_analyse/src/rule.rs +++ b/crates/pglt_analyse/src/rule.rs @@ -5,9 +5,9 @@ use pglt_diagnostics::{ Advices, Category, Diagnostic, DiagnosticTags, Location, LogCategory, MessageAndDescription, Visit, }; +use pglt_text_size::TextRange; use std::cmp::Ordering; use std::fmt::Debug; -use text_size::TextRange; use crate::{categories::RuleCategory, context::RuleContext, registry::RegistryVisitor}; diff --git a/crates/pglt_commands/Cargo.toml b/crates/pglt_commands/Cargo.toml index ffa9c214b..2507b9147 100644 --- a/crates/pglt_commands/Cargo.toml +++ b/crates/pglt_commands/Cargo.toml @@ -12,10 +12,10 @@ version = "0.0.0" [dependencies] -anyhow = "1.0.62" -async-std = "1.12.0" -sqlx.workspace = true -text-size.workspace = true +anyhow = "1.0.62" +async-std = "1.12.0" +pglt_text_size.workspace = true +sqlx.workspace = true [lib] doctest = false diff --git a/crates/pglt_completions/Cargo.toml b/crates/pglt_completions/Cargo.toml index 84a8abd2b..eec3d903e 100644 --- a/crates/pglt_completions/Cargo.toml +++ b/crates/pglt_completions/Cargo.toml @@ -14,7 +14,7 @@ version = "0.0.0" [dependencies] async-std = "1.12.0" -text-size.workspace = true +pglt_text_size.workspace = true pglt_schema_cache.workspace = true diff --git a/crates/pglt_completions/src/complete.rs b/crates/pglt_completions/src/complete.rs index 6049a33ad..1a1fd59ef 100644 --- a/crates/pglt_completions/src/complete.rs +++ b/crates/pglt_completions/src/complete.rs @@ -1,5 +1,5 @@ +use pglt_text_size::TextSize; use serde::{Deserialize, Serialize}; -use text_size::TextSize; use crate::{ builder::CompletionBuilder, diff --git a/crates/pglt_configuration/Cargo.toml b/crates/pglt_configuration/Cargo.toml index 4c45bfbbf..dde8592f1 100644 --- a/crates/pglt_configuration/Cargo.toml +++ b/crates/pglt_configuration/Cargo.toml @@ -20,11 +20,11 @@ pglt_analyse = { workspace = true } pglt_analyser = { workspace = true } pglt_console = { workspace = true } pglt_diagnostics = { workspace = true } +pglt_text_size = { workspace = true } rustc-hash = { workspace = true } schemars = { workspace = true, features = ["indexmap1"], optional = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["raw_value"] } -text-size = { workspace = true } toml = { workspace = true } [lib] diff --git a/crates/pglt_console/Cargo.toml b/crates/pglt_console/Cargo.toml index fde900ec0..ab4ce461e 100644 --- a/crates/pglt_console/Cargo.toml +++ b/crates/pglt_console/Cargo.toml @@ -12,8 +12,8 @@ version = "0.0.0" [dependencies] -pglt_markup = { workspace = true } -text-size = { workspace = true } +pglt_markup = { workspace = true } +pglt_text_size = { workspace = true } schemars = { workspace = true, optional = true } serde = { workspace = true, optional = true, features = ["derive"] } diff --git a/crates/pglt_console/src/markup.rs b/crates/pglt_console/src/markup.rs index a6781c231..4c046ba86 100644 --- a/crates/pglt_console/src/markup.rs +++ b/crates/pglt_console/src/markup.rs @@ -4,8 +4,8 @@ use std::{ io, }; +use pglt_text_size::TextSize; use termcolor::{Color, ColorSpec}; -use text_size::TextSize; use crate::fmt::{Display, Formatter, MarkupElements, Write}; diff --git a/crates/pglt_diagnostics/Cargo.toml b/crates/pglt_diagnostics/Cargo.toml index 0f4d80e2b..c477bfd7e 100644 --- a/crates/pglt_diagnostics/Cargo.toml +++ b/crates/pglt_diagnostics/Cargo.toml @@ -19,11 +19,11 @@ pglt_console = { workspace = true, features = ["serde_markup"] } pglt_diagnostics_categories = { workspace = true, features = ["serde"] } pglt_diagnostics_macros = { workspace = true } pglt_text_edit = { workspace = true } +pglt_text_size.workspace = true schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } termcolor = { workspace = true } -text-size.workspace = true unicode-width = { workspace = true } [features] diff --git a/crates/pglt_diagnostics/src/context.rs b/crates/pglt_diagnostics/src/context.rs index 01fa34489..f972a97be 100644 --- a/crates/pglt_diagnostics/src/context.rs +++ b/crates/pglt_diagnostics/src/context.rs @@ -251,7 +251,7 @@ mod internal { use pglt_console::{fmt, markup}; use pglt_text_edit::TextEdit; - use text_size::TextRange; + use pglt_text_size::TextRange; use crate::{ Advices, Backtrace, Category, Diagnostic, DiagnosticTags, LineIndex, LineIndexBuf, diff --git a/crates/pglt_diagnostics/src/display.rs b/crates/pglt_diagnostics/src/display.rs index b01a6e87a..735d9e483 100644 --- a/crates/pglt_diagnostics/src/display.rs +++ b/crates/pglt_diagnostics/src/display.rs @@ -669,8 +669,8 @@ mod tests { use pglt_diagnostics::{DiagnosticTags, Severity}; use pglt_diagnostics_categories::{Category, category}; use pglt_text_edit::TextEdit; + use pglt_text_size::{TextRange, TextSize}; use serde_json::{from_value, json}; - use text_size::{TextRange, TextSize}; use crate::{self as pglt_diagnostics}; use crate::{ diff --git a/crates/pglt_diagnostics/src/display/frame.rs b/crates/pglt_diagnostics/src/display/frame.rs index 86ca21b09..53b0c6a05 100644 --- a/crates/pglt_diagnostics/src/display/frame.rs +++ b/crates/pglt_diagnostics/src/display/frame.rs @@ -7,7 +7,7 @@ use std::{ }; use pglt_console::{fmt, markup}; -use text_size::{TextLen, TextRange, TextSize}; +use pglt_text_size::{TextLen, TextRange, TextSize}; use unicode_width::UnicodeWidthChar; use crate::{ diff --git a/crates/pglt_diagnostics/src/display_github.rs b/crates/pglt_diagnostics/src/display_github.rs index 0b4a8e689..2e9d4b77a 100644 --- a/crates/pglt_diagnostics/src/display_github.rs +++ b/crates/pglt_diagnostics/src/display_github.rs @@ -1,8 +1,8 @@ use crate::display::frame::SourceFile; use crate::{Diagnostic, Resource, Severity, diagnostic::internal::AsDiagnostic}; use pglt_console::{MarkupBuf, fmt, markup}; +use pglt_text_size::{TextRange, TextSize}; use std::io; -use text_size::{TextRange, TextSize}; /// Helper struct for printing a diagnostic as markup into any formatter /// implementing [pglt_console::fmt::Write]. diff --git a/crates/pglt_diagnostics/src/location.rs b/crates/pglt_diagnostics/src/location.rs index 4b9c8fe82..6678912d4 100644 --- a/crates/pglt_diagnostics/src/location.rs +++ b/crates/pglt_diagnostics/src/location.rs @@ -1,8 +1,8 @@ +use pglt_text_size::{TextRange, TextSize}; use serde::{Deserialize, Serialize}; use std::fmt::Debug; use std::ops::Range; use std::{borrow::Borrow, ops::Deref}; -use text_size::{TextRange, TextSize}; /// Represents the location of a diagnostic in a resource. #[derive(Debug, Default, Clone, Copy)] @@ -343,7 +343,7 @@ impl AsSourceCode for String { #[cfg(test)] mod tests { - use text_size::TextSize; + use pglt_text_size::TextSize; use super::LineIndexBuf; diff --git a/crates/pglt_diagnostics/src/serde.rs b/crates/pglt_diagnostics/src/serde.rs index 44eea25c1..d564f8321 100644 --- a/crates/pglt_diagnostics/src/serde.rs +++ b/crates/pglt_diagnostics/src/serde.rs @@ -2,11 +2,11 @@ use std::io; use pglt_console::{MarkupBuf, fmt, markup}; use pglt_text_edit::TextEdit; +use pglt_text_size::{TextRange, TextSize}; use serde::{ Deserialize, Deserializer, Serialize, Serializer, de::{self, SeqAccess}, }; -use text_size::{TextRange, TextSize}; use crate::{ Advices as _, Backtrace, Category, DiagnosticTags, LogCategory, Resource, Severity, SourceCode, @@ -358,8 +358,8 @@ impl<'de> Deserialize<'de> for DiagnosticTags { mod tests { use std::io; + use pglt_text_size::{TextRange, TextSize}; use serde_json::{Value, json}; - use text_size::{TextRange, TextSize}; use crate::{ self as pglt_diagnostics, {Advices, LogCategory, Visit}, diff --git a/crates/pglt_diagnostics/src/suggestion.rs b/crates/pglt_diagnostics/src/suggestion.rs index 0809b2bfc..324d6197d 100644 --- a/crates/pglt_diagnostics/src/suggestion.rs +++ b/crates/pglt_diagnostics/src/suggestion.rs @@ -1,7 +1,7 @@ use ::serde::{Deserialize, Serialize}; use pglt_console::MarkupBuf; use pglt_text_edit::TextEdit; -use text_size::TextRange; +use pglt_text_size::TextRange; /// Indicates how a tool should manage this suggestion. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] diff --git a/crates/pglt_lexer/Cargo.toml b/crates/pglt_lexer/Cargo.toml index e77e9a07e..daffbf245 100644 --- a/crates/pglt_lexer/Cargo.toml +++ b/crates/pglt_lexer/Cargo.toml @@ -18,7 +18,7 @@ pg_query.workspace = true pglt_diagnostics.workspace = true pglt_lexer_codegen.workspace = true -text-size.workspace = true +pglt_text_size.workspace = true [dev-dependencies] insta.workspace = true diff --git a/crates/pglt_lexer/src/diagnostics.rs b/crates/pglt_lexer/src/diagnostics.rs index 29cd72cd1..f19684375 100644 --- a/crates/pglt_lexer/src/diagnostics.rs +++ b/crates/pglt_lexer/src/diagnostics.rs @@ -1,5 +1,5 @@ use pglt_diagnostics::{Diagnostic, MessageAndDescription}; -use text_size::TextRange; +use pglt_text_size::TextRange; /// A specialized diagnostic for scan errors. /// diff --git a/crates/pglt_lexer/src/lib.rs b/crates/pglt_lexer/src/lib.rs index 3c4ed2af8..696d22e2d 100644 --- a/crates/pglt_lexer/src/lib.rs +++ b/crates/pglt_lexer/src/lib.rs @@ -3,9 +3,9 @@ pub mod diagnostics; use diagnostics::ScanError; use pg_query::protobuf::{KeywordKind, ScanToken}; +use pglt_text_size::{TextLen, TextRange, TextSize}; use regex::Regex; use std::{collections::VecDeque, sync::LazyLock}; -use text_size::{TextLen, TextRange, TextSize}; pub use crate::codegen::SyntaxKind; diff --git a/crates/pglt_lsp/Cargo.toml b/crates/pglt_lsp/Cargo.toml index 2a21e08c2..ddef9f9ac 100644 --- a/crates/pglt_lsp/Cargo.toml +++ b/crates/pglt_lsp/Cargo.toml @@ -12,25 +12,25 @@ version = "0.0.0" [dependencies] -anyhow = { workspace = true } -biome_deserialize = { workspace = true } -futures = "0.3.31" -pglt_analyse = { workspace = true } -pglt_completions = { workspace = true } -pglt_configuration = { workspace = true } -pglt_console = { workspace = true } -pglt_diagnostics = { workspace = true } -pglt_fs = { workspace = true } -pglt_lsp_converters = { workspace = true } -pglt_text_edit = { workspace = true } -pglt_workspace = { workspace = true } -rustc-hash = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -text-size.workspace = true -tokio = { workspace = true, features = ["rt", "io-std"] } -tower-lsp = { version = "0.20.0" } -tracing = { workspace = true, features = ["attributes"] } +anyhow = { workspace = true } +biome_deserialize = { workspace = true } +futures = "0.3.31" +pglt_analyse = { workspace = true } +pglt_completions = { workspace = true } +pglt_configuration = { workspace = true } +pglt_console = { workspace = true } +pglt_diagnostics = { workspace = true } +pglt_fs = { workspace = true } +pglt_lsp_converters = { workspace = true } +pglt_text_edit = { workspace = true } +pglt_text_size.workspace = true +pglt_workspace = { workspace = true } +rustc-hash = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +tokio = { workspace = true, features = ["rt", "io-std"] } +tower-lsp = { version = "0.20.0" } +tracing = { workspace = true, features = ["attributes"] } [dev-dependencies] pglt_test_utils = { workspace = true } diff --git a/crates/pglt_lsp/src/utils.rs b/crates/pglt_lsp/src/utils.rs index 1dc07f916..d1211b6c1 100644 --- a/crates/pglt_lsp/src/utils.rs +++ b/crates/pglt_lsp/src/utils.rs @@ -7,12 +7,12 @@ use pglt_diagnostics::{Diagnostic, DiagnosticTags, Location, PrintDescription, S use pglt_lsp_converters::line_index::LineIndex; use pglt_lsp_converters::{PositionEncoding, from_proto, to_proto}; use pglt_text_edit::{CompressedOp, DiffOp, TextEdit}; +use pglt_text_size::{TextRange, TextSize}; use std::any::Any; use std::borrow::Cow; use std::fmt::{Debug, Display}; use std::io; use std::ops::{Add, Range}; -use text_size::{TextRange, TextSize}; use tower_lsp::jsonrpc::Error as LspError; use tower_lsp::lsp_types; use tower_lsp::lsp_types::{self as lsp, CodeDescription, Url}; diff --git a/crates/pglt_lsp_converters/Cargo.toml b/crates/pglt_lsp_converters/Cargo.toml index 48687ba3a..c8bace046 100644 --- a/crates/pglt_lsp_converters/Cargo.toml +++ b/crates/pglt_lsp_converters/Cargo.toml @@ -12,10 +12,10 @@ version = "0.0.0" [dependencies] -anyhow = { workspace = true } -rustc-hash = { workspace = true } -text-size.workspace = true -tower-lsp = { version = "0.20.0" } +anyhow = { workspace = true } +pglt_text_size.workspace = true +rustc-hash = { workspace = true } +tower-lsp = { version = "0.20.0" } [dev-dependencies] diff --git a/crates/pglt_lsp_converters/src/from_proto.rs b/crates/pglt_lsp_converters/src/from_proto.rs index 1be893379..464a0934a 100644 --- a/crates/pglt_lsp_converters/src/from_proto.rs +++ b/crates/pglt_lsp_converters/src/from_proto.rs @@ -1,7 +1,7 @@ use crate::line_index::LineIndex; use crate::{LineCol, PositionEncoding, WideLineCol}; use anyhow::{Context, Result}; -use text_size::{TextRange, TextSize}; +use pglt_text_size::{TextRange, TextSize}; use tower_lsp::lsp_types; /// The function is used to convert a LSP position to TextSize. diff --git a/crates/pglt_lsp_converters/src/lib.rs b/crates/pglt_lsp_converters/src/lib.rs index 64aa31cb8..284114f72 100644 --- a/crates/pglt_lsp_converters/src/lib.rs +++ b/crates/pglt_lsp_converters/src/lib.rs @@ -1,6 +1,6 @@ //! The crate contains a set of converters to translate between `lsp-types` and `text_size` (and vice versa) types. -use text_size::TextSize; +use pglt_text_size::TextSize; use tower_lsp::lsp_types::{ClientCapabilities, PositionEncodingKind}; pub mod from_proto; @@ -91,7 +91,7 @@ mod tests { use crate::line_index::LineIndex; use crate::to_proto::position; use crate::{LineCol, PositionEncoding, WideEncoding}; - use text_size::TextSize; + use pglt_text_size::TextSize; use tower_lsp::lsp_types::Position; macro_rules! check_conversion { diff --git a/crates/pglt_lsp_converters/src/line_index.rs b/crates/pglt_lsp_converters/src/line_index.rs index 50376566e..ffd77bdff 100644 --- a/crates/pglt_lsp_converters/src/line_index.rs +++ b/crates/pglt_lsp_converters/src/line_index.rs @@ -3,8 +3,8 @@ use std::mem; +use pglt_text_size::TextSize; use rustc_hash::FxHashMap; -use text_size::TextSize; use crate::{LineCol, WideChar, WideEncoding, WideLineCol}; diff --git a/crates/pglt_lsp_converters/src/to_proto.rs b/crates/pglt_lsp_converters/src/to_proto.rs index 387abbb5e..f4446972c 100644 --- a/crates/pglt_lsp_converters/src/to_proto.rs +++ b/crates/pglt_lsp_converters/src/to_proto.rs @@ -1,7 +1,7 @@ use crate::PositionEncoding; use crate::line_index::LineIndex; use anyhow::{Context, Result}; -use text_size::{TextRange, TextSize}; +use pglt_text_size::{TextRange, TextSize}; use tower_lsp::lsp_types; /// The function is used to convert TextSize to a LSP position. diff --git a/crates/pglt_query_ext/Cargo.toml b/crates/pglt_query_ext/Cargo.toml index 99e94ee65..2c97a9f94 100644 --- a/crates/pglt_query_ext/Cargo.toml +++ b/crates/pglt_query_ext/Cargo.toml @@ -18,7 +18,7 @@ pg_query.workspace = true pglt_diagnostics.workspace = true pglt_lexer.workspace = true pglt_query_ext_codegen.workspace = true -text-size.workspace = true +pglt_text_size.workspace = true [lib] doctest = false diff --git a/crates/pglt_query_ext/src/diagnostics.rs b/crates/pglt_query_ext/src/diagnostics.rs index a091dc978..68879064d 100644 --- a/crates/pglt_query_ext/src/diagnostics.rs +++ b/crates/pglt_query_ext/src/diagnostics.rs @@ -1,5 +1,5 @@ use pglt_diagnostics::{Diagnostic, MessageAndDescription}; -use text_size::TextRange; +use pglt_text_size::TextRange; /// A specialized diagnostic for the libpg_query parser. /// diff --git a/crates/pglt_statement_splitter/Cargo.toml b/crates/pglt_statement_splitter/Cargo.toml index 3148d4f69..9fbc5fc93 100644 --- a/crates/pglt_statement_splitter/Cargo.toml +++ b/crates/pglt_statement_splitter/Cargo.toml @@ -15,8 +15,8 @@ version = "0.0.0" pglt_diagnostics = { workspace = true } pglt_lexer.workspace = true pglt_query_ext.workspace = true +pglt_text_size.workspace = true regex.workspace = true -text-size.workspace = true [dev-dependencies] ntest = "0.9.3" diff --git a/crates/pglt_statement_splitter/src/diagnostics.rs b/crates/pglt_statement_splitter/src/diagnostics.rs index 5288e08df..823b6ae76 100644 --- a/crates/pglt_statement_splitter/src/diagnostics.rs +++ b/crates/pglt_statement_splitter/src/diagnostics.rs @@ -1,5 +1,5 @@ use pglt_diagnostics::{Diagnostic, MessageAndDescription}; -use text_size::TextRange; +use pglt_text_size::TextRange; /// A specialized diagnostic for the statement splitter parser. /// diff --git a/crates/pglt_statement_splitter/src/lib.rs b/crates/pglt_statement_splitter/src/lib.rs index 7d74db037..0f5cc45c5 100644 --- a/crates/pglt_statement_splitter/src/lib.rs +++ b/crates/pglt_statement_splitter/src/lib.rs @@ -22,7 +22,7 @@ mod tests { use diagnostics::SplitDiagnostic; use ntest::timeout; use pglt_lexer::SyntaxKind; - use text_size::TextRange; + use pglt_text_size::TextRange; use super::*; diff --git a/crates/pglt_statement_splitter/src/parser.rs b/crates/pglt_statement_splitter/src/parser.rs index f89f28dda..1252d2bda 100644 --- a/crates/pglt_statement_splitter/src/parser.rs +++ b/crates/pglt_statement_splitter/src/parser.rs @@ -6,7 +6,7 @@ mod dml; pub use common::source; use pglt_lexer::{SyntaxKind, Token, WHITESPACE_TOKENS}; -use text_size::{TextRange, TextSize}; +use pglt_text_size::{TextRange, TextSize}; use crate::diagnostics::SplitDiagnostic; diff --git a/crates/pglt_text_edit/Cargo.toml b/crates/pglt_text_edit/Cargo.toml index 1025093b4..01bc0bbb7 100644 --- a/crates/pglt_text_edit/Cargo.toml +++ b/crates/pglt_text_edit/Cargo.toml @@ -12,10 +12,10 @@ version = "0.0.0" [dependencies] -schemars = { workspace = true, optional = true } -serde = { workspace = true, features = ["derive"] } -similar = { workspace = true, features = ["unicode"] } -text-size = { workspace = true, features = ["serde"] } +pglt_text_size = { workspace = true, features = ["serde"] } +schemars = { workspace = true, optional = true } +serde = { workspace = true, features = ["derive"] } +similar = { workspace = true, features = ["unicode"] } [features] schemars = ["dep:schemars"] diff --git a/crates/pglt_text_edit/src/lib.rs b/crates/pglt_text_edit/src/lib.rs index 116d7066e..185bc1ca5 100644 --- a/crates/pglt_text_edit/src/lib.rs +++ b/crates/pglt_text_edit/src/lib.rs @@ -10,10 +10,10 @@ use std::{cmp::Ordering, num::NonZeroU32}; +use pglt_text_size::{TextRange, TextSize}; use serde::{Deserialize, Serialize}; pub use similar::ChangeTag; use similar::{TextDiff, utils::TextDiffRemapper}; -use text_size::{TextRange, TextSize}; #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] diff --git a/crates/pglt_text_size/Cargo.toml b/crates/pglt_text_size/Cargo.toml new file mode 100644 index 000000000..ec5ce81f4 --- /dev/null +++ b/crates/pglt_text_size/Cargo.toml @@ -0,0 +1,28 @@ +[package] +authors.workspace = true +categories.workspace = true +description = "" +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "pglt_text_size" +repository.workspace = true +version = "0.0.0" + +[dependencies] +schemars = { workspace = true, optional = true } +serde = { workspace = true, optional = true } + +[features] +schema = ["dep:schemars", "serde"] +serde = ["dep:serde"] + +[dev-dependencies] +serde_test = "1.0" +static_assertions = "1.1" + +[[test]] +name = "serde" +path = "tests/serde.rs" +required-features = ["serde"] diff --git a/crates/pglt_text_size/src/lib.rs b/crates/pglt_text_size/src/lib.rs new file mode 100644 index 000000000..92bd36b19 --- /dev/null +++ b/crates/pglt_text_size/src/lib.rs @@ -0,0 +1,32 @@ +//! Newtypes for working with text sizes/ranges in a more type-safe manner. +//! +//! This library can help with two things: +//! * Reducing storage requirements for offsets and ranges, under the +//! assumption that 32 bits is enough. +//! * Providing standard vocabulary types for applications where text ranges +//! are pervasive. +//! +//! However, you should not use this library simply because you work with +//! strings. In the overwhelming majority of cases, using `usize` and +//! `std::ops::Range` is better. In particular, if you are publishing a +//! library, using only std types in the interface would make it more +//! interoperable. Similarly, if you are writing something like a lexer, which +//! produces, but does not *store* text ranges, then sticking to `usize` would +//! be better. +//! +//! Minimal Supported Rust Version: latest stable. + +#![forbid(unsafe_code)] +#![warn(missing_debug_implementations, missing_docs)] + +mod range; +mod size; +mod traits; + +#[cfg(feature = "serde")] +mod serde_impls; + +pub use crate::{range::TextRange, size::TextSize, traits::TextLen}; + +#[cfg(target_pointer_width = "16")] +compile_error!("text-size assumes usize >= u32 and does not work on 16-bit targets"); diff --git a/crates/pglt_text_size/src/range.rs b/crates/pglt_text_size/src/range.rs new file mode 100644 index 000000000..9b981642d --- /dev/null +++ b/crates/pglt_text_size/src/range.rs @@ -0,0 +1,456 @@ +use cmp::Ordering; + +use { + crate::TextSize, + std::{ + cmp, fmt, + ops::{Add, AddAssign, Bound, Index, IndexMut, Range, RangeBounds, Sub, SubAssign}, + }, +}; + +/// A range in text, represented as a pair of [`TextSize`][struct@TextSize]. +/// +/// It is a logic error for `start` to be greater than `end`. +#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] +pub struct TextRange { + // Invariant: start <= end + start: TextSize, + end: TextSize, +} + +impl fmt::Debug for TextRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}..{}", self.start().raw, self.end().raw) + } +} + +impl TextRange { + /// Creates a new `TextRange` with the given `start` and `end` (`start..end`). + /// + /// # Panics + /// + /// Panics if `end < start`. + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// let start = TextSize::from(5); + /// let end = TextSize::from(10); + /// let range = TextRange::new(start, end); + /// + /// assert_eq!(range.start(), start); + /// assert_eq!(range.end(), end); + /// assert_eq!(range.len(), end - start); + /// ``` + #[inline] + pub const fn new(start: TextSize, end: TextSize) -> TextRange { + assert!(start.raw <= end.raw); + TextRange { start, end } + } + + /// Create a new `TextRange` with the given `offset` and `len` (`offset..offset + len`). + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// let text = "0123456789"; + /// + /// let offset = TextSize::from(2); + /// let length = TextSize::from(5); + /// let range = TextRange::at(offset, length); + /// + /// assert_eq!(range, TextRange::new(offset, offset + length)); + /// assert_eq!(&text[range], "23456") + /// ``` + #[inline] + pub const fn at(offset: TextSize, len: TextSize) -> TextRange { + TextRange::new(offset, TextSize::new(offset.raw + len.raw)) + } + + /// Create a zero-length range at the specified offset (`offset..offset`). + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// let point: TextSize; + /// # point = TextSize::from(3); + /// let range = TextRange::empty(point); + /// assert!(range.is_empty()); + /// assert_eq!(range, TextRange::new(point, point)); + /// ``` + #[inline] + pub const fn empty(offset: TextSize) -> TextRange { + TextRange { + start: offset, + end: offset, + } + } + + /// Create a range up to the given end (`..end`). + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// let point: TextSize; + /// # point = TextSize::from(12); + /// let range = TextRange::up_to(point); + /// + /// assert_eq!(range.len(), point); + /// assert_eq!(range, TextRange::new(0.into(), point)); + /// assert_eq!(range, TextRange::at(0.into(), point)); + /// ``` + #[inline] + pub const fn up_to(end: TextSize) -> TextRange { + TextRange { + start: TextSize::new(0), + end, + } + } +} + +/// Identity methods. +impl TextRange { + /// The start point of this range. + #[inline] + pub const fn start(self) -> TextSize { + self.start + } + + /// The end point of this range. + #[inline] + pub const fn end(self) -> TextSize { + self.end + } + + /// The size of this range. + #[inline] + pub const fn len(self) -> TextSize { + // HACK for const fn: math on primitives only + TextSize { + raw: self.end().raw - self.start().raw, + } + } + + /// Check if this range is empty. + #[inline] + pub const fn is_empty(self) -> bool { + // HACK for const fn: math on primitives only + self.start().raw == self.end().raw + } +} + +/// Manipulation methods. +impl TextRange { + /// Check if this range contains an offset. + /// + /// The end index is considered excluded. + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// let (start, end): (TextSize, TextSize); + /// # start = 10.into(); end = 20.into(); + /// let range = TextRange::new(start, end); + /// assert!(range.contains(start)); + /// assert!(!range.contains(end)); + /// ``` + #[inline] + pub fn contains(self, offset: TextSize) -> bool { + self.start() <= offset && offset < self.end() + } + + /// Check if this range contains an offset. + /// + /// The end index is considered included. + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// let (start, end): (TextSize, TextSize); + /// # start = 10.into(); end = 20.into(); + /// let range = TextRange::new(start, end); + /// assert!(range.contains_inclusive(start)); + /// assert!(range.contains_inclusive(end)); + /// ``` + #[inline] + pub fn contains_inclusive(self, offset: TextSize) -> bool { + self.start() <= offset && offset <= self.end() + } + + /// Check if this range completely contains another range. + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// let larger = TextRange::new(0.into(), 20.into()); + /// let smaller = TextRange::new(5.into(), 15.into()); + /// assert!(larger.contains_range(smaller)); + /// assert!(!smaller.contains_range(larger)); + /// + /// // a range always contains itself + /// assert!(larger.contains_range(larger)); + /// assert!(smaller.contains_range(smaller)); + /// ``` + #[inline] + pub fn contains_range(self, other: TextRange) -> bool { + self.start() <= other.start() && other.end() <= self.end() + } + + /// The range covered by both ranges, if it exists. + /// If the ranges touch but do not overlap, the output range is empty. + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// assert_eq!( + /// TextRange::intersect( + /// TextRange::new(0.into(), 10.into()), + /// TextRange::new(5.into(), 15.into()), + /// ), + /// Some(TextRange::new(5.into(), 10.into())), + /// ); + /// ``` + #[inline] + pub fn intersect(self, other: TextRange) -> Option { + let start = cmp::max(self.start(), other.start()); + let end = cmp::min(self.end(), other.end()); + if end < start { + return None; + } + Some(TextRange::new(start, end)) + } + + /// Extends the range to cover `other` as well. + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// assert_eq!( + /// TextRange::cover( + /// TextRange::new(0.into(), 5.into()), + /// TextRange::new(15.into(), 20.into()), + /// ), + /// TextRange::new(0.into(), 20.into()), + /// ); + /// ``` + #[inline] + pub fn cover(self, other: TextRange) -> TextRange { + let start = cmp::min(self.start(), other.start()); + let end = cmp::max(self.end(), other.end()); + TextRange::new(start, end) + } + + /// Extends the range to cover `other` offsets as well. + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// assert_eq!( + /// TextRange::empty(0.into()).cover_offset(20.into()), + /// TextRange::new(0.into(), 20.into()), + /// ) + /// ``` + #[inline] + pub fn cover_offset(self, offset: TextSize) -> TextRange { + self.cover(TextRange::empty(offset)) + } + + /// Add an offset to this range. + /// + /// Note that this is not appropriate for changing where a `TextRange` is + /// within some string; rather, it is for changing the reference anchor + /// that the `TextRange` is measured against. + /// + /// The unchecked version (`Add::add`) will _always_ panic on overflow, + /// in contrast to primitive integers, which check in debug mode only. + #[inline] + pub fn checked_add(self, offset: TextSize) -> Option { + Some(TextRange { + start: self.start.checked_add(offset)?, + end: self.end.checked_add(offset)?, + }) + } + + /// Subtract an offset from this range. + /// + /// Note that this is not appropriate for changing where a `TextRange` is + /// within some string; rather, it is for changing the reference anchor + /// that the `TextRange` is measured against. + /// + /// The unchecked version (`Sub::sub`) will _always_ panic on overflow, + /// in contrast to primitive integers, which check in debug mode only. + #[inline] + pub fn checked_sub(self, offset: TextSize) -> Option { + Some(TextRange { + start: self.start.checked_sub(offset)?, + end: self.end.checked_sub(offset)?, + }) + } + + /// Relative order of the two ranges (overlapping ranges are considered + /// equal). + /// + /// + /// This is useful when, for example, binary searching an array of disjoint + /// ranges. + /// + /// # Examples + /// + /// ``` + /// # use text_size::*; + /// # use std::cmp::Ordering; + /// + /// let a = TextRange::new(0.into(), 3.into()); + /// let b = TextRange::new(4.into(), 5.into()); + /// assert_eq!(a.ordering(b), Ordering::Less); + /// + /// let a = TextRange::new(0.into(), 3.into()); + /// let b = TextRange::new(3.into(), 5.into()); + /// assert_eq!(a.ordering(b), Ordering::Less); + /// + /// let a = TextRange::new(0.into(), 3.into()); + /// let b = TextRange::new(2.into(), 5.into()); + /// assert_eq!(a.ordering(b), Ordering::Equal); + /// + /// let a = TextRange::new(0.into(), 3.into()); + /// let b = TextRange::new(2.into(), 2.into()); + /// assert_eq!(a.ordering(b), Ordering::Equal); + /// + /// let a = TextRange::new(2.into(), 3.into()); + /// let b = TextRange::new(2.into(), 2.into()); + /// assert_eq!(a.ordering(b), Ordering::Greater); + /// ``` + #[inline] + pub fn ordering(self, other: TextRange) -> Ordering { + if self.end() <= other.start() { + Ordering::Less + } else if other.end() <= self.start() { + Ordering::Greater + } else { + Ordering::Equal + } + } +} + +impl Index for str { + type Output = str; + #[inline] + fn index(&self, index: TextRange) -> &str { + &self[Range::::from(index)] + } +} + +impl Index for String { + type Output = str; + #[inline] + fn index(&self, index: TextRange) -> &str { + &self[Range::::from(index)] + } +} + +impl IndexMut for str { + #[inline] + fn index_mut(&mut self, index: TextRange) -> &mut str { + &mut self[Range::::from(index)] + } +} + +impl IndexMut for String { + #[inline] + fn index_mut(&mut self, index: TextRange) -> &mut str { + &mut self[Range::::from(index)] + } +} + +impl RangeBounds for TextRange { + fn start_bound(&self) -> Bound<&TextSize> { + Bound::Included(&self.start) + } + + fn end_bound(&self) -> Bound<&TextSize> { + Bound::Excluded(&self.end) + } +} + +impl From for Range +where + T: From, +{ + #[inline] + fn from(r: TextRange) -> Self { + r.start().into()..r.end().into() + } +} + +macro_rules! ops { + (impl $Op:ident for TextRange by fn $f:ident = $op:tt) => { + impl $Op<&TextSize> for TextRange { + type Output = TextRange; + #[inline] + fn $f(self, other: &TextSize) -> TextRange { + self $op *other + } + } + impl $Op for &TextRange + where + TextRange: $Op, + { + type Output = TextRange; + #[inline] + fn $f(self, other: T) -> TextRange { + *self $op other + } + } + }; +} + +impl Add for TextRange { + type Output = TextRange; + #[inline] + fn add(self, offset: TextSize) -> TextRange { + self.checked_add(offset) + .expect("TextRange +offset overflowed") + } +} + +impl Sub for TextRange { + type Output = TextRange; + #[inline] + fn sub(self, offset: TextSize) -> TextRange { + self.checked_sub(offset) + .expect("TextRange -offset overflowed") + } +} + +ops!(impl Add for TextRange by fn add = +); +ops!(impl Sub for TextRange by fn sub = -); + +impl AddAssign for TextRange +where + TextRange: Add, +{ + #[inline] + fn add_assign(&mut self, rhs: A) { + *self = *self + rhs + } +} + +impl SubAssign for TextRange +where + TextRange: Sub, +{ + #[inline] + fn sub_assign(&mut self, rhs: S) { + *self = *self - rhs + } +} diff --git a/crates/pglt_text_size/src/schemars_impls.rs b/crates/pglt_text_size/src/schemars_impls.rs new file mode 100644 index 000000000..8c216ccde --- /dev/null +++ b/crates/pglt_text_size/src/schemars_impls.rs @@ -0,0 +1,32 @@ +//! This module implements the [JsonSchema] trait from the [schemars] crate for +//! [TextSize] and [TextRange] if the `schemars` feature is enabled. This trait +//! exposes meta-information on how a given type is serialized and deserialized +//! using `serde`, and is currently used to generate TypeScript types for the node.js and wasm +//! bindings to the Workspace API + +use crate::{TextRange, TextSize}; +use schemars::{JsonSchema, r#gen::SchemaGenerator, schema::Schema}; + +impl JsonSchema for TextSize { + fn schema_name() -> String { + String::from("TextSize") + } + + fn json_schema(generator: &mut SchemaGenerator) -> Schema { + // TextSize is represented as a raw u32, see serde_impls.rs for the + // actual implementation + ::json_schema(generator) + } +} + +impl JsonSchema for TextRange { + fn schema_name() -> String { + String::from("TextRange") + } + + fn json_schema(generator: &mut SchemaGenerator) -> Schema { + // TextSize is represented as (TextSize, TextSize), see serde_impls.rs + // for the actual implementation + <(TextSize, TextSize)>::json_schema(generator) + } +} diff --git a/crates/pglt_text_size/src/serde_impls.rs b/crates/pglt_text_size/src/serde_impls.rs new file mode 100644 index 000000000..a422c75fa --- /dev/null +++ b/crates/pglt_text_size/src/serde_impls.rs @@ -0,0 +1,48 @@ +use { + crate::{TextRange, TextSize}, + serde::{Deserialize, Deserializer, Serialize, Serializer, de}, +}; + +impl Serialize for TextSize { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.raw.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for TextSize { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + u32::deserialize(deserializer).map(TextSize::from) + } +} + +impl Serialize for TextRange { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + (self.start(), self.end()).serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for TextRange { + #[allow(clippy::nonminimal_bool)] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let (start, end) = Deserialize::deserialize(deserializer)?; + if !(start <= end) { + return Err(de::Error::custom(format!( + "invalid range: {:?}..{:?}", + start, end + ))); + } + Ok(TextRange::new(start, end)) + } +} diff --git a/crates/pglt_text_size/src/size.rs b/crates/pglt_text_size/src/size.rs new file mode 100644 index 000000000..c950d2edd --- /dev/null +++ b/crates/pglt_text_size/src/size.rs @@ -0,0 +1,173 @@ +use { + crate::TextLen, + std::{ + convert::TryFrom, + fmt, iter, + num::TryFromIntError, + ops::{Add, AddAssign, Sub, SubAssign}, + u32, + }, +}; + +/// A measure of text length. Also, equivalently, an index into text. +/// +/// This is a UTF-8 bytes offset stored as `u32`, but +/// most clients should treat it as an opaque measure. +/// +/// For cases that need to escape `TextSize` and return to working directly +/// with primitive integers, `TextSize` can be converted losslessly to/from +/// `u32` via [`From`] conversions as well as losslessly be converted [`Into`] +/// `usize`. The `usize -> TextSize` direction can be done via [`TryFrom`]. +/// +/// These escape hatches are primarily required for unit testing and when +/// converting from UTF-8 size to another coordinate space, such as UTF-16. +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TextSize { + pub(crate) raw: u32, +} + +impl fmt::Debug for TextSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.raw) + } +} + +impl TextSize { + /// Creates a new instance of `TextSize` from a raw `u32`. + #[inline] + pub const fn new(raw: u32) -> TextSize { + TextSize { raw } + } + + /// The text size of some primitive text-like object. + /// + /// Accepts `char`, `&str`, and `&String`. + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// let char_size = TextSize::of('πŸ¦€'); + /// assert_eq!(char_size, TextSize::from(4)); + /// + /// let str_size = TextSize::of("rust-analyzer"); + /// assert_eq!(str_size, TextSize::from(13)); + /// ``` + #[inline] + pub fn of(text: T) -> TextSize { + text.text_len() + } +} + +/// Methods to act like a primitive integer type, where reasonably applicable. +// Last updated for parity with Rust 1.42.0. +impl TextSize { + /// Checked addition. Returns `None` if overflow occurred. + #[inline] + pub const fn checked_add(self, rhs: TextSize) -> Option { + match self.raw.checked_add(rhs.raw) { + Some(raw) => Some(TextSize { raw }), + None => None, + } + } + + /// Checked subtraction. Returns `None` if overflow occurred. + #[inline] + pub const fn checked_sub(self, rhs: TextSize) -> Option { + match self.raw.checked_sub(rhs.raw) { + Some(raw) => Some(TextSize { raw }), + None => None, + } + } +} + +impl From for TextSize { + #[inline] + fn from(raw: u32) -> Self { + TextSize { raw } + } +} + +impl From for u32 { + #[inline] + fn from(value: TextSize) -> Self { + value.raw + } +} + +impl TryFrom for TextSize { + type Error = TryFromIntError; + #[inline] + fn try_from(value: usize) -> Result { + Ok(u32::try_from(value)?.into()) + } +} + +impl From for usize { + #[inline] + fn from(value: TextSize) -> Self { + value.raw as usize + } +} + +macro_rules! ops { + (impl $Op:ident for TextSize by fn $f:ident = $op:tt) => { + impl $Op for TextSize { + type Output = TextSize; + #[inline] + fn $f(self, other: TextSize) -> TextSize { + TextSize { raw: self.raw $op other.raw } + } + } + impl $Op<&TextSize> for TextSize { + type Output = TextSize; + #[inline] + fn $f(self, other: &TextSize) -> TextSize { + self $op *other + } + } + impl $Op for &TextSize + where + TextSize: $Op, + { + type Output = TextSize; + #[inline] + fn $f(self, other: T) -> TextSize { + *self $op other + } + } + }; +} + +ops!(impl Add for TextSize by fn add = +); +ops!(impl Sub for TextSize by fn sub = -); + +impl AddAssign for TextSize +where + TextSize: Add, +{ + #[inline] + fn add_assign(&mut self, rhs: A) { + *self = *self + rhs + } +} + +impl SubAssign for TextSize +where + TextSize: Sub, +{ + #[inline] + fn sub_assign(&mut self, rhs: S) { + *self = *self - rhs + } +} + +impl iter::Sum for TextSize +where + TextSize: Add, +{ + #[inline] + fn sum>(iter: I) -> TextSize { + iter.fold(0.into(), Add::add) + } +} diff --git a/crates/pglt_text_size/src/traits.rs b/crates/pglt_text_size/src/traits.rs new file mode 100644 index 000000000..d0bb6c1f6 --- /dev/null +++ b/crates/pglt_text_size/src/traits.rs @@ -0,0 +1,36 @@ +use {crate::TextSize, std::convert::TryInto}; + +use priv_in_pub::Sealed; +mod priv_in_pub { + pub trait Sealed {} +} + +/// Primitives with a textual length that can be passed to [`TextSize::of`]. +pub trait TextLen: Copy + Sealed { + /// The textual length of this primitive. + fn text_len(self) -> TextSize; +} + +impl Sealed for &'_ str {} +impl TextLen for &'_ str { + #[inline] + fn text_len(self) -> TextSize { + self.len().try_into().unwrap() + } +} + +impl Sealed for &'_ String {} +impl TextLen for &'_ String { + #[inline] + fn text_len(self) -> TextSize { + self.as_str().text_len() + } +} + +impl Sealed for char {} +impl TextLen for char { + #[inline] + fn text_len(self) -> TextSize { + (self.len_utf8() as u32).into() + } +} diff --git a/crates/pglt_text_size/tests/auto_traits.rs b/crates/pglt_text_size/tests/auto_traits.rs new file mode 100644 index 000000000..0a64ba152 --- /dev/null +++ b/crates/pglt_text_size/tests/auto_traits.rs @@ -0,0 +1,18 @@ +use { + pglt_text_size::*, + static_assertions::*, + std::{ + fmt::Debug, + hash::Hash, + marker::{Send, Sync}, + panic::{RefUnwindSafe, UnwindSafe}, + }, +}; + +// auto traits +assert_impl_all!(TextSize: Send, Sync, Unpin, UnwindSafe, RefUnwindSafe); +assert_impl_all!(TextRange: Send, Sync, Unpin, UnwindSafe, RefUnwindSafe); + +// common traits +assert_impl_all!(TextSize: Copy, Debug, Default, Hash, Ord); +assert_impl_all!(TextRange: Copy, Debug, Default, Hash, Eq); diff --git a/crates/pglt_text_size/tests/constructors.rs b/crates/pglt_text_size/tests/constructors.rs new file mode 100644 index 000000000..ea4a4064c --- /dev/null +++ b/crates/pglt_text_size/tests/constructors.rs @@ -0,0 +1,24 @@ +use pglt_text_size::TextSize; + +#[derive(Copy, Clone)] +struct BadRope<'a>(&'a [&'a str]); + +impl BadRope<'_> { + fn text_len(self) -> TextSize { + self.0.iter().copied().map(TextSize::of).sum() + } +} + +#[test] +fn main() { + let x: char = 'c'; + let _ = TextSize::of(x); + + let x: &str = "hello"; + let _ = TextSize::of(x); + + let x: &String = &"hello".into(); + let _ = TextSize::of(x); + + let _ = BadRope(&[""]).text_len(); +} diff --git a/crates/pglt_text_size/tests/indexing.rs b/crates/pglt_text_size/tests/indexing.rs new file mode 100644 index 000000000..bb25ee622 --- /dev/null +++ b/crates/pglt_text_size/tests/indexing.rs @@ -0,0 +1,8 @@ +use pglt_text_size::*; + +#[test] +fn main() { + let range = TextRange::default(); + _ = &""[range]; + _ = &String::new()[range]; +} diff --git a/crates/pglt_text_size/tests/main.rs b/crates/pglt_text_size/tests/main.rs new file mode 100644 index 000000000..6b9fe923b --- /dev/null +++ b/crates/pglt_text_size/tests/main.rs @@ -0,0 +1,76 @@ +use {pglt_text_size::*, std::ops}; + +fn size(x: u32) -> TextSize { + TextSize::from(x) +} + +fn range(x: ops::Range) -> TextRange { + TextRange::new(x.start.into(), x.end.into()) +} + +#[test] +fn sum() { + let xs: Vec = vec![size(0), size(1), size(2)]; + assert_eq!(xs.iter().sum::(), size(3)); + assert_eq!(xs.into_iter().sum::(), size(3)); +} + +#[test] +fn math() { + assert_eq!(size(10) + size(5), size(15)); + assert_eq!(size(10) - size(5), size(5)); +} + +#[test] +fn checked_math() { + assert_eq!(size(1).checked_add(size(1)), Some(size(2))); + assert_eq!(size(1).checked_sub(size(1)), Some(size(0))); + assert_eq!(size(1).checked_sub(size(2)), None); + assert_eq!(size(!0).checked_add(size(1)), None); +} + +#[test] +#[rustfmt::skip] +fn contains() { + assert!( range(2..4).contains_range(range(2..3))); + assert!( ! range(2..4).contains_range(range(1..3))); +} + +#[test] +fn intersect() { + assert_eq!(range(1..2).intersect(range(2..3)), Some(range(2..2))); + assert_eq!(range(1..5).intersect(range(2..3)), Some(range(2..3))); + assert_eq!(range(1..2).intersect(range(3..4)), None); +} + +#[test] +fn cover() { + assert_eq!(range(1..2).cover(range(2..3)), range(1..3)); + assert_eq!(range(1..5).cover(range(2..3)), range(1..5)); + assert_eq!(range(1..2).cover(range(4..5)), range(1..5)); +} + +#[test] +fn cover_offset() { + assert_eq!(range(1..3).cover_offset(size(0)), range(0..3)); + assert_eq!(range(1..3).cover_offset(size(1)), range(1..3)); + assert_eq!(range(1..3).cover_offset(size(2)), range(1..3)); + assert_eq!(range(1..3).cover_offset(size(3)), range(1..3)); + assert_eq!(range(1..3).cover_offset(size(4)), range(1..4)); +} + +#[test] +#[rustfmt::skip] +fn contains_point() { + assert!( ! range(1..3).contains(size(0))); + assert!( range(1..3).contains(size(1))); + assert!( range(1..3).contains(size(2))); + assert!( ! range(1..3).contains(size(3))); + assert!( ! range(1..3).contains(size(4))); + + assert!( ! range(1..3).contains_inclusive(size(0))); + assert!( range(1..3).contains_inclusive(size(1))); + assert!( range(1..3).contains_inclusive(size(2))); + assert!( range(1..3).contains_inclusive(size(3))); + assert!( ! range(1..3).contains_inclusive(size(4))); +} diff --git a/crates/pglt_text_size/tests/serde.rs b/crates/pglt_text_size/tests/serde.rs new file mode 100644 index 000000000..e350b566e --- /dev/null +++ b/crates/pglt_text_size/tests/serde.rs @@ -0,0 +1,79 @@ +use {pglt_text_size::*, serde_test::*, std::ops}; + +fn size(x: u32) -> TextSize { + TextSize::from(x) +} + +fn range(x: ops::Range) -> TextRange { + TextRange::new(x.start.into(), x.end.into()) +} + +#[test] +fn size_serialization() { + assert_tokens(&size(00), &[Token::U32(00)]); + assert_tokens(&size(10), &[Token::U32(10)]); + assert_tokens(&size(20), &[Token::U32(20)]); + assert_tokens(&size(30), &[Token::U32(30)]); +} + +#[test] +fn range_serialization() { + assert_tokens( + &range(00..10), + &[ + Token::Tuple { len: 2 }, + Token::U32(00), + Token::U32(10), + Token::TupleEnd, + ], + ); + assert_tokens( + &range(10..20), + &[ + Token::Tuple { len: 2 }, + Token::U32(10), + Token::U32(20), + Token::TupleEnd, + ], + ); + assert_tokens( + &range(20..30), + &[ + Token::Tuple { len: 2 }, + Token::U32(20), + Token::U32(30), + Token::TupleEnd, + ], + ); + assert_tokens( + &range(30..40), + &[ + Token::Tuple { len: 2 }, + Token::U32(30), + Token::U32(40), + Token::TupleEnd, + ], + ); +} + +#[test] +fn invalid_range_deserialization() { + assert_tokens::( + &range(62..92), + &[ + Token::Tuple { len: 2 }, + Token::U32(62), + Token::U32(92), + Token::TupleEnd, + ], + ); + assert_de_tokens_error::( + &[ + Token::Tuple { len: 2 }, + Token::U32(92), + Token::U32(62), + Token::TupleEnd, + ], + "invalid range: 92..62", + ); +} diff --git a/crates/pglt_typecheck/Cargo.toml b/crates/pglt_typecheck/Cargo.toml index c078a922e..19fc7b65b 100644 --- a/crates/pglt_typecheck/Cargo.toml +++ b/crates/pglt_typecheck/Cargo.toml @@ -16,8 +16,8 @@ pglt_console.workspace = true pglt_diagnostics.workspace = true pglt_query_ext.workspace = true pglt_schema_cache.workspace = true +pglt_text_size.workspace = true sqlx.workspace = true -text-size.workspace = true tokio.workspace = true tree-sitter.workspace = true tree_sitter_sql.workspace = true diff --git a/crates/pglt_typecheck/src/diagnostics.rs b/crates/pglt_typecheck/src/diagnostics.rs index 495af5a67..741865cf3 100644 --- a/crates/pglt_typecheck/src/diagnostics.rs +++ b/crates/pglt_typecheck/src/diagnostics.rs @@ -2,8 +2,8 @@ use std::io; use pglt_console::markup; use pglt_diagnostics::{Advices, Diagnostic, LogCategory, MessageAndDescription, Severity, Visit}; +use pglt_text_size::TextRange; use sqlx::postgres::{PgDatabaseError, PgSeverity}; -use text_size::TextRange; /// A specialized diagnostic for the typechecker. /// diff --git a/crates/pglt_typecheck/src/lib.rs b/crates/pglt_typecheck/src/lib.rs index 90b787774..341f21b1c 100644 --- a/crates/pglt_typecheck/src/lib.rs +++ b/crates/pglt_typecheck/src/lib.rs @@ -2,11 +2,11 @@ mod diagnostics; pub use diagnostics::TypecheckDiagnostic; use diagnostics::create_type_error; +use pglt_text_size::TextRange; use sqlx::Executor; use sqlx::PgPool; use sqlx::postgres::PgDatabaseError; pub use sqlx::postgres::PgSeverity; -use text_size::TextRange; #[derive(Debug)] pub struct TypecheckParams<'a> { diff --git a/crates/pglt_workspace/Cargo.toml b/crates/pglt_workspace/Cargo.toml index 2f36d0d0f..a31d0141b 100644 --- a/crates/pglt_workspace/Cargo.toml +++ b/crates/pglt_workspace/Cargo.toml @@ -26,12 +26,12 @@ pglt_fs = { workspace = true, features = ["serde"] } pglt_query_ext = { workspace = true } pglt_schema_cache = { workspace = true } pglt_statement_splitter = { workspace = true } +pglt_text_size.workspace = true pglt_typecheck = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["raw_value"] } sqlx.workspace = true -text-size.workspace = true tokio = { workspace = true, features = ["rt", "rt-multi-thread"] } toml = { workspace = true } tracing = { workspace = true, features = ["attributes", "log"] } diff --git a/crates/pglt_workspace/src/workspace.rs b/crates/pglt_workspace/src/workspace.rs index 41139aac0..b765ee74e 100644 --- a/crates/pglt_workspace/src/workspace.rs +++ b/crates/pglt_workspace/src/workspace.rs @@ -4,8 +4,8 @@ pub use self::client::{TransportRequest, WorkspaceClient, WorkspaceTransport}; use pglt_analyse::RuleCategories; use pglt_configuration::{PartialConfiguration, RuleSelector}; use pglt_fs::PgLTPath; +use pglt_text_size::{TextRange, TextSize}; use serde::{Deserialize, Serialize}; -use text_size::{TextRange, TextSize}; use crate::WorkspaceError; diff --git a/crates/pglt_workspace/src/workspace/server/change.rs b/crates/pglt_workspace/src/workspace/server/change.rs index 174c75b96..5e3df2055 100644 --- a/crates/pglt_workspace/src/workspace/server/change.rs +++ b/crates/pglt_workspace/src/workspace/server/change.rs @@ -1,5 +1,5 @@ +use pglt_text_size::{TextLen, TextRange, TextSize}; use std::ops::{Add, Sub}; -use text_size::{TextLen, TextRange, TextSize}; use crate::workspace::{ChangeFileParams, ChangeParams}; @@ -416,7 +416,7 @@ impl ChangeParams { mod tests { use super::*; use pglt_diagnostics::Diagnostic; - use text_size::TextRange; + use pglt_text_size::TextRange; use crate::workspace::{ChangeFileParams, ChangeParams}; diff --git a/crates/pglt_workspace/src/workspace/server/document.rs b/crates/pglt_workspace/src/workspace/server/document.rs index ec0c184d9..cbb97a172 100644 --- a/crates/pglt_workspace/src/workspace/server/document.rs +++ b/crates/pglt_workspace/src/workspace/server/document.rs @@ -1,6 +1,6 @@ use pglt_diagnostics::{Diagnostic, DiagnosticExt, Severity, serde::Diagnostic as SDiagnostic}; use pglt_fs::PgLTPath; -use text_size::{TextRange, TextSize}; +use pglt_text_size::{TextRange, TextSize}; /// Global unique identifier for a statement #[derive(Debug, Hash, Eq, PartialEq, Clone)] diff --git a/lib/line_index/Cargo.toml b/lib/line_index/Cargo.toml deleted file mode 100644 index 55a438cb6..000000000 --- a/lib/line_index/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "line_index" -version = "0.0.0" -edition = "2021" - -[dependencies] -text-size.workspace = true - -[lib] -doctest = false diff --git a/lib/line_index/src/lib.rs b/lib/line_index/src/lib.rs deleted file mode 100644 index 6b61bf11d..000000000 --- a/lib/line_index/src/lib.rs +++ /dev/null @@ -1,217 +0,0 @@ -// The following code has been copied from rust-analyzer. - -//! `LineIndex` maps flat `TextSize` offsets into `(Line, Column)` -//! representation. -use std::{collections::HashMap, iter}; - -use text_size::TextRange; -use text_size::TextSize; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct LineIndex { - /// Offset the the beginning of each line, zero-based - pub newlines: Vec, - /// List of non-ASCII characters on each line - pub(crate) utf16_lines: HashMap>, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct LineColUtf16 { - /// Zero-based - pub line: u32, - /// Zero-based - pub col: u32, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct LineCol { - /// Zero-based - pub line: u32, - /// Zero-based utf8 offset - pub col: u32, -} - -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub(crate) struct Utf16Char { - /// Start offset of a character inside a line, zero-based - pub(crate) start: TextSize, - /// End offset of a character inside a line, zero-based - pub(crate) end: TextSize, -} - -impl Utf16Char { - /// Returns the length in 8-bit UTF-8 code units. - fn len(&self) -> TextSize { - self.end - self.start - } - - /// Returns the length in 16-bit UTF-16 code units. - fn len_utf16(&self) -> usize { - if self.len() == TextSize::from(4) { - 2 - } else { - 1 - } - } -} - -impl LineIndex { - pub fn new(text: &str) -> LineIndex { - let mut utf16_lines = HashMap::default(); - let mut utf16_chars = Vec::new(); - - let mut newlines = vec![0.into()]; - let mut curr_row = 0.into(); - let mut curr_col = 0.into(); - let mut line = 0; - for c in text.chars() { - let c_len = TextSize::of(c); - curr_row += c_len; - if c == '\n' { - newlines.push(curr_row); - - // Save any utf-16 characters seen in the previous line - if !utf16_chars.is_empty() { - utf16_lines.insert(line, utf16_chars); - utf16_chars = Vec::new(); - } - - // Prepare for processing the next line - curr_col = 0.into(); - line += 1; - continue; - } - - if !c.is_ascii() { - utf16_chars.push(Utf16Char { - start: curr_col, - end: curr_col + c_len, - }); - } - - curr_col += c_len; - } - - // Save any utf-16 characters seen in the last line - if !utf16_chars.is_empty() { - utf16_lines.insert(line, utf16_chars); - } - - LineIndex { - newlines, - utf16_lines, - } - } - - pub fn line_col(&self, offset: TextSize) -> LineCol { - let line = partition_point(&self.newlines, |&it| it <= offset) - 1; - let line_start_offset = self.newlines[line]; - let col = offset - line_start_offset; - LineCol { - line: line as u32, - col: col.into(), - } - } - - pub fn offset(&self, line_col: LineCol) -> Option { - Some(self.newlines.get(line_col.line as usize)? + TextSize::from(line_col.col)) - } - - pub fn to_utf16(&self, line_col: LineCol) -> Option { - let col = self.utf8_to_utf16_col(line_col.line, line_col.col.into()); - Some(LineColUtf16 { - line: line_col.line, - col: col as u32, - }) - } - - pub fn to_utf8(&self, line_col: LineColUtf16) -> Option { - let col = self.utf16_to_utf8_col(line_col.line, line_col.col); - Some(LineCol { - line: line_col.line, - col: col.into(), - }) - } - - pub fn lines(&self, range: TextRange) -> impl Iterator + '_ { - let lo = partition_point(&self.newlines, |&it| it < range.start()); - let hi = partition_point(&self.newlines, |&it| it <= range.end()); - let all = iter::once(range.start()) - .chain(self.newlines[lo..hi].iter().copied()) - .chain(iter::once(range.end())); - - all.clone() - .zip(all.skip(1)) - .map(|(lo, hi)| TextRange::new(lo, hi)) - .filter(|it| !it.is_empty()) - } - - fn utf8_to_utf16_col(&self, line: u32, col: TextSize) -> usize { - let mut res: usize = col.into(); - if let Some(utf16_chars) = self.utf16_lines.get(&line) { - for c in utf16_chars { - if c.end <= col { - res -= usize::from(c.len()) - c.len_utf16(); - } else { - // From here on, all utf16 characters come *after* the character we are mapping, - // so we don't need to take them into account - break; - } - } - } - res - } - - fn utf16_to_utf8_col(&self, line: u32, mut col: u32) -> TextSize { - if let Some(utf16_chars) = self.utf16_lines.get(&line) { - for c in utf16_chars { - if col > u32::from(c.start) { - col += u32::from(c.len()) - c.len_utf16() as u32; - } else { - // From here on, all utf16 characters come *after* the character we are mapping, - // so we don't need to take them into account - break; - } - } - } - - col.into() - } -} - -/// Returns `idx` such that: -/// -/// ```text -/// βˆ€ x in slice[..idx]: pred(x) -/// && βˆ€ x in slice[idx..]: !pred(x) -/// ``` -/// -/// https://github.com/rust-lang/rust/issues/73831 -fn partition_point(slice: &[T], mut pred: P) -> usize -where - P: FnMut(&T) -> bool, -{ - let mut left = 0; - let mut right = slice.len(); - - while left != right { - let mid = left + (right - left) / 2; - // SAFETY: - // When left < right, left <= mid < right. - // Therefore left always increases and right always decreases, - // and either of them is selected. - // In both cases left <= right is satisfied. - // Therefore if left < right in a step, - // left <= right is satisfied in the next step. - // Therefore as long as left != right, 0 <= left < right <= len is satisfied - // and if this case 0 <= mid < len is satisfied too. - let value = unsafe { slice.get_unchecked(mid) }; - if pred(value) { - left = mid + 1; - } else { - right = mid; - } - } - - left -} From 79b25a23485e91f8489298ee0008702f41a80377 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Wed, 12 Mar 2025 19:19:03 +0100 Subject: [PATCH 02/12] fix: test --- crates/pglt_text_size/src/range.rs | 22 +++++++++++----------- crates/pglt_text_size/src/size.rs | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/pglt_text_size/src/range.rs b/crates/pglt_text_size/src/range.rs index 9b981642d..028e59115 100644 --- a/crates/pglt_text_size/src/range.rs +++ b/crates/pglt_text_size/src/range.rs @@ -34,7 +34,7 @@ impl TextRange { /// # Examples /// /// ```rust - /// # use text_size::*; + /// # use pglt_text_size::*; /// let start = TextSize::from(5); /// let end = TextSize::from(10); /// let range = TextRange::new(start, end); @@ -54,7 +54,7 @@ impl TextRange { /// # Examples /// /// ```rust - /// # use text_size::*; + /// # use pglt_text_size::*; /// let text = "0123456789"; /// /// let offset = TextSize::from(2); @@ -74,7 +74,7 @@ impl TextRange { /// # Examples /// /// ```rust - /// # use text_size::*; + /// # use pglt_text_size::*; /// let point: TextSize; /// # point = TextSize::from(3); /// let range = TextRange::empty(point); @@ -94,7 +94,7 @@ impl TextRange { /// # Examples /// /// ```rust - /// # use text_size::*; + /// # use pglt_text_size::*; /// let point: TextSize; /// # point = TextSize::from(12); /// let range = TextRange::up_to(point); @@ -152,7 +152,7 @@ impl TextRange { /// # Examples /// /// ```rust - /// # use text_size::*; + /// # use pglt_text_size::*; /// let (start, end): (TextSize, TextSize); /// # start = 10.into(); end = 20.into(); /// let range = TextRange::new(start, end); @@ -171,7 +171,7 @@ impl TextRange { /// # Examples /// /// ```rust - /// # use text_size::*; + /// # use pglt_text_size::*; /// let (start, end): (TextSize, TextSize); /// # start = 10.into(); end = 20.into(); /// let range = TextRange::new(start, end); @@ -188,7 +188,7 @@ impl TextRange { /// # Examples /// /// ```rust - /// # use text_size::*; + /// # use pglt_text_size::*; /// let larger = TextRange::new(0.into(), 20.into()); /// let smaller = TextRange::new(5.into(), 15.into()); /// assert!(larger.contains_range(smaller)); @@ -209,7 +209,7 @@ impl TextRange { /// # Examples /// /// ```rust - /// # use text_size::*; + /// # use pglt_text_size::*; /// assert_eq!( /// TextRange::intersect( /// TextRange::new(0.into(), 10.into()), @@ -233,7 +233,7 @@ impl TextRange { /// # Examples /// /// ```rust - /// # use text_size::*; + /// # use pglt_text_size::*; /// assert_eq!( /// TextRange::cover( /// TextRange::new(0.into(), 5.into()), @@ -254,7 +254,7 @@ impl TextRange { /// # Examples /// /// ```rust - /// # use text_size::*; + /// # use pglt_text_size::*; /// assert_eq!( /// TextRange::empty(0.into()).cover_offset(20.into()), /// TextRange::new(0.into(), 20.into()), @@ -307,7 +307,7 @@ impl TextRange { /// # Examples /// /// ``` - /// # use text_size::*; + /// # use pglt_text_size::*; /// # use std::cmp::Ordering; /// /// let a = TextRange::new(0.into(), 3.into()); diff --git a/crates/pglt_text_size/src/size.rs b/crates/pglt_text_size/src/size.rs index c950d2edd..658cb13ae 100644 --- a/crates/pglt_text_size/src/size.rs +++ b/crates/pglt_text_size/src/size.rs @@ -46,7 +46,7 @@ impl TextSize { /// # Examples /// /// ```rust - /// # use text_size::*; + /// # use pglt_text_size::*; /// let char_size = TextSize::of('πŸ¦€'); /// assert_eq!(char_size, TextSize::from(4)); /// From 1e3eae6c758f2ea5710eb3d30a54ba5cdac87a4b Mon Sep 17 00:00:00 2001 From: psteinroe Date: Thu, 13 Mar 2025 18:15:07 +0100 Subject: [PATCH 03/12] chore: cleanup feature gates --- crates/pglt_analyse/Cargo.toml | 3 ++- crates/pglt_analyse/src/categories.rs | 26 +++++++++----------------- crates/pglt_analyse/src/rule.rs | 4 ++-- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/crates/pglt_analyse/Cargo.toml b/crates/pglt_analyse/Cargo.toml index 4574d90c1..96c4ea880 100644 --- a/crates/pglt_analyse/Cargo.toml +++ b/crates/pglt_analyse/Cargo.toml @@ -27,4 +27,5 @@ schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"], optional = true } [features] -serde = ["dep:serde", "dep:schemars", "dep:biome_deserialize", "dep:biome_deserialize_macros"] +schema = ["dep:schemars"] +serde = ["dep:serde", "dep:biome_deserialize", "dep:biome_deserialize_macros"] diff --git a/crates/pglt_analyse/src/categories.rs b/crates/pglt_analyse/src/categories.rs index 0b539eac4..d13e1a3da 100644 --- a/crates/pglt_analyse/src/categories.rs +++ b/crates/pglt_analyse/src/categories.rs @@ -2,10 +2,8 @@ use enumflags2::{BitFlags, bitflags}; use std::borrow::Cow; #[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[cfg_attr( - feature = "serde", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub enum RuleCategory { /// This rule performs static analysis of the source code to detect /// invalid or error-prone patterns, and emits diagnostics along with @@ -26,10 +24,8 @@ pub const SUPPRESSION_ACTION_CATEGORY: &str = "quickfix.suppressRule"; /// /// [CodeActionKind]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionKind #[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr( - feature = "serde", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub enum ActionCategory { /// Base kind for quickfix actions: 'quickfix'. /// @@ -110,10 +106,8 @@ impl ActionCategory { /// /// [Check the LSP spec](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionKind) for more information: #[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr( - feature = "serde", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub enum RefactorKind { /// This action describes a refactor with no particular sub-category None, @@ -150,10 +144,8 @@ pub enum RefactorKind { /// The sub-category of a source code action #[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr( - feature = "serde", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub enum SourceActionKind { /// This action describes a source action with no particular sub-category None, @@ -282,7 +274,7 @@ impl<'de> serde::Deserialize<'de> for RuleCategories { } } -#[cfg(feature = "serde")] +#[cfg(feature = "schema")] impl schemars::JsonSchema for RuleCategories { fn schema_name() -> String { String::from("RuleCategories") diff --git a/crates/pglt_analyse/src/rule.rs b/crates/pglt_analyse/src/rule.rs index 1f70cf844..6c53a0a22 100644 --- a/crates/pglt_analyse/src/rule.rs +++ b/crates/pglt_analyse/src/rule.rs @@ -271,8 +271,8 @@ impl RuleDiagnostic { } #[derive(Debug, Clone, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, schemars::JsonSchema))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub enum RuleSource { /// Rules from [Squawk](https://squawkhq.com) Squawk(&'static str), From d3d3b0143878438c850daa03f851f762a2052dab Mon Sep 17 00:00:00 2001 From: psteinroe Date: Fri, 14 Mar 2025 19:13:23 +0100 Subject: [PATCH 04/12] chore: cleanup remaining feature gates --- Cargo.lock | 1 + crates/pglt_cli/Cargo.toml | 2 -- crates/pglt_commands/Cargo.toml | 2 -- crates/pglt_completions/Cargo.toml | 2 -- crates/pglt_console/Cargo.toml | 3 +- crates/pglt_console/src/markup.rs | 18 ++++-------- crates/pglt_diagnostics/Cargo.toml | 6 ++-- crates/pglt_diagnostics_categories/Cargo.toml | 4 +++ crates/pglt_diagnostics_categories/build.rs | 2 +- crates/pglt_diagnostics_macros/Cargo.toml | 2 -- crates/pglt_flags/Cargo.toml | 2 -- crates/pglt_fs/Cargo.toml | 7 ++--- crates/pglt_fs/src/path.rs | 14 ++++------ crates/pglt_lsp/Cargo.toml | 2 -- crates/pglt_lsp_converters/Cargo.toml | 2 -- crates/pglt_markup/Cargo.toml | 2 -- crates/pglt_text_edit/Cargo.toml | 7 +++-- crates/pglt_text_edit/src/lib.rs | 28 ++++++++++++++----- crates/pglt_text_size/Cargo.toml | 2 +- crates/pglt_text_size/src/lib.rs | 3 ++ crates/pglt_treesitter_queries/Cargo.toml | 2 -- crates/pglt_type_resolver/Cargo.toml | 2 -- crates/pglt_typecheck/Cargo.toml | 2 -- crates/pglt_workspace/Cargo.toml | 7 +++-- crates/pglt_workspace/src/workspace.rs | 22 +++++++-------- 25 files changed, 70 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 28e0a3d65..83727039b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2620,6 +2620,7 @@ dependencies = [ "pglt_text_size", "pglt_typecheck", "rustc-hash 2.1.0", + "schemars", "serde", "serde_json", "sqlx", diff --git a/crates/pglt_cli/Cargo.toml b/crates/pglt_cli/Cargo.toml index d4ccc5e1f..204d20918 100644 --- a/crates/pglt_cli/Cargo.toml +++ b/crates/pglt_cli/Cargo.toml @@ -55,8 +55,6 @@ tikv-jemallocator = "0.6.0" [lib] doctest = false -[features] - [[bin]] name = "pglt" path = "src/main.rs" diff --git a/crates/pglt_commands/Cargo.toml b/crates/pglt_commands/Cargo.toml index 2507b9147..972158aee 100644 --- a/crates/pglt_commands/Cargo.toml +++ b/crates/pglt_commands/Cargo.toml @@ -19,5 +19,3 @@ sqlx.workspace = true [lib] doctest = false - -[features] diff --git a/crates/pglt_completions/Cargo.toml b/crates/pglt_completions/Cargo.toml index eec3d903e..4c8e6ef26 100644 --- a/crates/pglt_completions/Cargo.toml +++ b/crates/pglt_completions/Cargo.toml @@ -33,5 +33,3 @@ pglt_test_utils.workspace = true [lib] doctest = false - -[features] diff --git a/crates/pglt_console/Cargo.toml b/crates/pglt_console/Cargo.toml index ab4ce461e..2b5cfa864 100644 --- a/crates/pglt_console/Cargo.toml +++ b/crates/pglt_console/Cargo.toml @@ -25,7 +25,8 @@ unicode-width = { workspace = true } trybuild = "1.0.99" [features] -serde_markup = ["serde", "schemars"] +schema = ["dep:schemars", "pglt_text_size/schema"] +serde = ["dep:serde"] [lib] doctest = false diff --git a/crates/pglt_console/src/markup.rs b/crates/pglt_console/src/markup.rs index 4c046ba86..b4caadf7e 100644 --- a/crates/pglt_console/src/markup.rs +++ b/crates/pglt_console/src/markup.rs @@ -11,10 +11,8 @@ use crate::fmt::{Display, Formatter, MarkupElements, Write}; /// Enumeration of all the supported markup elements #[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr( - feature = "serde", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub enum MarkupElement<'fmt> { Emphasis, Dim, @@ -122,10 +120,8 @@ pub struct MarkupNode<'fmt> { } #[derive(Clone, PartialEq, Eq, Hash)] -#[cfg_attr( - feature = "serde", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct MarkupNodeBuf { pub elements: Vec>, pub content: String, @@ -181,10 +177,8 @@ impl Markup<'_> { } #[derive(Clone, Default, PartialEq, Eq, Hash)] -#[cfg_attr( - feature = "serde", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct MarkupBuf(pub Vec); impl MarkupBuf { diff --git a/crates/pglt_diagnostics/Cargo.toml b/crates/pglt_diagnostics/Cargo.toml index c477bfd7e..ef01ba54b 100644 --- a/crates/pglt_diagnostics/Cargo.toml +++ b/crates/pglt_diagnostics/Cargo.toml @@ -15,10 +15,10 @@ version = "0.0.0" backtrace = "0.3.74" bpaf = { workspace = true } enumflags2 = { workspace = true } -pglt_console = { workspace = true, features = ["serde_markup"] } +pglt_console = { workspace = true, features = ["serde"] } pglt_diagnostics_categories = { workspace = true, features = ["serde"] } pglt_diagnostics_macros = { workspace = true } -pglt_text_edit = { workspace = true } +pglt_text_edit = { workspace = true, features = ["serde"] } pglt_text_size.workspace = true schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } @@ -27,7 +27,7 @@ termcolor = { workspace = true } unicode-width = { workspace = true } [features] -schema = ["schemars", "pglt_text_edit/schemars", "pglt_diagnostics_categories/schemars"] +schema = ["dep:schemars", "pglt_text_edit/schema", "pglt_diagnostics_categories/schema", "pglt_console/schema"] [dev-dependencies] diff --git a/crates/pglt_diagnostics_categories/Cargo.toml b/crates/pglt_diagnostics_categories/Cargo.toml index 0771c05f6..fbc0d7834 100644 --- a/crates/pglt_diagnostics_categories/Cargo.toml +++ b/crates/pglt_diagnostics_categories/Cargo.toml @@ -15,5 +15,9 @@ version = "0.0.0" schemars = { workspace = true, optional = true } serde = { workspace = true, optional = true } +[features] +schema = ["dep:schemars"] +serde = ["dep:serde"] + [build-dependencies] quote = "1.0.14" diff --git a/crates/pglt_diagnostics_categories/build.rs b/crates/pglt_diagnostics_categories/build.rs index 1b2970c6d..9daa5413c 100644 --- a/crates/pglt_diagnostics_categories/build.rs +++ b/crates/pglt_diagnostics_categories/build.rs @@ -64,7 +64,7 @@ pub fn main() -> io::Result<()> { } } - #[cfg(feature = "schemars")] + #[cfg(feature = "schema")] impl schemars::JsonSchema for &'static Category { fn schema_name() -> String { String::from("Category") diff --git a/crates/pglt_diagnostics_macros/Cargo.toml b/crates/pglt_diagnostics_macros/Cargo.toml index 8cc10058c..3f77a86da 100644 --- a/crates/pglt_diagnostics_macros/Cargo.toml +++ b/crates/pglt_diagnostics_macros/Cargo.toml @@ -21,5 +21,3 @@ quote = { workspace = true } syn = { workspace = true } [dev-dependencies] - -[features] diff --git a/crates/pglt_flags/Cargo.toml b/crates/pglt_flags/Cargo.toml index eac82a48a..f3f5e40b6 100644 --- a/crates/pglt_flags/Cargo.toml +++ b/crates/pglt_flags/Cargo.toml @@ -15,5 +15,3 @@ version = "0.0.0" pglt_console = { workspace = true } [dev-dependencies] - -[features] diff --git a/crates/pglt_fs/Cargo.toml b/crates/pglt_fs/Cargo.toml index 11edb8778..8c3b237c5 100644 --- a/crates/pglt_fs/Cargo.toml +++ b/crates/pglt_fs/Cargo.toml @@ -20,14 +20,13 @@ pglt_diagnostics = { workspace = true } rayon = { workspace = true } rustc-hash = { workspace = true } schemars = { workspace = true, optional = true } -serde = { workspace = true } +serde = { workspace = true, optional = true } smallvec = { workspace = true } tracing = { workspace = true } [features] -serde = ["schemars", "pglt_diagnostics/schema"] - -[dev-dependencies] +schema = ["dep:schemars", "pglt_diagnostics/schema"] +serde = ["dep:serde"] [lib] doctest = false diff --git a/crates/pglt_fs/src/path.rs b/crates/pglt_fs/src/path.rs index cc481aea2..37c74f763 100644 --- a/crates/pglt_fs/src/path.rs +++ b/crates/pglt_fs/src/path.rs @@ -17,10 +17,8 @@ use crate::ConfigName; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Ord, PartialOrd, Hash)] #[repr(u8)] #[bitflags] -#[cfg_attr( - feature = "serde", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] // NOTE: The order of the variants is important, the one on the top has the highest priority pub enum FileKind { /// A configuration file has the highest priority. It's usually `pglt.toml` @@ -85,10 +83,8 @@ impl From for FileKinds { } #[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] -#[cfg_attr( - feature = "serde", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct PgLTPath { path: PathBuf, /// Determines the kind of the file inside PgLT. Some files are considered as configuration files, others as manifest files, and others as files to handle @@ -201,7 +197,7 @@ impl PgLTPath { } } -#[cfg(feature = "serde")] +#[cfg(feature = "schema")] impl schemars::JsonSchema for FileKinds { fn schema_name() -> String { String::from("FileKind") diff --git a/crates/pglt_lsp/Cargo.toml b/crates/pglt_lsp/Cargo.toml index ddef9f9ac..bb4e4226d 100644 --- a/crates/pglt_lsp/Cargo.toml +++ b/crates/pglt_lsp/Cargo.toml @@ -41,5 +41,3 @@ tower = { version = "0.4.13", features = ["timeout"] } [lib] doctest = false - -[features] diff --git a/crates/pglt_lsp_converters/Cargo.toml b/crates/pglt_lsp_converters/Cargo.toml index c8bace046..9c83bf68a 100644 --- a/crates/pglt_lsp_converters/Cargo.toml +++ b/crates/pglt_lsp_converters/Cargo.toml @@ -21,5 +21,3 @@ tower-lsp = { version = "0.20.0" } [lib] doctest = false - -[features] diff --git a/crates/pglt_markup/Cargo.toml b/crates/pglt_markup/Cargo.toml index 80ef7ed24..f206c8e69 100644 --- a/crates/pglt_markup/Cargo.toml +++ b/crates/pglt_markup/Cargo.toml @@ -20,5 +20,3 @@ quote = "1.0.14" [lib] proc-macro = true - -[features] diff --git a/crates/pglt_text_edit/Cargo.toml b/crates/pglt_text_edit/Cargo.toml index 01bc0bbb7..7dc110efc 100644 --- a/crates/pglt_text_edit/Cargo.toml +++ b/crates/pglt_text_edit/Cargo.toml @@ -12,13 +12,14 @@ version = "0.0.0" [dependencies] -pglt_text_size = { workspace = true, features = ["serde"] } +pglt_text_size = { workspace = true } schemars = { workspace = true, optional = true } -serde = { workspace = true, features = ["derive"] } +serde = { workspace = true, features = ["derive"], optional = true } similar = { workspace = true, features = ["unicode"] } [features] -schemars = ["dep:schemars"] +schema = ["dep:schemars", "pglt_text_size/schema"] +serde = ["dep:serde", "pglt_text_size/serde"] [dev-dependencies] diff --git a/crates/pglt_text_edit/src/lib.rs b/crates/pglt_text_edit/src/lib.rs index 185bc1ca5..d5612114e 100644 --- a/crates/pglt_text_edit/src/lib.rs +++ b/crates/pglt_text_edit/src/lib.rs @@ -11,26 +11,40 @@ use std::{cmp::Ordering, num::NonZeroU32}; use pglt_text_size::{TextRange, TextSize}; -use serde::{Deserialize, Serialize}; pub use similar::ChangeTag; use similar::{TextDiff, utils::TextDiffRemapper}; -#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] pub struct TextEdit { dictionary: String, ops: Vec, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] pub enum CompressedOp { DiffOp(DiffOp), EqualLines { line_count: NonZeroU32 }, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] pub enum DiffOp { Equal { range: TextRange }, Insert { range: TextRange }, diff --git a/crates/pglt_text_size/Cargo.toml b/crates/pglt_text_size/Cargo.toml index ec5ce81f4..3e398fdb6 100644 --- a/crates/pglt_text_size/Cargo.toml +++ b/crates/pglt_text_size/Cargo.toml @@ -15,7 +15,7 @@ schemars = { workspace = true, optional = true } serde = { workspace = true, optional = true } [features] -schema = ["dep:schemars", "serde"] +schema = ["dep:schemars"] serde = ["dep:serde"] [dev-dependencies] diff --git a/crates/pglt_text_size/src/lib.rs b/crates/pglt_text_size/src/lib.rs index 92bd36b19..133f6192e 100644 --- a/crates/pglt_text_size/src/lib.rs +++ b/crates/pglt_text_size/src/lib.rs @@ -26,6 +26,9 @@ mod traits; #[cfg(feature = "serde")] mod serde_impls; +#[cfg(feature = "schema")] +mod schemars_impls; + pub use crate::{range::TextRange, size::TextSize, traits::TextLen}; #[cfg(target_pointer_width = "16")] diff --git a/crates/pglt_treesitter_queries/Cargo.toml b/crates/pglt_treesitter_queries/Cargo.toml index ed6e65b98..056b066a8 100644 --- a/crates/pglt_treesitter_queries/Cargo.toml +++ b/crates/pglt_treesitter_queries/Cargo.toml @@ -20,5 +20,3 @@ tree_sitter_sql.workspace = true [lib] doctest = false - -[features] diff --git a/crates/pglt_type_resolver/Cargo.toml b/crates/pglt_type_resolver/Cargo.toml index 929e8a919..d5afdc6da 100644 --- a/crates/pglt_type_resolver/Cargo.toml +++ b/crates/pglt_type_resolver/Cargo.toml @@ -19,5 +19,3 @@ pglt_schema_cache.workspace = true [lib] doctest = false - -[features] diff --git a/crates/pglt_typecheck/Cargo.toml b/crates/pglt_typecheck/Cargo.toml index 19fc7b65b..367939886 100644 --- a/crates/pglt_typecheck/Cargo.toml +++ b/crates/pglt_typecheck/Cargo.toml @@ -28,5 +28,3 @@ pglt_test_utils.workspace = true [lib] doctest = false - -[features] diff --git a/crates/pglt_workspace/Cargo.toml b/crates/pglt_workspace/Cargo.toml index a31d0141b..29e561df6 100644 --- a/crates/pglt_workspace/Cargo.toml +++ b/crates/pglt_workspace/Cargo.toml @@ -29,6 +29,7 @@ pglt_statement_splitter = { workspace = true } pglt_text_size.workspace = true pglt_typecheck = { workspace = true } rustc-hash = { workspace = true } +schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["raw_value"] } sqlx.workspace = true @@ -38,10 +39,12 @@ tracing = { workspace = true, features = ["attributes", "log"] tree-sitter.workspace = true tree_sitter_sql.workspace = true + +[features] +schema = ["dep:schemars", "pglt_configuration/schema", "pglt_fs/schema"] + [dev-dependencies] tempfile = "3.15.0" [lib] doctest = false - -[features] diff --git a/crates/pglt_workspace/src/workspace.rs b/crates/pglt_workspace/src/workspace.rs index b765ee74e..1bd67cda4 100644 --- a/crates/pglt_workspace/src/workspace.rs +++ b/crates/pglt_workspace/src/workspace.rs @@ -13,6 +13,7 @@ mod client; mod server; #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct OpenFileParams { pub path: PgLTPath, pub content: String, @@ -20,11 +21,13 @@ pub struct OpenFileParams { } #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct CloseFileParams { pub path: PgLTPath, } #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct ChangeFileParams { pub path: PgLTPath, pub version: i32, @@ -32,6 +35,7 @@ pub struct ChangeFileParams { } #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct PullDiagnosticsParams { pub path: PgLTPath, pub categories: RuleCategories, @@ -41,6 +45,7 @@ pub struct PullDiagnosticsParams { } #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct CompletionParams { /// The File for which a completion is requested. pub path: PgLTPath, @@ -49,24 +54,15 @@ pub struct CompletionParams { } #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct PullDiagnosticsResult { pub diagnostics: Vec, pub errors: usize, 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](pglt_diagnostics::Applicability::Always) fixes - SafeFixes, - /// Applies [safe](pglt_diagnostics::Applicability::Always) and [unsafe](pglt_diagnostics::Applicability::MaybeIncorrect) fixes - SafeAndUnsafeFixes, - /// Applies suppression comments to existing diagnostics when using `--suppress` - ApplySuppressions, -} - #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct ChangeParams { /// The range of the file that changed. If `None`, the whole file changed. pub range: Option, @@ -80,11 +76,13 @@ impl ChangeParams { } #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct IsPathIgnoredParams { pub pglt_path: PgLTPath, } #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct UpdateSettingsParams { pub configuration: PartialConfiguration, pub vcs_base_path: Option, @@ -94,11 +92,13 @@ pub struct UpdateSettingsParams { } #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct GetFileContentParams { pub path: PgLTPath, } #[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct ServerInfo { /// The name of the server as defined by the server. pub name: String, From c2ecf285b769a3754105ab456bc9882625fc5d2b Mon Sep 17 00:00:00 2001 From: psteinroe Date: Fri, 14 Mar 2025 20:20:19 +0100 Subject: [PATCH 05/12] refactor: migrate to json config --- Cargo.lock | 4 -- crates/pglt_analyse/src/options.rs | 2 +- crates/pglt_analyse/src/rule.rs | 6 ++- crates/pglt_analyser/CONTRIBUTING.md | 33 +++++++----- crates/pglt_cli/src/cli_options.rs | 2 +- crates/pglt_cli/src/commands/init.rs | 2 +- crates/pglt_cli/src/commands/mod.rs | 8 +-- crates/pglt_cli/src/diagnostics.rs | 4 +- crates/pglt_configuration/Cargo.toml | 1 - crates/pglt_configuration/src/database.rs | 2 +- crates/pglt_configuration/src/diagnostics.rs | 4 +- crates/pglt_configuration/src/files.rs | 2 +- crates/pglt_configuration/src/lib.rs | 4 +- crates/pglt_configuration/src/migrations.rs | 2 +- crates/pglt_configuration/src/vcs.rs | 6 +-- crates/pglt_diagnostics/src/advice.rs | 2 +- crates/pglt_diagnostics/src/diagnostic.rs | 4 +- crates/pglt_diagnostics/src/location.rs | 2 +- crates/pglt_diagnostics/src/serde.rs | 8 +-- crates/pglt_fs/src/fs.rs | 8 +-- crates/pglt_fs/src/path.rs | 7 ++- crates/pglt_lsp/Cargo.toml | 1 - crates/pglt_lsp/src/server.rs | 14 +++-- crates/pglt_lsp/src/session.rs | 2 +- crates/pglt_lsp/tests/server.rs | 8 +-- crates/pglt_workspace/Cargo.toml | 1 - crates/pglt_workspace/src/configuration.rs | 10 ++-- crates/pglt_workspace/src/workspace/server.rs | 2 +- docs/checking_migrations.md | 15 +++--- docs/cli_reference.md | 12 ++--- docs/codegen/Cargo.toml | 1 - docs/codegen/src/default_configuration.rs | 4 +- docs/codegen/src/rules_docs.rs | 18 +++++-- docs/index.md | 52 ++++++++++--------- docs/linting_migrations.md | 29 ----------- docs/rules/adding-required-field.md | 14 +++-- docs/rules/ban-drop-column.md | 14 +++-- docs/rules/ban-drop-not-null.md | 14 +++-- docs/rules/ban-drop-table.md | 14 +++-- docs/schemas/0.0.0/schema.json | 14 ++--- docs/schemas/latest/schema.json | 14 ++--- 41 files changed, 195 insertions(+), 171 deletions(-) delete mode 100644 docs/linting_migrations.md diff --git a/Cargo.lock b/Cargo.lock index 83727039b..8b01e85fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -972,7 +972,6 @@ dependencies = [ "schemars", "serde", "serde_json", - "toml", ] [[package]] @@ -2315,7 +2314,6 @@ dependencies = [ "schemars", "serde", "serde_json", - "toml", ] [[package]] @@ -2438,7 +2436,6 @@ dependencies = [ "serde_json", "sqlx", "tokio", - "toml", "tower", "tower-lsp", "tracing", @@ -2626,7 +2623,6 @@ dependencies = [ "sqlx", "tempfile", "tokio", - "toml", "tracing", "tree-sitter", "tree_sitter_sql", diff --git a/crates/pglt_analyse/src/options.rs b/crates/pglt_analyse/src/options.rs index 20ac7236f..6083356cc 100644 --- a/crates/pglt_analyse/src/options.rs +++ b/crates/pglt_analyse/src/options.rs @@ -45,7 +45,7 @@ impl AnalyserRules { /// A set of information useful to the analyser infrastructure #[derive(Debug, Default)] pub struct AnalyserOptions { - /// A data structured derived from the [`pglt.toml`] file + /// A data structured derived from the [`pglt.json`] file pub rules: AnalyserRules, } diff --git a/crates/pglt_analyse/src/rule.rs b/crates/pglt_analyse/src/rule.rs index 6c53a0a22..c7f384898 100644 --- a/crates/pglt_analyse/src/rule.rs +++ b/crates/pglt_analyse/src/rule.rs @@ -12,7 +12,11 @@ use std::fmt::Debug; use crate::{categories::RuleCategory, context::RuleContext, registry::RegistryVisitor}; #[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize), + serde(rename_all = "camelCase") +)] /// Static metadata containing information about a rule pub struct RuleMetadata { /// It marks if a rule is deprecated, and if so a reason has to be provided. diff --git a/crates/pglt_analyser/CONTRIBUTING.md b/crates/pglt_analyser/CONTRIBUTING.md index c855ac592..06cf6052b 100644 --- a/crates/pglt_analyser/CONTRIBUTING.md +++ b/crates/pglt_analyser/CONTRIBUTING.md @@ -79,15 +79,24 @@ Let's assume that the rule we implement support the following options: - `threshold`: an integer between 0 and 255; - `behaviorExceptions`: an array of strings. -We would like to set the options in the `pglt.toml` configuration file: - -```toml -[linter.rules.safety.myRule] -level = "warn" -options = { - behavior = "A" - threshold = 20 - behaviorExceptions = ["one", "two"] +We would like to set the options in the `pglt.json` configuration file: + +```json +{ + "linter": { + "rules": { + "safety": { + "myRule": { + "level": "warn", + "options": { + "behavior": "A", + "threshold": 20, + "behaviorExceptions": ["one", "two"] + } + } + } + } + } } ``` @@ -132,16 +141,16 @@ We currently require implementing _serde_'s traits `Deserialize`/`Serialize`. Also, we use other `serde` macros to adjust the JSON configuration: -- `rename_all = "snake_case"`: it renames all fields in camel-case, so they are in line with the naming style of the `pglt.toml`. +- `rename_all = "camelCase"`: it renames all fields in camel-case, so they are in line with the naming style of the `pglt.json`. - `deny_unknown_fields`: it raises an error if the configuration contains extraneous fields. -- `default`: it uses the `Default` value when the field is missing from `pglt.toml`. This macro makes the field optional. +- `default`: it uses the `Default` value when the field is missing from `pglt.json`. This macro makes the field optional. You can simply use a derive macros: ```rust #[derive(Debug, Default, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[serde(rename_all = "snake_case", deny_unknown_fields, default)] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] pub struct MyRuleOptions { #[serde(default, skip_serializing_if = "is_default")] main_behavior: Behavior, diff --git a/crates/pglt_cli/src/cli_options.rs b/crates/pglt_cli/src/cli_options.rs index de28072a5..f43bf4747 100644 --- a/crates/pglt_cli/src/cli_options.rs +++ b/crates/pglt_cli/src/cli_options.rs @@ -26,7 +26,7 @@ pub struct CliOptions { #[bpaf(long("verbose"), switch, fallback(false))] pub verbose: bool, - /// Set the file path to the configuration file, or the directory path to find `pglt.toml`. + /// Set the file path to the configuration file, or the directory path to find `pglt.json`. /// If used, it disables the default configuration file resolution. #[bpaf(long("config-path"), argument("PATH"), optional)] pub config_path: Option, diff --git a/crates/pglt_cli/src/commands/init.rs b/crates/pglt_cli/src/commands/init.rs index a4a6d8510..c94d62b31 100644 --- a/crates/pglt_cli/src/commands/init.rs +++ b/crates/pglt_cli/src/commands/init.rs @@ -7,7 +7,7 @@ use pglt_workspace::configuration::create_config; pub(crate) fn init(mut session: CliSession) -> Result<(), CliDiagnostic> { let fs = &mut session.app.fs; create_config(fs, PartialConfiguration::init())?; - let file_created = ConfigName::pglt_toml(); + let file_created = ConfigName::pglt_json(); session.app.console.log(markup! { " Welcome to the Postgres Language Tools! Let's get you started... diff --git a/crates/pglt_cli/src/commands/mod.rs b/crates/pglt_cli/src/commands/mod.rs index a925b2c6b..c6edafaae 100644 --- a/crates/pglt_cli/src/commands/mod.rs +++ b/crates/pglt_cli/src/commands/mod.rs @@ -58,7 +58,7 @@ pub enum PgltCommand { 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 `pglt.toml` + /// flag and the `defaultBranch` is not set in your `pglt.json` #[bpaf(long("since"), argument("REF"))] since: Option, @@ -91,7 +91,7 @@ pub enum PgltCommand { )] log_path: PathBuf, /// Allows to set a custom file path to the configuration file, - /// or a custom directory path to find `pglt.toml` + /// or a custom directory path to find `pglt.json` #[bpaf(env("PGLT_LOG_PREFIX_NAME"), long("config-path"), argument("PATH"))] config_path: Option, }, @@ -127,7 +127,7 @@ pub enum PgltCommand { )] log_path: PathBuf, /// Allows to set a custom file path to the configuration file, - /// or a custom directory path to find `pglt.toml` + /// or a custom directory path to find `pglt.json` #[bpaf(env("PGLT_CONFIG_PATH"), long("config-path"), argument("PATH"))] config_path: Option, /// Bogus argument to make the command work with vscode-languageclient @@ -164,7 +164,7 @@ pub enum PgltCommand { #[bpaf(long("stop-on-disconnect"), hide_usage)] stop_on_disconnect: bool, /// Allows to set a custom file path to the configuration file, - /// or a custom directory path to find `pglt.toml` + /// or a custom directory path to find `pglt.json` #[bpaf(env("PGLT_CONFIG_PATH"), long("config-path"), argument("PATH"))] config_path: Option, }, diff --git a/crates/pglt_cli/src/diagnostics.rs b/crates/pglt_cli/src/diagnostics.rs index 07e43b7dc..506cf9d84 100644 --- a/crates/pglt_cli/src/diagnostics.rs +++ b/crates/pglt_cli/src/diagnostics.rs @@ -48,7 +48,7 @@ pub enum CliDiagnostic { IoError(IoDiagnostic), /// The daemon is not running ServerNotRunning(ServerNotRunning), - /// The end configuration (`pglt.toml` + other options) is incompatible with the command + /// The end configuration (`pglt.json` + other options) is incompatible with the command IncompatibleEndConfiguration(IncompatibleEndConfiguration), /// No files processed during the file system traversal NoFilesWereProcessed(NoFilesWereProcessed), @@ -410,7 +410,7 @@ impl CliDiagnostic { Self::ServerNotRunning(ServerNotRunning) } - /// Emitted when the end configuration (`pglt.toml` file + CLI arguments + LSP configuration) + /// Emitted when the end configuration (`pglt.json` file + CLI arguments + LSP configuration) /// results in a combination of options that doesn't allow to run the command correctly. /// /// A reason needs to be provided diff --git a/crates/pglt_configuration/Cargo.toml b/crates/pglt_configuration/Cargo.toml index dde8592f1..27c61167d 100644 --- a/crates/pglt_configuration/Cargo.toml +++ b/crates/pglt_configuration/Cargo.toml @@ -25,7 +25,6 @@ rustc-hash = { workspace = true } schemars = { workspace = true, features = ["indexmap1"], optional = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["raw_value"] } -toml = { workspace = true } [lib] doctest = false diff --git a/crates/pglt_configuration/src/database.rs b/crates/pglt_configuration/src/database.rs index 2cf6cbd48..6ec5773f5 100644 --- a/crates/pglt_configuration/src/database.rs +++ b/crates/pglt_configuration/src/database.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Deserialize, Eq, Partial, PartialEq, Serialize)] #[partial(derive(Bpaf, Clone, Eq, PartialEq, Merge))] #[partial(cfg_attr(feature = "schema", derive(schemars::JsonSchema)))] -#[partial(serde(rename_all = "snake_case", default, deny_unknown_fields))] +#[partial(serde(rename_all = "camelCase", default, deny_unknown_fields))] pub struct DatabaseConfiguration { /// The host of the database. #[partial(bpaf(long("host")))] diff --git a/crates/pglt_configuration/src/diagnostics.rs b/crates/pglt_configuration/src/diagnostics.rs index fe21c5a60..868783706 100644 --- a/crates/pglt_configuration/src/diagnostics.rs +++ b/crates/pglt_configuration/src/diagnostics.rs @@ -24,9 +24,9 @@ pub enum ConfigurationDiagnostic { } impl ConfigurationDiagnostic { - pub fn new_deserialization_error(error: toml::de::Error) -> Self { + pub fn new_deserialization_error(error: serde_json::Error) -> Self { Self::DeserializationError(DeserializationError { - message: error.message().to_string(), + message: error.to_string(), }) } diff --git a/crates/pglt_configuration/src/files.rs b/crates/pglt_configuration/src/files.rs index 9997695d6..c8e3cde29 100644 --- a/crates/pglt_configuration/src/files.rs +++ b/crates/pglt_configuration/src/files.rs @@ -14,7 +14,7 @@ pub const DEFAULT_FILE_SIZE_LIMIT: NonZeroU64 = #[derive(Clone, Debug, Deserialize, Eq, Partial, PartialEq, Serialize)] #[partial(derive(Bpaf, Clone, Eq, PartialEq, Merge))] #[partial(cfg_attr(feature = "schema", derive(schemars::JsonSchema)))] -#[partial(serde(rename_all = "snake_case", default, deny_unknown_fields))] +#[partial(serde(rename_all = "camelCase", default, deny_unknown_fields))] pub struct FilesConfiguration { /// The maximum allowed size for source code files in bytes. Files above /// this limit will be ignored for performance reasons. Defaults to 1 MiB diff --git a/crates/pglt_configuration/src/lib.rs b/crates/pglt_configuration/src/lib.rs index 1ffb672a4..fe2f1daa9 100644 --- a/crates/pglt_configuration/src/lib.rs +++ b/crates/pglt_configuration/src/lib.rs @@ -1,4 +1,4 @@ -//! This module contains the configuration of `pglt.toml` +//! This module contains the configuration of `pglt.json` //! //! The configuration is divided by "tool", and then it's possible to further customise it //! by language. The language might further options divided by tool. @@ -43,7 +43,7 @@ pub const VERSION: &str = match option_env!("PGLT_VERSION") { #[derive(Clone, Debug, Default, Deserialize, Eq, Partial, PartialEq, Serialize)] #[partial(derive(Bpaf, Clone, Eq, PartialEq, Merge))] #[partial(cfg_attr(feature = "schema", derive(schemars::JsonSchema)))] -#[partial(serde(deny_unknown_fields, rename_all = "snake_case"))] +#[partial(serde(deny_unknown_fields, rename_all = "camelCase"))] pub struct Configuration { /// The configuration of the VCS integration #[partial(type, bpaf(external(partial_vcs_configuration), optional, hide_usage))] diff --git a/crates/pglt_configuration/src/migrations.rs b/crates/pglt_configuration/src/migrations.rs index 2fa99c695..c134e1a41 100644 --- a/crates/pglt_configuration/src/migrations.rs +++ b/crates/pglt_configuration/src/migrations.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; /// The configuration of the filesystem #[derive(Clone, Debug, Deserialize, Eq, Partial, PartialEq, Serialize, Default)] #[partial(derive(Bpaf, Clone, Eq, PartialEq, Merge))] -#[partial(serde(rename_all = "snake_case", default, deny_unknown_fields))] +#[partial(serde(rename_all = "camelCase", default, deny_unknown_fields))] #[partial(cfg_attr(feature = "schema", derive(schemars::JsonSchema)))] pub struct MigrationsConfiguration { /// The directory where the migration files are stored diff --git a/crates/pglt_configuration/src/vcs.rs b/crates/pglt_configuration/src/vcs.rs index be61abdaf..9e659eea0 100644 --- a/crates/pglt_configuration/src/vcs.rs +++ b/crates/pglt_configuration/src/vcs.rs @@ -11,7 +11,7 @@ const GIT_IGNORE_FILE_NAME: &str = ".gitignore"; #[partial(derive(Bpaf, Clone, Deserializable, Eq, Merge, PartialEq))] #[partial(deserializable(with_validator))] #[partial(cfg_attr(feature = "schema", derive(schemars::JsonSchema)))] -#[partial(serde(deny_unknown_fields))] +#[partial(serde(deny_unknown_fields, rename_all = "camelCase"))] pub struct VcsConfiguration { /// Whether we should integrate itself with the VCS client #[partial(bpaf(long("vcs-enabled"), argument("true|false")))] @@ -28,7 +28,7 @@ pub struct VcsConfiguration { pub use_ignore_file: bool, /// The folder where we should check for VCS files. By default, we will use the same - /// folder where `pglt.toml` was found. + /// folder where `pglt.json` was found. /// /// If we can't find the configuration, it will attempt to use the current working directory. /// If no current working directory can't be found, we won't use the VCS integration, and a diagnostic @@ -91,7 +91,7 @@ impl DeserializableValidator for PartialVcsConfiguration { Clone, Copy, Debug, Default, Deserialize, Deserializable, Eq, Merge, PartialEq, Serialize, )] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] -#[serde(rename_all = "snake_case")] +#[serde(rename_all = "camelCase")] pub enum VcsClientKind { #[default] /// Integration with the git client as VCS diff --git a/crates/pglt_diagnostics/src/advice.rs b/crates/pglt_diagnostics/src/advice.rs index afaa7ca6c..b7b4babc8 100644 --- a/crates/pglt_diagnostics/src/advice.rs +++ b/crates/pglt_diagnostics/src/advice.rs @@ -86,7 +86,7 @@ pub trait Visit { /// to the user. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] -#[serde(rename_all = "snake_case")] +#[serde(rename_all = "camelCase")] pub enum LogCategory { /// The advice doesn't have any specific category, the message will be /// printed as plain markup. diff --git a/crates/pglt_diagnostics/src/diagnostic.rs b/crates/pglt_diagnostics/src/diagnostic.rs index 5eb40da23..0914e72ea 100644 --- a/crates/pglt_diagnostics/src/diagnostic.rs +++ b/crates/pglt_diagnostics/src/diagnostic.rs @@ -117,7 +117,7 @@ pub trait Diagnostic: Debug { #[derive( Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default, )] -#[serde(rename_all = "snake_case")] +#[serde(rename_all = "camelCase")] /// The severity to associate to a diagnostic. pub enum Severity { /// Reports a hint. @@ -164,7 +164,7 @@ impl Display for Severity { /// Internal enum used to automatically generate bit offsets for [DiagnosticTags] /// and help with the implementation of `serde` and `schemars` for tags. #[derive(Debug, Copy, Clone, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] +#[serde(rename_all = "camelCase")] #[bitflags] #[repr(u8)] pub(super) enum DiagnosticTag { diff --git a/crates/pglt_diagnostics/src/location.rs b/crates/pglt_diagnostics/src/location.rs index 6678912d4..3b847975d 100644 --- a/crates/pglt_diagnostics/src/location.rs +++ b/crates/pglt_diagnostics/src/location.rs @@ -39,7 +39,7 @@ impl Eq for Location<'_> {} /// Represents the resource a diagnostic is associated with. #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] +#[serde(rename_all = "camelCase")] pub enum Resource

{ /// The diagnostic is related to the content of the command line arguments. Argv, diff --git a/crates/pglt_diagnostics/src/serde.rs b/crates/pglt_diagnostics/src/serde.rs index d564f8321..aa67ebdef 100644 --- a/crates/pglt_diagnostics/src/serde.rs +++ b/crates/pglt_diagnostics/src/serde.rs @@ -15,7 +15,7 @@ use crate::{ /// Serializable representation for a [Diagnostic](super::Diagnostic). #[derive(Clone, Debug, Serialize, Deserialize)] -#[cfg_attr(not(target_arch = "wasm32"), serde(rename_all = "snake_case"))] +#[cfg_attr(not(target_arch = "wasm32"), serde(rename_all = "camelCase"))] #[cfg_attr(test, derive(Eq, PartialEq))] pub struct Diagnostic { category: Option<&'static Category>, @@ -137,7 +137,7 @@ impl std::fmt::Display for PrintDescription<'_, D } #[derive(Clone, Debug, Serialize, Deserialize)] -#[cfg_attr(not(target_arch = "wasm32"), serde(rename_all = "snake_case"))] +#[cfg_attr(not(target_arch = "wasm32"), serde(rename_all = "camelCase"))] #[cfg_attr(test, derive(Eq, PartialEq))] struct Location { path: Option>, @@ -159,7 +159,7 @@ impl From> for Location { /// Implementation of [Visitor] collecting serializable [Advice] into a vector. #[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] +#[serde(rename_all = "camelCase")] #[cfg_attr(test, derive(Eq, PartialEq))] struct Advices { advices: Vec, @@ -245,7 +245,7 @@ impl super::Advices for Advices { /// See the [Visitor] trait for additional documentation on all the supported /// advice types. #[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] +#[serde(rename_all = "camelCase")] #[cfg_attr(test, derive(Eq, PartialEq))] enum Advice { Log(LogCategory, MarkupBuf), diff --git a/crates/pglt_fs/src/fs.rs b/crates/pglt_fs/src/fs.rs index 749e4fd7c..aed73bb6f 100644 --- a/crates/pglt_fs/src/fs.rs +++ b/crates/pglt_fs/src/fs.rs @@ -18,14 +18,14 @@ mod os; pub struct ConfigName; impl ConfigName { - const PGLT_TOML: [&'static str; 1] = ["pglt.toml"]; + const PGLT_JSON: [&'static str; 1] = ["pglt.json"]; - pub const fn pglt_toml() -> &'static str { - Self::PGLT_TOML[0] + pub const fn pglt_json() -> &'static str { + Self::PGLT_JSON[0] } pub const fn file_names() -> [&'static str; 1] { - Self::PGLT_TOML + Self::PGLT_JSON } } diff --git a/crates/pglt_fs/src/path.rs b/crates/pglt_fs/src/path.rs index 37c74f763..9562d3acb 100644 --- a/crates/pglt_fs/src/path.rs +++ b/crates/pglt_fs/src/path.rs @@ -21,7 +21,7 @@ use crate::ConfigName; #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] // NOTE: The order of the variants is important, the one on the top has the highest priority pub enum FileKind { - /// A configuration file has the highest priority. It's usually `pglt.toml` + /// A configuration file has the highest priority. It's usually `pglt.json` /// /// Other third-party configuration files might be added in the future Config, @@ -173,11 +173,10 @@ impl PgLTPath { } /// The priority of the file. - /// - `pglt.toml` has the highest priority - /// - `package.json` and `tsconfig.json`/`jsconfig.json` have the second-highest priority, and they are considered as manifest files + /// - `pglt.json` has the highest priority /// - Other files are considered as files to handle fn priority(file_name: &OsStr) -> FileKinds { - if file_name == ConfigName::pglt_toml() { + if file_name == ConfigName::pglt_json() { FileKind::Config.into() } else { FileKind::Handleable.into() diff --git a/crates/pglt_lsp/Cargo.toml b/crates/pglt_lsp/Cargo.toml index bb4e4226d..29eb0e3ee 100644 --- a/crates/pglt_lsp/Cargo.toml +++ b/crates/pglt_lsp/Cargo.toml @@ -36,7 +36,6 @@ tracing = { workspace = true, features = ["attributes"] } pglt_test_utils = { workspace = true } sqlx = { workspace = true } tokio = { workspace = true, features = ["macros"] } -toml = { workspace = true } tower = { version = "0.4.13", features = ["timeout"] } [lib] diff --git a/crates/pglt_lsp/src/server.rs b/crates/pglt_lsp/src/server.rs index 584b7a95e..966a45e43 100644 --- a/crates/pglt_lsp/src/server.rs +++ b/crates/pglt_lsp/src/server.rs @@ -74,8 +74,9 @@ impl LSPServer { DidChangeWatchedFilesRegistrationOptions { watchers: vec![FileSystemWatcher { glob_pattern: GlobPattern::String(format!( - "{}/pglt.toml", - base_path.display() + "{}/{}", + base_path.display(), + ConfigName::pglt_json() )), kind: Some(WatchKind::all()), },], @@ -148,7 +149,10 @@ impl LanguageServer for LSPServer { async fn initialized(&self, params: InitializedParams) { let _ = params; - info!("Attempting to load the configuration from 'pglt.toml' file"); + info!( + "Attempting to load the configuration from '{}' file", + ConfigName::pglt_json() + ); futures::join!(self.session.load_workspace_settings()); @@ -188,8 +192,8 @@ impl LanguageServer for LSPServer { Ok(file_path) => { let base_path = self.session.base_path(); if let Some(base_path) = base_path { - let possible_config_toml = file_path.strip_prefix(&base_path); - if let Ok(watched_file) = possible_config_toml { + let possible_config_json = file_path.strip_prefix(&base_path); + if let Ok(watched_file) = possible_config_json { if ConfigName::file_names() .contains(&&*watched_file.display().to_string()) { diff --git a/crates/pglt_lsp/src/session.rs b/crates/pglt_lsp/src/session.rs index c74ab46bd..26f13cbba 100644 --- a/crates/pglt_lsp/src/session.rs +++ b/crates/pglt_lsp/src/session.rs @@ -400,7 +400,7 @@ impl Session { .map(|params| ¶ms.client_capabilities) } - /// This function attempts to read the `pglt.toml` configuration file from + /// This function attempts to read the `pglt.json` configuration file from /// the root URI and update the workspace settings accordingly #[tracing::instrument(level = "trace", skip(self))] pub(crate) async fn load_workspace_settings(&self) { diff --git a/crates/pglt_lsp/tests/server.rs b/crates/pglt_lsp/tests/server.rs index 6a4a76542..a87fe5402 100644 --- a/crates/pglt_lsp/tests/server.rs +++ b/crates/pglt_lsp/tests/server.rs @@ -372,8 +372,8 @@ async fn test_database_connection() -> Result<()> { ..Default::default() }); fs.insert( - url!("pglt.toml").to_file_path().unwrap(), - toml::to_string(&conf).unwrap(), + url!("pglt.json").to_file_path().unwrap(), + serde_json::to_string_pretty(&conf).unwrap(), ); let (service, client) = factory @@ -484,8 +484,8 @@ async fn test_completions() -> Result<()> { ..Default::default() }); fs.insert( - url!("pglt.toml").to_file_path().unwrap(), - toml::to_string(&conf).unwrap(), + url!("pglt.json").to_file_path().unwrap(), + serde_json::to_string_pretty(&conf).unwrap(), ); let (service, client) = factory diff --git a/crates/pglt_workspace/Cargo.toml b/crates/pglt_workspace/Cargo.toml index 29e561df6..c746c0eff 100644 --- a/crates/pglt_workspace/Cargo.toml +++ b/crates/pglt_workspace/Cargo.toml @@ -34,7 +34,6 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["raw_value"] } sqlx.workspace = true tokio = { workspace = true, features = ["rt", "rt-multi-thread"] } -toml = { workspace = true } tracing = { workspace = true, features = ["attributes", "log"] } tree-sitter.workspace = true tree_sitter_sql.workspace = true diff --git a/crates/pglt_workspace/src/configuration.rs b/crates/pglt_workspace/src/configuration.rs index f9df091fe..5fc3648af 100644 --- a/crates/pglt_workspace/src/configuration.rs +++ b/crates/pglt_workspace/src/configuration.rs @@ -99,7 +99,7 @@ fn load_config( if file_system.path_is_file(config_file_path) { let content = file_system.read_file_from_path(config_file_path)?; - let deserialized = toml::from_str::(&content) + let deserialized = serde_json::from_str::(&content) .map_err(ConfigurationDiagnostic::new_deserialization_error)?; return Ok(Some(ConfigurationPayload { @@ -120,7 +120,7 @@ fn load_config( ConfigurationPathHint::None => file_system.working_directory().unwrap_or_default(), }; - // We first search for `pgtoml.json` + // We first search for `pglt.json` if let Some(auto_search_result) = file_system.auto_search( &configuration_directory, ConfigName::file_names().as_slice(), @@ -128,7 +128,7 @@ fn load_config( )? { let AutoSearchResult { content, file_path } = auto_search_result; - let deserialized = toml::from_str::(&content) + let deserialized = serde_json::from_str::(&content) .map_err(ConfigurationDiagnostic::new_deserialization_error)?; Ok(Some(ConfigurationPayload { @@ -152,7 +152,7 @@ pub fn create_config( fs: &mut DynRef, configuration: PartialConfiguration, ) -> Result<(), WorkspaceError> { - let path = PathBuf::from(ConfigName::pglt_toml()); + let path = PathBuf::from(ConfigName::pglt_json()); if fs.path_exists(&path) { return Err(ConfigurationDiagnostic::new_already_exists().into()); @@ -168,7 +168,7 @@ pub fn create_config( } })?; - let contents = toml::ser::to_string_pretty(&configuration) + let contents = serde_json::to_string_pretty(&configuration) .map_err(|_| ConfigurationDiagnostic::new_serialization_error())?; config_file diff --git a/crates/pglt_workspace/src/workspace/server.rs b/crates/pglt_workspace/src/workspace/server.rs index 4b4b9a6ee..8101bbba6 100644 --- a/crates/pglt_workspace/src/workspace/server.rs +++ b/crates/pglt_workspace/src/workspace/server.rs @@ -108,7 +108,7 @@ impl WorkspaceServer { fn is_ignored(&self, path: &Path) -> bool { let file_name = path.file_name().and_then(|s| s.to_str()); // Never ignore PgLT's config file regardless `include`/`ignore` - (file_name != Some(ConfigName::pglt_toml())) && + (file_name != Some(ConfigName::pglt_json())) && // Apply top-level `include`/`ignore (self.is_ignored_by_top_level_config(path) || self.is_ignored_by_migration_config(path)) } diff --git a/docs/checking_migrations.md b/docs/checking_migrations.md index cf96614a2..6c48832e9 100644 --- a/docs/checking_migrations.md +++ b/docs/checking_migrations.md @@ -8,13 +8,16 @@ To run it, simply point at your migrations directory. pglt check supabase/migrations ``` -When you are setting it up in an existing project, you might want to ignore all migrations that are already applied. To do so, add `migrations_dir` and `after` to your `pglt.toml` file +When you are setting it up in an existing project, you might want to ignore all migrations that are already applied. To do so, add `migrationsDir` and `after` to your `pglt.json` file -```toml -[migrations] -migrations_dir = "supabase/migrations" -after = 1740868021 +```json +{ + "migrations": { + "migrationsDir": "supabase/migrations", + "after": 1740868021 + } +} ``` Alternatively, pass them directly. @@ -25,5 +28,5 @@ pglt check supabase/migrations --migrations-dir="supabase/migrations" --after=17 This will only check migrations after the specified timestamp. -For pre-commit hooks and when working locally, use `--staged` to only lint files that have been staged. In CI environments, you most likely want to use `--changed` to only lint files that have been changed compared to your `vcs.default_branch` configuration. If `default_branch` is not set in your `pglt.toml`, use `--since=REF` to specify the base branch to compare against. +For pre-commit hooks and when working locally, use `--staged` to only lint files that have been staged. In CI environments, you most likely want to use `--changed` to only lint files that have been changed compared to your `vcs.default_branch` configuration. If `default_branch` is not set in your `pglt.json`, use `--since=REF` to specify the base branch to compare against. diff --git a/docs/cli_reference.md b/docs/cli_reference.md index 2dfe7ee6d..755638615 100644 --- a/docs/cli_reference.md +++ b/docs/cli_reference.md @@ -62,7 +62,7 @@ Shows the version information and quit. - **` --verbose`** — Print additional diagnostics, and some diagnostics show more information. Also, print out what files were processed and which ones were modified. - **` --config-path`**=_`PATH`_ — - Set the file path to the configuration file, or the directory path to find `pglt.toml`. If used, it disables the default configuration file resolution. + Set the file path to the configuration file, or the directory path to find `pglt.json`. If used, it disables the default configuration file resolution. - **` --max-diagnostics`**=_`>`_ — Cap the amount of diagnostics displayed. When `none` is provided, the limit is lifted. @@ -111,7 +111,7 @@ Runs everything to the requested files. - **` --vcs-use-ignore-file`**=_``_ — Whether we should use the VCS ignore file. When [true], we will ignore the files specified in the ignore file. - **` --vcs-root`**=_`PATH`_ — - The folder where we should check for VCS files. By default, we will use the same folder where `pglt.toml` was found. + The folder where we should check for VCS files. By default, we will use the same folder where `pglt.json` was found. If we can't find the configuration, it will attempt to use the current working directory. If no current working directory can't be found, we won't use the VCS integration, and a diagnostic will be emitted - **` --vcs-default-branch`**=_`BRANCH`_ — @@ -149,7 +149,7 @@ Runs everything to the requested files. - **` --verbose`** — Print additional diagnostics, and some diagnostics show more information. Also, print out what files were processed and which ones were modified. - **` --config-path`**=_`PATH`_ — - Set the file path to the configuration file, or the directory path to find `pglt.toml`. If used, it disables the default configuration file resolution. + Set the file path to the configuration file, or the directory path to find `pglt.json`. If used, it disables the default configuration file resolution. - **` --max-diagnostics`**=_`>`_ — Cap the amount of diagnostics displayed. When `none` is provided, the limit is lifted. @@ -197,7 +197,7 @@ Runs everything to the requested files. - **` --changed`** — 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. - **` --since`**=_`REF`_ — - 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 `pglt.toml` + 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 `pglt.json` - **`-h`**, **`--help`** — Prints help information @@ -220,7 +220,7 @@ Starts the daemon server process. Uses environment variable **`PGLT_LOG_PATH`** - **` --config-path`**=_`PATH`_ — - Allows to set a custom file path to the configuration file, or a custom directory path to find `pglt.toml` + Allows to set a custom file path to the configuration file, or a custom directory path to find `pglt.json` Uses environment variable **`PGLT_LOG_PREFIX_NAME`** - **`-h`**, **`--help`** — @@ -267,7 +267,7 @@ Acts as a server for the Language Server Protocol over stdin/stdout. Uses environment variable **`PGLT_LOG_PATH`** - **` --config-path`**=_`PATH`_ — - Allows to set a custom file path to the configuration file, or a custom directory path to find `pglt.toml` + Allows to set a custom file path to the configuration file, or a custom directory path to find `pglt.json` Uses environment variable **`PGLT_CONFIG_PATH`** - **`-h`**, **`--help`** — diff --git a/docs/codegen/Cargo.toml b/docs/codegen/Cargo.toml index 646490f82..ba1bc52f3 100644 --- a/docs/codegen/Cargo.toml +++ b/docs/codegen/Cargo.toml @@ -13,7 +13,6 @@ version = "0.0.0" [dependencies] regex = { workspace = true } -toml = { workspace = true } anyhow = { workspace = true } bpaf = { workspace = true, features = ["docgen"] } schemars = { workspace = true } diff --git a/docs/codegen/src/default_configuration.rs b/docs/codegen/src/default_configuration.rs index 12f305b82..1a9bdadd7 100644 --- a/docs/codegen/src/default_configuration.rs +++ b/docs/codegen/src/default_configuration.rs @@ -8,8 +8,8 @@ pub fn generate_default_configuration(docs_dir: &Path) -> anyhow::Result<()> { let index_path = docs_dir.join("index.md"); let printed_config = format!( - "\n```toml\n{}```\n", - toml::ser::to_string_pretty(&PartialConfiguration::init())? + "\n```json\n{}```\n", + serde_json::to_string_pretty(&PartialConfiguration::init())? ); let data = fs::read_to_string(&index_path)?; diff --git a/docs/codegen/src/rules_docs.rs b/docs/codegen/src/rules_docs.rs index d62435921..c9dc1615d 100644 --- a/docs/codegen/src/rules_docs.rs +++ b/docs/codegen/src/rules_docs.rs @@ -113,14 +113,22 @@ fn write_how_to_configure( content: &mut Vec, ) -> io::Result<()> { writeln!(content, "## How to configure")?; - let toml = format!( - r#"[linter.rules.{group}] -{rule} = "error" + let json = format!( + r#" +{{ + "linter": {{ + "rules": {{ + "{group}": {{ + "{rule}": "error" + }} + }} + }} +}} "# ); - writeln!(content, "```toml")?; - writeln!(content, "{}", toml)?; + writeln!(content, "```json")?; + writeln!(content, "{}", json)?; writeln!(content, "```")?; Ok(()) diff --git a/docs/index.md b/docs/index.md index 0e417a21d..50e95176b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -29,39 +29,41 @@ Our current focus is on refining and enhancing these core features while buildin ## Configuration -We recommend that you create a `pglt.toml` configuration file for each project. This eliminates the need to repeat the CLI options each time you run a command, and ensures that we use the same configuration in your editor. Some options are also only available from a configuration file. If you are happy with the defaults, you don’t need to create a configuration file. To create the `pglt.toml` file, run the `init` command in the root folder of your project: +We recommend that you create a `pglt.json` configuration file for each project. This eliminates the need to repeat the CLI options each time you run a command, and ensures that we use the same configuration in your editor. Some options are also only available from a configuration file. If you are happy with the defaults, you don’t need to create a configuration file. To create the `pglt.json` file, run the `init` command in the root folder of your project: ```sh pglt init ``` -After running the `init` command, you’ll have a new `pglt.toml` file in your directory: +After running the `init` command, you’ll have a `pglt.json` file in your directory: [//]: # (BEGIN DEFAULT_CONFIGURATION) -```toml -[vcs] -enabled = false -client_kind = "git" -use_ignore_file = false - -[files] -ignore = [] - -[linter] -enabled = true - -[linter.rules] -recommended = true - -[db] -host = "127.0.0.1" -port = 5432 -username = "postgres" -password = "postgres" -database = "postgres" -conn_timeout_secs = 10 -``` +```json +{ + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignore": [] + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "db": { + "host": "127.0.0.1", + "port": 5432, + "username": "postgres", + "password": "postgres", + "database": "postgres", + "connTimeoutSecs": 10 + } +}``` [//]: # (END DEFAULT_CONFIGURATION) diff --git a/docs/linting_migrations.md b/docs/linting_migrations.md deleted file mode 100644 index ce69283a6..000000000 --- a/docs/linting_migrations.md +++ /dev/null @@ -1,29 +0,0 @@ -# Linting Migrations - -Postgres Language Tools comes with a `check` command that can be integrated into your development workflow to catch problematic schema changes and encourage best practices. - -To run it, simply point at your migrations directory. - -```sh -pglt check supabase/migrations -``` - -When you are setting it up in an existing project, you might want to ignore all migrations that are already applied. To do so, add `migrations_dir` and `after` to your `pglt.toml` file - - -```toml -[migrations] -migrations_dir = "supabase/migrations" -after = 1740868021 -``` - -Alternatively, pass them directly. - -``` -pglt check supabase/migrations --migrations-dir="supabase/migrations" --after=1740868021 -``` - -This will only check migrations after the specified timestamp. - -For pre-commit hooks and when working locally, use `--staged` to only lint files that have been staged. In CI environments, you most likely want to use `--changed` to only lint files that have been changed compared to your `defaultBranch` configuration. If `defaultBranch` is not set in your `pglt.toml`, use `--since=REF` to specify the base branch to compare against. - diff --git a/docs/rules/adding-required-field.md b/docs/rules/adding-required-field.md index ee4a85182..63dea2818 100644 --- a/docs/rules/adding-required-field.md +++ b/docs/rules/adding-required-field.md @@ -24,8 +24,16 @@ alter table test add column count int not null; alter table test add column count int not null default 0; ## How to configure -```toml -[linter.rules.safety] -addingRequiredField = "error" +```json + +{ + "linter": { + "rules": { + "safety": { + "addingRequiredField": "error" + } + } + } +} ``` diff --git a/docs/rules/ban-drop-column.md b/docs/rules/ban-drop-column.md index a06ae41dc..49a0d054b 100644 --- a/docs/rules/ban-drop-column.md +++ b/docs/rules/ban-drop-column.md @@ -35,8 +35,16 @@ code-block.sql lint/safety/banDropColumn ━━━━━━━━━━━━━ ``` ## How to configure -```toml -[linter.rules.safety] -banDropColumn = "error" +```json + +{ + "linter": { + "rules": { + "safety": { + "banDropColumn": "error" + } + } + } +} ``` diff --git a/docs/rules/ban-drop-not-null.md b/docs/rules/ban-drop-not-null.md index 449812fe0..ccf49f956 100644 --- a/docs/rules/ban-drop-not-null.md +++ b/docs/rules/ban-drop-not-null.md @@ -35,8 +35,16 @@ code-block.sql lint/safety/banDropNotNull ━━━━━━━━━━━━ ``` ## How to configure -```toml -[linter.rules.safety] -banDropNotNull = "error" +```json + +{ + "linter": { + "rules": { + "safety": { + "banDropNotNull": "error" + } + } + } +} ``` diff --git a/docs/rules/ban-drop-table.md b/docs/rules/ban-drop-table.md index d814b8bc9..f2f341561 100644 --- a/docs/rules/ban-drop-table.md +++ b/docs/rules/ban-drop-table.md @@ -36,8 +36,16 @@ code-block.sql lint/safety/banDropTable ━━━━━━━━━━━━━ ``` ## How to configure -```toml -[linter.rules.safety] -banDropTable = "error" +```json + +{ + "linter": { + "rules": { + "safety": { + "banDropTable": "error" + } + } + } +} ``` diff --git a/docs/schemas/0.0.0/schema.json b/docs/schemas/0.0.0/schema.json index 05981eb0b..13c43d08c 100644 --- a/docs/schemas/0.0.0/schema.json +++ b/docs/schemas/0.0.0/schema.json @@ -66,7 +66,7 @@ "description": "The configuration of the database connection.", "type": "object", "properties": { - "conn_timeout_secs": { + "connTimeoutSecs": { "description": "The connection timeout in seconds.", "type": [ "integer", @@ -141,7 +141,7 @@ } ] }, - "max_size": { + "maxSize": { "description": "The maximum allowed size for source code files in bytes. Files above this limit will be ignored for performance reasons. Defaults to 1 MiB", "type": [ "integer", @@ -212,7 +212,7 @@ "format": "uint64", "minimum": 0.0 }, - "migrations_dir": { + "migrationsDir": { "description": "The directory where the migration files are stored", "type": [ "string", @@ -375,7 +375,7 @@ "description": "Set of properties to integrate with a VCS software.", "type": "object", "properties": { - "client_kind": { + "clientKind": { "description": "The kind of client.", "anyOf": [ { @@ -386,7 +386,7 @@ } ] }, - "default_branch": { + "defaultBranch": { "description": "The main branch of the project", "type": [ "string", @@ -401,13 +401,13 @@ ] }, "root": { - "description": "The folder where we should check for VCS files. By default, we will use the same folder where `pglt.toml` was found.\n\nIf we can't find the configuration, it will attempt to use the current working directory. If no current working directory can't be found, we won't use the VCS integration, and a diagnostic will be emitted", + "description": "The folder where we should check for VCS files. By default, we will use the same folder where `pglt.json` was found.\n\nIf we can't find the configuration, it will attempt to use the current working directory. If no current working directory can't be found, we won't use the VCS integration, and a diagnostic will be emitted", "type": [ "string", "null" ] }, - "use_ignore_file": { + "useIgnoreFile": { "description": "Whether we should use the VCS ignore file. When [true], we will ignore the files specified in the ignore file.", "type": [ "boolean", diff --git a/docs/schemas/latest/schema.json b/docs/schemas/latest/schema.json index 05981eb0b..13c43d08c 100644 --- a/docs/schemas/latest/schema.json +++ b/docs/schemas/latest/schema.json @@ -66,7 +66,7 @@ "description": "The configuration of the database connection.", "type": "object", "properties": { - "conn_timeout_secs": { + "connTimeoutSecs": { "description": "The connection timeout in seconds.", "type": [ "integer", @@ -141,7 +141,7 @@ } ] }, - "max_size": { + "maxSize": { "description": "The maximum allowed size for source code files in bytes. Files above this limit will be ignored for performance reasons. Defaults to 1 MiB", "type": [ "integer", @@ -212,7 +212,7 @@ "format": "uint64", "minimum": 0.0 }, - "migrations_dir": { + "migrationsDir": { "description": "The directory where the migration files are stored", "type": [ "string", @@ -375,7 +375,7 @@ "description": "Set of properties to integrate with a VCS software.", "type": "object", "properties": { - "client_kind": { + "clientKind": { "description": "The kind of client.", "anyOf": [ { @@ -386,7 +386,7 @@ } ] }, - "default_branch": { + "defaultBranch": { "description": "The main branch of the project", "type": [ "string", @@ -401,13 +401,13 @@ ] }, "root": { - "description": "The folder where we should check for VCS files. By default, we will use the same folder where `pglt.toml` was found.\n\nIf we can't find the configuration, it will attempt to use the current working directory. If no current working directory can't be found, we won't use the VCS integration, and a diagnostic will be emitted", + "description": "The folder where we should check for VCS files. By default, we will use the same folder where `pglt.json` was found.\n\nIf we can't find the configuration, it will attempt to use the current working directory. If no current working directory can't be found, we won't use the VCS integration, and a diagnostic will be emitted", "type": [ "string", "null" ] }, - "use_ignore_file": { + "useIgnoreFile": { "description": "Whether we should use the VCS ignore file. When [true], we will ignore the files specified in the ignore file.", "type": [ "boolean", From 0f53352d895546b5baa54ff883f03254d0a75278 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Fri, 14 Mar 2025 21:01:07 +0100 Subject: [PATCH 06/12] fix: lint --- crates/pglt_lsp/src/handlers/text_document.rs | 2 +- crates/pglt_workspace/src/workspace/server.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pglt_lsp/src/handlers/text_document.rs b/crates/pglt_lsp/src/handlers/text_document.rs index 78279cd11..8f28d6c80 100644 --- a/crates/pglt_lsp/src/handlers/text_document.rs +++ b/crates/pglt_lsp/src/handlers/text_document.rs @@ -7,7 +7,7 @@ use pglt_workspace::workspace::{ ChangeFileParams, ChangeParams, CloseFileParams, GetFileContentParams, OpenFileParams, }; use tower_lsp::lsp_types; -use tracing::{error, field}; +use tracing::error; /// Handler for `textDocument/didOpen` LSP notification #[tracing::instrument(level = "debug", skip(session), err)] diff --git a/crates/pglt_workspace/src/workspace/server.rs b/crates/pglt_workspace/src/workspace/server.rs index 8101bbba6..469e7818f 100644 --- a/crates/pglt_workspace/src/workspace/server.rs +++ b/crates/pglt_workspace/src/workspace/server.rs @@ -3,7 +3,7 @@ use std::{fs, panic::RefUnwindSafe, path::Path, sync::RwLock}; use analyser::AnalyserVisitorBuilder; use async_helper::run_async; use change::StatementChange; -use dashmap::{DashMap, DashSet}; +use dashmap::DashMap; use db_connection::DbConnection; use document::{Document, Statement}; use futures::{StreamExt, stream}; From 7068df3f6f5916ff4cc9b775fb32559aab66e388 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Fri, 14 Mar 2025 21:17:29 +0100 Subject: [PATCH 07/12] feat: add schema field --- crates/pglt_configuration/src/lib.rs | 9 +++ crates/pglt_workspace/src/configuration.rs | 89 ++++++++++++++++++++-- 2 files changed, 93 insertions(+), 5 deletions(-) diff --git a/crates/pglt_configuration/src/lib.rs b/crates/pglt_configuration/src/lib.rs index fe2f1daa9..5fe7cc3ed 100644 --- a/crates/pglt_configuration/src/lib.rs +++ b/crates/pglt_configuration/src/lib.rs @@ -45,6 +45,11 @@ pub const VERSION: &str = match option_env!("PGLT_VERSION") { #[partial(cfg_attr(feature = "schema", derive(schemars::JsonSchema)))] #[partial(serde(deny_unknown_fields, rename_all = "camelCase"))] pub struct Configuration { + /// A field for the [JSON schema](https://json-schema.org/) specification + #[partial(serde(rename = "$schema"))] + #[partial(bpaf(hide))] + pub schema: String, + /// The configuration of the VCS integration #[partial(type, bpaf(external(partial_vcs_configuration), optional, hide_usage))] pub vcs: VcsConfiguration, @@ -79,6 +84,10 @@ impl PartialConfiguration { /// Returns the initial configuration. pub fn init() -> Self { Self { + // TODO: Update this once we have a static url + schema: Some(format!( + "https://supabase-community.github.io/postgres_lsp/schemas/{VERSION}/schema.json" + )), files: Some(PartialFilesConfiguration { ignore: Some(Default::default()), ..Default::default() diff --git a/crates/pglt_workspace/src/configuration.rs b/crates/pglt_workspace/src/configuration.rs index 5fc3648af..671009b18 100644 --- a/crates/pglt_workspace/src/configuration.rs +++ b/crates/pglt_workspace/src/configuration.rs @@ -7,7 +7,7 @@ use std::{ use pglt_analyse::AnalyserRules; use pglt_configuration::{ ConfigurationDiagnostic, ConfigurationPathHint, ConfigurationPayload, PartialConfiguration, - push_to_analyser_rules, + VERSION, push_to_analyser_rules, }; use pglt_fs::{AutoSearchResult, ConfigName, FileSystem, OpenOptions}; @@ -97,7 +97,7 @@ fn load_config( // we'll load it directly if let ConfigurationPathHint::FromUser(ref config_file_path) = base_path { if file_system.path_is_file(config_file_path) { - let content = file_system.read_file_from_path(config_file_path)?; + let content = strip_jsonc_comments(&file_system.read_file_from_path(config_file_path)?); let deserialized = serde_json::from_str::(&content) .map_err(ConfigurationDiagnostic::new_deserialization_error)?; @@ -128,8 +128,9 @@ fn load_config( )? { let AutoSearchResult { content, file_path } = auto_search_result; - let deserialized = serde_json::from_str::(&content) - .map_err(ConfigurationDiagnostic::new_deserialization_error)?; + let deserialized = + serde_json::from_str::(&strip_jsonc_comments(&content)) + .map_err(ConfigurationDiagnostic::new_deserialization_error)?; Ok(Some(ConfigurationPayload { deserialized, @@ -150,7 +151,7 @@ fn load_config( /// - the program doesn't have the write rights pub fn create_config( fs: &mut DynRef, - configuration: PartialConfiguration, + configuration: &mut PartialConfiguration, ) -> Result<(), WorkspaceError> { let path = PathBuf::from(ConfigName::pglt_json()); @@ -168,6 +169,19 @@ pub fn create_config( } })?; + // we now check if biome is installed inside `node_modules` and if so, we use the schema from there + if VERSION == "0.0.0" { + let schema_path = Path::new("./node_modules/@pglt/pglt/schema.json"); + let options = OpenOptions::default().read(true); + if fs.open_with_options(schema_path, options).is_ok() { + configuration.schema = schema_path.to_str().map(String::from); + } + } else { + configuration.schema = Some(format!( + "https://supabase-community.github.io/postgres_lsp/schemas/{VERSION}/schema.json" + )); + } + let contents = serde_json::to_string_pretty(&configuration) .map_err(|_| ConfigurationDiagnostic::new_serialization_error())?; @@ -186,3 +200,68 @@ pub fn to_analyser_rules(settings: &Settings) -> AnalyserRules { } analyser_rules } + +/// Takes a string of jsonc content and returns a comment free version +/// which should parse fine as regular json. +/// Nested block comments are supported. +pub fn strip_jsonc_comments(jsonc_input: &str) -> String { + let mut json_output = String::new(); + + let mut block_comment_depth: u8 = 0; + let mut is_in_string: bool = false; // Comments cannot be in strings + + for line in jsonc_input.split('\n') { + let mut last_char: Option = None; + for cur_char in line.chars() { + // Check whether we're in a string + if block_comment_depth == 0 && last_char != Some('\\') && cur_char == '"' { + is_in_string = !is_in_string; + } + + // Check for line comment start + if !is_in_string && last_char == Some('/') && cur_char == '/' { + last_char = None; + json_output.push_str(" "); + break; // Stop outputting or parsing this line + } + // Check for block comment start + if !is_in_string && last_char == Some('/') && cur_char == '*' { + block_comment_depth += 1; + last_char = None; + json_output.push_str(" "); + // Check for block comment end + } else if !is_in_string && last_char == Some('*') && cur_char == '/' { + block_comment_depth = block_comment_depth.saturating_sub(1); + last_char = None; + json_output.push_str(" "); + // Output last char if not in any block comment + } else { + if block_comment_depth == 0 { + if let Some(last_char) = last_char { + json_output.push(last_char); + } + } else { + json_output.push_str(" "); + } + last_char = Some(cur_char); + } + } + + // Add last char and newline if not in any block comment + if let Some(last_char) = last_char { + if block_comment_depth == 0 { + json_output.push(last_char); + } else { + json_output.push(' '); + } + } + + // Remove trailing whitespace from line + while json_output.ends_with(' ') { + json_output.pop(); + } + json_output.push('\n'); + } + + json_output +} From 1e42b89ea6093572cdb53469291f65e95c349c89 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Fri, 14 Mar 2025 21:26:34 +0100 Subject: [PATCH 08/12] migrate to jsonc config --- crates/pglt_analyse/src/options.rs | 2 +- crates/pglt_analyser/CONTRIBUTING.md | 6 ++--- crates/pglt_cli/src/cli_options.rs | 2 +- crates/pglt_cli/src/commands/init.rs | 5 ++-- crates/pglt_cli/src/commands/mod.rs | 8 +++--- crates/pglt_cli/src/diagnostics.rs | 4 +-- crates/pglt_configuration/src/lib.rs | 2 +- crates/pglt_configuration/src/vcs.rs | 2 +- crates/pglt_fs/src/fs.rs | 8 +++--- crates/pglt_fs/src/path.rs | 6 ++--- crates/pglt_lsp/src/server.rs | 4 +-- crates/pglt_lsp/src/session.rs | 2 +- crates/pglt_lsp/tests/server.rs | 4 +-- crates/pglt_workspace/src/configuration.rs | 4 +-- crates/pglt_workspace/src/workspace/server.rs | 2 +- docs/checking_migrations.md | 4 +-- docs/cli_reference.md | 12 ++++----- docs/index.md | 4 +-- docs/schemas/0.0.0/schema.json | 2 +- docs/schemas/latest/schema.json | 2 +- pglt.jsonc | 26 +++++++++++++++++++ pglt.toml | 21 --------------- 22 files changed, 69 insertions(+), 63 deletions(-) create mode 100644 pglt.jsonc delete mode 100644 pglt.toml diff --git a/crates/pglt_analyse/src/options.rs b/crates/pglt_analyse/src/options.rs index 6083356cc..9645799a0 100644 --- a/crates/pglt_analyse/src/options.rs +++ b/crates/pglt_analyse/src/options.rs @@ -45,7 +45,7 @@ impl AnalyserRules { /// A set of information useful to the analyser infrastructure #[derive(Debug, Default)] pub struct AnalyserOptions { - /// A data structured derived from the [`pglt.json`] file + /// A data structured derived from the [`pglt.jsonc`] file pub rules: AnalyserRules, } diff --git a/crates/pglt_analyser/CONTRIBUTING.md b/crates/pglt_analyser/CONTRIBUTING.md index 06cf6052b..8bf1f7854 100644 --- a/crates/pglt_analyser/CONTRIBUTING.md +++ b/crates/pglt_analyser/CONTRIBUTING.md @@ -79,7 +79,7 @@ Let's assume that the rule we implement support the following options: - `threshold`: an integer between 0 and 255; - `behaviorExceptions`: an array of strings. -We would like to set the options in the `pglt.json` configuration file: +We would like to set the options in the `pglt.jsonc` configuration file: ```json { @@ -141,9 +141,9 @@ We currently require implementing _serde_'s traits `Deserialize`/`Serialize`. Also, we use other `serde` macros to adjust the JSON configuration: -- `rename_all = "camelCase"`: it renames all fields in camel-case, so they are in line with the naming style of the `pglt.json`. +- `rename_all = "camelCase"`: it renames all fields in camel-case, so they are in line with the naming style of the `pglt.jsonc`. - `deny_unknown_fields`: it raises an error if the configuration contains extraneous fields. -- `default`: it uses the `Default` value when the field is missing from `pglt.json`. This macro makes the field optional. +- `default`: it uses the `Default` value when the field is missing from `pglt.jsonc`. This macro makes the field optional. You can simply use a derive macros: diff --git a/crates/pglt_cli/src/cli_options.rs b/crates/pglt_cli/src/cli_options.rs index f43bf4747..0527e96f4 100644 --- a/crates/pglt_cli/src/cli_options.rs +++ b/crates/pglt_cli/src/cli_options.rs @@ -26,7 +26,7 @@ pub struct CliOptions { #[bpaf(long("verbose"), switch, fallback(false))] pub verbose: bool, - /// Set the file path to the configuration file, or the directory path to find `pglt.json`. + /// Set the file path to the configuration file, or the directory path to find `pglt.jsonc`. /// If used, it disables the default configuration file resolution. #[bpaf(long("config-path"), argument("PATH"), optional)] pub config_path: Option, diff --git a/crates/pglt_cli/src/commands/init.rs b/crates/pglt_cli/src/commands/init.rs index c94d62b31..b94f6179e 100644 --- a/crates/pglt_cli/src/commands/init.rs +++ b/crates/pglt_cli/src/commands/init.rs @@ -6,8 +6,9 @@ use pglt_workspace::configuration::create_config; pub(crate) fn init(mut session: CliSession) -> Result<(), CliDiagnostic> { let fs = &mut session.app.fs; - create_config(fs, PartialConfiguration::init())?; - let file_created = ConfigName::pglt_json(); + let config = &mut PartialConfiguration::init(); + create_config(fs, config)?; + let file_created = ConfigName::pglt_jsonc(); session.app.console.log(markup! { " Welcome to the Postgres Language Tools! Let's get you started... diff --git a/crates/pglt_cli/src/commands/mod.rs b/crates/pglt_cli/src/commands/mod.rs index c6edafaae..16b7b9768 100644 --- a/crates/pglt_cli/src/commands/mod.rs +++ b/crates/pglt_cli/src/commands/mod.rs @@ -58,7 +58,7 @@ pub enum PgltCommand { 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 `pglt.json` + /// flag and the `defaultBranch` is not set in your `pglt.jsonc` #[bpaf(long("since"), argument("REF"))] since: Option, @@ -91,7 +91,7 @@ pub enum PgltCommand { )] log_path: PathBuf, /// Allows to set a custom file path to the configuration file, - /// or a custom directory path to find `pglt.json` + /// or a custom directory path to find `pglt.jsonc` #[bpaf(env("PGLT_LOG_PREFIX_NAME"), long("config-path"), argument("PATH"))] config_path: Option, }, @@ -127,7 +127,7 @@ pub enum PgltCommand { )] log_path: PathBuf, /// Allows to set a custom file path to the configuration file, - /// or a custom directory path to find `pglt.json` + /// or a custom directory path to find `pglt.jsonc` #[bpaf(env("PGLT_CONFIG_PATH"), long("config-path"), argument("PATH"))] config_path: Option, /// Bogus argument to make the command work with vscode-languageclient @@ -164,7 +164,7 @@ pub enum PgltCommand { #[bpaf(long("stop-on-disconnect"), hide_usage)] stop_on_disconnect: bool, /// Allows to set a custom file path to the configuration file, - /// or a custom directory path to find `pglt.json` + /// or a custom directory path to find `pglt.jsonc` #[bpaf(env("PGLT_CONFIG_PATH"), long("config-path"), argument("PATH"))] config_path: Option, }, diff --git a/crates/pglt_cli/src/diagnostics.rs b/crates/pglt_cli/src/diagnostics.rs index 506cf9d84..374dac38c 100644 --- a/crates/pglt_cli/src/diagnostics.rs +++ b/crates/pglt_cli/src/diagnostics.rs @@ -48,7 +48,7 @@ pub enum CliDiagnostic { IoError(IoDiagnostic), /// The daemon is not running ServerNotRunning(ServerNotRunning), - /// The end configuration (`pglt.json` + other options) is incompatible with the command + /// The end configuration (`pglt.jsonc` + other options) is incompatible with the command IncompatibleEndConfiguration(IncompatibleEndConfiguration), /// No files processed during the file system traversal NoFilesWereProcessed(NoFilesWereProcessed), @@ -410,7 +410,7 @@ impl CliDiagnostic { Self::ServerNotRunning(ServerNotRunning) } - /// Emitted when the end configuration (`pglt.json` file + CLI arguments + LSP configuration) + /// Emitted when the end configuration (`pglt.jsonc` file + CLI arguments + LSP configuration) /// results in a combination of options that doesn't allow to run the command correctly. /// /// A reason needs to be provided diff --git a/crates/pglt_configuration/src/lib.rs b/crates/pglt_configuration/src/lib.rs index 5fe7cc3ed..75b4578c0 100644 --- a/crates/pglt_configuration/src/lib.rs +++ b/crates/pglt_configuration/src/lib.rs @@ -1,4 +1,4 @@ -//! This module contains the configuration of `pglt.json` +//! This module contains the configuration of `pglt.jsonc` //! //! The configuration is divided by "tool", and then it's possible to further customise it //! by language. The language might further options divided by tool. diff --git a/crates/pglt_configuration/src/vcs.rs b/crates/pglt_configuration/src/vcs.rs index 9e659eea0..61f9aa209 100644 --- a/crates/pglt_configuration/src/vcs.rs +++ b/crates/pglt_configuration/src/vcs.rs @@ -28,7 +28,7 @@ pub struct VcsConfiguration { pub use_ignore_file: bool, /// The folder where we should check for VCS files. By default, we will use the same - /// folder where `pglt.json` was found. + /// folder where `pglt.jsonc` was found. /// /// If we can't find the configuration, it will attempt to use the current working directory. /// If no current working directory can't be found, we won't use the VCS integration, and a diagnostic diff --git a/crates/pglt_fs/src/fs.rs b/crates/pglt_fs/src/fs.rs index aed73bb6f..06844accd 100644 --- a/crates/pglt_fs/src/fs.rs +++ b/crates/pglt_fs/src/fs.rs @@ -18,14 +18,14 @@ mod os; pub struct ConfigName; impl ConfigName { - const PGLT_JSON: [&'static str; 1] = ["pglt.json"]; + const PGLT_JSONC: [&'static str; 1] = ["pglt.jsonc"]; - pub const fn pglt_json() -> &'static str { - Self::PGLT_JSON[0] + pub const fn pglt_jsonc() -> &'static str { + Self::PGLT_JSONC[0] } pub const fn file_names() -> [&'static str; 1] { - Self::PGLT_JSON + Self::PGLT_JSONC } } diff --git a/crates/pglt_fs/src/path.rs b/crates/pglt_fs/src/path.rs index 9562d3acb..5d1cb20c6 100644 --- a/crates/pglt_fs/src/path.rs +++ b/crates/pglt_fs/src/path.rs @@ -21,7 +21,7 @@ use crate::ConfigName; #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] // NOTE: The order of the variants is important, the one on the top has the highest priority pub enum FileKind { - /// A configuration file has the highest priority. It's usually `pglt.json` + /// A configuration file has the highest priority. It's usually `pglt.jsonc` /// /// Other third-party configuration files might be added in the future Config, @@ -173,10 +173,10 @@ impl PgLTPath { } /// The priority of the file. - /// - `pglt.json` has the highest priority + /// - `pglt.jsonc` has the highest priority /// - Other files are considered as files to handle fn priority(file_name: &OsStr) -> FileKinds { - if file_name == ConfigName::pglt_json() { + if file_name == ConfigName::pglt_jsonc() { FileKind::Config.into() } else { FileKind::Handleable.into() diff --git a/crates/pglt_lsp/src/server.rs b/crates/pglt_lsp/src/server.rs index 966a45e43..5fe0e1721 100644 --- a/crates/pglt_lsp/src/server.rs +++ b/crates/pglt_lsp/src/server.rs @@ -76,7 +76,7 @@ impl LSPServer { glob_pattern: GlobPattern::String(format!( "{}/{}", base_path.display(), - ConfigName::pglt_json() + ConfigName::pglt_jsonc() )), kind: Some(WatchKind::all()), },], @@ -151,7 +151,7 @@ impl LanguageServer for LSPServer { info!( "Attempting to load the configuration from '{}' file", - ConfigName::pglt_json() + ConfigName::pglt_jsonc() ); futures::join!(self.session.load_workspace_settings()); diff --git a/crates/pglt_lsp/src/session.rs b/crates/pglt_lsp/src/session.rs index 26f13cbba..48457bcfb 100644 --- a/crates/pglt_lsp/src/session.rs +++ b/crates/pglt_lsp/src/session.rs @@ -400,7 +400,7 @@ impl Session { .map(|params| ¶ms.client_capabilities) } - /// This function attempts to read the `pglt.json` configuration file from + /// This function attempts to read the `pglt.jsonc` configuration file from /// the root URI and update the workspace settings accordingly #[tracing::instrument(level = "trace", skip(self))] pub(crate) async fn load_workspace_settings(&self) { diff --git a/crates/pglt_lsp/tests/server.rs b/crates/pglt_lsp/tests/server.rs index a87fe5402..60bdb70cb 100644 --- a/crates/pglt_lsp/tests/server.rs +++ b/crates/pglt_lsp/tests/server.rs @@ -372,7 +372,7 @@ async fn test_database_connection() -> Result<()> { ..Default::default() }); fs.insert( - url!("pglt.json").to_file_path().unwrap(), + url!("pglt.jsonc").to_file_path().unwrap(), serde_json::to_string_pretty(&conf).unwrap(), ); @@ -484,7 +484,7 @@ async fn test_completions() -> Result<()> { ..Default::default() }); fs.insert( - url!("pglt.json").to_file_path().unwrap(), + url!("pglt.jsonc").to_file_path().unwrap(), serde_json::to_string_pretty(&conf).unwrap(), ); diff --git a/crates/pglt_workspace/src/configuration.rs b/crates/pglt_workspace/src/configuration.rs index 671009b18..d99d265bd 100644 --- a/crates/pglt_workspace/src/configuration.rs +++ b/crates/pglt_workspace/src/configuration.rs @@ -120,7 +120,7 @@ fn load_config( ConfigurationPathHint::None => file_system.working_directory().unwrap_or_default(), }; - // We first search for `pglt.json` + // We first search for `pglt.jsonc` if let Some(auto_search_result) = file_system.auto_search( &configuration_directory, ConfigName::file_names().as_slice(), @@ -153,7 +153,7 @@ pub fn create_config( fs: &mut DynRef, configuration: &mut PartialConfiguration, ) -> Result<(), WorkspaceError> { - let path = PathBuf::from(ConfigName::pglt_json()); + let path = PathBuf::from(ConfigName::pglt_jsonc()); if fs.path_exists(&path) { return Err(ConfigurationDiagnostic::new_already_exists().into()); diff --git a/crates/pglt_workspace/src/workspace/server.rs b/crates/pglt_workspace/src/workspace/server.rs index 469e7818f..bb90a2369 100644 --- a/crates/pglt_workspace/src/workspace/server.rs +++ b/crates/pglt_workspace/src/workspace/server.rs @@ -108,7 +108,7 @@ impl WorkspaceServer { fn is_ignored(&self, path: &Path) -> bool { let file_name = path.file_name().and_then(|s| s.to_str()); // Never ignore PgLT's config file regardless `include`/`ignore` - (file_name != Some(ConfigName::pglt_json())) && + (file_name != Some(ConfigName::pglt_jsonc())) && // Apply top-level `include`/`ignore (self.is_ignored_by_top_level_config(path) || self.is_ignored_by_migration_config(path)) } diff --git a/docs/checking_migrations.md b/docs/checking_migrations.md index 6c48832e9..6bd2c2483 100644 --- a/docs/checking_migrations.md +++ b/docs/checking_migrations.md @@ -8,7 +8,7 @@ To run it, simply point at your migrations directory. pglt check supabase/migrations ``` -When you are setting it up in an existing project, you might want to ignore all migrations that are already applied. To do so, add `migrationsDir` and `after` to your `pglt.json` file +When you are setting it up in an existing project, you might want to ignore all migrations that are already applied. To do so, add `migrationsDir` and `after` to your `pglt.jsonc` file ```json @@ -28,5 +28,5 @@ pglt check supabase/migrations --migrations-dir="supabase/migrations" --after=17 This will only check migrations after the specified timestamp. -For pre-commit hooks and when working locally, use `--staged` to only lint files that have been staged. In CI environments, you most likely want to use `--changed` to only lint files that have been changed compared to your `vcs.default_branch` configuration. If `default_branch` is not set in your `pglt.json`, use `--since=REF` to specify the base branch to compare against. +For pre-commit hooks and when working locally, use `--staged` to only lint files that have been staged. In CI environments, you most likely want to use `--changed` to only lint files that have been changed compared to your `vcs.default_branch` configuration. If `default_branch` is not set in your `pglt.jsonc`, use `--since=REF` to specify the base branch to compare against. diff --git a/docs/cli_reference.md b/docs/cli_reference.md index 755638615..919b0d3e3 100644 --- a/docs/cli_reference.md +++ b/docs/cli_reference.md @@ -62,7 +62,7 @@ Shows the version information and quit. - **` --verbose`** — Print additional diagnostics, and some diagnostics show more information. Also, print out what files were processed and which ones were modified. - **` --config-path`**=_`PATH`_ — - Set the file path to the configuration file, or the directory path to find `pglt.json`. If used, it disables the default configuration file resolution. + Set the file path to the configuration file, or the directory path to find `pglt.jsonc`. If used, it disables the default configuration file resolution. - **` --max-diagnostics`**=_`>`_ — Cap the amount of diagnostics displayed. When `none` is provided, the limit is lifted. @@ -111,7 +111,7 @@ Runs everything to the requested files. - **` --vcs-use-ignore-file`**=_``_ — Whether we should use the VCS ignore file. When [true], we will ignore the files specified in the ignore file. - **` --vcs-root`**=_`PATH`_ — - The folder where we should check for VCS files. By default, we will use the same folder where `pglt.json` was found. + The folder where we should check for VCS files. By default, we will use the same folder where `pglt.jsonc` was found. If we can't find the configuration, it will attempt to use the current working directory. If no current working directory can't be found, we won't use the VCS integration, and a diagnostic will be emitted - **` --vcs-default-branch`**=_`BRANCH`_ — @@ -149,7 +149,7 @@ Runs everything to the requested files. - **` --verbose`** — Print additional diagnostics, and some diagnostics show more information. Also, print out what files were processed and which ones were modified. - **` --config-path`**=_`PATH`_ — - Set the file path to the configuration file, or the directory path to find `pglt.json`. If used, it disables the default configuration file resolution. + Set the file path to the configuration file, or the directory path to find `pglt.jsonc`. If used, it disables the default configuration file resolution. - **` --max-diagnostics`**=_`>`_ — Cap the amount of diagnostics displayed. When `none` is provided, the limit is lifted. @@ -197,7 +197,7 @@ Runs everything to the requested files. - **` --changed`** — 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. - **` --since`**=_`REF`_ — - 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 `pglt.json` + 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 `pglt.jsonc` - **`-h`**, **`--help`** — Prints help information @@ -220,7 +220,7 @@ Starts the daemon server process. Uses environment variable **`PGLT_LOG_PATH`** - **` --config-path`**=_`PATH`_ — - Allows to set a custom file path to the configuration file, or a custom directory path to find `pglt.json` + Allows to set a custom file path to the configuration file, or a custom directory path to find `pglt.jsonc` Uses environment variable **`PGLT_LOG_PREFIX_NAME`** - **`-h`**, **`--help`** — @@ -267,7 +267,7 @@ Acts as a server for the Language Server Protocol over stdin/stdout. Uses environment variable **`PGLT_LOG_PATH`** - **` --config-path`**=_`PATH`_ — - Allows to set a custom file path to the configuration file, or a custom directory path to find `pglt.json` + Allows to set a custom file path to the configuration file, or a custom directory path to find `pglt.jsonc` Uses environment variable **`PGLT_CONFIG_PATH`** - **`-h`**, **`--help`** — diff --git a/docs/index.md b/docs/index.md index 50e95176b..cfd3efc9f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -29,13 +29,13 @@ Our current focus is on refining and enhancing these core features while buildin ## Configuration -We recommend that you create a `pglt.json` configuration file for each project. This eliminates the need to repeat the CLI options each time you run a command, and ensures that we use the same configuration in your editor. Some options are also only available from a configuration file. If you are happy with the defaults, you don’t need to create a configuration file. To create the `pglt.json` file, run the `init` command in the root folder of your project: +We recommend that you create a `pglt.jsonc` configuration file for each project. This eliminates the need to repeat the CLI options each time you run a command, and ensures that we use the same configuration in your editor. Some options are also only available from a configuration file. If you are happy with the defaults, you don’t need to create a configuration file. To create the `pglt.jsonc` file, run the `init` command in the root folder of your project: ```sh pglt init ``` -After running the `init` command, you’ll have a `pglt.json` file in your directory: +After running the `init` command, you’ll have a `pglt.jsonc` file in your directory: [//]: # (BEGIN DEFAULT_CONFIGURATION) diff --git a/docs/schemas/0.0.0/schema.json b/docs/schemas/0.0.0/schema.json index 13c43d08c..d1c80857e 100644 --- a/docs/schemas/0.0.0/schema.json +++ b/docs/schemas/0.0.0/schema.json @@ -401,7 +401,7 @@ ] }, "root": { - "description": "The folder where we should check for VCS files. By default, we will use the same folder where `pglt.json` was found.\n\nIf we can't find the configuration, it will attempt to use the current working directory. If no current working directory can't be found, we won't use the VCS integration, and a diagnostic will be emitted", + "description": "The folder where we should check for VCS files. By default, we will use the same folder where `pglt.jsonc` was found.\n\nIf we can't find the configuration, it will attempt to use the current working directory. If no current working directory can't be found, we won't use the VCS integration, and a diagnostic will be emitted", "type": [ "string", "null" diff --git a/docs/schemas/latest/schema.json b/docs/schemas/latest/schema.json index 13c43d08c..d1c80857e 100644 --- a/docs/schemas/latest/schema.json +++ b/docs/schemas/latest/schema.json @@ -401,7 +401,7 @@ ] }, "root": { - "description": "The folder where we should check for VCS files. By default, we will use the same folder where `pglt.json` was found.\n\nIf we can't find the configuration, it will attempt to use the current working directory. If no current working directory can't be found, we won't use the VCS integration, and a diagnostic will be emitted", + "description": "The folder where we should check for VCS files. By default, we will use the same folder where `pglt.jsonc` was found.\n\nIf we can't find the configuration, it will attempt to use the current working directory. If no current working directory can't be found, we won't use the VCS integration, and a diagnostic will be emitted", "type": [ "string", "null" diff --git a/pglt.jsonc b/pglt.jsonc new file mode 100644 index 000000000..ee9eccc16 --- /dev/null +++ b/pglt.jsonc @@ -0,0 +1,26 @@ +{ + "$schema": "./docs/schemas/latest/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignore": [] + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + // YOU CAN COMMENT ME OUT :) + "db": { + "host": "127.0.0.1", + "port": 5432, + "username": "postgres", + "password": "postgres", + "database": "postgres", + "connTimeoutSecs": 10 + } +} diff --git a/pglt.toml b/pglt.toml deleted file mode 100644 index 5d0f672a8..000000000 --- a/pglt.toml +++ /dev/null @@ -1,21 +0,0 @@ -[vcs] -enabled = false -client_kind = "git" -use_ignore_file = false - -[files] -ignore = [] - -[linter] -enabled = true - -[linter.rules] -recommended = true - -[db] -host = "127.0.0.1" -port = 5432 -username = "postgres" -password = "postgres" -database = "postgres" -conn_timeout_secs = 10 From 4288e295c3111c82818b8d630be1c72715cad51e Mon Sep 17 00:00:00 2001 From: psteinroe Date: Sat, 15 Mar 2025 18:30:01 +0100 Subject: [PATCH 09/12] fix: run docs codegen --- docs/index.md | 1 + docs/schemas/0.0.0/schema.json | 7 +++++++ docs/schemas/latest/schema.json | 7 +++++++ 3 files changed, 15 insertions(+) diff --git a/docs/index.md b/docs/index.md index cfd3efc9f..b2d8b2fa8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -41,6 +41,7 @@ After running the `init` command, you’ll have a `pglt.jsonc` file in your dire ```json { + "": "https://supabase-community.github.io/postgres_lsp/schemas/0.0.0/schema.json", "vcs": { "enabled": false, "clientKind": "git", diff --git a/docs/schemas/0.0.0/schema.json b/docs/schemas/0.0.0/schema.json index d1c80857e..5595cb6b9 100644 --- a/docs/schemas/0.0.0/schema.json +++ b/docs/schemas/0.0.0/schema.json @@ -4,6 +4,13 @@ "description": "The configuration that is contained inside the configuration file.", "type": "object", "properties": { + "$schema": { + "description": "A field for the [JSON schema](https://json-schema.org/) specification", + "type": [ + "string", + "null" + ] + }, "db": { "description": "The configuration of the database connection", "anyOf": [ diff --git a/docs/schemas/latest/schema.json b/docs/schemas/latest/schema.json index d1c80857e..5595cb6b9 100644 --- a/docs/schemas/latest/schema.json +++ b/docs/schemas/latest/schema.json @@ -4,6 +4,13 @@ "description": "The configuration that is contained inside the configuration file.", "type": "object", "properties": { + "$schema": { + "description": "A field for the [JSON schema](https://json-schema.org/) specification", + "type": [ + "string", + "null" + ] + }, "db": { "description": "The configuration of the database connection", "anyOf": [ From 5610084f8c706e935a278308f968abceb32c4242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Steinr=C3=B6tter?= Date: Sun, 16 Mar 2025 16:08:11 +0100 Subject: [PATCH 10/12] Update configuration.rs Co-authored-by: Julian Domke <68325451+juleswritescode@users.noreply.github.com> --- crates/pglt_workspace/src/configuration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pglt_workspace/src/configuration.rs b/crates/pglt_workspace/src/configuration.rs index d99d265bd..ba0cebca3 100644 --- a/crates/pglt_workspace/src/configuration.rs +++ b/crates/pglt_workspace/src/configuration.rs @@ -169,7 +169,7 @@ pub fn create_config( } })?; - // we now check if biome is installed inside `node_modules` and if so, we use the schema from there + // we now check if pglt is installed inside `node_modules` and if so, we use the schema from there if VERSION == "0.0.0" { let schema_path = Path::new("./node_modules/@pglt/pglt/schema.json"); let options = OpenOptions::default().read(true); From 2ec96c6959b6b94a81f784caf7a612b18c1d9d44 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Sun, 16 Mar 2025 17:16:52 +0100 Subject: [PATCH 11/12] add unit tests --- crates/pglt_workspace/src/configuration.rs | 107 +++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/crates/pglt_workspace/src/configuration.rs b/crates/pglt_workspace/src/configuration.rs index ba0cebca3..45a3feca0 100644 --- a/crates/pglt_workspace/src/configuration.rs +++ b/crates/pglt_workspace/src/configuration.rs @@ -265,3 +265,110 @@ pub fn strip_jsonc_comments(jsonc_input: &str) -> String { json_output } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_strip_jsonc_comments_line_comments() { + let input = r#"{ + "name": "test", // This is a line comment + "value": 42 // Another comment +}"#; + + let expected = r#"{ + "name": "test", + "value": 42 +} +"#; + + assert_eq!(strip_jsonc_comments(input), expected); + } + + #[test] + fn test_strip_jsonc_comments_block_comments() { + let input = r#"{ + /* This is a block comment */ + "name": "test", + "value": /* inline comment */ 42 +}"#; + + let expected = r#"{ + + "name": "test", + "value": 42 +} +"#; + + assert_eq!(strip_jsonc_comments(input), expected); + } + + #[test] + fn test_strip_jsonc_comments_nested_block_comments() { + let input = r#"{ + /* Outer comment /* Nested comment */ still outer */ + "name": "test" +}"#; + + let expected = r#"{ + + "name": "test" +} +"#; + + assert_eq!(strip_jsonc_comments(input), expected); + } + + #[test] + fn test_strip_jsonc_comments_in_strings() { + let input = r#"{ + "comment_like": "This is not a // comment", + "another": "This is not a /* block comment */ either" +}"#; + + let expected = r#"{ + "comment_like": "This is not a // comment", + "another": "This is not a /* block comment */ either" +} +"#; + + assert_eq!(strip_jsonc_comments(input), expected); + } + + #[test] + fn test_strip_jsonc_comments_escaped_quotes() { + let input = r#"{ + "escaped\": \"quote": "value", // Comment after escaped quotes + "normal": "value" // Normal comment +}"#; + + let expected = r#"{ + "escaped\": \"quote": "value", + "normal": "value" +} +"#; + + assert_eq!(strip_jsonc_comments(input), expected); + } + + #[test] + fn test_strip_jsonc_comments_multiline_block() { + let input = r#"{ + /* This is a + multiline block + comment */ + "name": "test" +}"#; + + let expected = r#"{ + + + + "name": "test" +} +"#; + + assert_eq!(strip_jsonc_comments(input), expected); + } +} From d11bc7a4a80d68d607ae287ca9aae664577c8ac5 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Sun, 16 Mar 2025 17:27:31 +0100 Subject: [PATCH 12/12] fix: replace_secion --- docs/codegen/src/utils.rs | 9 +++++++-- docs/index.md | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/codegen/src/utils.rs b/docs/codegen/src/utils.rs index 5646468eb..ac8cef08b 100644 --- a/docs/codegen/src/utils.rs +++ b/docs/codegen/src/utils.rs @@ -12,8 +12,13 @@ pub(crate) fn replace_section( section_identifier, section_identifier ); let re = Regex::new(&pattern).unwrap(); - re.replace_all(content, format!("${{1}}{}${{2}}", replacement)) - .to_string() + + // Use a replacement function instead of a replacement string to avoid + // issues with special characters like $ in the replacement text + re.replace_all(content, |caps: ®ex::Captures| { + format!("{}{}{}", &caps[1], replacement, &caps[2]) + }) + .to_string() } #[derive(Default)] diff --git a/docs/index.md b/docs/index.md index b2d8b2fa8..ffbafd3ce 100644 --- a/docs/index.md +++ b/docs/index.md @@ -41,7 +41,7 @@ After running the `init` command, you’ll have a `pglt.jsonc` file in your dire ```json { - "": "https://supabase-community.github.io/postgres_lsp/schemas/0.0.0/schema.json", + "$schema": "https://supabase-community.github.io/postgres_lsp/schemas/0.0.0/schema.json", "vcs": { "enabled": false, "clientKind": "git",