Skip to content

Commit e55af52

Browse files
committed
refactor(core): separated types into their own modules
1 parent 5f21fc8 commit e55af52

File tree

6 files changed

+166
-153
lines changed

6 files changed

+166
-153
lines changed

harper-core/src/linting/lint.rs

+2-150
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
use std::fmt::Display;
2-
3-
use is_macro::Is;
41
use serde::{Deserialize, Serialize};
52

63
use crate::Span;
74

5+
use super::{LintKind, Suggestion};
6+
87
/// An error found in text.
98
#[derive(Debug, Clone, Serialize, Deserialize)]
109
pub struct Lint {
@@ -37,150 +36,3 @@ impl Default for Lint {
3736
}
3837
}
3938
}
40-
41-
/// The general category a [`Lint`] falls into.
42-
/// There's no reason not to add a new item here if you are adding a new rule that doesn't fit
43-
/// the existing categories.
44-
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Is, Default)]
45-
pub enum LintKind {
46-
Spelling,
47-
Capitalization,
48-
Style,
49-
Formatting,
50-
Repetition,
51-
Enhancement,
52-
Readability,
53-
WordChoice,
54-
#[default]
55-
Miscellaneous,
56-
}
57-
58-
impl Display for LintKind {
59-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60-
let s = match self {
61-
LintKind::Spelling => "Spelling",
62-
LintKind::Capitalization => "Capitalization",
63-
LintKind::Formatting => "Formatting",
64-
LintKind::Repetition => "Repetition",
65-
LintKind::Readability => "Readability",
66-
LintKind::Miscellaneous => "Miscellaneous",
67-
LintKind::Enhancement => "Enhancement",
68-
LintKind::WordChoice => "Word Choice",
69-
LintKind::Style => "Style",
70-
};
71-
72-
write!(f, "{}", s)
73-
}
74-
}
75-
76-
/// A suggested edit that could resolve a [`Lint`].
77-
#[derive(Debug, Clone, Serialize, Deserialize, Is, PartialEq, Eq)]
78-
pub enum Suggestion {
79-
/// Replace the offending text with a specific character sequence.
80-
ReplaceWith(Vec<char>),
81-
/// Insert the provided characters _after_ the offending text.
82-
InsertAfter(Vec<char>),
83-
/// Remove the offending text.
84-
Remove,
85-
}
86-
87-
impl Suggestion {
88-
/// Variant of [`Self::replace_with_match_case`] that accepts a static string.
89-
pub fn replace_with_match_case_str(value: &'static str, template: &[char]) -> Self {
90-
Self::replace_with_match_case(value.chars().collect(), template)
91-
}
92-
93-
/// Construct an instance of [`Self::ReplaceWith`], but make the content match the case of the
94-
/// provided template.
95-
///
96-
/// For example, if we want to replace "You're" with "You are", we can provide "you are" and
97-
/// "You're".
98-
pub fn replace_with_match_case(mut value: Vec<char>, template: &[char]) -> Self {
99-
for (v, t) in value.iter_mut().zip(template.iter()) {
100-
if v.is_ascii_uppercase() != t.is_ascii_uppercase() {
101-
if t.is_uppercase() {
102-
*v = v.to_ascii_uppercase();
103-
} else {
104-
*v = v.to_ascii_lowercase();
105-
}
106-
}
107-
}
108-
109-
Self::ReplaceWith(value)
110-
}
111-
112-
/// Apply a suggestion to a given text.
113-
pub fn apply(&self, span: Span, source: &mut Vec<char>) {
114-
match self {
115-
Self::ReplaceWith(chars) => {
116-
// Avoid allocation if possible
117-
if chars.len() == span.len() {
118-
for (index, c) in chars.iter().enumerate() {
119-
source[index + span.start] = *c
120-
}
121-
} else {
122-
let popped = source.split_off(span.start);
123-
124-
source.extend(chars);
125-
source.extend(popped.into_iter().skip(span.len()));
126-
}
127-
}
128-
Self::Remove => {
129-
for i in span.end..source.len() {
130-
source[i - span.len()] = source[i];
131-
}
132-
133-
source.truncate(source.len() - span.len());
134-
}
135-
Self::InsertAfter(chars) => {
136-
let popped = source.split_off(span.end);
137-
source.extend(chars);
138-
source.extend(popped);
139-
}
140-
}
141-
}
142-
}
143-
144-
impl Display for Suggestion {
145-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146-
match self {
147-
Suggestion::ReplaceWith(with) => {
148-
write!(f, "Replace with: “{}”", with.iter().collect::<String>())
149-
}
150-
Suggestion::InsertAfter(with) => {
151-
write!(f, "Insert “{}”", with.iter().collect::<String>())
152-
}
153-
Suggestion::Remove => write!(f, "Remove error"),
154-
}
155-
}
156-
}
157-
158-
#[cfg(test)]
159-
mod tests {
160-
use crate::Span;
161-
162-
use super::Suggestion;
163-
164-
#[test]
165-
fn insert_comma_after() {
166-
let source = "This is a test";
167-
let mut source_chars = source.chars().collect();
168-
let sug = Suggestion::InsertAfter(vec![',']);
169-
sug.apply(Span::new(0, 4), &mut source_chars);
170-
171-
assert_eq!(source_chars, "This, is a test".chars().collect::<Vec<_>>());
172-
}
173-
174-
#[test]
175-
fn suggestion_your_match_case() {
176-
let template: Vec<_> = "You're".chars().collect();
177-
let value: Vec<_> = "you are".chars().collect();
178-
179-
let correct = "You are".chars().collect();
180-
181-
assert_eq!(
182-
Suggestion::replace_with_match_case(value, &template),
183-
Suggestion::ReplaceWith(correct)
184-
)
185-
}
186-
}

