From b6eefd896d660605edb4dcbf67b2febf0863fb1a Mon Sep 17 00:00:00 2001 From: Mads Hougesen Date: Thu, 21 Mar 2024 03:37:15 +0100 Subject: [PATCH] feat(swift): add support for swiftformat (#105) * feat(swift): add support for swiftformat * chore: update config schema * ci: remove update-schema --------- Co-authored-by: hougesen --- .github/workflows/validate.yml | 31 ---------- README.md | 7 ++- schemas/v0.0.2/mdsf.schema.json | 52 ++++++++++++++-- src/config.rs | 9 ++- src/formatters/mod.rs | 2 + src/formatters/swiftformat.rs | 42 +++++++++++++ src/languages/mod.rs | 5 +- src/languages/swift.rs | 101 ++++++++++++++++++++++++++++++++ 8 files changed, 205 insertions(+), 44 deletions(-) create mode 100644 src/formatters/swiftformat.rs create mode 100644 src/languages/swift.rs diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 65aeb7a7..47c042b7 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -14,38 +14,8 @@ env: RUST_BACKTRACE: full jobs: - update-schema: - strategy: - matrix: - os: - - ubuntu-latest - node: - - 20 - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - uses: Swatinem/rust-cache@v2 - - run: rustup toolchain install stable --profile minimal - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node }} - - - name: update schema - run: cargo run -- schema - - - name: update readme - run: node scripts/update-supported-languages.mjs - - - name: format - run: npx prettier --write schemas/ README.md - - - uses: EndBug/add-and-commit@v9 - with: - message: "chore: update config schema" - lint: name: lint - needs: [update-schema] strategy: matrix: os: @@ -66,7 +36,6 @@ jobs: test: name: test - needs: [update-schema] strategy: matrix: os: diff --git a/README.md b/README.md index b88f345b..7d5580a6 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ mdsf init | Go | `gofmt`, `gofumpt`, `goimports` | | GraphQL | `prettier` | | Groovy | `npm-groovy-lint` | -| Haskell | `fourmolu`, `hindent` | +| Haskell | `fourmolu`, `hindent`, `ormolu` | | Html | `prettier` | | Java | `clang-format`, `google-java-format` | | JavaScript | `biome`, `clang-format`, `deno_fmt`, `prettier` | @@ -94,16 +94,17 @@ mdsf init | Python | `autopep8`, `black`, `blue`, `isort`, `ruff`, `usort`, `yapf` | | ReScript | `rescript_format` | | Roc | `roc_format` | -| Ruby | `rubocop`, `rufo` | +| Ruby | `rubocop`, `rubyfmt`, `rufo` | | Rust | `rustfmt` | | Scala | `scalafmt` | | Shell | `beautysh`, `shfmt` | | Sql | `sql-formatter`, `sqlfluff` | +| Swift | `swiftformat` | | Toml | `taplo` | | TypeScript | `biome`, `deno_fmt`, `prettier` | | Vue | `prettier` | | Xml | `xmllint` | -| Yaml | `prettier` | +| Yaml | `prettier`, `yamlfmt` | | Zig | `zigfmt` | diff --git a/schemas/v0.0.2/mdsf.schema.json b/schemas/v0.0.2/mdsf.schema.json index c996dcc9..389837e9 100644 --- a/schemas/v0.0.2/mdsf.schema.json +++ b/schemas/v0.0.2/mdsf.schema.json @@ -160,7 +160,7 @@ "haskell": { "default": { "enabled": true, - "formatter": [["fourmolu", "hindent"]] + "formatter": [["fourmolu", "ormolu", "hindent"]] }, "allOf": [ { @@ -361,7 +361,7 @@ "ruby": { "default": { "enabled": true, - "formatter": [["rubocop", "rufo"]] + "formatter": [["rubocop", "rufo", "rubyfmt"]] }, "allOf": [ { @@ -413,6 +413,17 @@ } ] }, + "swift": { + "default": { + "enabled": true, + "formatter": "swiftformat" + }, + "allOf": [ + { + "$ref": "#/definitions/Lang_for_Swift" + } + ] + }, "toml": { "default": { "enabled": true, @@ -460,7 +471,7 @@ "yaml": { "default": { "enabled": true, - "formatter": "prettier" + "formatter": [["prettier", "yamlfmt"]] }, "allOf": [ { @@ -539,7 +550,7 @@ }, "Haskell": { "type": "string", - "enum": ["fourmolu", "hindent"] + "enum": ["fourmolu", "ormolu", "hindent"] }, "Html": { "type": "string", @@ -1009,6 +1020,18 @@ } } }, + "Lang_for_Swift": { + "type": "object", + "required": ["enabled", "formatter"], + "properties": { + "enabled": { + "type": "boolean" + }, + "formatter": { + "$ref": "#/definitions/MdsfFormatter_for_Swift" + } + } + }, "Lang_for_Toml": { "type": "object", "required": ["enabled", "formatter"], @@ -1570,6 +1593,19 @@ } ] }, + "MdsfFormatter_for_Swift": { + "anyOf": [ + { + "$ref": "#/definitions/Swift" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/MdsfFormatter_for_Swift" + } + } + ] + }, "MdsfFormatter_for_Toml": { "anyOf": [ { @@ -1686,7 +1722,7 @@ }, "Ruby": { "type": "string", - "enum": ["rubocop", "rufo"] + "enum": ["rubyfmt", "rubocop", "rufo"] }, "Rust": { "type": "string", @@ -1704,6 +1740,10 @@ "type": "string", "enum": ["sqlfluff", "sql-formatter"] }, + "Swift": { + "type": "string", + "enum": ["swiftformat"] + }, "Toml": { "type": "string", "enum": ["taplo"] @@ -1722,7 +1762,7 @@ }, "Yaml": { "type": "string", - "enum": ["prettier"] + "enum": ["prettier", "yamlfmt"] }, "Zig": { "type": "string", diff --git a/src/config.rs b/src/config.rs index 0a03a0b0..fd66f546 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,8 +6,8 @@ use crate::languages::{ 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, toml::Toml, - typescript::TypeScript, vue::Vue, xml::Xml, yaml::Yaml, zig::Zig, Lang, + 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)] @@ -125,6 +125,9 @@ pub struct MdsfConfig { #[serde(default)] pub shell: Lang, + #[serde(default)] + pub swift: Lang, + #[serde(default)] pub sql: Lang, @@ -136,7 +139,6 @@ pub struct MdsfConfig { #[serde(default)] pub vue: Lang, - #[serde(default)] pub xml: Lang, @@ -189,6 +191,7 @@ impl Default for MdsfConfig { rust: Lang::::default(), scala: Lang::::default(), shell: Lang::::default(), + swift: Lang::::default(), sql: Lang::::default(), toml: Lang::::default(), typescript: Lang::::default(), diff --git a/src/formatters/mod.rs b/src/formatters/mod.rs index 2cd26fb9..f52570fc 100644 --- a/src/formatters/mod.rs +++ b/src/formatters/mod.rs @@ -47,6 +47,7 @@ pub mod shfmt; pub mod sql_formatter; pub mod sqlfluff; pub mod stylua; +pub mod swiftformat; pub mod taplo; pub mod usort; pub mod xmllint; @@ -160,6 +161,7 @@ pub fn format_snippet(config: &MdsfConfig, language: &Language, code: &str) -> S Language::Scala => config.scala.format(snippet_path), Language::Shell => config.shell.format(snippet_path), Language::Sql => config.sql.format(snippet_path), + Language::Swift => config.swift.format(snippet_path), Language::Toml => config.toml.format(snippet_path), Language::TypeScript => config.typescript.format(snippet_path), Language::Vue => config.vue.format(snippet_path), diff --git a/src/formatters/swiftformat.rs b/src/formatters/swiftformat.rs new file mode 100644 index 00000000..fb0ef929 --- /dev/null +++ b/src/formatters/swiftformat.rs @@ -0,0 +1,42 @@ +use super::execute_command; + +#[inline] +pub fn format_using_swiftformat( + snippet_path: &std::path::Path, +) -> std::io::Result<(bool, Option)> { + let mut cmd = std::process::Command::new("swiftformat"); + + cmd.arg("--quiet").arg(snippet_path); + + execute_command(&mut cmd, snippet_path) +} + +#[cfg(test)] +mod test_swiftformat { + use crate::{formatters::setup_snippet, languages::Language}; + + use super::format_using_swiftformat; + + #[test_with::executable(swiftformat)] + #[test] + fn it_should_format_swift() { + let input = " func add(a:Int ,b:Int)->Int { + return a + b + }"; + + let expected_output = "func add(a: Int, b: Int) -> Int { + return a + b +} +"; + + let snippet = setup_snippet(input, Language::Swift.to_file_ext()) + .expect("it to create a snippet file"); + + let output = format_using_swiftformat(snippet.path()) + .expect("it to be successful") + .1 + .expect("it to be some"); + + assert_eq!(expected_output, output); + } +} diff --git a/src/languages/mod.rs b/src/languages/mod.rs index 6bd55002..4baf6388 100644 --- a/src/languages/mod.rs +++ b/src/languages/mod.rs @@ -40,6 +40,7 @@ pub enum Language { Scala, Shell, Sql, + Swift, Toml, TypeScript, Vue, @@ -48,7 +49,6 @@ pub enum Language { Zig, // TODO: PHP, // TODO: FSharp, - // TODO: Swift, // TODO: Svelte, // TODO: Julia, // TODO: Dockerfile, @@ -95,6 +95,7 @@ pub mod rust; pub mod scala; pub mod shell; pub mod sql; +pub mod swift; pub mod toml; pub mod typescript; pub mod vue; @@ -151,6 +152,7 @@ impl Language { "sql" | "bigquery" | "db2" | "db2i" | "hive" | "mariadb" | "mysql" | "n1ql" | "plsql" | "postgresql" | "redshift" | "singlestoredb" | "snowflake" | "spark" | "sqlite" | "transactsql" | "trino" | "tsql" => Some(Self::Sql), + "swift" => Some(Self::Swift), "toml" => Some(Self::Toml), "typescript" | "ts" | "tsx" => Some(Self::TypeScript), "vue" => Some(Self::Vue), @@ -201,6 +203,7 @@ impl Language { Self::Shell => ".sh", Self::Scala => ".scala", Self::Sql => ".sql", + Self::Swift => ".swift", Self::Toml => ".toml", Self::TypeScript => ".ts", Self::Vue => ".vue", diff --git a/src/languages/swift.rs b/src/languages/swift.rs new file mode 100644 index 00000000..6f2d3d9e --- /dev/null +++ b/src/languages/swift.rs @@ -0,0 +1,101 @@ +use schemars::JsonSchema; + +use crate::formatters::{swiftformat::format_using_swiftformat, MdsfFormatter}; + +use super::{Lang, LanguageFormatter}; + +#[derive(Debug, Default, serde::Serialize, serde::Deserialize, JsonSchema)] +#[cfg_attr(test, derive(PartialEq, Eq))] +pub enum Swift { + #[default] + #[serde(rename = "swiftformat")] + SwiftFormat, +} + +impl Default for Lang { + #[inline] + fn default() -> Self { + Self { + enabled: true, + formatter: MdsfFormatter::::default(), + } + } +} + +impl Default for MdsfFormatter { + #[inline] + fn default() -> Self { + Self::Single(Swift::SwiftFormat) + } +} + +impl LanguageFormatter for Swift { + #[inline] + fn format_snippet( + &self, + snippet_path: &std::path::Path, + ) -> std::io::Result<(bool, Option)> { + match self { + Self::SwiftFormat => format_using_swiftformat(snippet_path), + } + } +} + +#[cfg(test)] +mod test_swift { + use crate::{ + formatters::{setup_snippet, MdsfFormatter}, + languages::Lang, + }; + + use super::Swift; + + const INPUT: &str = " func add(a:Int ,b:Int)->Int { + return a + b + }"; + + const EXTENSION: &str = crate::languages::Language::Swift.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::Single(Swift::default()), + } + .format(snippet_path) + .expect("it to not fail") + .is_none()); + } + + #[test_with::executable(swiftformat)] + #[test] + fn test_swiftformat() { + let l = Lang:: { + enabled: true, + formatter: MdsfFormatter::Single(Swift::SwiftFormat), + }; + + 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 = "func add(a: Int, b: Int) -> Int { + return a + b +} +"; + + assert_eq!(output, expected_output); + } +}