Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow configuring diagnostic levels for lints #46

Merged
merged 5 commits into from
Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.1.0"
version = "0.2.0"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.60"
Expand All @@ -16,7 +16,7 @@ repository = "https://github.com/ethereum/eipw"
annotate-snippets = "0.9.1"
tokio = { version = "1.19.2", features = [ "fs", "macros", "rt-multi-thread" ] }
clap = { version = "3.2.8", features = [ "derive" ] }
eipw-lint = { version = "0.1.0", path = "eipw-lint", features = [ "tokio" ] }
eipw-lint = { version = "0.2.0", path = "eipw-lint", features = [ "tokio" ] }
serde_json = "1.0.81"

[patch.crates-io.comrak]
Expand Down
5 changes: 3 additions & 2 deletions eipw-lint-js/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "eipw-lint-js"
version = "0.1.7"
version = "0.2.0"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.60"
Expand All @@ -22,9 +22,10 @@ annotate-snippets = "0.9.1"
wasm-bindgen = { version = "0.2.81", features = [ "serde-serialize" ] }
wasm-bindgen-futures = "0.4.31"
console_error_panic_hook = { version = "0.1.7", optional = true }
eipw-lint = { version = "0.1.0", path = "../eipw-lint" }
eipw-lint = { version = "0.2.0", path = "../eipw-lint" }
js-sys = "0.3.58"
serde_json = "1.0.81"
serde = { version = "1.0", features = [ "derive" ] }

[dev-dependencies]
wasm-bindgen-test = "0.3.13"
Expand Down
67 changes: 54 additions & 13 deletions eipw-lint-js/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@

use eipw_lint::fetch::Fetch;
use eipw_lint::reporters::json::Json;
use eipw_lint::Linter;
use eipw_lint::{default_lints, Linter};

use js_sys::JsString;
use js_sys::{JsString, Object};

use serde::Deserialize;

use std::collections::HashMap;
use std::fmt;
use std::future::Future;
use std::path::PathBuf;
Expand Down Expand Up @@ -62,8 +65,46 @@ impl Fetch for NodeFetch {
}
}

#[derive(Debug, Deserialize)]
struct Opts {
#[serde(default)]
allow: Vec<String>,

#[serde(default)]
warn: Vec<String>,

#[serde(default)]
deny: Vec<String>,
}

impl Opts {
fn apply<R>(self, mut linter: Linter<R>) -> Linter<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 {
let (k, v) = lints.remove_entry(warn.as_str()).unwrap();
linter = linter.warn(k, v);
}
}

if !self.deny.is_empty() {
let mut lints: HashMap<_, _> = default_lints().collect();
for deny in self.deny {
let (k, v) = lints.remove_entry(deny.as_str()).unwrap();
linter = linter.deny(k, v);
}
}

linter
}
}

