diff --git a/Cargo.lock b/Cargo.lock index 8477a101..a91e0ec4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -363,20 +363,22 @@ checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591" [[package]] name = "eipw" -version = "0.4.1" +version = "0.5.0" dependencies = [ "annotate-snippets", "clap", "eipw-lint", "getrandom", + "serde", "serde_json", "thiserror", "tokio", + "toml", ] [[package]] name = "eipw-lint" -version = "0.4.1" +version = "0.5.0" dependencies = [ "annotate-snippets", "assert_matches", @@ -390,12 +392,13 @@ dependencies = [ "serde_json", "snafu", "tokio", + "toml", "url", ] [[package]] name = "eipw-lint-js" -version = "0.4.9" +version = "0.5.0" dependencies = [ "annotate-snippets", "console_error_panic_hook", @@ -429,6 +432,12 @@ dependencies = [ "syn 2.0.22", ] +[[package]] +name = "equivalent" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" + [[package]] name = "errno" version = "0.3.1" @@ -517,6 +526,12 @@ version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "heck" version = "0.4.1" @@ -562,6 +577,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -1151,6 +1176,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + [[package]] name = "servo_arc" version = "0.3.0" @@ -1359,6 +1393,40 @@ dependencies = [ "syn 2.0.22", ] +[[package]] +name = "toml" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebafdf5ad1220cb59e7d17cf4d2c72015297b75b19a10472f99b89225089240" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "typed-arena" version = "2.0.2" @@ -1606,3 +1674,12 @@ name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index 91a20be2..f8524e57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = [ "eipw-lint", "eipw-lint-js" ] [package] name = "eipw" description = "Ethereum Improvement Proposal linter that's one more than eipv" -version = "0.4.1" +version = "0.5.0" edition = "2021" license = "MPL-2.0" rust-version = "1.65" @@ -16,9 +16,11 @@ repository = "https://github.com/ethereum/eipw" annotate-snippets = "0.9.1" tokio = { version = "1.29.0", features = [ "macros" ] } clap = { version = "4.3.9", features = [ "derive" ] } -eipw-lint = { version = "0.4.1", path = "eipw-lint", features = [ "tokio" ] } +eipw-lint = { version = "0.5.0", path = "eipw-lint", features = [ "tokio" ] } serde_json = "1.0.99" thiserror = "1.0.40" +toml = "0.7.5" +serde = { version = "1.0.164", features = [ "derive" ] } [target.'cfg(target_arch = "wasm32")'.dependencies] tokio = { version = "1.29.0", features = [ "macros", "rt" ] } diff --git a/eipw-lint-js/Cargo.toml b/eipw-lint-js/Cargo.toml index 6f594234..ed2a9f4e 100644 --- a/eipw-lint-js/Cargo.toml +++ b/eipw-lint-js/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "eipw-lint-js" -version = "0.4.9" +version = "0.5.0" edition = "2021" license = "MPL-2.0" rust-version = "1.65" @@ -23,7 +23,7 @@ wasm-bindgen = { version = "0.2.87", features = [ "serde-serialize" ] } serde-wasm-bindgen = "0.5" wasm-bindgen-futures = "0.4.37" console_error_panic_hook = { version = "0.1.7", optional = true } -eipw-lint = { version = "0.4.1", path = "../eipw-lint" } +eipw-lint = { version = "0.5.0", path = "../eipw-lint" } js-sys = "0.3.64" serde_json = "1.0.99" serde = { version = "1.0", features = [ "derive" ] } diff --git a/eipw-lint-js/src/lib.rs b/eipw-lint-js/src/lib.rs index 3532b337..2a6bb871 100644 --- a/eipw-lint-js/src/lib.rs +++ b/eipw-lint-js/src/lib.rs @@ -5,6 +5,7 @@ */ use eipw_lint::fetch::Fetch; +use eipw_lint::lints::{DefaultLint, Lint}; use eipw_lint::reporters::{AdditionalHelp, Json}; use eipw_lint::{default_lints, Linter}; @@ -76,17 +77,20 @@ struct Opts { #[serde(default)] deny: Vec, + + #[serde(default)] + default_lints: Option>>, } impl Opts { - fn apply(self, mut linter: Linter) -> Linter { - for allow in self.allow { - linter = linter.allow(&allow); + fn apply<'a, 'b: 'a, R>(&'a self, mut linter: Linter<'b, R>) -> Linter<'a, R> { + for allow in &self.allow { + linter = linter.allow(allow); } if !self.warn.is_empty() { let mut lints: HashMap<_, _> = default_lints().collect(); - for warn in self.warn { + for warn in &self.warn { let (k, v) = lints.remove_entry(warn.as_str()).unwrap(); linter = linter.warn(k, v); } @@ -94,7 +98,7 @@ impl Opts { if !self.deny.is_empty() { let mut lints: HashMap<_, _> = default_lints().collect(); - for deny in self.deny { + for deny in &self.deny { let (k, v) = lints.remove_entry(deny.as_str()).unwrap(); linter = linter.deny(k, v); } @@ -116,13 +120,30 @@ pub async fn lint(sources: Vec, options: Option) -> Result)), + ); + } else { + linter = Linter::new(reporter); + } + linter = opts.apply(linter); + } else { + linter = Linter::new(reporter); } + linter = linter.set_fetch(NodeFetch); + for source in &sources { linter = linter.check_file(source); } diff --git a/eipw-lint-js/tests/main.rs b/eipw-lint-js/tests/main.rs index 92099feb..b8e68e2d 100644 --- a/eipw-lint-js/tests/main.rs +++ b/eipw-lint-js/tests/main.rs @@ -231,6 +231,89 @@ async fn lint_one_with_options() { assert_eq!(expected, actual); } +#[wasm_bindgen_test] +async fn lint_one_with_default_lints() { + let mut path = PathBuf::from("tests"); + path.push("eips"); + path.push("eip-1000.md"); + + let path = path.to_str().unwrap(); + + let opts = json!( + { + "default_lints": { + "banana": { + "kind": "preamble-regex", + "name": "requires", + "mode": "includes", + "pattern": "banana", + "message": "requires must include banana" + } + } + } + ); + + let opts_js = opts + .serialize(&serde_wasm_bindgen::Serializer::json_compatible()) + .unwrap(); + let opts = Object::try_from(&opts_js).unwrap().to_owned(); + + let result = lint(vec![JsValue::from_str(path)], Some(opts)) + .await + .ok() + .unwrap(); + + let actual: serde_json::Value = serde_wasm_bindgen::from_value(result).unwrap(); + let expected = json! { + [ + { + "formatted": "error[banana]: requires must include banana\n --> tests/eips/eip-1000.md:12:10\n |\n12 | requires: 20\n | ^^^ required pattern was not matched\n |\n = info: the pattern in question: `banana`\n = help: see https://ethereum.github.io/eipw/banana/", + "footer": [ + { + "annotation_type": "Info", + "id": null, + "label": "the pattern in question: `banana`" + }, + { + "annotation_type": "Help", + "id": null, + "label": "see https://ethereum.github.io/eipw/banana/" + } + ], + "opt": { + "anonymized_line_numbers": false, + "color": false + }, + "slices": [ + { + "annotations": [ + { + "annotation_type": "Error", + "label": "required pattern was not matched", + "range": [ + 9, + 12 + ] + } + ], + "fold": false, + "line_start": 12, + "origin": "tests/eips/eip-1000.md", + "source": "requires: 20" + } + ], + "title": { + "annotation_type": "Error", + "id": "banana", + "label": "requires must include banana" + } + } + ] + }; + + assert_eq!(expected, actual); +} + #[wasm_bindgen_test] async fn format_one() { let mut path = PathBuf::from("tests"); diff --git a/eipw-lint/Cargo.toml b/eipw-lint/Cargo.toml index e9752345..ab103f3c 100644 --- a/eipw-lint/Cargo.toml +++ b/eipw-lint/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "eipw-lint" -version = "0.4.1" +version = "0.5.0" edition = "2021" license = "MPL-2.0" rust-version = "1.65" @@ -31,3 +31,4 @@ tokio = { version = "1.29.0", features = [ "fs", "macros", "rt" ] } [dev-dependencies] assert_matches = "1.5.0" tokio = { version = "1.29.0", features = [ "macros", "rt" ] } +toml = "0.7.5" diff --git a/eipw-lint/src/lib.rs b/eipw-lint/src/lib.rs index 62626937..d120f906 100644 --- a/eipw-lint/src/lib.rs +++ b/eipw-lint/src/lib.rs @@ -17,7 +17,7 @@ use comrak::arena_tree::Node; use comrak::nodes::Ast; use comrak::{Arena, ComrakExtensionOptions, ComrakOptions}; -use crate::lints::{Context, Error as LintError, FetchContext, InnerContext, Lint, LintExt as _}; +use crate::lints::{Context, DefaultLint, Error as LintError, FetchContext, InnerContext, Lint}; use crate::modifiers::Modifier; use crate::preamble::Preamble; use crate::reporters::Reporter; @@ -69,6 +69,11 @@ fn default_modifiers() -> Vec> { } pub fn default_lints() -> impl Iterator)> { + default_lints_enum().map(|(name, lint)| (name, lint.boxed())) +} + +fn default_lints_enum() -> impl Iterator)> { + use self::DefaultLint::*; use lints::preamble::regex; use lints::{markdown, preamble}; @@ -76,85 +81,85 @@ pub fn default_lints() -> impl Iterator)> { // // Preamble // - ("preamble-no-dup", preamble::NoDuplicates.boxed()), - ("preamble-trim", preamble::Trim.boxed()), - ("preamble-eip", preamble::Uint("eip").boxed()), - ("preamble-author", preamble::Author("author").boxed()), - ("preamble-re-title", preamble::Regex { + ("preamble-no-dup", PreambleNoDuplicates(preamble::NoDuplicates)), + ("preamble-trim", PreambleTrim(preamble::Trim)), + ("preamble-eip", PreambleUint { name: preamble::Uint("eip") }), + ("preamble-author", PreambleAuthor { name: preamble::Author("author") } ), + ("preamble-re-title", PreambleRegex(preamble::Regex { name: "title", mode: regex::Mode::Excludes, pattern: r"(?i)standar\w*\b", message: "preamble header `title` should not contain `standard` (or similar words.)", - }.boxed()), - ("preamble-re-title-colon", preamble::Regex { + })), + ("preamble-re-title-colon", PreambleRegex(preamble::Regex { name: "title", mode: regex::Mode::Excludes, pattern: r":", message: "preamble header `title` should not contain `:`", - }.boxed()), + })), ( "preamble-refs-title", - preamble::ProposalRef("title").boxed(), + PreambleProposalRef { name: preamble::ProposalRef("title") }, ), ( "preamble-refs-description", - preamble::ProposalRef("description").boxed(), + PreambleProposalRef { name: preamble::ProposalRef("description") }, ), ( "preamble-re-title-erc-dash", - preamble::Regex { + PreambleRegex(preamble::Regex { name: "title", mode: regex::Mode::Excludes, pattern: r"(?i)erc[\s]*[0-9]+", message: "proposals must be referenced with the form `ERC-N` (not `ERCN` or `ERC N`)", - }.boxed(), + }), ), ( "preamble-re-title-eip-dash", - preamble::Regex { + PreambleRegex(preamble::Regex { name: "title", mode: regex::Mode::Excludes, pattern: r"(?i)eip[\s]*[0-9]+", message: "proposals must be referenced with the form `EIP-N` (not `EIPN` or `EIP N`)", - }.boxed(), + }), ), ( "preamble-re-description-erc-dash", - preamble::Regex { + PreambleRegex(preamble::Regex { name: "description", mode: regex::Mode::Excludes, pattern: r"(?i)erc[\s]*[0-9]+", message: "proposals must be referenced with the form `ERC-N` (not `ERCN` or `ERC N`)", - }.boxed(), + }), ), ( "preamble-re-description-eip-dash", - preamble::Regex { + PreambleRegex(preamble::Regex { name: "description", mode: regex::Mode::Excludes, pattern: r"(?i)eip[\s]*[0-9]+", message: "proposals must be referenced with the form `EIP-N` (not `EIPN` or `EIP N`)", - }.boxed(), + }), ), - ("preamble-re-description", preamble::Regex { + ("preamble-re-description", PreambleRegex(preamble::Regex { name: "description", mode: regex::Mode::Excludes, pattern: r"(?i)standar\w*\b", message: "preamble header `description` should not contain `standard` (or similar words.)", - }.boxed()), - ("preamble-re-description-colon", preamble::Regex { + })), + ("preamble-re-description-colon", PreambleRegex(preamble::Regex { name: "description", mode: regex::Mode::Excludes, pattern: r":", message: "preamble header `description` should not contain `:`", - }.boxed()), + })), ( "preamble-discussions-to", - preamble::Url("discussions-to").boxed(), + PreambleUrl { name: preamble::Url("discussions-to") }, ), ( "preamble-re-discussions-to", - preamble::Regex { + PreambleRegex(preamble::Regex { name: "discussions-to", mode: regex::Mode::Includes, pattern: "^https://ethereum-magicians.org/t/[^/]+/[0-9]+$", @@ -162,44 +167,44 @@ pub fn default_lints() -> impl Iterator)> { "preamble header `discussions-to` should ", "point to a thread on ethereum-magicians.org" ), - }.boxed(), + }), ), - ("preamble-list-author", preamble::List("author").boxed()), - ("preamble-list-requires", preamble::List("requires").boxed()), + ("preamble-list-author", PreambleList { name: preamble::List("author") }), + ("preamble-list-requires", PreambleList{name: preamble::List("requires")}), ( "preamble-len-requires", - preamble::Length { + PreambleLength(preamble::Length { name: "requires", min: Some(1), max: None, } - .boxed(), + ), ), ( "preamble-uint-requires", - preamble::UintList("requires").boxed(), + PreambleUintList { name: preamble::UintList("requires") }, ), ( "preamble-len-title", - preamble::Length { + PreambleLength(preamble::Length { name: "title", min: Some(2), max: Some(44), } - .boxed(), + ), ), ( "preamble-len-description", - preamble::Length { + PreambleLength(preamble::Length { name: "description", min: Some(2), max: Some(140), } - .boxed(), + ), ), ( "preamble-req", - preamble::Required(&[ + PreambleRequired { names: preamble::Required(vec![ "eip", "title", "description", @@ -209,11 +214,11 @@ pub fn default_lints() -> impl Iterator)> { "type", "created", ]) - .boxed(), + }, ), ( "preamble-order", - preamble::Order(&[ + PreambleOrder { names: preamble::Order(vec![ "eip", "title", "description", @@ -227,45 +232,45 @@ pub fn default_lints() -> impl Iterator)> { "requires", "withdrawal-reason", ]) - .boxed(), + }, ), - ("preamble-date-created", preamble::Date("created").boxed()), + ("preamble-date-created", PreambleDate { name: preamble::Date("created") } ), ( "preamble-req-last-call-deadline", - preamble::RequiredIfEq { + PreambleRequiredIfEq(preamble::RequiredIfEq { when: "status", equals: "Last Call", then: "last-call-deadline", } - .boxed(), + ), ), ( "preamble-date-last-call-deadline", - preamble::Date("last-call-deadline").boxed(), + PreambleDate { name: preamble::Date("last-call-deadline") }, ), ( "preamble-req-category", - preamble::RequiredIfEq { + PreambleRequiredIfEq(preamble::RequiredIfEq { when: "type", equals: "Standards Track", then: "category", } - .boxed(), + ), ), ( "preamble-req-withdrawal-reason", - preamble::RequiredIfEq { + PreambleRequiredIfEq(preamble::RequiredIfEq { when: "status", equals: "Withdrawn", then: "withdrawal-reason", } - .boxed(), + ), ), ( "preamble-enum-status", - preamble::OneOf { + PreambleOneOf(preamble::OneOf { name: "status", - values: &[ + values: vec![ "Draft", "Review", "Last Call", @@ -275,125 +280,128 @@ pub fn default_lints() -> impl Iterator)> { "Living", ], } - .boxed(), + ), ), ( "preamble-enum-type", - preamble::OneOf { + PreambleOneOf(preamble::OneOf { name: "type", - values: &["Standards Track", "Meta", "Informational"], + values: vec!["Standards Track", "Meta", "Informational"], } - .boxed(), + ), ), ( "preamble-enum-category", - preamble::OneOf { + PreambleOneOf(preamble::OneOf { name: "category", - values: &["Core", "Networking", "Interface", "ERC"], + values: vec!["Core", "Networking", "Interface", "ERC"], } - .boxed(), + ), ), ( "preamble-requires-status", - preamble::RequiresStatus { + PreambleRequiresStatus(preamble::RequiresStatus { requires: "requires", status: "status", - flow: &[ - &["Draft", "Stagnant"], - &["Review"], - &["Last Call"], - &["Final", "Withdrawn", "Living"], + flow: vec![ + vec!["Draft", "Stagnant"], + vec!["Review"], + vec!["Last Call"], + vec!["Final", "Withdrawn", "Living"], ] - }.boxed(), + }), ), ( "preamble-requires-ref-title", - preamble::RequireReferenced { + PreambleRequireReferenced(preamble::RequireReferenced { name: "title", requires: "requires", - }.boxed(), + }), ), ( "preamble-requires-ref-description", - preamble::RequireReferenced { + PreambleRequireReferenced(preamble::RequireReferenced { name: "description", requires: "requires", - }.boxed(), + }), ), ( "preamble-file-name", - preamble::FileName { + PreambleFileName(preamble::FileName { name: "eip", prefix: "eip-", suffix: ".md", - }.boxed(), + }), ), // // Markdown // ( "markdown-refs", - markdown::ProposalRef.boxed(), + MarkdownProposalRef(markdown::ProposalRef), ), ( "markdown-html-comments", - markdown::HtmlComments { + MarkdownHtmlComments(markdown::HtmlComments { name: "status", - warn_for: &[ + warn_for: vec![ "Draft", "Withdrawn", ], } - .boxed(), + ), ), ( "markdown-req-section", - markdown::SectionRequired(&[ + MarkdownSectionRequired { sections: markdown::SectionRequired(vec![ "Abstract", "Specification", "Rationale", "Security Considerations", "Copyright", ]) - .boxed(), + }, ), ( "markdown-order-section", - markdown::SectionOrder(&[ - "Abstract", - "Motivation", - "Specification", - "Rationale", - "Backwards Compatibility", - "Test Cases", - "Reference Implementation", - "Security Considerations", - "Copyright", - ]) - .boxed(), + MarkdownSectionOrder { + sections: markdown::SectionOrder(vec![ + "Abstract", + "Motivation", + "Specification", + "Rationale", + "Backwards Compatibility", + "Test Cases", + "Reference Implementation", + "Security Considerations", + "Copyright", + ]) + }, ), ( "markdown-re-erc-dash", - markdown::Regex { + MarkdownRegex(markdown::Regex { mode: markdown::regex::Mode::Excludes, pattern: r"(?i)erc[\s]*[0-9]+", message: "proposals must be referenced with the form `ERC-N` (not `ERCN` or `ERC N`)", - }.boxed(), + }), ), ( "markdown-re-eip-dash", - markdown::Regex { + MarkdownRegex(markdown::Regex { mode: markdown::regex::Mode::Excludes, pattern: r"(?i)eip[\s]*[0-9]+", message: "proposals must be referenced with the form `EIP-N` (not `EIPN` or `EIP N`)", - }.boxed(), + }), ), ( "markdown-link-first", - markdown::LinkFirst(r"(?i)(?:eip|erc)-[0-9]+").boxed(), + MarkdownLinkFirst { + pattern: markdown::LinkFirst(r"(?i)(?:eip|erc)-[0-9]+"), + } ), - ("markdown-rel-links", markdown::RelativeLinks { - exceptions: &[ + ("markdown-rel-links", MarkdownRelativeLinks(markdown::RelativeLinks { + exceptions: vec![ "^https://(www\\.)?github\\.com/ethereum/consensus-specs/blob/[a-f0-9]{40}/.+$", "^https://(www\\.)?github\\.com/ethereum/consensus-specs/commit/[a-f0-9]{40}$", @@ -406,23 +414,23 @@ pub fn default_lints() -> impl Iterator)> { "^https://[a-z]*\\.spec\\.whatwg\\.org/commit-snapshots/[0-9a-f]{40}/$", "^https://www\\.rfc-editor\\.org/rfc/.*$", ] - }.boxed()), + })), ( "markdown-link-status", - markdown::LinkStatus { + MarkdownLinkStatus(markdown::LinkStatus { status: "status", - flow: &[ - &["Draft", "Stagnant"], - &["Review"], - &["Last Call"], - &["Final", "Withdrawn", "Living"], + flow: vec![ + vec!["Draft", "Stagnant"], + vec!["Review"], + vec!["Last Call"], + vec!["Final", "Withdrawn", "Living"], ] - }.boxed(), + }), ), ( "markdown-json-cite", - markdown::JsonSchema { - additional_schemas: &[ + MarkdownJsonSchema(markdown::JsonSchema { + additional_schemas: vec![ ( "https://resource.citationstyles.org/schema/v1.0/input/json/csl-data.json", include_str!("lints/markdown/json_schema/csl-data.json"), @@ -435,7 +443,7 @@ pub fn default_lints() -> impl Iterator)> { "master/eipw-lint/src/lints/markdown/", "json_schema/citation.json", ), - }.boxed(), + }), ), ] .into_iter() @@ -509,18 +517,23 @@ where } impl<'a, R> Linter<'a, R> { - pub fn new(reporter: R) -> Self { + pub fn with_lints<'b: 'a>( + reporter: R, + lints: impl Iterator)>, + ) -> Self { Self { reporter, sources: Default::default(), fetch: Box::::default(), modifiers: default_modifiers(), - lints: default_lints() - .map(|(slug, lint)| (slug, (None, lint))) - .collect(), + lints: lints.map(|(slug, lint)| (slug, (None, lint))).collect(), } } + pub fn new(reporter: R) -> Self { + Self::with_lints(reporter, default_lints()) + } + pub fn warn(self, slug: &'a str, lint: T) -> Self where T: 'static + Lint, @@ -547,7 +560,7 @@ impl<'a, R> Linter<'a, R> { where T: 'static + Lint, { - self.lints.insert(slug, (level, lint.boxed())); + self.lints.insert(slug, (level, Box::new(lint))); self } @@ -839,3 +852,17 @@ fn process<'r, 'a>( origin, })) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialize_deserialize() { + type DefaultLints = HashMap>; + let config: DefaultLints<&str> = default_lints_enum().collect(); + + let serialized = toml::to_string_pretty(&config).unwrap(); + toml::from_str::>(&serialized).unwrap(); + } +} diff --git a/eipw-lint/src/lints.rs b/eipw-lint/src/lints.rs index 79bc76d2..7ccbb605 100644 --- a/eipw-lint/src/lints.rs +++ b/eipw-lint/src/lints.rs @@ -4,6 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +mod known_lints; pub mod markdown; pub mod preamble; @@ -16,6 +17,8 @@ use crate::reporters::{self, Reporter}; use educe::Educe; +pub use self::known_lints::DefaultLint; + use snafu::Snafu; use std::cell::RefCell; @@ -198,16 +201,3 @@ impl Lint for Box { lint.lint(slug, ctx) } } - -pub(crate) trait LintExt: Lint { - fn boxed(self) -> Box; -} - -impl LintExt for T -where - T: 'static + Lint, -{ - fn boxed(self) -> Box { - Box::new(self) as Box - } -} diff --git a/eipw-lint/src/lints/known_lints.rs b/eipw-lint/src/lints/known_lints.rs new file mode 100644 index 00000000..a498afd4 --- /dev/null +++ b/eipw-lint/src/lints/known_lints.rs @@ -0,0 +1,298 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use crate::{Context, FetchContext}; + +use serde::{Deserialize, Serialize}; + +use std::fmt::{Debug, Display}; + +use super::{markdown, preamble, Lint}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "kind", rename_all = "kebab-case")] +#[non_exhaustive] +pub enum DefaultLint { + PreambleAuthor { + name: preamble::Author, + }, + PreambleDate { + name: preamble::Date, + }, + PreambleFileName(preamble::FileName), + PreambleLength(preamble::Length), + PreambleList { + name: preamble::List, + }, + PreambleNoDuplicates(preamble::NoDuplicates), + PreambleOneOf(preamble::OneOf), + PreambleOrder { + names: preamble::Order, + }, + PreambleProposalRef { + name: preamble::ProposalRef, + }, + PreambleRegex(preamble::Regex), + PreambleRequireReferenced(preamble::RequireReferenced), + PreambleRequired { + names: preamble::Required, + }, + PreambleRequiredIfEq(preamble::RequiredIfEq), + PreambleRequiresStatus(preamble::RequiresStatus), + PreambleTrim(preamble::Trim), + PreambleUint { + name: preamble::Uint, + }, + PreambleUintList { + name: preamble::UintList, + }, + PreambleUrl { + name: preamble::Url, + }, + + MarkdownHtmlComments(markdown::HtmlComments), + MarkdownJsonSchema(markdown::JsonSchema), + MarkdownLinkFirst { + pattern: markdown::LinkFirst, + }, + MarkdownLinkStatus(markdown::LinkStatus), + MarkdownProposalRef(markdown::ProposalRef), + MarkdownRegex(markdown::Regex), + MarkdownRelativeLinks(markdown::RelativeLinks), + MarkdownSectionOrder { + sections: markdown::SectionOrder, + }, + MarkdownSectionRequired { + sections: markdown::SectionRequired, + }, +} + +impl DefaultLint +where + S: 'static + + Display + + Debug + + AsRef + + Clone + + PartialEq + + for<'eq> PartialEq<&'eq str>, +{ + pub(crate) fn boxed(self) -> Box { + match self { + Self::PreambleAuthor { name } => Box::new(name), + Self::PreambleDate { name } => Box::new(name), + Self::PreambleFileName(l) => Box::new(l), + Self::PreambleLength(l) => Box::new(l), + Self::PreambleList { name } => Box::new(name), + Self::PreambleNoDuplicates(l) => Box::new(l), + Self::PreambleOneOf(l) => Box::new(l), + Self::PreambleOrder { names } => Box::new(names), + Self::PreambleProposalRef { name } => Box::new(name), + Self::PreambleRegex(l) => Box::new(l), + Self::PreambleRequireReferenced(l) => Box::new(l), + Self::PreambleRequired { names } => Box::new(names), + Self::PreambleRequiredIfEq(l) => Box::new(l), + Self::PreambleRequiresStatus(l) => Box::new(l), + Self::PreambleTrim(l) => Box::new(l), + Self::PreambleUint { name } => Box::new(name), + Self::PreambleUintList { name } => Box::new(name), + Self::PreambleUrl { name } => Box::new(name), + + Self::MarkdownHtmlComments(l) => Box::new(l), + Self::MarkdownJsonSchema(l) => Box::new(l), + Self::MarkdownLinkFirst { pattern } => Box::new(pattern), + Self::MarkdownLinkStatus(l) => Box::new(l), + Self::MarkdownProposalRef(l) => Box::new(l), + Self::MarkdownRegex(l) => Box::new(l), + Self::MarkdownRelativeLinks(l) => Box::new(l), + Self::MarkdownSectionOrder { sections } => Box::new(sections), + Self::MarkdownSectionRequired { sections } => Box::new(sections), + } + } +} + +impl DefaultLint +where + S: Display + Debug + AsRef + Clone + PartialEq + for<'eq> PartialEq<&'eq str>, +{ + pub(crate) fn as_inner(&self) -> &dyn Lint { + match self { + Self::PreambleAuthor { name } => name, + Self::PreambleDate { name } => name, + Self::PreambleFileName(l) => l, + Self::PreambleLength(l) => l, + Self::PreambleList { name } => name, + Self::PreambleNoDuplicates(l) => l, + Self::PreambleOneOf(l) => l, + Self::PreambleOrder { names } => names, + Self::PreambleProposalRef { name } => name, + Self::PreambleRegex(l) => l, + Self::PreambleRequireReferenced(l) => l, + Self::PreambleRequired { names } => names, + Self::PreambleRequiredIfEq(l) => l, + Self::PreambleRequiresStatus(l) => l, + Self::PreambleTrim(l) => l, + Self::PreambleUint { name } => name, + Self::PreambleUintList { name } => name, + Self::PreambleUrl { name } => name, + + Self::MarkdownHtmlComments(l) => l, + Self::MarkdownJsonSchema(l) => l, + Self::MarkdownLinkFirst { pattern } => pattern, + Self::MarkdownLinkStatus(l) => l, + Self::MarkdownProposalRef(l) => l, + Self::MarkdownRegex(l) => l, + Self::MarkdownRelativeLinks(l) => l, + Self::MarkdownSectionOrder { sections } => sections, + Self::MarkdownSectionRequired { sections } => sections, + } + } +} + +impl DefaultLint +where + S: AsRef, +{ + pub(crate) fn map_to_str(&self) -> DefaultLint<&str> { + match self { + Self::PreambleAuthor { name } => DefaultLint::PreambleAuthor { + name: preamble::Author(name.0.as_ref()), + }, + Self::PreambleDate { name } => DefaultLint::PreambleDate { + name: preamble::Date(name.0.as_ref()), + }, + Self::PreambleFileName(l) => DefaultLint::PreambleFileName(preamble::FileName { + name: l.name.as_ref(), + prefix: l.prefix.as_ref(), + suffix: l.suffix.as_ref(), + }), + Self::PreambleLength(l) => DefaultLint::PreambleLength(preamble::Length { + max: l.max, + min: l.min, + name: l.name.as_ref(), + }), + Self::PreambleList { name } => DefaultLint::PreambleList { + name: preamble::List(name.0.as_ref()), + }, + Self::PreambleNoDuplicates(_) => { + DefaultLint::PreambleNoDuplicates(preamble::NoDuplicates) + } + Self::PreambleOneOf(l) => DefaultLint::PreambleOneOf(preamble::OneOf { + name: l.name.as_ref(), + values: l.values.iter().map(AsRef::as_ref).collect(), + }), + Self::PreambleOrder { names } => DefaultLint::PreambleOrder { + names: preamble::Order(names.0.iter().map(AsRef::as_ref).collect()), + }, + Self::PreambleProposalRef { name } => DefaultLint::PreambleProposalRef { + name: preamble::ProposalRef(name.0.as_ref()), + }, + Self::PreambleRegex(l) => DefaultLint::PreambleRegex(preamble::Regex { + message: l.message.as_ref(), + mode: l.mode, + name: l.name.as_ref(), + pattern: l.pattern.as_ref(), + }), + Self::PreambleRequireReferenced(l) => { + DefaultLint::PreambleRequireReferenced(preamble::RequireReferenced { + name: l.name.as_ref(), + requires: l.requires.as_ref(), + }) + } + Self::PreambleRequired { names } => DefaultLint::PreambleRequired { + names: preamble::Required(names.0.iter().map(AsRef::as_ref).collect()), + }, + Self::PreambleRequiredIfEq(l) => { + DefaultLint::PreambleRequiredIfEq(preamble::RequiredIfEq { + equals: l.equals.as_ref(), + then: l.then.as_ref(), + when: l.when.as_ref(), + }) + } + Self::PreambleRequiresStatus(l) => { + DefaultLint::PreambleRequiresStatus(preamble::RequiresStatus { + requires: l.requires.as_ref(), + status: l.status.as_ref(), + flow: l + .flow + .iter() + .map(|v| v.iter().map(AsRef::as_ref).collect()) + .collect(), + }) + } + Self::PreambleTrim(_) => DefaultLint::PreambleTrim(preamble::Trim), + Self::PreambleUint { name } => DefaultLint::PreambleUint { + name: preamble::Uint(name.0.as_ref()), + }, + Self::PreambleUintList { name } => DefaultLint::PreambleUintList { + name: preamble::UintList(name.0.as_ref()), + }, + Self::PreambleUrl { name } => DefaultLint::PreambleUrl { + name: preamble::Url(name.0.as_ref()), + }, + + Self::MarkdownHtmlComments(l) => { + DefaultLint::MarkdownHtmlComments(markdown::HtmlComments { + name: l.name.as_ref(), + warn_for: l.warn_for.iter().map(AsRef::as_ref).collect(), + }) + } + Self::MarkdownJsonSchema(l) => DefaultLint::MarkdownJsonSchema(markdown::JsonSchema { + help: l.help.as_ref(), + language: l.language.as_ref(), + schema: l.schema.as_ref(), + additional_schemas: l + .additional_schemas + .iter() + .map(|(a, b)| (a.as_ref(), b.as_ref())) + .collect(), + }), + Self::MarkdownLinkFirst { pattern } => DefaultLint::MarkdownLinkFirst { + pattern: markdown::LinkFirst(pattern.0.as_ref()), + }, + Self::MarkdownLinkStatus(l) => DefaultLint::MarkdownLinkStatus(markdown::LinkStatus { + status: l.status.as_ref(), + flow: l + .flow + .iter() + .map(|v| v.iter().map(AsRef::as_ref).collect()) + .collect(), + }), + Self::MarkdownProposalRef(_) => DefaultLint::MarkdownProposalRef(markdown::ProposalRef), + Self::MarkdownRegex(l) => DefaultLint::MarkdownRegex(markdown::Regex { + message: l.message.as_ref(), + mode: l.mode, + pattern: l.pattern.as_ref(), + }), + Self::MarkdownRelativeLinks(l) => { + DefaultLint::MarkdownRelativeLinks(markdown::RelativeLinks { + exceptions: l.exceptions.iter().map(AsRef::as_ref).collect(), + }) + } + Self::MarkdownSectionOrder { sections } => DefaultLint::MarkdownSectionOrder { + sections: markdown::SectionOrder(sections.0.iter().map(AsRef::as_ref).collect()), + }, + Self::MarkdownSectionRequired { sections } => DefaultLint::MarkdownSectionRequired { + sections: markdown::SectionRequired(sections.0.iter().map(AsRef::as_ref).collect()), + }, + } + } +} + +impl Lint for DefaultLint +where + S: std::fmt::Debug + AsRef, +{ + fn find_resources<'a>(&self, ctx: &FetchContext<'a>) -> Result<(), super::Error> { + let lint = self.map_to_str(); + lint.as_inner().find_resources(ctx) + } + + fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), super::Error> { + let lint = self.map_to_str(); + lint.as_inner().lint(slug, ctx) + } +} diff --git a/eipw-lint/src/lints/markdown/html_comments.rs b/eipw-lint/src/lints/markdown/html_comments.rs index d50e1985..1bc80d18 100644 --- a/eipw-lint/src/lints/markdown/html_comments.rs +++ b/eipw-lint/src/lints/markdown/html_comments.rs @@ -13,20 +13,27 @@ use crate::lints::{Context, Error, Lint}; use scraper::node::Node as HtmlNode; use scraper::Html; -#[derive(Debug)] -pub struct HtmlComments<'n> { - pub name: &'n str, - pub warn_for: &'n [&'n str], +use serde::{Deserialize, Serialize}; + +use std::fmt::{Debug, Display}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct HtmlComments { + pub name: S, + pub warn_for: Vec, } -impl<'n> Lint for HtmlComments<'n> { +impl Lint for HtmlComments +where + S: Display + Debug + AsRef + for<'eq> PartialEq<&'eq str>, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let field = match ctx.preamble().by_name(self.name) { + let field = match ctx.preamble().by_name(self.name.as_ref()) { None => return Ok(()), Some(s) => s.value().trim(), }; - let warn = self.warn_for.contains(&field); + let warn = self.warn_for.iter().any(|e| e == &field); // Downgrade diagnostic level if header's value is in `warn_for`. let annotation_type = if warn && ctx.annotation_type() == AnnotationType::Error { @@ -62,7 +69,12 @@ impl<'n> Lint for HtmlComments<'n> { if !slices.is_empty() { let label = match warn { true => { - let joined = self.warn_for.join("`, `"); + let joined = self + .warn_for + .iter() + .map(AsRef::as_ref) + .collect::>() + .join("`, `"); format!( "HTML comments are only allowed while `{}` is one of: `{joined}`", self.name, diff --git a/eipw-lint/src/lints/markdown/json_schema.rs b/eipw-lint/src/lints/markdown/json_schema.rs index 6f12a895..adf5ca61 100644 --- a/eipw-lint/src/lints/markdown/json_schema.rs +++ b/eipw-lint/src/lints/markdown/json_schema.rs @@ -14,27 +14,35 @@ use crate::tree::{self, Next, TraverseExt}; use jsonschema::{CompilationOptions, JSONSchema}; +use serde::{Deserialize, Serialize}; + use snafu::{FromString as _, Whatever}; -#[derive(Debug)] -pub struct JsonSchema<'n> { - pub language: &'n str, - pub additional_schemas: &'n [(&'n str, &'n str)], - pub schema: &'n str, - pub help: &'n str, +use std::fmt::{Debug, Display}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct JsonSchema { + pub language: S, + pub additional_schemas: Vec<(S, S)>, + pub schema: S, + pub help: S, } -impl<'n> Lint for JsonSchema<'n> { +impl Lint for JsonSchema +where + S: Debug + Display + AsRef, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let value: serde_json::Value = serde_json::from_str(self.schema).map_err(Error::custom)?; + let value: serde_json::Value = + serde_json::from_str(self.schema.as_ref()).map_err(Error::custom)?; let mut options = CompilationOptions::default(); options.with_draft(jsonschema::Draft::Draft7); - for (url, json_text) in self.additional_schemas { + for (url, json_text) in &self.additional_schemas { let value: serde_json::Value = - serde_json::from_str(json_text).map_err(Error::custom)?; + serde_json::from_str(json_text.as_ref()).map_err(Error::custom)?; options.with_document(url.to_string(), value); } @@ -47,8 +55,8 @@ impl<'n> Lint for JsonSchema<'n> { ctx, schema, slug, - language: self.language, - help: self.help, + language: self.language.as_ref(), + help: self.help.as_ref(), }; ctx.body().traverse().visit(&mut visitor)?; diff --git a/eipw-lint/src/lints/markdown/link_first.rs b/eipw-lint/src/lints/markdown/link_first.rs index 14f1e837..9fb9d8ae 100644 --- a/eipw-lint/src/lints/markdown/link_first.rs +++ b/eipw-lint/src/lints/markdown/link_first.rs @@ -13,19 +13,27 @@ use crate::tree::{self, Next, TraverseExt}; use ::regex::Regex; +use serde::{Deserialize, Serialize}; + use std::collections::HashSet; +use std::fmt::{Debug, Display}; -#[derive(Debug)] -pub struct LinkFirst<'n>(pub &'n str); +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(transparent)] +pub struct LinkFirst(pub S); -impl<'n> Lint for LinkFirst<'n> { +impl Lint for LinkFirst +where + S: Display + Debug + AsRef, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let re = Regex::new(self.0).map_err(Error::custom)?; + let pattern = self.0.as_ref(); + let re = Regex::new(pattern).map_err(Error::custom)?; let mut visitor = Visitor { ctx, re, - pattern: self.0, + pattern, slug, linked: Default::default(), link_depth: 0, diff --git a/eipw-lint/src/lints/markdown/link_status.rs b/eipw-lint/src/lints/markdown/link_status.rs index b3b22c4b..c518134a 100644 --- a/eipw-lint/src/lints/markdown/link_status.rs +++ b/eipw-lint/src/lints/markdown/link_status.rs @@ -12,19 +12,25 @@ use crate::lints::{Context, Error, FetchContext, Lint}; use regex::Regex; +use serde::{Deserialize, Serialize}; + use std::collections::{HashMap, HashSet}; +use std::fmt::{Debug, Display}; use std::path::PathBuf; -#[derive(Debug)] -pub struct LinkStatus<'n> { - pub status: &'n str, - pub flow: &'n [&'n [&'n str]], +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LinkStatus { + pub status: S, + pub flow: Vec>, } -impl<'n> LinkStatus<'n> { +impl LinkStatus +where + S: AsRef, +{ fn tier(&self, map: &HashMap<&str, usize>, ctx: &Context<'_, '_>) -> usize { ctx.preamble() - .by_name(self.status) + .by_name(self.status.as_ref()) .map(|f| f.value()) .map(str::trim) .and_then(|s| map.get(s)) @@ -58,7 +64,10 @@ impl<'n> LinkStatus<'n> { } } -impl<'n> Lint for LinkStatus<'n> { +impl Lint for LinkStatus +where + S: Debug + Display + AsRef, +{ fn find_resources<'a>(&self, ctx: &FetchContext<'a>) -> Result<(), Error> { Self::find_links(ctx.body()) .map(|x| x.1) @@ -72,8 +81,8 @@ impl<'n> Lint for LinkStatus<'n> { fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { let mut map = HashMap::new(); for (tier, values) in self.flow.iter().enumerate() { - for value in *values { - map.insert(*value, tier + 1); + for value in values { + map.insert(value.as_ref(), tier + 1); } } @@ -119,7 +128,7 @@ impl<'n> Lint for LinkStatus<'n> { url.display(), self.status, ctx.preamble() - .by_name(self.status) + .by_name(self.status.as_ref()) .map(|f| f.value()) .unwrap_or("") .trim(), diff --git a/eipw-lint/src/lints/markdown/proposal_ref.rs b/eipw-lint/src/lints/markdown/proposal_ref.rs index fd5799d4..21a5e1e0 100644 --- a/eipw-lint/src/lints/markdown/proposal_ref.rs +++ b/eipw-lint/src/lints/markdown/proposal_ref.rs @@ -13,10 +13,12 @@ use crate::tree::{self, Next, TraverseExt}; use regex::Regex; +use serde::{Deserialize, Serialize}; + use std::collections::HashSet; use std::path::PathBuf; -#[derive(Debug)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct ProposalRef; impl ProposalRef { diff --git a/eipw-lint/src/lints/markdown/regex.rs b/eipw-lint/src/lints/markdown/regex.rs index 6e3ddabf..88d63313 100644 --- a/eipw-lint/src/lints/markdown/regex.rs +++ b/eipw-lint/src/lints/markdown/regex.rs @@ -13,8 +13,13 @@ use crate::tree::{self, Next, TraverseExt}; use ::regex::Regex as TextRegex; -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +use serde::{Deserialize, Serialize}; + +use std::fmt::{Debug, Display}; + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] #[non_exhaustive] +#[serde(rename_all = "kebab-case")] pub enum Mode { /// Ensure that each syntax node individually doesn't contain the pattern. Excludes, @@ -22,23 +27,27 @@ pub enum Mode { // matches the pattern. } -#[derive(Debug)] -pub struct Regex<'n> { +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct Regex { pub mode: Mode, - pub pattern: &'n str, - pub message: &'n str, + pub pattern: S, + pub message: S, } -impl<'n> Lint for Regex<'n> { +impl Lint for Regex +where + S: Display + Debug + AsRef, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let re = TextRegex::new(self.pattern).map_err(Error::custom)?; + let pattern = self.pattern.as_ref(); + let re = TextRegex::new(pattern).map_err(Error::custom)?; let mut visitor = match self.mode { Mode::Excludes => ExcludesVisitor { ctx, re, - message: self.message, - pattern: self.pattern, + message: self.message.as_ref(), + pattern, slug, }, }; diff --git a/eipw-lint/src/lints/markdown/relative_links.rs b/eipw-lint/src/lints/markdown/relative_links.rs index 11153aa0..69624026 100644 --- a/eipw-lint/src/lints/markdown/relative_links.rs +++ b/eipw-lint/src/lints/markdown/relative_links.rs @@ -16,18 +16,25 @@ use regex::{Regex, RegexSet}; use scraper::node::Node as HtmlNode; use scraper::Html; +use serde::{Deserialize, Serialize}; + use snafu::Snafu; -#[derive(Debug)] -pub struct RelativeLinks<'e> { - pub exceptions: &'e [&'e str], +use std::fmt::{Debug, Display}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct RelativeLinks { + pub exceptions: Vec, } -impl<'e> Lint for RelativeLinks<'e> { +impl Lint for RelativeLinks +where + S: Debug + Display + AsRef, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { let re = Regex::new("(^/)|(://)").unwrap(); - let exceptions = RegexSet::new(self.exceptions).map_err(Error::custom)?; + let exceptions = RegexSet::new(&self.exceptions).map_err(Error::custom)?; let mut visitor = Visitor::default(); ctx.body().traverse().visit(&mut visitor)?; diff --git a/eipw-lint/src/lints/markdown/section_order.rs b/eipw-lint/src/lints/markdown/section_order.rs index 0c303d42..30ecfeda 100644 --- a/eipw-lint/src/lints/markdown/section_order.rs +++ b/eipw-lint/src/lints/markdown/section_order.rs @@ -10,13 +10,19 @@ use comrak::nodes::{Ast, NodeHeading, NodeValue}; use crate::lints::{Context, Error, Lint}; +use serde::{Deserialize, Serialize}; + use std::collections::HashMap; -use std::fmt::Write; +use std::fmt::{Debug, Display, Write}; -#[derive(Debug)] -pub struct SectionOrder<'n>(pub &'n [&'n str]); +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(transparent)] +pub struct SectionOrder(pub Vec); -impl<'n> SectionOrder<'n> { +impl SectionOrder +where + S: AsRef + for<'eq> PartialEq<&'eq str>, +{ fn find_preceding(&self, present: &[&str], needle: &str) -> Option<&str> { let needle_idx = match self.0.iter().position(|x| *x == needle) { None | Some(0) => return None, @@ -24,7 +30,12 @@ impl<'n> SectionOrder<'n> { }; for (idx, name) in self.0.iter().enumerate().rev() { - if *name != needle && present.contains(name) && idx < needle_idx { + let name = name.as_ref(); + if name == needle || idx >= needle_idx { + continue; + } + + if present.iter().any(|e| e == &name) { return Some(name); } } @@ -33,7 +44,10 @@ impl<'n> SectionOrder<'n> { } } -impl<'n> Lint for SectionOrder<'n> { +impl Lint for SectionOrder +where + S: Debug + Display + AsRef + for<'eq> PartialEq<&'eq str>, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { // Collect the headings. let headings: Vec<_> = ctx @@ -70,7 +84,7 @@ impl<'n> Lint for SectionOrder<'n> { // Check for unknown sections. let unknowns: Vec<_> = headings .iter() - .filter(|(_, f)| !self.0.contains(&f.as_str())) + .filter(|(_, f)| !self.0.iter().any(|e| e == &f.as_str())) .map(|(line_start, _)| Slice { line_start: *line_start, fold: false, @@ -99,7 +113,8 @@ impl<'n> Lint for SectionOrder<'n> { let mut max_line = 0; for name in self.0.iter() { - if let Some(line_start) = map.get(*name).copied() { + let name = name.as_ref(); + if let Some(line_start) = map.get(name).copied() { let cur = max_line; max_line = line_start; diff --git a/eipw-lint/src/lints/markdown/section_required.rs b/eipw-lint/src/lints/markdown/section_required.rs index b8d97132..05f0e640 100644 --- a/eipw-lint/src/lints/markdown/section_required.rs +++ b/eipw-lint/src/lints/markdown/section_required.rs @@ -10,10 +10,18 @@ use comrak::nodes::{Ast, NodeHeading, NodeValue}; use crate::lints::{Context, Error, Lint}; -#[derive(Debug)] -pub struct SectionRequired<'n>(pub &'n [&'n str]); +use serde::{Deserialize, Serialize}; -impl<'n> Lint for SectionRequired<'n> { +use std::fmt::{Debug, Display}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(transparent)] +pub struct SectionRequired(pub Vec); + +impl Lint for SectionRequired +where + S: Debug + Display + AsRef + Clone + PartialEq, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { // Collect the headings. let headings: Vec<_> = ctx @@ -54,7 +62,7 @@ impl<'n> Lint for SectionRequired<'n> { // TODO: I'm sure this is horribly inefficient! missing.retain(|i| { for text in &headings { - if *i == text { + if i == text { return false; } } @@ -65,7 +73,12 @@ impl<'n> Lint for SectionRequired<'n> { return Ok(()); } - let label = format!("body is missing section(s): `{}`", missing.join("`, `")); + let missing_txt = missing + .iter() + .map(AsRef::as_ref) + .collect::>() + .join("`, `"); + let label = format!("body is missing section(s): `{}`", missing_txt); ctx.report(Snippet { title: Some(Annotation { annotation_type: ctx.annotation_type(), diff --git a/eipw-lint/src/lints/preamble/author.rs b/eipw-lint/src/lints/preamble/author.rs index 7f262170..ff88f410 100644 --- a/eipw-lint/src/lints/preamble/author.rs +++ b/eipw-lint/src/lints/preamble/author.rs @@ -5,10 +5,15 @@ */ use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; + use regex::RegexSet; use crate::lints::{Context, Error, Lint}; +use serde::{Deserialize, Serialize}; + +use std::fmt::{Debug, Display}; + fn footer() -> Vec> { vec![ Annotation { @@ -34,12 +39,16 @@ fn footer() -> Vec> { ] } -#[derive(Debug)] -pub struct Author<'n>(pub &'n str); +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Author(pub S); -impl<'n> Lint for Author<'n> { +impl Lint for Author +where + S: Debug + Display + AsRef, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let field = match ctx.preamble().by_name(self.0) { + let field = match ctx.preamble().by_name(self.0.as_ref()) { None => return Ok(()), Some(s) => s, }; diff --git a/eipw-lint/src/lints/preamble/date.rs b/eipw-lint/src/lints/preamble/date.rs index 04203d7f..853454e2 100644 --- a/eipw-lint/src/lints/preamble/date.rs +++ b/eipw-lint/src/lints/preamble/date.rs @@ -10,12 +10,20 @@ use chrono::NaiveDate; use crate::lints::{Context, Error, Lint}; -#[derive(Debug)] -pub struct Date<'n>(pub &'n str); +use serde::{Deserialize, Serialize}; -impl<'n> Lint for Date<'n> { +use std::fmt::{Debug, Display}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Date(pub S); + +impl Lint for Date +where + S: Debug + Display + AsRef, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let field = match ctx.preamble().by_name(self.0) { + let field = match ctx.preamble().by_name(self.0.as_ref()) { None => return Ok(()), Some(s) => s, }; diff --git a/eipw-lint/src/lints/preamble/file_name.rs b/eipw-lint/src/lints/preamble/file_name.rs index 6a1b4f76..c96a3142 100644 --- a/eipw-lint/src/lints/preamble/file_name.rs +++ b/eipw-lint/src/lints/preamble/file_name.rs @@ -8,18 +8,24 @@ use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, Sou use crate::lints::{Context, Error, Lint}; +use serde::{Deserialize, Serialize}; + +use std::fmt::{Debug, Display}; use std::path::Path; -#[derive(Debug)] -pub struct FileName<'n> { - pub name: &'n str, - pub prefix: &'n str, - pub suffix: &'n str, +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct FileName { + pub name: S, + pub prefix: S, + pub suffix: S, } -impl<'n> Lint for FileName<'n> { +impl Lint for FileName +where + S: Display + Debug + AsRef, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let field = match ctx.preamble().by_name(self.name) { + let field = match ctx.preamble().by_name(self.name.as_ref()) { None => return Ok(()), Some(s) => s, }; diff --git a/eipw-lint/src/lints/preamble/length.rs b/eipw-lint/src/lints/preamble/length.rs index e8481613..0f5bab8a 100644 --- a/eipw-lint/src/lints/preamble/length.rs +++ b/eipw-lint/src/lints/preamble/length.rs @@ -8,16 +8,23 @@ use annotate_snippets::snippet::{Annotation, Slice, Snippet, SourceAnnotation}; use crate::lints::{Context, Error, Lint}; -#[derive(Debug)] -pub struct Length<'n> { - pub name: &'n str, +use serde::{Deserialize, Serialize}; + +use std::fmt::{Debug, Display}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct Length { + pub name: S, pub min: Option, pub max: Option, } -impl<'n> Lint for Length<'n> { +impl Lint for Length +where + S: Debug + Display + AsRef, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let field = match ctx.preamble().by_name(self.name) { + let field = match ctx.preamble().by_name(self.name.as_ref()) { None => return Ok(()), Some(f) => f, }; diff --git a/eipw-lint/src/lints/preamble/list.rs b/eipw-lint/src/lints/preamble/list.rs index cd68c659..2510c9e9 100644 --- a/eipw-lint/src/lints/preamble/list.rs +++ b/eipw-lint/src/lints/preamble/list.rs @@ -8,12 +8,20 @@ use annotate_snippets::snippet::{Annotation, Slice, Snippet, SourceAnnotation}; use crate::lints::{Context, Error, Lint}; -#[derive(Debug)] -pub struct List<'n>(pub &'n str); +use serde::{Deserialize, Serialize}; -impl<'n> Lint for List<'n> { +use std::fmt::{Debug, Display}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(transparent)] +pub struct List(pub S); + +impl Lint for List +where + S: Debug + Display + AsRef, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let field = match ctx.preamble().by_name(self.0) { + let field = match ctx.preamble().by_name(self.0.as_ref()) { None => return Ok(()), Some(s) => s, }; diff --git a/eipw-lint/src/lints/preamble/no_duplicates.rs b/eipw-lint/src/lints/preamble/no_duplicates.rs index d0be9fad..0eb030a4 100644 --- a/eipw-lint/src/lints/preamble/no_duplicates.rs +++ b/eipw-lint/src/lints/preamble/no_duplicates.rs @@ -8,9 +8,11 @@ use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, Sou use crate::lints::{Context, Error, Lint}; +use serde::{Deserialize, Serialize}; + use std::collections::hash_map::{Entry, HashMap}; -#[derive(Debug)] +#[derive(Debug, Clone, Copy, Deserialize, Serialize)] pub struct NoDuplicates; impl Lint for NoDuplicates { diff --git a/eipw-lint/src/lints/preamble/one_of.rs b/eipw-lint/src/lints/preamble/one_of.rs index 849076d2..dac1ba88 100644 --- a/eipw-lint/src/lints/preamble/one_of.rs +++ b/eipw-lint/src/lints/preamble/one_of.rs @@ -8,26 +8,36 @@ use annotate_snippets::snippet::{Annotation, Slice, Snippet, SourceAnnotation}; use crate::lints::{Context, Error, Lint}; -#[derive(Debug)] -pub struct OneOf<'n> { - pub name: &'n str, - pub values: &'n [&'n str], +use serde::{Deserialize, Serialize}; + +use std::fmt::{Debug, Display}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OneOf { + pub name: S, + pub values: Vec, } -impl<'n> Lint for OneOf<'n> { +impl Lint for OneOf +where + S: Debug + Display + AsRef + for<'eq> PartialEq<&'eq str>, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let field = match ctx.preamble().by_name(self.name) { + let field = match ctx.preamble().by_name(self.name.as_ref()) { None => return Ok(()), Some(f) => f, }; - if self.values.contains(&field.value().trim()) { + let value = field.value().trim(); + + if self.values.iter().any(|e| e == &value) { return Ok(()); } let label = format!("preamble header `{}` has an unrecognized value", self.name); - let slice_label = format!("must be one of: `{}`", self.values.join("`, `")); + let values: Vec<_> = self.values.iter().map(|a| a.as_ref()).collect(); + let slice_label = format!("must be one of: `{}`", values.join("`, `")); let name_count = field.name().chars().count(); let value_count = field.value().chars().count(); diff --git a/eipw-lint/src/lints/preamble/order.rs b/eipw-lint/src/lints/preamble/order.rs index 9f0742bf..f6387ce6 100644 --- a/eipw-lint/src/lints/preamble/order.rs +++ b/eipw-lint/src/lints/preamble/order.rs @@ -8,20 +8,27 @@ use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, Sou use crate::lints::{Context, Error, Lint}; -use std::fmt::Write; +use serde::{Deserialize, Serialize}; -#[derive(Debug)] -pub struct Order<'n>(pub &'n [&'n str]); +use std::fmt::{Debug, Display, Write}; -impl<'n> Order<'n> { +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Order(pub Vec); + +impl Order +where + S: AsRef, +{ fn find_preceding(&self, present: &[&str], needle: &str) -> Option<&str> { - let needle_idx = match self.0.iter().position(|x| *x == needle) { + let needle_idx = match self.0.iter().position(|x| x.as_ref() == needle) { None | Some(0) => return None, Some(i) => i, }; for (idx, name) in self.0.iter().enumerate().rev() { - if *name != needle && present.contains(name) && idx < needle_idx { + let name = name.as_ref(); + if name != needle && present.contains(&name) && idx < needle_idx { return Some(name); } } @@ -30,13 +37,16 @@ impl<'n> Order<'n> { } } -impl<'n> Lint for Order<'n> { +impl Lint for Order +where + S: Debug + Display + AsRef + for<'eq> PartialEq<&'eq str>, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { // Check for unknown headers. let unknowns: Vec<_> = ctx .preamble() .fields() - .filter(|f| !self.0.contains(&f.name())) + .filter(|f| !self.0.iter().any(|e| e == &f.name())) .map(|f| Slice { line_start: f.line_start(), fold: false, @@ -68,7 +78,7 @@ impl<'n> Lint for Order<'n> { // Check that headers are in the correct order. let mut max_line = 0; for name in self.0.iter() { - if let Some(field) = ctx.preamble().by_name(name) { + if let Some(field) = ctx.preamble().by_name(name.as_ref()) { let cur = max_line; max_line = field.line_start(); diff --git a/eipw-lint/src/lints/preamble/proposal_ref.rs b/eipw-lint/src/lints/preamble/proposal_ref.rs index 958f7628..19a81d2d 100644 --- a/eipw-lint/src/lints/preamble/proposal_ref.rs +++ b/eipw-lint/src/lints/preamble/proposal_ref.rs @@ -10,21 +10,28 @@ use crate::lints::{Context, Error, FetchContext, Lint}; use regex::Regex; +use serde::{Deserialize, Serialize}; + +use std::fmt::{Debug, Display}; use std::path::Path; -#[derive(Debug)] -pub struct ProposalRef<'n>(pub &'n str); +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(transparent)] +pub struct ProposalRef(pub S); -impl<'n> ProposalRef<'n> { +impl ProposalRef { fn regex() -> Regex { // NB: This regex is used to calculate a path, so be careful of directory traversal. Regex::new(r"(?i)\b(?:eip|erc)-([0-9]+)\b").unwrap() } } -impl<'n> Lint for ProposalRef<'n> { +impl Lint for ProposalRef +where + S: Debug + Display + AsRef, +{ fn find_resources<'a>(&self, ctx: &FetchContext<'a>) -> Result<(), Error> { - let field = match ctx.preamble().by_name(self.0) { + let field = match ctx.preamble().by_name(self.0.as_ref()) { None => return Ok(()), Some(s) => s, }; @@ -40,7 +47,7 @@ impl<'n> Lint for ProposalRef<'n> { } fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let field = match ctx.preamble().by_name(self.0) { + let field = match ctx.preamble().by_name(self.0.as_ref()) { None => return Ok(()), Some(s) => s, }; diff --git a/eipw-lint/src/lints/preamble/regex.rs b/eipw-lint/src/lints/preamble/regex.rs index 4deb910d..07ac79ac 100644 --- a/eipw-lint/src/lints/preamble/regex.rs +++ b/eipw-lint/src/lints/preamble/regex.rs @@ -8,31 +8,39 @@ use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, Sou use crate::lints::{Context, Error, Lint}; -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +use serde::{Deserialize, Serialize}; + +use std::fmt::{Debug, Display}; + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] #[non_exhaustive] +#[serde(rename_all = "kebab-case")] pub enum Mode { Includes, Excludes, } -#[derive(Debug)] -pub struct Regex<'n> { - pub name: &'n str, +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct Regex { + pub name: S, pub mode: Mode, - pub pattern: &'n str, - pub message: &'n str, + pub pattern: S, + pub message: S, } -impl<'n> Lint for Regex<'n> { +impl Lint for Regex +where + S: Debug + Display + AsRef, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let field = match ctx.preamble().by_name(self.name) { + let field = match ctx.preamble().by_name(self.name.as_ref()) { None => return Ok(()), Some(s) => s, }; let value = field.value().trim(); - let re = ::regex::Regex::new(self.pattern).map_err(Error::custom)?; + let re = ::regex::Regex::new(self.pattern.as_ref()).map_err(Error::custom)?; let matches = re.is_match(value); let slice_label = match (self.mode, matches) { @@ -55,7 +63,7 @@ impl<'n> Lint for Regex<'n> { title: Some(Annotation { annotation_type: ctx.annotation_type(), id: Some(slug), - label: Some(self.message), + label: Some(self.message.as_ref()), }), slices: vec![Slice { fold: false, diff --git a/eipw-lint/src/lints/preamble/require_referenced.rs b/eipw-lint/src/lints/preamble/require_referenced.rs index ea89d600..729b8eb6 100644 --- a/eipw-lint/src/lints/preamble/require_referenced.rs +++ b/eipw-lint/src/lints/preamble/require_referenced.rs @@ -10,22 +10,29 @@ use crate::lints::{Context, Error, Lint}; use regex::Regex; -#[derive(Debug)] -pub struct RequireReferenced<'n> { - pub name: &'n str, - pub requires: &'n str, +use serde::{Deserialize, Serialize}; + +use std::fmt::{Debug, Display}; + +#[derive(Debug, Clone, Copy, Deserialize, Serialize)] +pub struct RequireReferenced { + pub name: S, + pub requires: S, } -impl<'n> Lint for RequireReferenced<'n> { +impl Lint for RequireReferenced +where + S: Debug + Display + AsRef, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let field = match ctx.preamble().by_name(self.name) { + let field = match ctx.preamble().by_name(self.name.as_ref()) { None => return Ok(()), Some(f) => f, }; let requires_txt = ctx .preamble() - .by_name(self.requires) + .by_name(self.requires.as_ref()) .map(|f| f.value()) .unwrap_or_default(); diff --git a/eipw-lint/src/lints/preamble/required.rs b/eipw-lint/src/lints/preamble/required.rs index 23e68029..5142f492 100644 --- a/eipw-lint/src/lints/preamble/required.rs +++ b/eipw-lint/src/lints/preamble/required.rs @@ -8,16 +8,24 @@ use annotate_snippets::snippet::{Annotation, Slice, Snippet}; use crate::lints::{Context, Error, Lint}; -#[derive(Debug)] -pub struct Required<'n>(pub &'n [&'n str]); +use serde::{Deserialize, Serialize}; -impl<'n> Lint for Required<'n> { +use std::fmt::{Debug, Display}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Required(pub Vec); + +impl Lint for Required +where + S: Debug + Display + AsRef, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { let missing = self .0 .iter() + .map(AsRef::as_ref) .filter(|name| ctx.preamble().by_name(name).is_none()) - .cloned() .collect::>() .join("`, `"); diff --git a/eipw-lint/src/lints/preamble/required_if_eq.rs b/eipw-lint/src/lints/preamble/required_if_eq.rs index b93dc9b9..a75ba56c 100644 --- a/eipw-lint/src/lints/preamble/required_if_eq.rs +++ b/eipw-lint/src/lints/preamble/required_if_eq.rs @@ -8,27 +8,36 @@ use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, Sou use crate::lints::{Context, Error, Lint}; -#[derive(Debug)] -pub struct RequiredIfEq<'b> { - pub when: &'b str, - pub equals: &'b str, - pub then: &'b str, +use serde::{Deserialize, Serialize}; + +use std::fmt::{Debug, Display}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct RequiredIfEq { + pub when: S, + pub equals: S, + pub then: S, } -impl<'n> Lint for RequiredIfEq<'n> { +impl Lint for RequiredIfEq +where + S: Debug + Display + AsRef, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let then_opt = ctx.preamble().by_name(self.then); - let when_opt = ctx.preamble().by_name(self.when); + let then_opt = ctx.preamble().by_name(self.then.as_ref()); + let when_opt = ctx.preamble().by_name(self.when.as_ref()); + + let equals = self.equals.as_ref(); match (when_opt, then_opt) { // Correct. (None, None) => (), // Correct. - (Some(when), Some(_)) if when.value().trim() == self.equals => (), + (Some(when), Some(_)) if when.value().trim() == equals => (), // Correct. - (Some(when), None) if when.value().trim() != self.equals => (), + (Some(when), None) if when.value().trim() != equals => (), // Incorrect. (Some(when), None) => { diff --git a/eipw-lint/src/lints/preamble/requires_status.rs b/eipw-lint/src/lints/preamble/requires_status.rs index a9992472..dd7c6ac5 100644 --- a/eipw-lint/src/lints/preamble/requires_status.rs +++ b/eipw-lint/src/lints/preamble/requires_status.rs @@ -8,20 +8,26 @@ use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, Sou use crate::lints::{Context, Error, FetchContext, Lint}; +use serde::{Deserialize, Serialize}; + use std::collections::HashMap; +use std::fmt::{Debug, Display}; use std::path::PathBuf; -#[derive(Debug)] -pub struct RequiresStatus<'n> { - pub requires: &'n str, - pub status: &'n str, - pub flow: &'n [&'n [&'n str]], +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RequiresStatus { + pub requires: S, + pub status: S, + pub flow: Vec>, } -impl<'n> RequiresStatus<'n> { +impl RequiresStatus +where + S: AsRef, +{ fn tier(&self, map: &HashMap<&str, usize>, ctx: &Context<'_, '_>) -> usize { ctx.preamble() - .by_name(self.status) + .by_name(self.status.as_ref()) .map(|f| f.value()) .map(str::trim) .and_then(|s| map.get(s)) @@ -30,9 +36,12 @@ impl<'n> RequiresStatus<'n> { } } -impl<'n> Lint for RequiresStatus<'n> { +impl Lint for RequiresStatus +where + S: Display + Debug + AsRef, +{ fn find_resources<'a>(&self, ctx: &FetchContext<'a>) -> Result<(), Error> { - let field = match ctx.preamble().by_name(self.requires) { + let field = match ctx.preamble().by_name(self.requires.as_ref()) { None => return Ok(()), Some(s) => s, }; @@ -51,15 +60,16 @@ impl<'n> Lint for RequiresStatus<'n> { } fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let field = match ctx.preamble().by_name(self.requires) { + let field = match ctx.preamble().by_name(self.requires.as_ref()) { None => return Ok(()), Some(s) => s, }; let mut map = HashMap::new(); for (tier, values) in self.flow.iter().enumerate() { - for value in *values { - map.insert(*value, tier + 1); + for value in values.iter() { + let value = value.as_ref(); + map.insert(value, tier + 1); } } @@ -138,7 +148,7 @@ impl<'n> Lint for RequiresStatus<'n> { self.requires, self.status, ctx.preamble() - .by_name(self.status) + .by_name(self.status.as_ref()) .map(|f| f.value()) .unwrap_or("") .trim(), diff --git a/eipw-lint/src/lints/preamble/trim.rs b/eipw-lint/src/lints/preamble/trim.rs index fc3380da..84b362b6 100644 --- a/eipw-lint/src/lints/preamble/trim.rs +++ b/eipw-lint/src/lints/preamble/trim.rs @@ -8,7 +8,9 @@ use annotate_snippets::snippet::{Annotation, Slice, Snippet, SourceAnnotation}; use crate::lints::{Context, Error, Lint}; -#[derive(Debug)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] pub struct Trim; impl Lint for Trim { diff --git a/eipw-lint/src/lints/preamble/uint.rs b/eipw-lint/src/lints/preamble/uint.rs index 40c8cf5b..b9d316c1 100644 --- a/eipw-lint/src/lints/preamble/uint.rs +++ b/eipw-lint/src/lints/preamble/uint.rs @@ -8,12 +8,20 @@ use annotate_snippets::snippet::{Annotation, Slice, Snippet, SourceAnnotation}; use crate::lints::{Context, Error, Lint}; -#[derive(Debug)] -pub struct Uint<'n>(pub &'n str); +use serde::{Deserialize, Serialize}; -impl<'n> Lint for Uint<'n> { +use std::fmt::{Debug, Display}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Uint(pub S); + +impl Lint for Uint +where + S: Display + Debug + AsRef, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let field = match ctx.preamble().by_name(self.0) { + let field = match ctx.preamble().by_name(self.0.as_ref()) { None => return Ok(()), Some(s) => s, }; @@ -50,12 +58,16 @@ impl<'n> Lint for Uint<'n> { } } -#[derive(Debug)] -pub struct UintList<'n>(pub &'n str); +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +#[serde(transparent)] +pub struct UintList(pub S); -impl<'n> Lint for UintList<'n> { +impl Lint for UintList +where + S: Debug + Display + AsRef, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let field = match ctx.preamble().by_name(self.0) { + let field = match ctx.preamble().by_name(self.0.as_ref()) { None => return Ok(()), Some(s) => s, }; diff --git a/eipw-lint/src/lints/preamble/url.rs b/eipw-lint/src/lints/preamble/url.rs index b141508f..86fd3d45 100644 --- a/eipw-lint/src/lints/preamble/url.rs +++ b/eipw-lint/src/lints/preamble/url.rs @@ -8,12 +8,20 @@ use annotate_snippets::snippet::{Annotation, Slice, Snippet, SourceAnnotation}; use crate::lints::{Context, Error, Lint}; -#[derive(Debug)] -pub struct Url<'n>(pub &'n str); +use serde::{Deserialize, Serialize}; -impl<'n> Lint for Url<'n> { +use std::fmt::{Debug, Display}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Url(pub S); + +impl Lint for Url +where + S: Debug + Display + AsRef, +{ fn lint<'a, 'b>(&self, slug: &'a str, ctx: &Context<'a, 'b>) -> Result<(), Error> { - let field = match ctx.preamble().by_name(self.0) { + let field = match ctx.preamble().by_name(self.0.as_ref()) { Some(f) => f, None => return Ok(()), }; diff --git a/eipw-lint/tests/lint_markdown_html_comments.rs b/eipw-lint/tests/lint_markdown_html_comments.rs index e3220a3d..e270bf4e 100644 --- a/eipw-lint/tests/lint_markdown_html_comments.rs +++ b/eipw-lint/tests/lint_markdown_html_comments.rs @@ -27,7 +27,7 @@ text after "markdown-html-comments", HtmlComments { name: "header", - warn_for: &["value1"], + warn_for: vec!["value1"], }, ) .check_slice(None, src) @@ -65,7 +65,7 @@ text after "markdown-html-comments", HtmlComments { name: "header", - warn_for: &["value1"], + warn_for: vec!["value1"], }, ) .check_slice(None, src) diff --git a/eipw-lint/tests/lint_markdown_json_schema.rs b/eipw-lint/tests/lint_markdown_json_schema.rs index 9d15ce7d..60f9081c 100644 --- a/eipw-lint/tests/lint_markdown_json_schema.rs +++ b/eipw-lint/tests/lint_markdown_json_schema.rs @@ -25,7 +25,7 @@ header: value1 "markdown-json-schema", JsonSchema { language: "hello", - additional_schemas: &[], + additional_schemas: vec![], schema: "{}", help: "see https://example.com/schema.json", }, @@ -64,7 +64,7 @@ header: value1 "markdown-json-schema", JsonSchema { language: "hello", - additional_schemas: &[], + additional_schemas: vec![], schema: "{}", help: "see https://example.com/schema.json", }, @@ -95,7 +95,7 @@ header: value1 "markdown-json-schema", JsonSchema { language: "hello", - additional_schemas: &[], + additional_schemas: vec![], help: "see https://example.com/schema.json", schema: r#"{ "$schema": "http://json-schema.org/draft-07/schema#", @@ -134,7 +134,7 @@ header: value1 "markdown-json-schema", JsonSchema { language: "hello", - additional_schemas: &[], + additional_schemas: vec![], help: "see https://example.com/schema.json", schema: r#"{ "$schema": "http://json-schema.org/draft-07/schema#", @@ -182,7 +182,7 @@ header: value1 "markdown-json-schema", JsonSchema { language: "hello", - additional_schemas: &[( + additional_schemas: vec![( "http://example.com/additional.json", r#"{ "type": "integer" }"#, )], @@ -233,7 +233,7 @@ header: value1 "markdown-json-schema", JsonSchema { language: "hello", - additional_schemas: &[( + additional_schemas: vec![( "http://example.com/additional.json", r#"{ "type": "integer" }"#, )], diff --git a/eipw-lint/tests/lint_markdown_relative_links.rs b/eipw-lint/tests/lint_markdown_relative_links.rs index 4d96dea4..64c66fb5 100644 --- a/eipw-lint/tests/lint_markdown_relative_links.rs +++ b/eipw-lint/tests/lint_markdown_relative_links.rs @@ -22,7 +22,7 @@ header: value1 .deny( "markdown-rel", RelativeLinks { - exceptions: &[ + exceptions: vec![ "^https://(www\\.)?github\\.com/ethereum/consensus-specs/blob/[a-f0-9]{40}/.+$", ], }, @@ -47,7 +47,12 @@ header: value1 let reports = Linter::>::default() .clear_lints() - .deny("markdown-rel", RelativeLinks { exceptions: &[] }) + .deny( + "markdown-rel", + RelativeLinks { + exceptions: Vec::<&str>::new(), + }, + ) .check_slice(None, src) .run() .await @@ -75,7 +80,12 @@ header: value1 let reports = Linter::>::default() .clear_lints() - .deny("markdown-rel", RelativeLinks { exceptions: &[] }) + .deny( + "markdown-rel", + RelativeLinks { + exceptions: Vec::<&str>::new(), + }, + ) .check_slice(None, src) .run() .await @@ -103,7 +113,12 @@ Hello [hi](/foo)! let reports = Linter::>::default() .clear_lints() - .deny("markdown-rel", RelativeLinks { exceptions: &[] }) + .deny( + "markdown-rel", + RelativeLinks { + exceptions: Vec::<&str>::new(), + }, + ) .check_slice(None, src) .run() .await @@ -131,7 +146,12 @@ Hello [hi](./foo/bar)! let reports = Linter::>::default() .clear_lints() - .deny("markdown-rel", RelativeLinks { exceptions: &[] }) + .deny( + "markdown-rel", + RelativeLinks { + exceptions: Vec::<&str>::new(), + }, + ) .check_slice(None, src) .run() .await @@ -154,7 +174,12 @@ Hello [hi][hello]! let reports = Linter::>::default() .clear_lints() - .deny("markdown-rel", RelativeLinks { exceptions: &[] }) + .deny( + "markdown-rel", + RelativeLinks { + exceptions: Vec::<&str>::new(), + }, + ) .check_slice(None, src) .run() .await @@ -184,7 +209,12 @@ Hello [hi][hello]! let reports = Linter::>::default() .clear_lints() - .deny("markdown-rel", RelativeLinks { exceptions: &[] }) + .deny( + "markdown-rel", + RelativeLinks { + exceptions: Vec::<&str>::new(), + }, + ) .check_slice(None, src) .run() .await @@ -207,7 +237,12 @@ hello world let reports = Linter::>::default() .clear_lints() - .deny("markdown-rel", RelativeLinks { exceptions: &[] }) + .deny( + "markdown-rel", + RelativeLinks { + exceptions: Vec::<&str>::new(), + }, + ) .check_slice(None, src) .run() .await @@ -235,7 +270,12 @@ header: value1 let reports = Linter::>::default() .clear_lints() - .deny("markdown-rel", RelativeLinks { exceptions: &[] }) + .deny( + "markdown-rel", + RelativeLinks { + exceptions: Vec::<&str>::new(), + }, + ) .check_slice(None, src) .run() .await @@ -263,7 +303,12 @@ header: value1 let reports = Linter::>::default() .clear_lints() - .deny("markdown-rel", RelativeLinks { exceptions: &[] }) + .deny( + "markdown-rel", + RelativeLinks { + exceptions: Vec::<&str>::new(), + }, + ) .check_slice(None, src) .run() .await @@ -291,7 +336,12 @@ header: value1 let reports = Linter::>::default() .clear_lints() - .deny("markdown-rel", RelativeLinks { exceptions: &[] }) + .deny( + "markdown-rel", + RelativeLinks { + exceptions: Vec::<&str>::new(), + }, + ) .check_slice(None, src) .run() .await @@ -312,7 +362,12 @@ header: value1 let reports = Linter::>::default() .clear_lints() - .deny("markdown-rel", RelativeLinks { exceptions: &[] }) + .deny( + "markdown-rel", + RelativeLinks { + exceptions: Vec::<&str>::new(), + }, + ) .check_slice(None, src) .run() .await @@ -333,7 +388,12 @@ header: value1 let reports = Linter::>::default() .clear_lints() - .deny("markdown-rel", RelativeLinks { exceptions: &[] }) + .deny( + "markdown-rel", + RelativeLinks { + exceptions: Vec::<&str>::new(), + }, + ) .check_slice(None, src) .run() .await diff --git a/eipw-lint/tests/lint_markdown_section_order.rs b/eipw-lint/tests/lint_markdown_section_order.rs index 3675454e..9ce92d97 100644 --- a/eipw-lint/tests/lint_markdown_section_order.rs +++ b/eipw-lint/tests/lint_markdown_section_order.rs @@ -19,7 +19,7 @@ header: value1 let reports = Linter::>::default() .clear_lints() - .deny("markdown-section-order", SectionOrder(&[])) + .deny("markdown-section-order", SectionOrder(Vec::<&str>::new())) .check_slice(None, src) .run() .await @@ -51,7 +51,7 @@ header: value1 let reports = Linter::>::default() .clear_lints() - .deny("markdown-section-order", SectionOrder(&["Banana"])) + .deny("markdown-section-order", SectionOrder(vec!["Banana"])) .check_slice(None, src) .run() .await @@ -87,7 +87,7 @@ header: value1 .clear_lints() .deny( "markdown-section-order", - SectionOrder(&["Foo", "Banana", "Bar"]), + SectionOrder(vec!["Foo", "Banana", "Bar"]), ) .check_slice(None, src) .run() @@ -123,7 +123,7 @@ header: value1 .clear_lints() .deny( "markdown-section-order", - SectionOrder(&["Orange", "Foo", "Pear", "Banana", "Bar"]), + SectionOrder(vec!["Orange", "Foo", "Pear", "Banana", "Bar"]), ) .check_slice(None, src) .run() @@ -159,7 +159,7 @@ header: value1 .clear_lints() .deny( "markdown-section-order", - SectionOrder(&["Foo", "Banana", "Bar"]), + SectionOrder(vec!["Foo", "Banana", "Bar"]), ) .check_slice(None, src) .run() diff --git a/eipw-lint/tests/lint_markdown_section_required.rs b/eipw-lint/tests/lint_markdown_section_required.rs index 776f36ac..997406cc 100644 --- a/eipw-lint/tests/lint_markdown_section_required.rs +++ b/eipw-lint/tests/lint_markdown_section_required.rs @@ -21,7 +21,7 @@ header: value1 .clear_lints() .deny( "markdown-section-req", - SectionRequired(&["Banana", "Orange"]), + SectionRequired(vec!["Banana", "Orange"]), ) .check_slice(None, src) .run() @@ -50,7 +50,7 @@ header: value1 .clear_lints() .deny( "markdown-section-req", - SectionRequired(&["Banana", "Orange"]), + SectionRequired(vec!["Banana", "Orange"]), ) .check_slice(None, src) .run() @@ -83,7 +83,7 @@ header: value1 .clear_lints() .deny( "markdown-section-req", - SectionRequired(&["Banana", "Orange"]), + SectionRequired(vec!["Banana", "Orange"]), ) .check_slice(None, src) .run() diff --git a/eipw-lint/tests/lint_preamble_one_of.rs b/eipw-lint/tests/lint_preamble_one_of.rs index 1abc6350..0c0ab854 100644 --- a/eipw-lint/tests/lint_preamble_one_of.rs +++ b/eipw-lint/tests/lint_preamble_one_of.rs @@ -23,7 +23,7 @@ hello world"#; "preamble-one-of", OneOf { name: "a1", - values: &["v1", "v2"], + values: vec!["v1", "v2"], }, ) .check_slice(None, src) @@ -58,7 +58,7 @@ hello world"#; "preamble-one-of", OneOf { name: "a1", - values: &["v1", "v2"], + values: vec!["v1", "v2"], }, ) .check_slice(None, src) @@ -93,7 +93,7 @@ hello world"#; "preamble-one-of", OneOf { name: "a1", - values: &["v1", "v2"], + values: vec!["v1", "v2"], }, ) .check_slice(None, src) @@ -128,7 +128,7 @@ hello world"#; "preamble-one-of", OneOf { name: "a1", - values: &["v1", "v2"], + values: vec!["v1", "v2"], }, ) .check_slice(None, src) @@ -154,7 +154,7 @@ hello world"#; "preamble-one-of", OneOf { name: "a1", - values: &["v1", "v2"], + values: vec!["v1", "v2"], }, ) .check_slice(None, src) diff --git a/eipw-lint/tests/lint_preamble_order.rs b/eipw-lint/tests/lint_preamble_order.rs index 43fa84ad..38261bdc 100644 --- a/eipw-lint/tests/lint_preamble_order.rs +++ b/eipw-lint/tests/lint_preamble_order.rs @@ -17,7 +17,7 @@ hello world"#; let reports = Linter::>::default() .clear_lints() - .deny("preamble-order", Order(&["a1", "b2"])) + .deny("preamble-order", Order(vec!["a1", "b2"])) .check_slice(None, src) .run() .await @@ -44,7 +44,7 @@ hello world"#; let reports = Linter::>::default() .clear_lints() - .deny("preamble-order", Order(&["a1", "b2"])) + .deny("preamble-order", Order(vec!["a1", "b2"])) .check_slice(None, src) .run() .await @@ -73,7 +73,7 @@ hello world"#; let reports = Linter::>::default() .clear_lints() - .deny("preamble-order", Order(&["a1", "b2"])) + .deny("preamble-order", Order(vec!["a1", "b2"])) .check_slice(None, src) .run() .await @@ -104,7 +104,7 @@ hello world"#; let reports = Linter::>::default() .clear_lints() - .deny("preamble-order", Order(&["é1", "á2"])) + .deny("preamble-order", Order(vec!["é1", "á2"])) .check_slice(None, src) .run() .await @@ -132,7 +132,7 @@ hello world"#; let reports = Linter::>::default() .clear_lints() - .deny("preamble-order", Order(&["a1", "b2"])) + .deny("preamble-order", Order(vec!["a1", "b2"])) .check_slice(None, src) .run() .await @@ -160,7 +160,7 @@ hello world"#; let reports = Linter::>::default() .clear_lints() - .deny("preamble-order", Order(&["a1", "a2", "b2"])) + .deny("preamble-order", Order(vec!["a1", "a2", "b2"])) .check_slice(None, src) .run() .await diff --git a/eipw-lint/tests/lint_preamble_required.rs b/eipw-lint/tests/lint_preamble_required.rs index 0d36447a..e97b3536 100644 --- a/eipw-lint/tests/lint_preamble_required.rs +++ b/eipw-lint/tests/lint_preamble_required.rs @@ -19,7 +19,7 @@ hello world"#; let reports = Linter::>::default() .clear_lints() - .deny("preamble-required", Required(&["a1", "b2"])) + .deny("preamble-required", Required(vec!["a1", "b2"])) .check_slice(None, src) .run() .await @@ -43,7 +43,7 @@ hello world"#; let reports = Linter::>::default() .clear_lints() - .deny("preamble-required", Required(&["a1", "b2"])) + .deny("preamble-required", Required(vec!["a1", "b2"])) .check_slice(None, src) .run() .await @@ -71,7 +71,7 @@ hello world"#; let reports = Linter::>::default() .clear_lints() - .deny("preamble-required", Required(&["a1", "b2"])) + .deny("preamble-required", Required(vec!["a1", "b2"])) .check_slice(None, src) .run() .await diff --git a/src/main.rs b/src/main.rs index 1877122d..9470e82a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,12 +8,15 @@ use annotate_snippets::snippet::Snippet; use clap::{Parser, ValueEnum}; +use eipw_lint::lints::{DefaultLint, Lint}; use eipw_lint::reporters::count::Count; use eipw_lint::reporters::{AdditionalHelp, Json, Reporter, Text}; use eipw_lint::{default_lints, Linter}; +use serde::{Deserialize, Serialize}; + use std::collections::HashMap; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; #[derive(Debug, Parser)] struct Opts { @@ -44,6 +47,10 @@ struct Opts { /// Lints to disable. #[clap(long, short('A'))] allow: Vec, + + /// Path to file defining alternate default lints. + #[clap(long, short('c'))] + config: Option, } #[derive(ValueEnum, Clone, Debug)] @@ -83,6 +90,20 @@ fn list_lints() { println!(); } +#[cfg(target_arch = "wasm32")] +async fn read_config(_path: &Path) -> Lints { + todo!() +} + +#[cfg(not(target_arch = "wasm32"))] +async fn read_config(path: &Path) -> Lints { + let contents = tokio::fs::read_to_string(path) + .await + .expect("couldn't read config file"); + + toml::from_str(&contents).expect("couldn't parse config file") +} + #[cfg(target_arch = "wasm32")] async fn collect_sources(_sources: Vec) -> Result, std::io::Error> { todo!() @@ -125,6 +146,11 @@ async fn collect_sources(sources: Vec) -> Result, std::io: Ok(output) } +#[derive(Debug, Serialize, Deserialize)] +struct Lints { + lints: HashMap>, +} + #[cfg_attr(target_arch = "wasm32", tokio::main(flavor = "current_thread"))] #[cfg_attr(not(target_arch = "wasm32"), tokio::main)] async fn run(opts: Opts) -> Result<(), usize> { @@ -147,7 +173,20 @@ async fn run(opts: Opts) -> Result<(), usize> { }); let reporter = Count::new(reporter); - let mut linter = Linter::new(reporter); + let lints: Lints; + let mut linter; + if let Some(ref path) = opts.config { + lints = read_config(path).await; + linter = Linter::with_lints( + reporter, + lints + .lints + .iter() + .map(|(k, v)| (k.as_str(), Box::new(v.clone()) as Box)), + ); + } else { + linter = Linter::new(reporter); + } if opts.no_default_lints { linter = linter.clear_lints();