harper-core/src/linting/lint_kind.rs

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use std::fmt::Display;
2+
3+
use is_macro::Is;
4+
use serde::{Deserialize, Serialize};
5+
6+
/// The general category a [`Lint`] falls into.
7+
/// There's no reason not to add a new item here if you are adding a new rule that doesn't fit
8+
/// the existing categories.
9+
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Is, Default)]
10+
pub enum LintKind {
11+
Spelling,
12+
Capitalization,
13+
Style,
14+
Formatting,
15+
Repetition,
16+
Enhancement,
17+
Readability,
18+
WordChoice,
19+
#[default]
20+
Miscellaneous,
21+
}
22+
23+
impl Display for LintKind {
24+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25+
let s = match self {
26+
LintKind::Spelling => "Spelling",
27+
LintKind::Capitalization => "Capitalization",
28+
LintKind::Formatting => "Formatting",
29+
LintKind::Repetition => "Repetition",
30+
LintKind::Readability => "Readability",
31+
LintKind::Miscellaneous => "Miscellaneous",
32+
LintKind::Enhancement => "Enhancement",
33+
LintKind::WordChoice => "Word Choice",
34+
LintKind::Style => "Style",
35+
};
36+
37+
write!(f, "{}", s)
38+
}
39+
}

harper-core/src/linting/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod lets_confusion;
1515
mod linking_verbs;
1616
mod lint;
1717
mod lint_group;
18+
mod lint_kind;
1819
mod long_sentences;
1920
mod matcher;
2021
mod merge_linters;
@@ -32,6 +33,7 @@ mod somewhat_something;
3233
mod spaces;
3334
mod spell_check;
3435
mod spelled_numbers;
36+
mod suggestion;
3537
mod terminating_conjunctions;
3638
mod that_which;
3739
mod unclosed_quotes;
@@ -48,8 +50,9 @@ pub use dot_initialisms::DotInitialisms;
4850
pub use ellipsis_length::EllipsisLength;
4951
pub use lets_confusion::LetsConfusion;
5052
pub use linking_verbs::LinkingVerbs;
51-
pub use lint::{Lint, LintKind, Suggestion};
53+
pub use lint::Lint;
5254
pub use lint_group::{LintGroup, LintGroupConfig};
55+
pub use lint_kind::LintKind;
5356
pub use long_sentences::LongSentences;
5457
pub use matcher::Matcher;
5558
pub use merge_words::MergeWords;
@@ -69,6 +72,7 @@ pub use somewhat_something::SomewhatSomething;
6972
pub use spaces::Spaces;
7073
pub use spell_check::SpellCheck;
7174
pub use spelled_numbers::SpelledNumbers;
75+
pub use suggestion::Suggestion;
7276
pub use terminating_conjunctions::TerminatingConjunctions;
7377
pub use that_which::ThatWhich;
7478
pub use unclosed_quotes::UnclosedQuotes;

harper-core/src/linting/sentence_capitalization.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use itertools::Itertools;
22

3-
use super::lint::Suggestion;
3+
use super::Suggestion;
44
use super::{Lint, LintKind, Linter};
55
use crate::document::Document;
66
use crate::{Token, TokenKind, TokenStringExt};

