Skip to content

Commit

Permalink
feat: added lint priorities
Browse files Browse the repository at this point in the history
Also used lint priorities to implement proper overlap removal
  • Loading branch information
elijah-potter committed Jan 31, 2024
1 parent c04817a commit 9b6f4d9
Show file tree
Hide file tree
Showing 11 changed files with 62 additions and 32 deletions.
34 changes: 34 additions & 0 deletions harper-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,37 @@ pub use linting::{Lint, LintKind, Linter, Suggestion};
pub use span::Span;
pub use spell::Dictionary;
pub use token::{FatToken, Punctuation, Token, TokenKind, TokenStringExt};

/// A utility function that removes overlapping lints in a vector,
/// keeping the more important ones.
///
/// Note: this function will change the ordering of the lints.
pub fn remove_overlaps(lints: &mut Vec<Lint>) {
if lints.len() < 2 {
return;
}

lints.sort_by_key(|l| l.span.start);

let mut remove_indices = Vec::new();

for i in 0..lints.len() - 1 {
let cur = &lints[i];
let next = &lints[i + 1];

if cur.span.overlaps_with(next.span) {
// Remember, lower priority means higher importance.
if next.priority < cur.priority {
remove_indices.push(i);
} else {
remove_indices.push(i + 1);
}
}
}

let mut index = 0;
lints.retain(|_| {
index += 1;
!remove_indices.contains(&(index - 1))
})
}
17 changes: 16 additions & 1 deletion harper-core/src/linting/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,27 @@ use serde::{Deserialize, Serialize};

use crate::span::Span;

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Lint {
pub span: Span,
pub lint_kind: LintKind,
pub suggestions: Vec<Suggestion>,
pub message: String,
/// A numerical value for the importance of a lint.
/// Lower = more important.
pub priority: u8,
}

impl Default for Lint {
fn default() -> Self {
Self {
span: Default::default(),
lint_kind: Default::default(),
suggestions: Default::default(),
message: Default::default(),
priority: 127,
}
}
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, Is, Default)]
Expand Down
2 changes: 1 addition & 1 deletion harper-core/src/linting/lint_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ impl LintSet {
.add_long_sentences()
.add_unclosed_quotes()
.add_sentence_capitalization()
.add_spell_check(dictionary)
.add_matcher()
.add_spell_check(dictionary)
.add_spaces();
self
}
Expand Down
3 changes: 2 additions & 1 deletion harper-core/src/linting/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ impl Matcher {
"simply","grammatical" => "simple grammatical",
"you","r" => "your",
"that","s" => "that's",
"That","s" => "that's",
"That","s" => "That's",
"that","s" => "that is",
"That","s" => "that is",
"ms" => "milliseconds",
Expand Down Expand Up @@ -228,6 +228,7 @@ impl Linter for Matcher {
"Did you mean “{}”?",
trigger.replace_with.iter().collect::<String>()
),
priority: 15,
})
}
}
Expand Down
1 change: 1 addition & 0 deletions harper-core/src/linting/repeated_words.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ impl Linter for RepeatedWords {
lint_kind: LintKind::Repetition,
suggestions: vec![Suggestion::ReplaceWith(Vec::new())],
message: "Did you mean to repeat this word?".to_string(),
..Default::default()
})
}
}
Expand Down
1 change: 1 addition & 0 deletions harper-core/src/linting/sentence_capitalization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ impl Linter for SentenceCapitalization {
suggestions: vec![Suggestion::ReplaceWith(
first_letter.to_uppercase().collect_vec(),
)],
priority: 31,
message: "This sentence does not start with a capital letter"
.to_string(),
})
Expand Down
1 change: 1 addition & 0 deletions harper-core/src/linting/spaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ impl Linter for Spaces {
"There are {} spaces where there should be only one.",
count
),
priority: 15,
})
}
}
Expand Down
1 change: 1 addition & 0 deletions harper-core/src/linting/spell_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ impl Linter for SpellCheck {
"Did you mean to spell “{}” this way?",
document.get_span_content_str(word.span)
),
priority: 63,
})
}

Expand Down
1 change: 1 addition & 0 deletions harper-core/src/linting/unclosed_quotes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ impl Linter for UnclosedQuotes {
lint_kind: LintKind::Formatting,
suggestions: vec![],
message: "This quote has no termination.".to_string(),
priority: 255,
})
}
}
Expand Down
6 changes: 4 additions & 2 deletions harper-wasm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::sync::Mutex;

use harper_core::{Dictionary, Document, LintSet, Linter};
use harper_core::{remove_overlaps, Dictionary, Document, LintSet, Linter};
use once_cell::sync::Lazy;
use serde::Serialize;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
Expand All @@ -27,7 +27,9 @@ pub fn setup() {
pub fn lint(text: String) -> Vec<JsValue> {
let document = Document::new_markdown(&text);

let lints = LINTER.lock().unwrap().lint(&document);
let mut lints = LINTER.lock().unwrap().lint(&document);

remove_overlaps(&mut lints);

lints
.into_iter()
Expand Down
27 changes: 0 additions & 27 deletions web/src/lib/analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,6 @@ export async function lintText(text: string, useWasm = defaultUseWasm): Promise<

// We only want to show fixable errors.
lints = lints.filter((lint) => lint.suggestions.length > 0);
// The `Underlines` component assumes the lints do not overlap.
lints = removeOverlaps(lints);

console.timeEnd('lintText');

Expand Down Expand Up @@ -136,28 +134,3 @@ export async function applySuggestion(
return res.text;
}
}

/** Removes lints whose spans overlap.
* NOTE: __Will__ reorder the lints. */
function removeOverlaps(lints: Lint[]) {
const sorted = lints.sort((a, b) => a.span.start - b.span.start);

let overlapsFound = false;

const filtered = sorted.filter((value, idx, arr) => {
const next = arr[idx + 1];

if (next != null && next.span.start < value.span.end) {
overlapsFound = true;
return false;
} else {
return true;
}
});

if (overlapsFound) {
return removeOverlaps(filtered);
} else {
return filtered;
}
}

0 comments on commit 9b6f4d9

Please sign in to comment.