diff --git a/Cargo.lock b/Cargo.lock index 165c808..0c38f87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,6 +166,7 @@ version = "0.2.2" dependencies = [ "clap", "futures", + "paste", "regex", "schemars", "serde", @@ -174,12 +175,30 @@ dependencies = [ "tokio", ] +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "dyn-clone" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "equivalent" version = "1.0.1" @@ -309,6 +328,19 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "insta" +version = "1.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "serde", + "similar", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -321,12 +353,24 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "lock_api" version = "0.4.12" @@ -396,6 +440,11 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "git+https://github.com/elcoosp/paste?branch=feat%2Fdash#4efa1dd157f1a9bc8994fffd653bb2e1c744828a" + [[package]] name = "pin-project-lite" version = "0.2.15" @@ -435,6 +484,26 @@ dependencies = [ "bitflags", ] +[[package]] +name = "ref-cast" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "regex" version = "1.11.1" @@ -482,6 +551,7 @@ version = "0.2.2" dependencies = [ "clap", "commitlint-rs", + "insta", "schemars", "serde", "serde_json", @@ -489,11 +559,12 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.21" +version = "1.0.0-alpha.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +checksum = "8e467e37661682d17a6db96cf0526f8a2ccf3a642d94624a9ba9a61a1f5ed729" dependencies = [ "dyn-clone", + "ref-cast", "schemars_derive", "serde", "serde_json", @@ -501,9 +572,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.21" +version = "1.0.0-alpha.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +checksum = "e2af93b12c2850cd3231ca5194ffbfcbaa8a1c7e2bb550e5e8a3a77bcdb3ebec" dependencies = [ "proc-macro2", "quote", @@ -582,6 +653,12 @@ dependencies = [ "libc", ] +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + [[package]] name = "slab" version = "0.4.9" diff --git a/Cargo.toml b/Cargo.toml index c6c2b71..de0800d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,6 @@ readme = "README.md" repository = "https://github.com/KeisukeYamashita/commitlint-rs" exclude = ["/web"] edition = "2021" +[profile.dev.package] +insta.opt-level = 3 +similar.opt-level = 3 diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 362aed4..ef76d4d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -11,17 +11,16 @@ repository.workspace = true license.workspace = true edition.workspace = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [[bin]] name = "commitlint" path = "src/main.rs" [dependencies] +paste = { git = "https://github.com/elcoosp/paste", branch = "feat/dash" } clap = { version = "4.5.4", features = ["derive", "env", "string"] } futures = "0.3.30" regex = "1.10.5" -schemars = { version = "0.8.21", optional = true } +schemars = { version = "1.0.0-alpha.16", optional = true } serde = { version = "1.0.201", features = ["derive"] } serde_json = "1.0.121" serde_yaml = "0.9.34" diff --git a/cli/json-schema/config.json b/cli/json-schema/config.json new file mode 100644 index 0000000..2df719e --- /dev/null +++ b/cli/json-schema/config.json @@ -0,0 +1,519 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Config", + "description": "Config represents the configuration of commitlint.", + "type": "object", + "properties": { + "rules": { + "description": "Rules represents the rules of commitlint.", + "$ref": "#/$defs/Rules" + } + }, + "required": [ + "rules" + ], + "$defs": { + "BodyEmpty": { + "description": "[BodyEmpty] represents the [`body-empty`](https://keisukeyamashita.github.io/commitlint-rs/rules/body-empty) rule.", + "type": "object", + "properties": { + "level": { + "description": "Level represents the level of the rule.", + "anyOf": [ + { + "$ref": "#/$defs/Level" + }, + { + "type": "null" + } + ] + } + } + }, + "BodyMaxLength": { + "description": "[BodyMaxLength] represents the [`body-max-length`](https://keisukeyamashita.github.io/commitlint-rs/rules/body-max-length) rule.", + "type": "object", + "properties": { + "length": { + "description": "Length represents the maximum length of the \"body\".", + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "level": { + "description": "Level represents the level of the rule.", + "anyOf": [ + { + "$ref": "#/$defs/Level" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "length" + ] + }, + "DescriptionEmpty": { + "description": "[DescriptionEmpty] represents the [`description-empty`](https://keisukeyamashita.github.io/commitlint-rs/rules/description-empty) rule.", + "type": "object", + "properties": { + "level": { + "description": "Level represents the level of the rule.", + "anyOf": [ + { + "$ref": "#/$defs/Level" + }, + { + "type": "null" + } + ] + } + } + }, + "DescriptionFormat": { + "description": "[DescriptionFormat] represents the [`description-format`](https://keisukeyamashita.github.io/commitlint-rs/rules/description-format) rule.", + "type": "object", + "properties": { + "format": { + "description": "Format represents the format of the \"description\".", + "type": [ + "string", + "null" + ] + }, + "level": { + "description": "Level represents the level of the rule.", + "anyOf": [ + { + "$ref": "#/$defs/Level" + }, + { + "type": "null" + } + ] + } + } + }, + "DescriptionMaxLength": { + "description": "[DescriptionMaxLength] represents the [`description-max-length`](https://keisukeyamashita.github.io/commitlint-rs/rules/description-max-length) rule.", + "type": "object", + "properties": { + "length": { + "description": "Length represents the maximum length of the \"description\".", + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "level": { + "description": "Level represents the level of the rule.", + "anyOf": [ + { + "$ref": "#/$defs/Level" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "length" + ] + }, + "FootersEmpty": { + "description": "[FootersEmpty] represents the [`footers-empty`](https://keisukeyamashita.github.io/commitlint-rs/rules/footers-empty) rule.", + "type": "object", + "properties": { + "level": { + "description": "Level represents the level of the rule.", + "anyOf": [ + { + "$ref": "#/$defs/Level" + }, + { + "type": "null" + } + ] + } + } + }, + "Level": { + "description": "Level represents the level of a rule.", + "type": "string", + "enum": [ + "error", + "ignore", + "warning" + ] + }, + "Rules": { + "description": "Rules represents the rules of commitlint.\n See: https://commitlint.js.org/reference/rules.html", + "type": "object", + "properties": { + "type": { + "anyOf": [ + { + "$ref": "#/$defs/Type" + }, + { + "type": "null" + } + ] + }, + "body-empty": { + "anyOf": [ + { + "$ref": "#/$defs/BodyEmpty" + }, + { + "type": "null" + } + ] + }, + "body-max-length": { + "anyOf": [ + { + "$ref": "#/$defs/BodyMaxLength" + }, + { + "type": "null" + } + ] + }, + "description-empty": { + "anyOf": [ + { + "$ref": "#/$defs/DescriptionEmpty" + }, + { + "type": "null" + } + ] + }, + "description-format": { + "anyOf": [ + { + "$ref": "#/$defs/DescriptionFormat" + }, + { + "type": "null" + } + ] + }, + "description-max-length": { + "anyOf": [ + { + "$ref": "#/$defs/DescriptionMaxLength" + }, + { + "type": "null" + } + ] + }, + "footers-empty": { + "anyOf": [ + { + "$ref": "#/$defs/FootersEmpty" + }, + { + "type": "null" + } + ] + }, + "scope": { + "anyOf": [ + { + "$ref": "#/$defs/Scope" + }, + { + "type": "null" + } + ] + }, + "scope-empty": { + "anyOf": [ + { + "$ref": "#/$defs/ScopeEmpty" + }, + { + "type": "null" + } + ] + }, + "scope-format": { + "anyOf": [ + { + "$ref": "#/$defs/ScopeFormat" + }, + { + "type": "null" + } + ] + }, + "scope-max-length": { + "anyOf": [ + { + "$ref": "#/$defs/ScopeMaxLength" + }, + { + "type": "null" + } + ] + }, + "subject-empty": { + "anyOf": [ + { + "$ref": "#/$defs/SubjectEmpty" + }, + { + "type": "null" + } + ] + }, + "type-empty": { + "anyOf": [ + { + "$ref": "#/$defs/TypeEmpty" + }, + { + "type": "null" + } + ] + }, + "type-format": { + "anyOf": [ + { + "$ref": "#/$defs/TypeFormat" + }, + { + "type": "null" + } + ] + }, + "type-max-length": { + "anyOf": [ + { + "$ref": "#/$defs/TypeMaxLength" + }, + { + "type": "null" + } + ] + } + } + }, + "Scope": { + "description": "[Scope] represents the [`scope`](https://keisukeyamashita.github.io/commitlint-rs/rules/scope) rule.", + "type": "object", + "properties": { + "level": { + "description": "Level represents the level of the rule.", + "anyOf": [ + { + "$ref": "#/$defs/Level" + }, + { + "type": "null" + } + ] + }, + "optional": { + "description": "Optional scope. If true, even if the scope is not present, it is allowed.", + "type": "boolean" + }, + "options": { + "description": "Options represents the options of the rule. If the option is empty, it means that no \"scope\" is allowed.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "optional", + "options" + ] + }, + "ScopeEmpty": { + "description": "[ScopeEmpty] represents the [`scope-empty`](https://keisukeyamashita.github.io/commitlint-rs/rules/scope-empty) rule.", + "type": "object", + "properties": { + "level": { + "description": "Level represents the level of the rule.", + "anyOf": [ + { + "$ref": "#/$defs/Level" + }, + { + "type": "null" + } + ] + } + } + }, + "ScopeFormat": { + "description": "[ScopeFormat] represents the [`scope-format`](https://keisukeyamashita.github.io/commitlint-rs/rules/scope-format) rule.", + "type": "object", + "properties": { + "format": { + "description": "Format represents the format of the \"scope\".", + "type": [ + "string", + "null" + ] + }, + "level": { + "description": "Level represents the level of the rule.", + "anyOf": [ + { + "$ref": "#/$defs/Level" + }, + { + "type": "null" + } + ] + } + } + }, + "ScopeMaxLength": { + "description": "[ScopeMaxLength] represents the [`scope-max-length`](https://keisukeyamashita.github.io/commitlint-rs/rules/scope-max-length) rule.", + "type": "object", + "properties": { + "length": { + "description": "Length represents the maximum length of the \"scope\".", + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "level": { + "description": "Level represents the level of the rule.", + "anyOf": [ + { + "$ref": "#/$defs/Level" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "length" + ] + }, + "SubjectEmpty": { + "description": "[SubjectEmpty] represents the [`subject-empty`](https://keisukeyamashita.github.io/commitlint-rs/rules/subject-empty) rule.", + "type": "object", + "properties": { + "level": { + "description": "Level represents the level of the rule.", + "anyOf": [ + { + "$ref": "#/$defs/Level" + }, + { + "type": "null" + } + ] + } + } + }, + "Type": { + "description": "[Type] represents the [`type`](https://keisukeyamashita.github.io/commitlint-rs/rules/type) rule.", + "type": "object", + "properties": { + "level": { + "description": "Level represents the level of the rule.", + "anyOf": [ + { + "$ref": "#/$defs/Level" + }, + { + "type": "null" + } + ] + }, + "options": { + "description": "Options represents the options of the rule. If the option is empty, it means that no \"type\" is allowed.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "options" + ] + }, + "TypeEmpty": { + "description": "[TypeEmpty] represents the [`type-empty`](https://keisukeyamashita.github.io/commitlint-rs/rules/type-empty) rule.", + "type": "object", + "properties": { + "level": { + "description": "Level represents the level of the rule.", + "anyOf": [ + { + "$ref": "#/$defs/Level" + }, + { + "type": "null" + } + ] + } + } + }, + "TypeFormat": { + "description": "[TypeFormat] represents the [`type-format`](https://keisukeyamashita.github.io/commitlint-rs/rules/type-format) rule.", + "type": "object", + "properties": { + "format": { + "description": "Format represents the format of the \"type\".", + "type": [ + "string", + "null" + ] + }, + "level": { + "description": "Level represents the level of the rule.", + "anyOf": [ + { + "$ref": "#/$defs/Level" + }, + { + "type": "null" + } + ] + } + } + }, + "TypeMaxLength": { + "description": "[TypeMaxLength] represents the [`type-max-length`](https://keisukeyamashita.github.io/commitlint-rs/rules/type-max-length) rule.", + "type": "object", + "properties": { + "length": { + "description": "Length represents the maximum length of the \"type\".", + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "level": { + "description": "Level represents the level of the rule.", + "anyOf": [ + { + "$ref": "#/$defs/Level" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "length" + ] + } + } +} \ No newline at end of file diff --git a/cli/src/args.rs b/cli/src/args.rs index d2e34cb..dcf631f 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -56,7 +56,7 @@ impl Args { if let Some(edit) = self.edit.as_deref() { if edit != "false" { let msg = std::fs::read_to_string(edit) - .expect(format!("Failed to read commit message from {}", edit).as_str()); + .unwrap_or_else(|_| panic!("Failed to read commit message from {}", edit)); return Ok(vec![Message::new(msg)]); } } @@ -87,13 +87,8 @@ impl Args { } let default_path = std::path::PathBuf::from(".git").join("COMMIT_EDITMSG"); - let msg = std::fs::read_to_string(&default_path).expect( - format!( - "Failed to read commit message from {}", - default_path.display() - ) - .as_str(), - ); + let msg = std::fs::read_to_string(&default_path).unwrap_or_else(|_| panic!("Failed to read commit message from {}", + default_path.display())); Ok(vec![Message::new(msg)]) } } diff --git a/cli/src/rule.rs b/cli/src/rule.rs index 12a2727..ca315ba 100644 --- a/cli/src/rule.rs +++ b/cli/src/rule.rs @@ -1,7 +1,6 @@ -use std::fmt::Debug; - use crate::{message::Message, result::Violation}; use serde::{Deserialize, Serialize}; +use std::fmt::Debug; use self::{ body_empty::BodyEmpty, body_max_length::BodyMaxLength, description_empty::DescriptionEmpty, @@ -239,3 +238,88 @@ pub enum Level { #[serde(rename = "warning")] Warning, } +/// Create a struct with length field that should impl [Rule] +#[macro_export] +macro_rules! make_length_rule { + ( + $ident:ident, + $length_of_what:literal + ) => { + $crate::make_rule! { + $ident, + #[doc = concat!("Length represents the maximum length of the ",stringify!($length_of_what),".")] + length: usize + } + }; +} +/// Create a struct with format field that should impl [Rule] +#[macro_export] +macro_rules! make_format_rule { + ( + $ident:ident, + $format_of_what:literal + ) => { + $crate::make_rule! { + $ident, + #[doc = concat!("Format represents the format of the ",stringify!($format_of_what),".")] + format: Option + } + }; +} +/// Create a struct with options field that should impl [Rule] +#[macro_export] +macro_rules! make_options_rule { + ( + $ident:ident, + $options_what: literal, + $( + $( + #[$field_meta:meta] + )* + $field_name:ident: $field_type:ty + ),*) => { + $crate::make_rule! { + $ident, + $( + $( + #[$field_meta] + )* + $field_name: $field_type, + + ),* + #[doc = concat!("Options represents the options of the rule. If the option is empty, it means that no ",stringify!($options_what)," is allowed.")] + options: Vec + } + }; +} + +/// Create a struct that should impl [Rule] +#[macro_export] +macro_rules! make_rule { + ( + $ident:ident, + $( + $( + #[$field_meta:meta] + )* + $field_name:ident: $field_type:ty + ),*) => { paste::paste! { + #[doc = "[" $ident "] represents the [`"[<$ident:dash>]"`](https://keisukeyamashita.github.io/commitlint-rs/rules/"[<$ident:dash>]") rule."] + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] + #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] + pub struct $ident { + /// Level represents the level of the rule. + /// + // Note that currently the default literal is not supported. + // See: https://github.com/serde-rs/serde/issues/368 + level: Option, + $( + $( + #[$field_meta] + )* + $field_name: $field_type + ),* + } + } +}; +} diff --git a/cli/src/rule/body_empty.rs b/cli/src/rule/body_empty.rs index ecfb444..097dca8 100644 --- a/cli/src/rule/body_empty.rs +++ b/cli/src/rule/body_empty.rs @@ -1,17 +1,8 @@ -use crate::{message::Message, result::Violation, rule::Rule}; -use serde::{Deserialize, Serialize}; - use super::Level; +use crate::{make_rule, message::Message, result::Violation, rule::Rule}; -/// BodyEmpty represents the body-empty rule. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct BodyEmpty { - /// Level represents the level of the rule. - /// - // Note that currently the default literal is not supported. - // See: https://github.com/serde-rs/serde/issues/368 - level: Option, +make_rule! { + BodyEmpty, } /// BodyEmpty represents the body-empty rule. diff --git a/cli/src/rule/body_max_length.rs b/cli/src/rule/body_max_length.rs index ea8b869..cf3a66e 100644 --- a/cli/src/rule/body_max_length.rs +++ b/cli/src/rule/body_max_length.rs @@ -1,20 +1,9 @@ -use crate::{message::Message, result::Violation, rule::Rule}; -use serde::{Deserialize, Serialize}; +use crate::{make_length_rule, message::Message, result::Violation, rule::Rule}; use super::Level; - -/// BodyMaxLength represents the body-max-length rule. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct BodyMaxLength { - /// Level represents the level of the rule. - /// - // Note that currently the default literal is not supported. - // See: https://github.com/serde-rs/serde/issues/368 - level: Option, - - /// Length represents the maximum length of the body. - length: usize, +make_length_rule! { + BodyMaxLength, + "body" } /// BodyMaxLength represents the body-max-length rule. diff --git a/cli/src/rule/description_empty.rs b/cli/src/rule/description_empty.rs index 4c7e7a2..b3d964c 100644 --- a/cli/src/rule/description_empty.rs +++ b/cli/src/rule/description_empty.rs @@ -1,17 +1,8 @@ -use crate::{message::Message, result::Violation, rule::Rule}; -use serde::{Deserialize, Serialize}; +use crate::{make_rule, message::Message, result::Violation, rule::Rule}; use super::Level; - -/// DescriptionEmpty represents the subject-empty rule. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct DescriptionEmpty { - /// Level represents the level of the rule. - /// - // Note that currently the default literal is not supported. - // See: https://github.com/serde-rs/serde/issues/368 - level: Option, +make_rule! { + DescriptionEmpty, } /// DescriptionEmpty represents the description-empty rule. diff --git a/cli/src/rule/description_format.rs b/cli/src/rule/description_format.rs index dc54ff4..cae74d5 100644 --- a/cli/src/rule/description_format.rs +++ b/cli/src/rule/description_format.rs @@ -1,20 +1,9 @@ -use crate::{message::Message, result::Violation, rule::Rule}; -use serde::{Deserialize, Serialize}; - use super::Level; +use crate::{make_format_rule, message::Message, result::Violation, rule::Rule}; -/// DescriptionFormat represents the description-format rule. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct DescriptionFormat { - /// Level represents the level of the rule. - /// - // Note that currently the default literal is not supported. - // See: https://github.com/serde-rs/serde/issues/368 - level: Option, - - /// Format represents the format of the description. - format: Option, +make_format_rule! { + DescriptionFormat, + "description" } /// DescriptionFormat represents the description-format rule. diff --git a/cli/src/rule/description_max_length.rs b/cli/src/rule/description_max_length.rs index babed77..0d9d7b6 100644 --- a/cli/src/rule/description_max_length.rs +++ b/cli/src/rule/description_max_length.rs @@ -1,20 +1,10 @@ -use crate::{message::Message, result::Violation, rule::Rule}; -use serde::{Deserialize, Serialize}; +use crate::{make_length_rule, message::Message, result::Violation, rule::Rule}; use super::Level; -/// DescriptionMaxLength represents the description-max-length rule. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct DescriptionMaxLength { - /// Level represents the level of the rule. - /// - // Note that currently the default literal is not supported. - // See: https://github.com/serde-rs/serde/issues/368 - level: Option, - - /// Length represents the maximum length of the description. - length: usize, +make_length_rule! { + DescriptionMaxLength, + "description" } /// DescriptionMaxLength represents the description-max-length rule. diff --git a/cli/src/rule/footers_empty.rs b/cli/src/rule/footers_empty.rs index f4970c3..966be42 100644 --- a/cli/src/rule/footers_empty.rs +++ b/cli/src/rule/footers_empty.rs @@ -1,17 +1,8 @@ -use crate::{message::Message, result::Violation, rule::Rule}; -use serde::{Deserialize, Serialize}; +use crate::{make_rule, message::Message, result::Violation, rule::Rule}; use super::Level; - -/// FootersEmpty represents the footer-empty rule. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct FootersEmpty { - /// Level represents the level of the rule. - /// - // Note that currently the default literal is not supported. - // See: https://github.com/serde-rs/serde/issues/368 - level: Option, +make_rule! { + FootersEmpty, } /// FooterEmpty represents the footer-empty rule. diff --git a/cli/src/rule/scope.rs b/cli/src/rule/scope.rs index 57bd7b5..cdecc9b 100644 --- a/cli/src/rule/scope.rs +++ b/cli/src/rule/scope.rs @@ -1,25 +1,11 @@ -use crate::{message::Message, result::Violation, rule::Rule}; -use serde::{Deserialize, Serialize}; - use super::Level; +use crate::{make_options_rule, message::Message, result::Violation, rule::Rule}; -/// Scope represents the subject-empty rule. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct Scope { - /// Level represents the level of the rule. - /// - // Note that currently the default literal is not supported. - // See: https://github.com/serde-rs/serde/issues/368 - level: Option, - - /// Options represents the options of the rule. - /// If the option is empty, it means that no scope is allowed. - options: Vec, - - /// Optional scope. - /// If true, even if the scope is not present, it is allowed. - optional: bool, +make_options_rule! { + Scope, + "scope", + #[doc = "Optional scope. If true, even if the scope is not present, it is allowed."] + optional: bool } /// Scope represents the scope rule. diff --git a/cli/src/rule/scope_empty.rs b/cli/src/rule/scope_empty.rs index e2d0219..ec553c1 100644 --- a/cli/src/rule/scope_empty.rs +++ b/cli/src/rule/scope_empty.rs @@ -1,17 +1,8 @@ -use crate::{message::Message, result::Violation, rule::Rule}; -use serde::{Deserialize, Serialize}; - use super::Level; +use crate::{make_rule, message::Message, result::Violation, rule::Rule}; -/// ScopeEmpty represents the subject-empty rule. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct ScopeEmpty { - /// Level represents the level of the rule. - /// - // Note that currently the default literal is not supported. - // See: https://github.com/serde-rs/serde/issues/368 - level: Option, +make_rule! { + ScopeEmpty, } /// ScopeEmpty represents the scope-empty rule. diff --git a/cli/src/rule/scope_format.rs b/cli/src/rule/scope_format.rs index e567834..cc2c0ff 100644 --- a/cli/src/rule/scope_format.rs +++ b/cli/src/rule/scope_format.rs @@ -1,20 +1,9 @@ -use crate::{message::Message, result::Violation, rule::Rule}; -use serde::{Deserialize, Serialize}; +use crate::{make_format_rule, message::Message, result::Violation, rule::Rule}; use super::Level; - -/// ScopeFormat represents the scope-format rule. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct ScopeFormat { - /// Level represents the level of the rule. - /// - // Note that currently the default literal is not supported. - // See: https://github.com/serde-rs/serde/issues/368 - level: Option, - - /// Format represents the format of the scope. - format: Option, +make_format_rule! { + ScopeFormat, + "scope" } /// ScopeFormat represents the scope-format rule. diff --git a/cli/src/rule/scope_max_length.rs b/cli/src/rule/scope_max_length.rs index f33da11..ad635f9 100644 --- a/cli/src/rule/scope_max_length.rs +++ b/cli/src/rule/scope_max_length.rs @@ -1,22 +1,10 @@ -use crate::{message::Message, result::Violation, rule::Rule}; -use serde::{Deserialize, Serialize}; - use super::Level; +use crate::{make_length_rule, message::Message, result::Violation, rule::Rule}; -/// ScopeMaxLength represents the description-max-length rule. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct ScopeMaxLength { - /// Level represents the level of the rule. - /// - // Note that currently the default literal is not supported. - // See: https://github.com/serde-rs/serde/issues/368 - level: Option, - - /// Length represents the maximum length of the scope. - length: usize, +make_length_rule! { + ScopeMaxLength, + "scope" } - /// ScopeMaxLength represents the scope-max-length rule. impl Rule for ScopeMaxLength { const NAME: &'static str = "scope-max-length"; diff --git a/cli/src/rule/subject_empty.rs b/cli/src/rule/subject_empty.rs index 9b65adb..f5d6235 100644 --- a/cli/src/rule/subject_empty.rs +++ b/cli/src/rule/subject_empty.rs @@ -1,17 +1,9 @@ -use crate::{message::Message, result::Violation, rule::Rule}; -use serde::{Deserialize, Serialize}; +use crate::{make_rule, message::Message, result::Violation, rule::Rule}; use super::Level; -/// SubjectEmpty represents the subject-empty rule. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct SubjectEmpty { - /// Level represents the level of the rule. - /// - // Note that currently the default literal is not supported. - // See: https://github.com/serde-rs/serde/issues/368 - level: Option, +make_rule! { + SubjectEmpty, } /// SubjectEmpty represents the subject-empty rule. diff --git a/cli/src/rule/type.rs b/cli/src/rule/type.rs index 39599a0..b4158d3 100644 --- a/cli/src/rule/type.rs +++ b/cli/src/rule/type.rs @@ -1,21 +1,9 @@ -use crate::{message::Message, result::Violation, rule::Rule}; -use serde::{Deserialize, Serialize}; +use crate::{make_options_rule, message::Message, result::Violation, rule::Rule}; use super::Level; - -/// Type represents the subject-empty rule. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct Type { - /// Level represents the level of the rule. - /// - // Note that currently the default literal is not supported. - // See: https://github.com/serde-rs/serde/issues/368 - level: Option, - - /// Options represents the options of the rule. - /// If the option is empty, it means that no Type is allowed. - options: Vec, +make_options_rule! { + Type, + "type", } /// Type represents the type rule. diff --git a/cli/src/rule/type_empty.rs b/cli/src/rule/type_empty.rs index f9e425b..31881e0 100644 --- a/cli/src/rule/type_empty.rs +++ b/cli/src/rule/type_empty.rs @@ -1,17 +1,8 @@ -use crate::{message::Message, result::Violation, rule::Rule}; -use serde::{Deserialize, Serialize}; +use crate::{make_rule, message::Message, result::Violation, rule::Rule}; use super::Level; - -/// TypeEmpty represents the type-empty rule. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct TypeEmpty { - /// Level represents the level of the rule. - /// - // Note that currently the default literal is not supported. - // See: https://github.com/serde-rs/serde/issues/368 - level: Option, +make_rule! { + TypeEmpty, } /// TypeEmpty represents the type-empty rule. diff --git a/cli/src/rule/type_format.rs b/cli/src/rule/type_format.rs index 364d017..4f65b2a 100644 --- a/cli/src/rule/type_format.rs +++ b/cli/src/rule/type_format.rs @@ -1,20 +1,9 @@ -use crate::{message::Message, result::Violation, rule::Rule}; -use serde::{Deserialize, Serialize}; +use crate::{make_format_rule, message::Message, result::Violation, rule::Rule}; use super::Level; - -/// TypeFormat represents the type-format rule. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct TypeFormat { - /// Level represents the level of the rule. - /// - // Note that currently the default literal is not supported. - // See: https://github.com/serde-rs/serde/issues/368 - level: Option, - - /// Format represents the format of the type. - format: Option, +make_format_rule! { + TypeFormat, + "type" } /// TypeFormat represents the type-format rule. diff --git a/cli/src/rule/type_max_length.rs b/cli/src/rule/type_max_length.rs index 1df8dbb..f7a71fa 100644 --- a/cli/src/rule/type_max_length.rs +++ b/cli/src/rule/type_max_length.rs @@ -1,20 +1,10 @@ -use crate::{message::Message, result::Violation, rule::Rule}; -use serde::{Deserialize, Serialize}; +use crate::{make_length_rule, message::Message, result::Violation, rule::Rule}; use super::Level; -/// TypeMaxLength represents the description-max-length rule. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct TypeMaxLength { - /// Level represents the level of the rule. - /// - // Note that currently the default literal is not supported. - // See: https://github.com/serde-rs/serde/issues/368 - level: Option, - - /// Length represents the maximum length of the type. - length: usize, +make_length_rule! { + TypeMaxLength, + "type" } /// TypeMaxLength represents the type-max-length rule. diff --git a/cspell.yml b/cspell.yml new file mode 100644 index 0000000..a18bdb5 --- /dev/null +++ b/cspell.yml @@ -0,0 +1 @@ +words: [commitlint, schemars, commitlintrc, Keke, insta, binstall, serde, Keisuke, astrojs, splitn] \ No newline at end of file diff --git a/schema/Cargo.toml b/schema/Cargo.toml index 567aa99..290d1b7 100644 --- a/schema/Cargo.toml +++ b/schema/Cargo.toml @@ -14,6 +14,8 @@ edition.workspace = true [dependencies] clap = { version = "4.5.21", features = ["derive"] } cli = { path = "../cli", features = ["schemars"], package = "commitlint-rs" } -schemars = { version = "0.8.21" } +schemars = { version = "1.0.0-alpha.16" } serde = { version = "1.0.215", features = ["derive"] } serde_json = "1.0.133" +[dev-dependencies] +insta = { version = "1.41.1", features = ["yaml"] } diff --git a/schema/src/main.rs b/schema/src/main.rs index 989d768..c47416c 100644 --- a/schema/src/main.rs +++ b/schema/src/main.rs @@ -1,7 +1,6 @@ use clap::Parser; use cli::config::Config; use std::fs; - /// CLI Arguments #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -13,8 +12,17 @@ struct Args { fn main() { let args = Args::parse(); - let config_schema = schemars::schema_for!(Config); let config_schema_json = serde_json::to_string_pretty(&config_schema).unwrap(); fs::write(&args.path, config_schema_json).unwrap(); } +#[cfg(test)] +mod tests { + #[test] + fn snap_json_schema() { + use cli::config::Config; + use insta::assert_yaml_snapshot; + let config_schema = schemars::schema_for!(Config); + assert_yaml_snapshot!(config_schema); + } +} diff --git a/schema/src/snapshots/schema__tests__snap_json_schema.snap b/schema/src/snapshots/schema__tests__snap_json_schema.snap new file mode 100644 index 0000000..9b4e197 --- /dev/null +++ b/schema/src/snapshots/schema__tests__snap_json_schema.snap @@ -0,0 +1,283 @@ +--- +source: schema/src/main.rs +expression: config_schema +snapshot_kind: text +--- +$schema: "https://json-schema.org/draft/2020-12/schema" +title: Config +description: Config represents the configuration of commitlint. +type: object +properties: + rules: + description: Rules represents the rules of commitlint. + $ref: "#/$defs/Rules" +required: + - rules +$defs: + BodyEmpty: + description: "[BodyEmpty] represents the [`body-empty`](https://keisukeyamashita.github.io/commitlint-rs/rules/body-empty) rule." + type: object + properties: + level: + description: Level represents the level of the rule. + anyOf: + - $ref: "#/$defs/Level" + - type: "null" + BodyMaxLength: + description: "[BodyMaxLength] represents the [`body-max-length`](https://keisukeyamashita.github.io/commitlint-rs/rules/body-max-length) rule." + type: object + properties: + length: + description: "Length represents the maximum length of the \"body\"." + type: integer + format: uint + minimum: 0 + level: + description: Level represents the level of the rule. + anyOf: + - $ref: "#/$defs/Level" + - type: "null" + required: + - length + DescriptionEmpty: + description: "[DescriptionEmpty] represents the [`description-empty`](https://keisukeyamashita.github.io/commitlint-rs/rules/description-empty) rule." + type: object + properties: + level: + description: Level represents the level of the rule. + anyOf: + - $ref: "#/$defs/Level" + - type: "null" + DescriptionFormat: + description: "[DescriptionFormat] represents the [`description-format`](https://keisukeyamashita.github.io/commitlint-rs/rules/description-format) rule." + type: object + properties: + format: + description: "Format represents the format of the \"description\"." + type: + - string + - "null" + level: + description: Level represents the level of the rule. + anyOf: + - $ref: "#/$defs/Level" + - type: "null" + DescriptionMaxLength: + description: "[DescriptionMaxLength] represents the [`description-max-length`](https://keisukeyamashita.github.io/commitlint-rs/rules/description-max-length) rule." + type: object + properties: + length: + description: "Length represents the maximum length of the \"description\"." + type: integer + format: uint + minimum: 0 + level: + description: Level represents the level of the rule. + anyOf: + - $ref: "#/$defs/Level" + - type: "null" + required: + - length + FootersEmpty: + description: "[FootersEmpty] represents the [`footers-empty`](https://keisukeyamashita.github.io/commitlint-rs/rules/footers-empty) rule." + type: object + properties: + level: + description: Level represents the level of the rule. + anyOf: + - $ref: "#/$defs/Level" + - type: "null" + Level: + description: Level represents the level of a rule. + type: string + enum: + - error + - ignore + - warning + Rules: + description: "Rules represents the rules of commitlint.\n See: https://commitlint.js.org/reference/rules.html" + type: object + properties: + type: + anyOf: + - $ref: "#/$defs/Type" + - type: "null" + body-empty: + anyOf: + - $ref: "#/$defs/BodyEmpty" + - type: "null" + body-max-length: + anyOf: + - $ref: "#/$defs/BodyMaxLength" + - type: "null" + description-empty: + anyOf: + - $ref: "#/$defs/DescriptionEmpty" + - type: "null" + description-format: + anyOf: + - $ref: "#/$defs/DescriptionFormat" + - type: "null" + description-max-length: + anyOf: + - $ref: "#/$defs/DescriptionMaxLength" + - type: "null" + footers-empty: + anyOf: + - $ref: "#/$defs/FootersEmpty" + - type: "null" + scope: + anyOf: + - $ref: "#/$defs/Scope" + - type: "null" + scope-empty: + anyOf: + - $ref: "#/$defs/ScopeEmpty" + - type: "null" + scope-format: + anyOf: + - $ref: "#/$defs/ScopeFormat" + - type: "null" + scope-max-length: + anyOf: + - $ref: "#/$defs/ScopeMaxLength" + - type: "null" + subject-empty: + anyOf: + - $ref: "#/$defs/SubjectEmpty" + - type: "null" + type-empty: + anyOf: + - $ref: "#/$defs/TypeEmpty" + - type: "null" + type-format: + anyOf: + - $ref: "#/$defs/TypeFormat" + - type: "null" + type-max-length: + anyOf: + - $ref: "#/$defs/TypeMaxLength" + - type: "null" + Scope: + description: "[Scope] represents the [`scope`](https://keisukeyamashita.github.io/commitlint-rs/rules/scope) rule." + type: object + properties: + level: + description: Level represents the level of the rule. + anyOf: + - $ref: "#/$defs/Level" + - type: "null" + optional: + description: "Optional scope. If true, even if the scope is not present, it is allowed." + type: boolean + options: + description: "Options represents the options of the rule. If the option is empty, it means that no \"scope\" is allowed." + type: array + items: + type: string + required: + - optional + - options + ScopeEmpty: + description: "[ScopeEmpty] represents the [`scope-empty`](https://keisukeyamashita.github.io/commitlint-rs/rules/scope-empty) rule." + type: object + properties: + level: + description: Level represents the level of the rule. + anyOf: + - $ref: "#/$defs/Level" + - type: "null" + ScopeFormat: + description: "[ScopeFormat] represents the [`scope-format`](https://keisukeyamashita.github.io/commitlint-rs/rules/scope-format) rule." + type: object + properties: + format: + description: "Format represents the format of the \"scope\"." + type: + - string + - "null" + level: + description: Level represents the level of the rule. + anyOf: + - $ref: "#/$defs/Level" + - type: "null" + ScopeMaxLength: + description: "[ScopeMaxLength] represents the [`scope-max-length`](https://keisukeyamashita.github.io/commitlint-rs/rules/scope-max-length) rule." + type: object + properties: + length: + description: "Length represents the maximum length of the \"scope\"." + type: integer + format: uint + minimum: 0 + level: + description: Level represents the level of the rule. + anyOf: + - $ref: "#/$defs/Level" + - type: "null" + required: + - length + SubjectEmpty: + description: "[SubjectEmpty] represents the [`subject-empty`](https://keisukeyamashita.github.io/commitlint-rs/rules/subject-empty) rule." + type: object + properties: + level: + description: Level represents the level of the rule. + anyOf: + - $ref: "#/$defs/Level" + - type: "null" + Type: + description: "[Type] represents the [`type`](https://keisukeyamashita.github.io/commitlint-rs/rules/type) rule." + type: object + properties: + level: + description: Level represents the level of the rule. + anyOf: + - $ref: "#/$defs/Level" + - type: "null" + options: + description: "Options represents the options of the rule. If the option is empty, it means that no \"type\" is allowed." + type: array + items: + type: string + required: + - options + TypeEmpty: + description: "[TypeEmpty] represents the [`type-empty`](https://keisukeyamashita.github.io/commitlint-rs/rules/type-empty) rule." + type: object + properties: + level: + description: Level represents the level of the rule. + anyOf: + - $ref: "#/$defs/Level" + - type: "null" + TypeFormat: + description: "[TypeFormat] represents the [`type-format`](https://keisukeyamashita.github.io/commitlint-rs/rules/type-format) rule." + type: object + properties: + format: + description: "Format represents the format of the \"type\"." + type: + - string + - "null" + level: + description: Level represents the level of the rule. + anyOf: + - $ref: "#/$defs/Level" + - type: "null" + TypeMaxLength: + description: "[TypeMaxLength] represents the [`type-max-length`](https://keisukeyamashita.github.io/commitlint-rs/rules/type-max-length) rule." + type: object + properties: + length: + description: "Length represents the maximum length of the \"type\"." + type: integer + format: uint + minimum: 0 + level: + description: Level represents the level of the rule. + anyOf: + - $ref: "#/$defs/Level" + - type: "null" + required: + - length