diff --git a/Cargo.lock b/Cargo.lock index 15811238e..0e49f6834 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -474,6 +474,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + [[package]] name = "edit-distance" version = "2.1.3" @@ -623,6 +629,12 @@ dependencies = [ "walkdir", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.15.0" @@ -689,6 +701,17 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.7.0" @@ -696,7 +719,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", ] [[package]] @@ -1107,12 +1130,47 @@ dependencies = [ "uriparse", ] +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "semver", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.90", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +dependencies = [ + "serde", +] + [[package]] name = "serde" version = "1.0.217" @@ -1153,6 +1211,17 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "serde_json" version = "1.0.134" @@ -1378,7 +1447,7 @@ version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ - "indexmap", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", @@ -1471,6 +1540,7 @@ dependencies = [ "maplit", "proc-exit", "regex", + "schemars", "serde", "serde-sarif", "serde_json", @@ -1500,7 +1570,7 @@ dependencies = [ "divan", "edit-distance", "heck", - "indexmap", + "indexmap 2.7.0", "itertools 0.14.0", "phf", "snapbox", diff --git a/config.schema.json b/config.schema.json new file mode 100644 index 000000000..ce239ec0d --- /dev/null +++ b/config.schema.json @@ -0,0 +1,330 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "type": "object", + "properties": { + "files": { + "default": { + "extend-exclude": [], + "ignore-dot": null, + "ignore-files": null, + "ignore-global": null, + "ignore-hidden": null, + "ignore-parent": null, + "ignore-vcs": null + }, + "allOf": [ + { + "$ref": "#/definitions/Walk" + } + ] + }, + "default": { + "default": { + "binary": null, + "check-file": null, + "check-filename": null, + "extend-identifiers": {}, + "extend-ignore-identifiers-re": [], + "extend-ignore-re": [], + "extend-ignore-words-re": [], + "extend-words": {}, + "identifier-leading-digits": null, + "ignore-hex": null, + "locale": null, + "unicode": null + }, + "allOf": [ + { + "$ref": "#/definitions/EngineConfig" + } + ] + }, + "type": { + "default": {}, + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/GlobEngineConfig" + } + } + }, + "additionalProperties": false, + "definitions": { + "Walk": { + "type": "object", + "properties": { + "extend-exclude": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "ignore-hidden": { + "description": "Skip hidden files and directories.", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "ignore-files": { + "description": "Respect ignore files.", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "ignore-dot": { + "description": "Respect .ignore files.", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "ignore-vcs": { + "description": "Respect ignore files in vcs directories.", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "ignore-global": { + "description": "Respect global ignore files.", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "ignore-parent": { + "description": "Respect ignore files in parent directories.", + "default": null, + "type": [ + "boolean", + "null" + ] + } + }, + "additionalProperties": false + }, + "EngineConfig": { + "type": "object", + "properties": { + "binary": { + "description": "Check binary files.", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "check-filename": { + "description": "Verifying spelling in file names.", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "check-file": { + "description": "Verifying spelling in files.", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "extend-ignore-re": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "unicode": { + "description": "Allow unicode characters in identifiers (and not just ASCII)", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "ignore-hex": { + "description": "Do not check identifiers that appear to be hexadecimal values.", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "identifier-leading-digits": { + "description": "Allow identifiers to start with digits, in addition to letters.", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "locale": { + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/Locale" + }, + { + "type": "null" + } + ] + }, + "extend-ignore-identifiers-re": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "extend-identifiers": { + "default": {}, + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "extend-ignore-words-re": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "extend-words": { + "default": {}, + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "Locale": { + "type": "string", + "enum": [ + "en", + "en-us", + "en-gb", + "en-ca", + "en-au" + ] + }, + "GlobEngineConfig": { + "type": "object", + "properties": { + "extend-glob": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "binary": { + "description": "Check binary files.", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "check-filename": { + "description": "Verifying spelling in file names.", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "check-file": { + "description": "Verifying spelling in files.", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "extend-ignore-re": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "unicode": { + "description": "Allow unicode characters in identifiers (and not just ASCII)", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "ignore-hex": { + "description": "Do not check identifiers that appear to be hexadecimal values.", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "identifier-leading-digits": { + "description": "Allow identifiers to start with digits, in addition to letters.", + "default": null, + "type": [ + "boolean", + "null" + ] + }, + "locale": { + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/Locale" + }, + { + "type": "null" + } + ] + }, + "extend-ignore-identifiers-re": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "extend-identifiers": { + "default": {}, + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "extend-ignore-words-re": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "extend-words": { + "default": {}, + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/crates/typos-cli/Cargo.toml b/crates/typos-cli/Cargo.toml index 02df268f1..3f3d35a13 100644 --- a/crates/typos-cli/Cargo.toml +++ b/crates/typos-cli/Cargo.toml @@ -33,7 +33,7 @@ pre-release-replacements = [ default = ["dict", "vars"] dict = ["dep:typos-dict"] vars = ["dep:typos-vars"] - +unstable-schema = ["dep:schemars"] [[bin]] name = "typos" @@ -78,6 +78,7 @@ serde_regex = "1.1.0" regex = "1.10.4" encoding_rs = "0.8.34" serde-sarif = "0.7.0" +schemars = { version = "0.8.21", features = ["preserve_order","semver"], optional = true } [dev-dependencies] assert_fs = "1.1" diff --git a/crates/typos-cli/src/config.rs b/crates/typos-cli/src/config.rs index 34a19946e..ab301a2d0 100644 --- a/crates/typos-cli/src/config.rs +++ b/crates/typos-cli/src/config.rs @@ -1,3 +1,5 @@ +#![allow(unused_qualifications)] // schemars + use std::collections::HashMap; use kstring::KString; @@ -19,6 +21,7 @@ const PYPROJECT_TOML: &str = "pyproject.toml"; #[serde(deny_unknown_fields)] #[serde(default)] #[serde(rename_all = "kebab-case")] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct Config { pub files: Walk, pub default: EngineConfig, @@ -142,6 +145,7 @@ impl Config { #[serde(deny_unknown_fields)] #[serde(default)] #[serde(rename_all = "kebab-case")] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct Walk { pub extend_exclude: Vec, /// Skip hidden files and directories. @@ -232,7 +236,12 @@ impl Walk { #[serde(deny_unknown_fields)] #[serde(default)] #[serde(transparent)] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TypeEngineConfig { + #[cfg_attr( + feature = "unstable-schema", + schemars(schema_with = "hashmap_string_t::") + )] pub patterns: HashMap, } @@ -301,7 +310,9 @@ impl TypeEngineConfig { //#[serde(deny_unknown_fields)] // Doesn't work with `flatten` #[serde(default)] #[serde(rename_all = "kebab-case")] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct GlobEngineConfig { + #[cfg_attr(feature = "unstable-schema", schemars(schema_with = "vec_string"))] pub extend_glob: Vec, #[serde(flatten)] pub engine: EngineConfig, @@ -318,6 +329,7 @@ impl GlobEngineConfig { //#[serde(deny_unknown_fields)] // Doesn't work with `flatten` #[serde(default)] #[serde(rename_all = "kebab-case")] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct EngineConfig { /// Check binary files. pub binary: Option, @@ -330,6 +342,7 @@ pub struct EngineConfig { #[serde(flatten)] pub dict: DictConfig, #[serde(with = "serde_regex")] + #[cfg_attr(feature = "unstable-schema", schemars(schema_with = "vec_string"))] pub extend_ignore_re: Vec, } @@ -400,6 +413,7 @@ impl Eq for EngineConfig {} #[serde(deny_unknown_fields)] #[serde(default)] #[serde(rename_all = "kebab-case")] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct TokenizerConfig { /// Allow unicode characters in identifiers (and not just ASCII) pub unicode: Option, @@ -448,13 +462,24 @@ impl TokenizerConfig { #[serde(deny_unknown_fields)] #[serde(default)] #[serde(rename_all = "kebab-case")] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct DictConfig { pub locale: Option, #[serde(with = "serde_regex")] + #[cfg_attr(feature = "unstable-schema", schemars(schema_with = "vec_string"))] pub extend_ignore_identifiers_re: Vec, + #[cfg_attr( + feature = "unstable-schema", + schemars(schema_with = "hashmap_string_string") + )] pub extend_identifiers: HashMap, #[serde(with = "serde_regex")] + #[cfg_attr(feature = "unstable-schema", schemars(schema_with = "vec_string"))] pub extend_ignore_words_re: Vec, + #[cfg_attr( + feature = "unstable-schema", + schemars(schema_with = "hashmap_string_string") + )] pub extend_words: HashMap, } @@ -554,6 +579,7 @@ impl Eq for DictConfig {} #[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "kebab-case")] #[derive(Default)] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub enum Locale { #[default] En, @@ -606,10 +632,38 @@ impl std::fmt::Display for Locale { } } +#[cfg(feature = "unstable-schema")] +fn vec_string(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + type Type = Vec; + ::json_schema(gen) +} + +#[cfg(feature = "unstable-schema")] +fn hashmap_string_string(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + type Type = HashMap; + ::json_schema(gen) +} + +#[cfg(feature = "unstable-schema")] +fn hashmap_string_t( + gen: &mut schemars::gen::SchemaGenerator, +) -> schemars::schema::Schema { + type Type = HashMap; + as schemars::JsonSchema>::json_schema(gen) +} + #[cfg(test)] mod test { use super::*; + #[cfg(feature = "unstable-schema")] + #[test] + fn dump_schema() { + let schema = schemars::schema_for!(Config); + let dump = serde_json::to_string_pretty(&schema).unwrap(); + snapbox::assert_data_eq!(dump, snapbox::file!("../../../config.schema.json").raw()); + } + #[test] fn test_from_defaults() { let null = Config::default();