#[wasm_bindgen]
pub async fn lint(sources: Vec<JsValue>) -> Result<JsValue, JsValue> {
pub async fn lint(sources: Vec<JsValue>, options: Option<Object>) -> Result<JsValue, JsError> {
let sources: Vec<_> = sources
.into_iter()
.map(|v| v.as_string().unwrap())
Expand All @@ -72,31 +113,31 @@ pub async fn lint(sources: Vec<JsValue>) -> Result<JsValue, JsValue> {

let mut linter = Linter::new(Json::default()).set_fetch(NodeFetch);

if let Some(options) = options {
let opts: Opts = options.into_serde()?;
linter = opts.apply(linter);
}

for source in &sources {
linter = linter.check_file(source);
}

let reporter = match linter.run().await {
Ok(r) => r,
Err(e) => return Err(JsValue::from_str(&e.to_string())),
};
let reporter = linter.run().await?;

Ok(JsValue::from_serde(&reporter.into_reports()).unwrap())
}

#[wasm_bindgen]
pub fn format(snippet: &JsValue) -> Result<String, JsValue> {
let value: serde_json::Value = snippet
.into_serde()
.map_err(|e| JsValue::from_str(&e.to_string()))?;
pub fn format(snippet: &JsValue) -> Result<String, JsError> {
let value: serde_json::Value = snippet.into_serde()?;

let obj = match value {
serde_json::Value::Object(o) => o,
_ => return Err(JsValue::from_str("expected object")),
_ => return Err(JsError::new("expected object")),
};

match obj.get("formatted") {
Some(serde_json::Value::String(s)) => Ok(s.into()),
_ => Err(JsValue::from_str("expected `formatted` to be a string")),
_ => Err(JsError::new("expected `formatted` to be a string")),
}
}
84 changes: 81 additions & 3 deletions eipw-lint-js/tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use eipw_lint_js::{format, lint};

use js_sys::Object;
use serde_json::json;

use std::path::PathBuf;
Expand All @@ -22,7 +23,10 @@ async fn lint_one() {

let path = path.to_str().unwrap();

let result = lint(vec![JsValue::from_str(path)]).await.unwrap();
let result = lint(vec![JsValue::from_str(path)], None)
.await
.ok()
.unwrap();

let actual: serde_json::Value = result.into_serde().unwrap();
let expected = json! {
Expand Down Expand Up @@ -70,6 +74,77 @@ async fn lint_one() {
assert_eq!(expected, actual);
}

#[wasm_bindgen_test]
async fn lint_one_with_options() {
let mut path = PathBuf::from("tests");
path.push("eips");
path.push("eip-1000.md");

let path = path.to_str().unwrap();

let opts = json!(
{
"warn": ["preamble-requires-status"],
"allow": [],
"deny": []
}
);

let opts = Object::try_from(&JsValue::from_serde(&opts).unwrap())
.unwrap()
.to_owned();

let result = lint(vec![JsValue::from_str(path)], Some(opts))
.await
.ok()
.unwrap();

let actual: serde_json::Value = result.into_serde().unwrap();
let expected = json! {
[
{
"formatted": "warning[preamble-requires-status]: preamble header `requires` contains items not stable enough for a `status` of `Last Call`\n --> tests/eips/eip-1000.md:12:10\n |\n12 | requires: 20\n | --- has a less advanced status\n |\n = help: valid `status` values for this proposal are: `Draft`, `Stagnant`",
"footer": [
{
"annotation_type": "Help",
"id": null,
"label": "valid `status` values for this proposal are: `Draft`, `Stagnant`"
}
],
"opt": {
"anonymized_line_numbers": false,
"color": false
},
"slices": [
{
"annotations": [
{
"annotation_type": "Warning",
"label": "has a less advanced status",
"range": [
9,
12
]
}
],
"fold": false,
"line_start": 12,
"origin": "tests/eips/eip-1000.md",
"source": "requires: 20"
}
],
"title": {
"annotation_type": "Warning",
"id": "preamble-requires-status",
"label": "preamble header `requires` contains items not stable enough for a `status` of `Last Call`"
}
}
]
};

assert_eq!(expected, actual);
}

#[wasm_bindgen_test]
async fn format_one() {
let mut path = PathBuf::from("tests");
Expand All @@ -78,11 +153,14 @@ async fn format_one() {

let path = path.to_str().unwrap();

let result = lint(vec![JsValue::from_str(path)]).await.unwrap();
let result = lint(vec![JsValue::from_str(path)], None)
.await
.ok()
.unwrap();

let snippets: Vec<serde_json::Value> = result.into_serde().unwrap();
let snippet = JsValue::from_serde(&snippets[0]).unwrap();
let actual = format(&snippet).unwrap();
let actual = format(&snippet).ok().unwrap();

let expected = r#"error[preamble-requires-status]: preamble header `requires` contains items not stable enough for a `status` of `Last Call`
--> tests/eips/eip-1000.md:12:10
Expand Down
2 changes: 1 addition & 1 deletion eipw-lint/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "eipw-lint"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.60"
Expand Down
48 changes: 32 additions & 16 deletions eipw-lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ impl<'a> Source<'a> {
#[educe(Debug)]
#[must_use]
pub struct Linter<'a, R> {
lints: HashMap<&'a str, Box<dyn Lint>>,
lints: HashMap<&'a str, (AnnotationType, Box<dyn Lint>)>,
sources: Vec<Source<'a>>,

#[educe(Debug(ignore))]
Expand All @@ -401,23 +401,36 @@ impl<'a, R> Linter<'a, R> {
Self {
reporter,
sources: Default::default(),
lints: default_lints().collect(),
fetch: Box::new(fetch::DefaultFetch::default()),
lints: default_lints()
.map(|(slug, lint)| (slug, (AnnotationType::Error, lint)))
.collect(),
}
}

pub fn add_lint<T>(mut self, slug: &'a str, lint: T) -> Self
pub fn warn<T>(self, slug: &'a str, lint: T) -> Self
where
T: 'static + Lint,
{
if self.lints.insert(slug, lint.boxed()).is_some() {
panic!("duplicate slug: {}", slug);
}
self.add_lint(AnnotationType::Warning, slug, lint)
}

pub fn deny<T>(self, slug: &'a str, lint: T) -> Self
where
T: 'static + Lint,
{
self.add_lint(AnnotationType::Error, slug, lint)
}

fn add_lint<T>(mut self, level: AnnotationType, slug: &'a str, lint: T) -> Self
where
T: 'static + Lint,
{
self.lints.insert(slug, (level, lint.boxed()));
self
}

pub fn remove_lint(mut self, slug: &str) -> Self {
pub fn allow(mut self, slug: &str) -> Self {
if self.lints.remove(slug).is_none() {
panic!("no lint with the slug: {}", slug);
}
Expand Down Expand Up @@ -488,9 +501,11 @@ where
eips: Default::default(),
};

lint.find_resources(&context).with_context(|_| LintSnafu {
origin: source_origin.clone(),
})?;
lint.1
.find_resources(&context)
.with_context(|_| LintSnafu {
origin: source_origin.clone(),
})?;

let eips = context.eips.into_inner();

Expand Down Expand Up @@ -557,13 +572,14 @@ where
None => continue,
};

let context = Context {
inner,
reporter: &self.reporter,
eips: &parsed_eips,
};
for (slug, (annotation_type, lint)) in &lints {
let context = Context {
inner: inner.clone(),
reporter: &self.reporter,
eips: &parsed_eips,
annotation_type: *annotation_type,
};

for (slug, lint) in &lints {
lint.lint(slug, &context).with_context(|_| LintSnafu {
origin: origin.clone(),
})?;
Expand Down
Loading