harper-core/src/linting/spell_check.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use hashbrown::HashMap;
22
use smallvec::ToSmallVec;
33

4-
use super::lint::Suggestion;
4+
use super::Suggestion;
55
use super::{Lint, LintKind, Linter};
66
use crate::document::Document;
77
use crate::spell::suggest_correct_spelling;

harper-core/src/linting/suggestion.rs

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use std::fmt::Display;
2+
3+
use is_macro::Is;
4+
use serde::{Deserialize, Serialize};
5+
6+
use crate::Span;
7+
8+
/// A suggested edit that could resolve a [`Lint`].
9+
#[derive(Debug, Clone, Serialize, Deserialize, Is, PartialEq, Eq)]
10+
pub enum Suggestion {
11+
/// Replace the offending text with a specific character sequence.
12+
ReplaceWith(Vec<char>),
13+
/// Insert the provided characters _after_ the offending text.
14+
InsertAfter(Vec<char>),
15+
/// Remove the offending text.
16+
Remove,
17+
}
18+
19+
impl Suggestion {
20+
/// Variant of [`Self::replace_with_match_case`] that accepts a static string.
21+
pub fn replace_with_match_case_str(value: &'static str, template: &[char]) -> Self {
22+
Self::replace_with_match_case(value.chars().collect(), template)
23+
}
24+
25+
/// Construct an instance of [`Self::ReplaceWith`], but make the content match the case of the
26+
/// provided template.
27+
///
28+
/// For example, if we want to replace "You're" with "You are", we can provide "you are" and
29+
/// "You're".
30+
pub fn replace_with_match_case(mut value: Vec<char>, template: &[char]) -> Self {
31+
for (v, t) in value.iter_mut().zip(template.iter()) {
32+
if v.is_ascii_uppercase() != t.is_ascii_uppercase() {
33+
if t.is_uppercase() {
34+
*v = v.to_ascii_uppercase();
35+
} else {
36+
*v = v.to_ascii_lowercase();
37+
}
38+
}
39+
}
40+
41+
Self::ReplaceWith(value)
42+
}
43+
44+
/// Apply a suggestion to a given text.
45+
pub fn apply(&self, span: Span, source: &mut Vec<char>) {
46+
match self {
47+
Self::ReplaceWith(chars) => {
48+
// Avoid allocation if possible
49+
if chars.len() == span.len() {
50+
for (index, c) in chars.iter().enumerate() {
51+
source[index + span.start] = *c
52+
}
53+
} else {
54+
let popped = source.split_off(span.start);
55+
56+
source.extend(chars);
57+
source.extend(popped.into_iter().skip(span.len()));
58+
}
59+
}
60+
Self::Remove => {
61+
for i in span.end..source.len() {
62+
source[i - span.len()] = source[i];
63+
}
64+
65+
source.truncate(source.len() - span.len());
66+
}
67+
Self::InsertAfter(chars) => {
68+
let popped = source.split_off(span.end);
69+
source.extend(chars);
70+
source.extend(popped);
71+
}
72+
}
73+
}
74+
}
75+
76+
impl Display for Suggestion {
77+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78+
match self {
79+
Suggestion::ReplaceWith(with) => {
80+
write!(f, "Replace with: “{}”", with.iter().collect::<String>())
81+
}
82+
Suggestion::InsertAfter(with) => {
83+
write!(f, "Insert “{}”", with.iter().collect::<String>())
84+
}
85+
Suggestion::Remove => write!(f, "Remove error"),
86+
}
87+
}
88+
}
89+
90+
#[cfg(test)]
91+
mod tests {
92+
use crate::Span;
93+
94+
use super::Suggestion;
95+
96+
#[test]
97+
fn insert_comma_after() {
98+
let source = "This is a test";
99+
let mut source_chars = source.chars().collect();
100+
let sug = Suggestion::InsertAfter(vec![',']);
101+
sug.apply(Span::new(0, 4), &mut source_chars);
102+
103+
assert_eq!(source_chars, "This, is a test".chars().collect::<Vec<_>>());
104+
}
105+
106+
#[test]
107+
fn suggestion_your_match_case() {
108+
let template: Vec<_> = "You're".chars().collect();
109+
let value: Vec<_> = "you are".chars().collect();
110+
111+
let correct = "You are".chars().collect();
112+
113+
assert_eq!(
114+
Suggestion::replace_with_match_case(value, &template),
115+
Suggestion::ReplaceWith(correct)
116+
)
117+
}
118+
}

0 commit comments

Comments
 (0)