From 602bc749f2914032b2354c8f27fd3553796c3fcd Mon Sep 17 00:00:00 2001 From: Mads Hougesen Date: Sat, 23 Mar 2024 15:51:14 +0100 Subject: [PATCH] feat(fsharp): add support for fantomas (#128) Closes #110 --- README.md | 1 + schemas/v0.0.2/mdsf.schema.json | 40 ++++++++++++++ src/config.rs | 18 +++--- src/formatters/fantomas.rs | 40 ++++++++++++++ src/formatters/mod.rs | 2 + src/languages/fsharp.rs | 98 +++++++++++++++++++++++++++++++++ src/languages/mod.rs | 5 +- 7 files changed, 196 insertions(+), 8 deletions(-) create mode 100644 src/formatters/fantomas.rs create mode 100644 src/languages/fsharp.rs diff --git a/README.md b/README.md index a0cdcf80..5c4d9325 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ mdsf init | Elixir | `mix_format` | | Elm | `elm-format` | | Erlang | `efmt`, `erlfmt` | +| FSharp | `fantomas` | | Gleam | `gleam_format` | | Go | `gofmt`, `gofumpt`, `goimports` | | GraphQL | `prettier` | diff --git a/schemas/v0.0.2/mdsf.schema.json b/schemas/v0.0.2/mdsf.schema.json index fde06476..26140222 100644 --- a/schemas/v0.0.2/mdsf.schema.json +++ b/schemas/v0.0.2/mdsf.schema.json @@ -124,6 +124,17 @@ } ] }, + "fsharp": { + "default": { + "enabled": true, + "formatter": "fantomas" + }, + "allOf": [ + { + "$ref": "#/definitions/Lang_for_FSharp" + } + ] + }, "gleam": { "default": { "enabled": true, @@ -547,6 +558,10 @@ "type": "string", "enum": ["erlfmt", "efmt"] }, + "FSharp": { + "type": "string", + "enum": ["fantomas"] + }, "Gleam": { "type": "string", "enum": ["gleam_format"] @@ -723,6 +738,18 @@ } } }, + "Lang_for_FSharp": { + "type": "object", + "required": ["enabled", "formatter"], + "properties": { + "enabled": { + "type": "boolean" + }, + "formatter": { + "$ref": "#/definitions/MdsfFormatter_for_FSharp" + } + } + }, "Lang_for_Gleam": { "type": "object", "required": ["enabled", "formatter"], @@ -1282,6 +1309,19 @@ } ] }, + "MdsfFormatter_for_FSharp": { + "anyOf": [ + { + "$ref": "#/definitions/FSharp" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/MdsfFormatter_for_FSharp" + } + } + ] + }, "MdsfFormatter_for_Gleam": { "anyOf": [ { diff --git a/src/config.rs b/src/config.rs index b66dfc79..94a2e9e0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,13 +2,13 @@ use schemars::JsonSchema; use crate::languages::{ blade::Blade, c::C, clojure::Clojure, cpp::Cpp, crystal::Crystal, csharp::CSharp, css::Css, - dart::Dart, elixir::Elixir, elm::Elm, erlang::Erlang, gleam::Gleam, go::Go, graphql::GraphQL, - groovy::Groovy, haskell::Haskell, html::Html, java::Java, javascript::JavaScript, json::Json, - just::Just, kotlin::Kotlin, lua::Lua, markdown::Markdown, nim::Nim, objective_c::ObjectiveC, - ocaml::OCaml, perl::Perl, protobuf::Protobuf, purescript::PureScript, python::Python, - rescript::ReScript, roc::Roc, ruby::Ruby, rust::Rust, scala::Scala, shell::Shell, sql::Sql, - swift::Swift, toml::Toml, typescript::TypeScript, vue::Vue, xml::Xml, yaml::Yaml, zig::Zig, - Lang, + dart::Dart, elixir::Elixir, elm::Elm, erlang::Erlang, fsharp::FSharp, gleam::Gleam, go::Go, + graphql::GraphQL, groovy::Groovy, haskell::Haskell, html::Html, java::Java, + javascript::JavaScript, json::Json, just::Just, kotlin::Kotlin, lua::Lua, markdown::Markdown, + nim::Nim, objective_c::ObjectiveC, ocaml::OCaml, perl::Perl, protobuf::Protobuf, + purescript::PureScript, python::Python, rescript::ReScript, roc::Roc, ruby::Ruby, rust::Rust, + scala::Scala, shell::Shell, sql::Sql, swift::Swift, toml::Toml, typescript::TypeScript, + vue::Vue, xml::Xml, yaml::Yaml, zig::Zig, Lang, }; #[derive(Debug, serde::Serialize, serde::Deserialize, JsonSchema)] @@ -51,6 +51,9 @@ pub struct MdsfConfig { #[serde(default)] pub erlang: Lang, + #[serde(default)] + pub fsharp: Lang, + #[serde(default)] pub gleam: Lang, @@ -170,6 +173,7 @@ impl Default for MdsfConfig { elixir: Lang::::default(), elm: Lang::::default(), erlang: Lang::::default(), + fsharp: Lang::::default(), gleam: Lang::::default(), go: Lang::::default(), graphql: Lang::::default(), diff --git a/src/formatters/fantomas.rs b/src/formatters/fantomas.rs new file mode 100644 index 00000000..e9c4b56e --- /dev/null +++ b/src/formatters/fantomas.rs @@ -0,0 +1,40 @@ +use super::execute_command; + +#[inline] +pub fn format_using_fantomas( + snippet_path: &std::path::Path, +) -> std::io::Result<(bool, Option)> { + let mut cmd = std::process::Command::new("fantomas"); + + cmd.arg(snippet_path); + + execute_command(&mut cmd, snippet_path) +} + +#[cfg(test)] +mod test_fantomas { + use crate::{ + formatters::{fantomas::format_using_fantomas, setup_snippet}, + languages::Language, + }; + + #[test_with::executable(fantomas)] + #[test] + fn it_should_format_ocaml() { + let input = " +let add a b = a + b + "; + let expected_output = "let add a b = a + b +"; + + let snippet = setup_snippet(input, Language::FSharp.to_file_ext()) + .expect("it to create a snippet file"); + + let output = format_using_fantomas(snippet.path()) + .expect("it to be successful") + .1 + .expect("it to be some"); + + assert_eq!(expected_output, output); + } +} diff --git a/src/formatters/mod.rs b/src/formatters/mod.rs index f78d7852..99afce6b 100644 --- a/src/formatters/mod.rs +++ b/src/formatters/mod.rs @@ -19,6 +19,7 @@ pub mod deno_fmt; pub mod efmt; pub mod elm_format; pub mod erlfmt; +pub mod fantomas; pub mod fourmolu; pub mod gleam_format; pub mod gofmt; @@ -140,6 +141,7 @@ pub fn format_snippet(config: &MdsfConfig, language: &Language, code: &str) -> S Language::Elixir => config.elixir.format(snippet_path), Language::Elm => config.elm.format(snippet_path), Language::Erlang => config.erlang.format(snippet_path), + Language::FSharp => config.fsharp.format(snippet_path), Language::Gleam => config.gleam.format(snippet_path), Language::Go => config.go.format(snippet_path), Language::GraphQL => config.graphql.format(snippet_path), diff --git a/src/languages/fsharp.rs b/src/languages/fsharp.rs new file mode 100644 index 00000000..e06f15d8 --- /dev/null +++ b/src/languages/fsharp.rs @@ -0,0 +1,98 @@ +use schemars::JsonSchema; + +use crate::formatters::{fantomas::format_using_fantomas, MdsfFormatter}; + +use super::{Lang, LanguageFormatter}; + +#[derive(Debug, Default, serde::Serialize, serde::Deserialize, JsonSchema)] +#[cfg_attr(test, derive(PartialEq, Eq))] +pub enum FSharp { + #[default] + #[serde(rename = "fantomas")] + Fantomas, +} + +impl Default for Lang { + #[inline] + fn default() -> Self { + Self { + enabled: true, + formatter: MdsfFormatter::::default(), + } + } +} + +impl Default for MdsfFormatter { + #[inline] + fn default() -> Self { + Self::Single(FSharp::Fantomas) + } +} + +impl LanguageFormatter for FSharp { + #[inline] + fn format_snippet( + &self, + snippet_path: &std::path::Path, + ) -> std::io::Result<(bool, Option)> { + match self { + Self::Fantomas => format_using_fantomas(snippet_path), + } + } +} + +#[cfg(test)] +mod test_fsharp { + use crate::{ + formatters::{setup_snippet, MdsfFormatter}, + languages::Lang, + }; + + use super::FSharp; + + const INPUT: &str = " +let add a b = + a + b + "; + const EXTENSION: &str = crate::languages::Language::FSharp.to_file_ext(); + + #[test] + fn it_should_be_enabled_by_default() { + assert!(Lang::::default().enabled); + } + + #[test] + fn it_should_not_format_when_enabled_is_false() { + let snippet = setup_snippet(INPUT, EXTENSION).expect("it to save the file"); + let snippet_path = snippet.path(); + + assert!(Lang:: { + enabled: false, + formatter: MdsfFormatter::::default(), + } + .format(snippet_path) + .expect("it to not fail") + .is_none()); + } + + #[test_with::executable(fantomas)] + #[test] + fn test_fantomas() { + let l = Lang:: { + enabled: true, + formatter: MdsfFormatter::Single(FSharp::Fantomas), + }; + + let snippet = setup_snippet(INPUT, EXTENSION).expect("it to save the file"); + let snippet_path = snippet.path(); + + let output = l + .format(snippet_path) + .expect("it to not fail") + .expect("it to be a snippet"); + + let expected_output = "let add a b = a + b +"; + assert_eq!(output, expected_output); + } +} diff --git a/src/languages/mod.rs b/src/languages/mod.rs index b2a0cc72..420e04f9 100644 --- a/src/languages/mod.rs +++ b/src/languages/mod.rs @@ -14,6 +14,7 @@ pub enum Language { Elixir, Elm, Erlang, + FSharp, Gleam, Go, GraphQL, @@ -49,7 +50,6 @@ pub enum Language { Yaml, Zig, // TODO: PHP, - // TODO: FSharp, // TODO: Svelte, // TODO: Julia, // TODO: Dockerfile, @@ -69,6 +69,7 @@ pub mod dart; pub mod elixir; pub mod elm; pub mod erlang; +pub mod fsharp; pub mod gleam; pub mod go; pub mod graphql; @@ -125,6 +126,7 @@ impl Language { "elixir" => Some(Self::Elixir), "elm" => Some(Self::Elm), "erlang" => Some(Self::Erlang), + "fsharp" => Some(Self::FSharp), "gleam" => Some(Self::Gleam), "go" | "golang" => Some(Self::Go), "graphql" | "gql" => Some(Self::GraphQL), @@ -179,6 +181,7 @@ impl Language { Self::Elixir => ".ex", Self::Elm => ".elm", Self::Erlang => ".erl", + Self::FSharp => ".fs", Self::Gleam => ".gleam", Self::Go => ".go", Self::GraphQL => ".gql",