From bfd6805a8c6880cd0161f6e57189b9fb8c6bcc7f Mon Sep 17 00:00:00 2001 From: jkomyno Date: Tue, 9 Apr 2024 14:55:36 +0200 Subject: [PATCH 1/4] feat: complete format support --- Cargo.lock | 2 ++ prisma-fmt/src/lib.rs | 26 +++++++++++++++++++++----- prisma-fmt/src/schema_file_input.rs | 2 +- prisma-schema-wasm/src/lib.rs | 2 +- psl/schema-ast/Cargo.toml | 2 ++ psl/schema-ast/src/source_file.rs | 12 ++++++++++++ 6 files changed, 39 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d838995c6f95..0e280de84db2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4502,6 +4502,8 @@ dependencies = [ "diagnostics", "pest", "pest_derive", + "serde", + "serde_json", ] [[package]] diff --git a/prisma-fmt/src/lib.rs b/prisma-fmt/src/lib.rs index c1449b3b2053..a59184d56117 100644 --- a/prisma-fmt/src/lib.rs +++ b/prisma-fmt/src/lib.rs @@ -13,6 +13,7 @@ mod validate; use log::*; use lsp_types::{Position, Range}; use psl::parser_database::ast; +use schema_file_input::SchemaFileInput; /// The API is modelled on an LSP [completion /// request](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-16.md#textDocument_completion). @@ -52,16 +53,30 @@ pub fn code_actions(schema: String, params: &str) -> String { /// The function returns the formatted schema, as a string. /// /// Of the DocumentFormattingParams, we only take into account tabSize, at the moment. -pub fn format(schema: &str, params: &str) -> String { +pub fn format(datamodel: String, params: &str) -> String { + let schema: SchemaFileInput = match serde_json::from_str(params) { + Ok(params) => params, + Err(_) => { + return datamodel; + } + }; + let params: lsp_types::DocumentFormattingParams = match serde_json::from_str(params) { Ok(params) => params, - Err(err) => { - warn!("Error parsing DocumentFormattingParams params: {}", err); - return schema.to_owned(); + Err(_) => { + return datamodel; } }; - psl::reformat(schema, params.options.tab_size as usize).unwrap_or_else(|| schema.to_owned()) + let indent_width = params.options.tab_size as usize; + + match schema { + SchemaFileInput::Single(single) => psl::reformat(&single, indent_width).unwrap_or_else(|| datamodel), + SchemaFileInput::Multiple(multiple) => { + let result = psl::reformat_multiple(multiple, indent_width); + serde_json::to_string(&result).unwrap() + } + } } pub fn lint(schema: String) -> String { @@ -268,6 +283,7 @@ pub fn offset_to_position(offset: usize, document: &str) -> Position { #[cfg(test)] mod tests { + use super::format; use lsp_types::Position; // On Windows, a newline is actually two characters. diff --git a/prisma-fmt/src/schema_file_input.rs b/prisma-fmt/src/schema_file_input.rs index a7204510ed8b..493ac6eea17d 100644 --- a/prisma-fmt/src/schema_file_input.rs +++ b/prisma-fmt/src/schema_file_input.rs @@ -10,7 +10,7 @@ use serde::Deserialize; #[serde(untagged)] pub(crate) enum SchemaFileInput { Single(String), - Multiple(Vec<(String, String)>), + Multiple(Vec<(String, SourceFile)>), } impl From for Vec<(String, SourceFile)> { diff --git a/prisma-schema-wasm/src/lib.rs b/prisma-schema-wasm/src/lib.rs index 43288dd32f54..073f13377243 100644 --- a/prisma-schema-wasm/src/lib.rs +++ b/prisma-schema-wasm/src/lib.rs @@ -29,7 +29,7 @@ fn register_panic_hook() { #[wasm_bindgen] pub fn format(schema: String, params: String) -> String { register_panic_hook(); - prisma_fmt::format(&schema, ¶ms) + prisma_fmt::format(schema, ¶ms) } /// Docs: https://prisma.github.io/prisma-engines/doc/prisma_fmt/fn.get_config.html diff --git a/psl/schema-ast/Cargo.toml b/psl/schema-ast/Cargo.toml index be98278dfbbe..0eab4dd05a59 100644 --- a/psl/schema-ast/Cargo.toml +++ b/psl/schema-ast/Cargo.toml @@ -8,3 +8,5 @@ diagnostics = { path = "../diagnostics" } pest = "2.1.3" pest_derive = "2.1.0" +serde.workspace = true +serde_json.workspace = true diff --git a/psl/schema-ast/src/source_file.rs b/psl/schema-ast/src/source_file.rs index 63329ad93c39..b53e2eaa5c16 100644 --- a/psl/schema-ast/src/source_file.rs +++ b/psl/schema-ast/src/source_file.rs @@ -1,11 +1,23 @@ use std::sync::Arc; +use serde::{Deserialize, Deserializer}; + /// A Prisma schema document. #[derive(Debug, Clone)] pub struct SourceFile { contents: Contents, } +impl<'de> Deserialize<'de> for SourceFile { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: String = serde::de::Deserialize::deserialize(deserializer)?; + Ok(s.into()) + } +} + impl Default for SourceFile { fn default() -> Self { Self { From 3368b83117dab1fae2aa1bd7a060f7ee9fa55b2e Mon Sep 17 00:00:00 2001 From: jkomyno Date: Wed, 10 Apr 2024 09:54:56 +0200 Subject: [PATCH 2/4] feat: complete lint support --- prisma-fmt/src/lib.rs | 9 +++++-- prisma-fmt/src/lint.rs | 59 +++++++++++++++++++++++++++++++++++++----- prisma-fmt/src/main.rs | 6 ++--- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/prisma-fmt/src/lib.rs b/prisma-fmt/src/lib.rs index a59184d56117..ca31c32eb979 100644 --- a/prisma-fmt/src/lib.rs +++ b/prisma-fmt/src/lib.rs @@ -80,7 +80,13 @@ pub fn format(datamodel: String, params: &str) -> String { } pub fn lint(schema: String) -> String { - lint::run(&schema) + let schema: SchemaFileInput = match serde_json::from_str(&schema) { + Ok(params) => params, + Err(serde_err) => { + panic!("Failed to deserialize SchemaFileInput: {serde_err}"); + } + }; + lint::run(schema) } /// Function that throws a human-friendly error message when the schema is invalid, following the JSON formatting @@ -283,7 +289,6 @@ pub fn offset_to_position(offset: usize, document: &str) -> Position { #[cfg(test)] mod tests { - use super::format; use lsp_types::Position; // On Windows, a newline is actually two characters. diff --git a/prisma-fmt/src/lint.rs b/prisma-fmt/src/lint.rs index a52a8105aff8..6ccef59750f5 100644 --- a/prisma-fmt/src/lint.rs +++ b/prisma-fmt/src/lint.rs @@ -1,5 +1,7 @@ use psl::diagnostics::{DatamodelError, DatamodelWarning}; +use crate::schema_file_input::SchemaFileInput; + #[derive(serde::Serialize)] pub struct MiniError { start: usize, @@ -8,8 +10,11 @@ pub struct MiniError { is_warning: bool, } -pub(crate) fn run(schema: &str) -> String { - let schema = psl::validate(schema.into()); +pub(crate) fn run(schema: SchemaFileInput) -> String { + let schema = match schema { + SchemaFileInput::Single(file) => psl::validate(file.into()), + SchemaFileInput::Multiple(files) => psl::validate_multi_file(files), + }; let diagnostics = &schema.diagnostics; let mut mini_errors: Vec = diagnostics @@ -45,19 +50,20 @@ fn print_diagnostics(diagnostics: Vec) -> String { #[cfg(test)] mod tests { + use super::SchemaFileInput; use expect_test::expect; use indoc::indoc; - fn lint(s: &str) -> String { - let result = super::run(s); + fn lint(schema: SchemaFileInput) -> String { + let result = super::run(schema); let value: serde_json::Value = serde_json::from_str(&result).unwrap(); serde_json::to_string_pretty(&value).unwrap() } #[test] - fn deprecated_preview_features_should_give_a_warning() { - let dml = indoc! {r#" + fn single_deprecated_preview_features_should_give_a_warning() { + let schema = indoc! {r#" datasource db { provider = "postgresql" url = env("DATABASE_URL") @@ -72,6 +78,45 @@ mod tests { id String @id } "#}; + let datamodel = SchemaFileInput::Single(schema.to_string()); + + let expected = expect![[r#" + [ + { + "start": 149, + "end": 163, + "text": "Preview feature \"createMany\" is deprecated. The functionality can be used without specifying it as a preview feature.", + "is_warning": true + } + ]"#]]; + + expected.assert_eq(&lint(datamodel)); + } + + #[test] + fn multi_deprecated_preview_features_should_give_a_warning() { + let schema1 = indoc! {r#" + datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + } + + generator client { + provider = "prisma-client-js" + previewFeatures = ["createMany"] + } + "#}; + + let schema2 = indoc! {r#" + model A { + id String @id + } + "#}; + + let datamodel = SchemaFileInput::Multiple(vec![ + ("schema1.prisma".to_string(), schema1.into()), + ("schema2.prisma".to_string(), schema2.into()), + ]); let expected = expect![[r#" [ @@ -83,6 +128,6 @@ mod tests { } ]"#]]; - expected.assert_eq(&lint(dml)); + expected.assert_eq(&lint(datamodel)); } } diff --git a/prisma-fmt/src/main.rs b/prisma-fmt/src/main.rs index 5c7a02d917b8..fa9c39ad0641 100644 --- a/prisma-fmt/src/main.rs +++ b/prisma-fmt/src/main.rs @@ -1,6 +1,6 @@ mod actions; mod format; -mod lint; +// mod lint; mod native; mod preview; @@ -30,7 +30,7 @@ pub struct FormatOpts { /// Prisma Datamodel v2 formatter pub enum FmtOpts { /// Specifies linter mode - Lint, + // Lint, /// Specifies format mode Format(FormatOpts), /// Specifies Native Types mode @@ -46,7 +46,7 @@ pub enum FmtOpts { fn main() { match FmtOpts::from_args() { FmtOpts::DebugPanic => panic!("This is the debugPanic artificial panic"), - FmtOpts::Lint => plug(lint::run), + // FmtOpts::Lint => plug(lint::run), FmtOpts::Format(opts) => format::run(opts), FmtOpts::NativeTypes => plug(native::run), FmtOpts::ReferentialActions => plug(actions::run), From d6187a87b15b2a529db43e930dea73490818d47c Mon Sep 17 00:00:00 2001 From: jkomyno Date: Wed, 10 Apr 2024 10:02:52 +0200 Subject: [PATCH 3/4] chore: clippy --- prisma-fmt/src/lib.rs | 2 +- prisma-fmt/src/schema_file_input.rs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/prisma-fmt/src/lib.rs b/prisma-fmt/src/lib.rs index ca31c32eb979..5a5e2fb4cf16 100644 --- a/prisma-fmt/src/lib.rs +++ b/prisma-fmt/src/lib.rs @@ -71,7 +71,7 @@ pub fn format(datamodel: String, params: &str) -> String { let indent_width = params.options.tab_size as usize; match schema { - SchemaFileInput::Single(single) => psl::reformat(&single, indent_width).unwrap_or_else(|| datamodel), + SchemaFileInput::Single(single) => psl::reformat(&single, indent_width).unwrap_or(datamodel), SchemaFileInput::Multiple(multiple) => { let result = psl::reformat_multiple(multiple, indent_width); serde_json::to_string(&result).unwrap() diff --git a/prisma-fmt/src/schema_file_input.rs b/prisma-fmt/src/schema_file_input.rs index 493ac6eea17d..26d3a177c0f6 100644 --- a/prisma-fmt/src/schema_file_input.rs +++ b/prisma-fmt/src/schema_file_input.rs @@ -17,10 +17,7 @@ impl From for Vec<(String, SourceFile)> { fn from(value: SchemaFileInput) -> Self { match value { SchemaFileInput::Single(content) => vec![("schema.prisma".to_owned(), content.into())], - SchemaFileInput::Multiple(file_list) => file_list - .into_iter() - .map(|(filename, content)| (filename, content.into())) - .collect(), + SchemaFileInput::Multiple(file_list) => file_list, } } } From be1faff81ac8a57f8b2be5368d18c61807f5a295 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Wed, 10 Apr 2024 10:06:26 +0200 Subject: [PATCH 4/4] feat: ensure format never panics and always returns original datamodel --- prisma-fmt/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/prisma-fmt/src/lib.rs b/prisma-fmt/src/lib.rs index 5a5e2fb4cf16..8cbca952234d 100644 --- a/prisma-fmt/src/lib.rs +++ b/prisma-fmt/src/lib.rs @@ -46,15 +46,17 @@ pub fn code_actions(schema: String, params: &str) -> String { } /// The two parameters are: -/// - The Prisma schema to reformat, as a string. +/// - The [`SchemaFileInput`] to reformat, as a string. /// - An LSP /// [DocumentFormattingParams](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-16.md#textDocument_formatting) object, as JSON. /// /// The function returns the formatted schema, as a string. +/// If the schema or any of the provided parameters is invalid, the function returns the original schema. +/// This function never panics. /// /// Of the DocumentFormattingParams, we only take into account tabSize, at the moment. pub fn format(datamodel: String, params: &str) -> String { - let schema: SchemaFileInput = match serde_json::from_str(params) { + let schema: SchemaFileInput = match serde_json::from_str(&datamodel) { Ok(params) => params, Err(_) => { return datamodel; @@ -74,7 +76,7 @@ pub fn format(datamodel: String, params: &str) -> String { SchemaFileInput::Single(single) => psl::reformat(&single, indent_width).unwrap_or(datamodel), SchemaFileInput::Multiple(multiple) => { let result = psl::reformat_multiple(multiple, indent_width); - serde_json::to_string(&result).unwrap() + serde_json::to_string(&result).unwrap_or(datamodel) } } }