From f465bec169caaa257660387743165cceedcfad48 Mon Sep 17 00:00:00 2001 From: Andrew Dunbar Date: Fri, 7 Nov 2025 02:52:03 +0800 Subject: [PATCH 001/178] =?UTF-8?q?feat:=20more=20bigger=E2=86=92bigger,?= =?UTF-8?q?=20most=20best=E2=86=92best=20(#2148)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: more bigger→bigger, most best→best Resolves #508 * fix: resolve problems from "Run 'check-rust'" --- harper-core/src/linting/lint_group.rs | 18 +++--- harper-core/src/linting/mod.rs | 2 + harper-core/src/linting/more_better.rs | 89 ++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 harper-core/src/linting/more_better.rs diff --git a/harper-core/src/linting/lint_group.rs b/harper-core/src/linting/lint_group.rs index d65009c3..d38af552 100644 --- a/harper-core/src/linting/lint_group.rs +++ b/harper-core/src/linting/lint_group.rs @@ -89,6 +89,7 @@ use super::mixed_bag::MixedBag; use super::modal_of::ModalOf; use super::modal_seem::ModalSeem; use super::months::Months; +use super::more_better::MoreBetter; use super::most_number::MostNumber; use super::most_of_the_times::MostOfTheTimes; use super::multiple_sequential_pronouns::MultipleSequentialPronouns; @@ -463,14 +464,13 @@ impl LintGroup { insert_expr_rule!(BackInTheDay, true); insert_expr_rule!(BeAllowed, true); insert_expr_rule!(BestOfAllTime, true); - insert_expr_rule!(Bought, true); insert_expr_rule!(BoringWords, false); + insert_expr_rule!(Bought, true); insert_expr_rule!(Cant, true); insert_struct_rule!(CapitalizePersonalPronouns, true); insert_expr_rule!(CautionaryTale, true); insert_expr_rule!(ChangeTack, true); insert_expr_rule!(ChockFull, true); - insert_struct_rule!(OrthographicConsistency, true); insert_struct_rule!(CommaFixes, true); insert_struct_rule!(CompoundNouns, true); insert_expr_rule!(CompoundSubjectI, true); @@ -517,14 +517,14 @@ impl LintGroup { insert_struct_rule!(LongSentences, true); insert_expr_rule!(LookingForwardTo, true); insert_struct_rule!(MergeWords, true); - insert_expr_rule!(SplitWords, true); - insert_expr_rule!(Misspell, true); insert_expr_rule!(MissingPreposition, true); insert_expr_rule!(MissingTo, true); + insert_expr_rule!(Misspell, true); insert_expr_rule!(MixedBag, true); insert_expr_rule!(ModalOf, true); insert_expr_rule!(ModalSeem, true); insert_expr_rule!(Months, true); + insert_expr_rule!(MoreBetter, true); insert_expr_rule!(MostNumber, true); insert_expr_rule!(MostOfTheTimes, true); insert_expr_rule!(MultipleSequentialPronouns, true); @@ -534,8 +534,8 @@ impl LintGroup { insert_struct_rule!(NoOxfordComma, false); insert_expr_rule!(Nobody, true); insert_struct_rule!(NominalWants, true); - insert_struct_rule!(NounVerbConfusion, true); insert_expr_rule!(NounCountability, true); + insert_struct_rule!(NounVerbConfusion, true); insert_struct_rule!(NumberSuffixCapitalization, true); insert_struct_rule!(OfCourse, true); insert_expr_rule!(OnFloor, true); @@ -543,6 +543,7 @@ impl LintGroup { insert_expr_rule!(OneAndTheSame, true); insert_expr_rule!(OpenCompounds, true); insert_expr_rule!(OpenTheLight, true); + insert_struct_rule!(OrthographicConsistency, true); insert_struct_rule!(OughtToBe, true); insert_expr_rule!(OutOfDate, true); insert_struct_rule!(OxfordComma, true); @@ -567,18 +568,19 @@ impl LintGroup { insert_expr_rule!(ShootOneselfInTheFoot, true); insert_expr_rule!(SimplePastToPastParticiple, true); insert_expr_rule!(SinceDuration, true); + insert_expr_rule!(SomeWithoutArticle, true); insert_expr_rule!(SomethingIs, true); insert_expr_rule!(SomewhatSomething, true); insert_expr_rule!(SoughtAfter, true); - insert_expr_rule!(SomeWithoutArticle, true); insert_struct_rule!(Spaces, true); insert_struct_rule!(SpelledNumbers, false); + insert_expr_rule!(SplitWords, true); insert_expr_rule!(ThatThan, true); insert_expr_rule!(ThatWhich, true); insert_expr_rule!(TheHowWhy, true); insert_struct_rule!(TheMy, true); - insert_expr_rule!(Theres, true); insert_expr_rule!(ThenThan, true); + insert_expr_rule!(Theres, true); insert_expr_rule!(ThingThink, true); insert_expr_rule!(ThoughThought, true); insert_expr_rule!(ThrowAway, true); @@ -589,8 +591,8 @@ impl LintGroup { insert_struct_rule!(UnclosedQuotes, true); insert_expr_rule!(UpdatePlaceNames, true); insert_expr_rule!(UseGenitive, false); - insert_expr_rule!(ViceVersa, true); insert_expr_rule!(VeryUnique, true); + insert_expr_rule!(ViceVersa, true); insert_expr_rule!(WasAloud, true); insert_expr_rule!(WayTooAdjective, true); insert_expr_rule!(WellEducated, true); diff --git a/harper-core/src/linting/mod.rs b/harper-core/src/linting/mod.rs index 6bafe41b..571a2f20 100644 --- a/harper-core/src/linting/mod.rs +++ b/harper-core/src/linting/mod.rs @@ -96,6 +96,7 @@ mod mixed_bag; mod modal_of; mod modal_seem; mod months; +mod more_better; mod most_number; mod most_of_the_times; mod multiple_sequential_pronouns; @@ -267,6 +268,7 @@ pub use mixed_bag::MixedBag; pub use modal_of::ModalOf; pub use modal_seem::ModalSeem; pub use months::Months; +pub use more_better::MoreBetter; pub use most_number::MostNumber; pub use most_of_the_times::MostOfTheTimes; pub use multiple_sequential_pronouns::MultipleSequentialPronouns; diff --git a/harper-core/src/linting/more_better.rs b/harper-core/src/linting/more_better.rs new file mode 100644 index 00000000..8672b8ac --- /dev/null +++ b/harper-core/src/linting/more_better.rs @@ -0,0 +1,89 @@ +use crate::expr::{Expr, SequenceExpr}; +use crate::linting::{ExprLinter, Lint, LintKind, Suggestion}; +use crate::token::Token; +use crate::token_string_ext::TokenStringExt; + +pub struct MoreBetter { + expr: Box, +} + +impl Default for MoreBetter { + fn default() -> Self { + Self { + expr: Box::new(SequenceExpr::any_of(vec![ + Box::new( + SequenceExpr::default() + .t_aco("more") + .t_ws() + .then_comparative_adjective(), + ), + Box::new( + SequenceExpr::default() + .t_aco("most") + .t_ws() + .then_superlative_adjective(), + ), + ])), + } + } +} + +impl ExprLinter for MoreBetter { + fn expr(&self) -> &dyn Expr { + self.expr.as_ref() + } + + fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option { + let phrase_span = toks.span()?; + + let degree_str = toks.first()?.span.get_content_string(src); + let adj_span = toks.last()?.span; + + let suggestion = Suggestion::replace_with_match_case( + adj_span.get_content(src).to_vec(), + phrase_span.get_content(src), + ); + + let message = format!( + "{} is already in the {} form, the {} is redundant", + adj_span.get_content_string(src), + if degree_str.eq_ignore_ascii_case("more") { + "comparative" + } else { + "superlative" + }, + degree_str, + ); + + Some(Lint { + span: phrase_span, + lint_kind: LintKind::Redundancy, + suggestions: vec![suggestion], + message, + ..Default::default() + }) + } + + fn description(&self) -> &'static str { + "Finds redundant paring of `more` or `most` with adjectives already in the comparative or superlative form." + } +} + +#[cfg(test)] +mod tests { + use crate::linting::{MoreBetter, tests::assert_suggestion_result}; + + #[test] + fn flag_most_biggest() { + assert_suggestion_result("Most biggest", MoreBetter::default(), "Biggest"); + } + + #[test] + fn flag_more_better_and_more_better() { + assert_suggestion_result( + "More bigger is more better", + MoreBetter::default(), + "Bigger is better", + ); + } +} From c50dfcdc40cff02dfdbba0458ad76defe8676044 Mon Sep 17 00:00:00 2001 From: Andrew Dunbar Date: Mon, 10 Nov 2025 04:41:10 +0800 Subject: [PATCH 002/178] feat: flag "allow to do" (#2159) Resolves #423 --- harper-core/src/linting/allow_to.rs | 119 ++++++++++++++++++++++++++ harper-core/src/linting/lint_group.rs | 2 + harper-core/src/linting/mod.rs | 2 + 3 files changed, 123 insertions(+) create mode 100644 harper-core/src/linting/allow_to.rs diff --git a/harper-core/src/linting/allow_to.rs b/harper-core/src/linting/allow_to.rs new file mode 100644 index 00000000..f90729a6 --- /dev/null +++ b/harper-core/src/linting/allow_to.rs @@ -0,0 +1,119 @@ +use crate::expr::{Expr, SequenceExpr}; +use crate::linting::{ExprLinter, Lint, LintKind}; +use crate::token::Token; +use crate::token_string_ext::TokenStringExt; + +pub struct AllowTo { + exp: Box, +} + +impl Default for AllowTo { + fn default() -> Self { + Self { + // Note: Does not include "allowed to", which is a legitimate usage in its own right. + exp: Box::new( + SequenceExpr::word_set(&["allow", "allowing", "allows"]) + .t_ws() + .t_aco("to") + .then_optional(SequenceExpr::default().t_ws().then_adverb()) + .t_ws() + .then_any_word(), + ), + } + } +} + +impl ExprLinter for AllowTo { + fn expr(&self) -> &dyn Expr { + self.exp.as_ref() + } + + fn match_to_lint(&self, toks: &[Token], _src: &[char]) -> Option { + let span = toks.span()?; + let first = toks.first()?; + let allow = first.span.get_content_string(_src); + + let message = format!( + "For correct usage, either add a subject between `{allow}` and `to` (e.g., `{allow} someone to do`) or use the present participle (e.g., `{allow} doing`)." + ); + + Some(Lint { + span, + lint_kind: LintKind::Grammar, + suggestions: vec![], + message, + ..Default::default() + }) + } + + fn description(&self) -> &'static str { + "Flags erroneous usage of `allow to` without a subject." + } +} + +#[cfg(test)] +mod tests { + use crate::linting::{ + AllowTo, + tests::{assert_lint_count, assert_no_lints}, + }; + + #[test] + fn flag_allow_to() { + assert_lint_count( + "Allow to change approval policy during running task # 4394.", + AllowTo::default(), + 1, + ); + } + + #[test] + fn flag_allowing_to() { + assert_lint_count( + "Allowing to have multiple views with different filtering # 952.", + AllowTo::default(), + 1, + ); + } + + #[test] + fn flag_allows_to() { + assert_lint_count( + "It is easily doable for classic IHostBuilder, because its extension allows to pass configure action", + AllowTo::default(), + 1, + ); + } + + #[test] + fn dont_flag_allowed_to() { + assert_no_lints( + "In C and C++ aliasing has to do with what expression types we are allowed to access stored values through.", + AllowTo::default(), + ); + } + + #[test] + fn dont_flag_allow_pronoun_to() { + assert_no_lints( + "It would be really great to allow me to enter body data using multipart form", + AllowTo::default(), + ); + } + + #[test] + fn dont_flag_allow_noun_to() { + assert_no_lints( + "Allows users to export SMART statistics from any connected hard drive", + AllowTo::default(), + ); + } + + #[test] + fn dont_flag_allow_np_to() { + assert_no_lints( + "This vulnerability allows an authenticated attacker to infer data from the database by measuring response times", + AllowTo::default(), + ); + } +} diff --git a/harper-core/src/linting/lint_group.rs b/harper-core/src/linting/lint_group.rs index d38af552..be6c0491 100644 --- a/harper-core/src/linting/lint_group.rs +++ b/harper-core/src/linting/lint_group.rs @@ -17,6 +17,7 @@ use super::adjective_double_degree::AdjectiveDoubleDegree; use super::adjective_of_a::AdjectiveOfA; use super::after_later::AfterLater; use super::all_intents_and_purposes::AllIntentsAndPurposes; +use super::allow_to::AllowTo; use super::am_in_the_morning::AmInTheMorning; use super::amounts_for::AmountsFor; use super::an_a::AnA; @@ -454,6 +455,7 @@ impl LintGroup { insert_struct_rule!(AdjectiveOfA, true); insert_expr_rule!(AfterLater, true); insert_expr_rule!(AllIntentsAndPurposes, true); + insert_expr_rule!(AllowTo, true); insert_struct_rule!(AmInTheMorning, true); insert_expr_rule!(AmountsFor, true); insert_struct_rule!(AnA, true); diff --git a/harper-core/src/linting/mod.rs b/harper-core/src/linting/mod.rs index 571a2f20..952bf909 100644 --- a/harper-core/src/linting/mod.rs +++ b/harper-core/src/linting/mod.rs @@ -8,6 +8,7 @@ mod adjective_double_degree; mod adjective_of_a; mod after_later; mod all_intents_and_purposes; +mod allow_to; mod am_in_the_morning; mod amounts_for; mod an_a; @@ -189,6 +190,7 @@ pub use adjective_double_degree::AdjectiveDoubleDegree; pub use adjective_of_a::AdjectiveOfA; pub use after_later::AfterLater; pub use all_intents_and_purposes::AllIntentsAndPurposes; +pub use allow_to::AllowTo; pub use am_in_the_morning::AmInTheMorning; pub use amounts_for::AmountsFor; pub use an_a::AnA; From 76d15dcbcd25ebac9051f83537626d23acb82158 Mon Sep 17 00:00:00 2001 From: Andrew Dunbar Date: Mon, 10 Nov 2025 04:41:58 +0800 Subject: [PATCH 003/178] =?UTF-8?q?feat:nerve=20wreck=E2=86=92nervous=20wr?= =?UTF-8?q?eck=20(#2154)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/linting/phrase_set_corrections/mod.rs | 9 +++ .../linting/phrase_set_corrections/tests.rs | 59 ++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/harper-core/src/linting/phrase_set_corrections/mod.rs b/harper-core/src/linting/phrase_set_corrections/mod.rs index 5f112f3a..0de592e8 100644 --- a/harper-core/src/linting/phrase_set_corrections/mod.rs +++ b/harper-core/src/linting/phrase_set_corrections/mod.rs @@ -374,6 +374,15 @@ pub fn lint_group() -> LintGroup { "Don't inflect `seem` in `make it seem`.", "Corrects `make it seems` to `make it seem`." ), + "NervousWreck" => ( + &[ + (&["nerve wreck", "nerve-wreck"], &["nervous wreck"]), + (&["nerve wrecks", "nerve-wrecks"], &["nervous wrecks"]), + ], + "Use `nervous wreck` when referring to a person who is extremely anxious or upset. `Nerve wreck` is non-standard but sometimes used for events or situations.", + "Suggests using `nervous wreck` when referring to a person's emotional state.", + LintKind::Eggcorn + ), "RiseTheQuestion" => ( &[ (&["rise the question"], &["raise the question"]), diff --git a/harper-core/src/linting/phrase_set_corrections/tests.rs b/harper-core/src/linting/phrase_set_corrections/tests.rs index 24f6d02f..9eb23860 100644 --- a/harper-core/src/linting/phrase_set_corrections/tests.rs +++ b/harper-core/src/linting/phrase_set_corrections/tests.rs @@ -1,5 +1,5 @@ use crate::linting::tests::{ - assert_lint_count, assert_nth_suggestion_result, assert_suggestion_result, + assert_lint_count, assert_no_lints, assert_nth_suggestion_result, assert_suggestion_result, }; use super::lint_group; @@ -1087,6 +1087,63 @@ fn corrects_made_it_seemed() { ); } +// NervousWreck + +#[test] +#[ignore = "Harper matches case by letter index as 'How Not to Be a Complete NervoUs wreck in an Interview'"] +fn correct_nerve_wreck_space_title_case() { + assert_suggestion_result( + "How Not to Be a Complete Nerve Wreck in an Interview", + lint_group(), + "How Not to Be a Complete Nervous Wreck in an Interview", + ); +} + +#[test] +fn correct_nerve_wreck_space() { + assert_suggestion_result( + "The nerve wreck you are makes you seem anxious and agitated so your employer will believe the complaints.", + lint_group(), + "The nervous wreck you are makes you seem anxious and agitated so your employer will believe the complaints.", + ); +} + +#[test] +fn correct_nerve_wreck_hyphen() { + assert_suggestion_result( + "the child receives little education and grows up to be a nerve-wreck", + lint_group(), + "the child receives little education and grows up to be a nervous wreck", + ); +} + +#[test] +fn correct_nerve_wreck_hyphen_plural() { + assert_suggestion_result( + "This helps us not to become nerve wrecks while looking at the side mirrors", + lint_group(), + "This helps us not to become nervous wrecks while looking at the side mirrors", + ); +} + +#[test] +#[ignore = "We can't detect when the altered form is used for an event rather than a person."] +fn dont_correct_it_was_a_nerve_wreck() { + assert_no_lints( + "It was a nerve-wreck, but I was also excited to see what would happen next.", + lint_group(), + ); +} + +#[test] +#[ignore = "We can't detect when the altered form is used for an event rather than a person."] +fn dont_correct_so_much_nerve_wreck() { + assert_no_lints( + "So much nerve wreck for such a simple game ...", + lint_group(), + ); +} + // RaiseTheQuestion // -raise the question- From 34f7a8321d3d76e3839e4142890e89a5806fb790 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 08:12:57 -0700 Subject: [PATCH 004/178] build(deps): bump burn from 0.19.0 to 0.19.1 (#2161) Bumps [burn](https://github.com/tracel-ai/burn) from 0.19.0 to 0.19.1. - [Release notes](https://github.com/tracel-ai/burn/releases) - [Commits](https://github.com/tracel-ai/burn/compare/v0.19.0...v0.19.1) --- updated-dependencies: - dependency-name: burn dependency-version: 0.19.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 160 ++++++++++++++++++------------------ harper-pos-utils/Cargo.toml | 2 +- 2 files changed, 81 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1acabcd5..511d0396 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -293,9 +293,9 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "burn" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddecb408e100eedc1175cf0fd8092507dcea92ef5c22e1e25be85af7fed4cd7f" +checksum = "0291ea5c68786545e239a02f63331cfe39da7485164ae05197d5be6f148d0557" dependencies = [ "burn-autodiff", "burn-candle", @@ -313,9 +313,9 @@ dependencies = [ [[package]] name = "burn-autodiff" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76610cf4fd32a9dd35b62989feb40a137a807f6ebfb015166a0b785b55657ff" +checksum = "917423a74bf4d39f17a6799089869648e3d2b6ac89d93901aab4aeb9a7f82138" dependencies = [ "burn-common", "burn-tensor", @@ -329,9 +329,9 @@ dependencies = [ [[package]] name = "burn-candle" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "336126c4874dac8458f65fe743958397833e08a50b702740c042cad6ead8343b" +checksum = "2891811d41ae30b5f1f660e7615b757b2cb4128af5e311b213656de3875e4acb" dependencies = [ "burn-common", "burn-tensor", @@ -342,9 +342,9 @@ dependencies = [ [[package]] name = "burn-common" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226fe90c2a4a182dd6e58630ec836772efb8fb263c173bd4cda6f76a08924ac7" +checksum = "5eb445304e4f91f8633d23c9a5258cd93639d13ce2ee47d4821fd519b683bf02" dependencies = [ "cubecl-common", "rayon", @@ -353,9 +353,9 @@ dependencies = [ [[package]] name = "burn-core" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9621a37f59cdfa4492398f84925acdb0a9fb10d515d4e6809b62353e27e21f5d" +checksum = "20c93e754864080a8c27b9a47e3b6f7d79013cf82c9ce00ed57c9ba51a3e34c5" dependencies = [ "ahash", "bincode", @@ -383,9 +383,9 @@ dependencies = [ [[package]] name = "burn-cubecl" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6089a87d4646d62b56b527f09120ec11fdd699cce773c93d029ef3f7751911e" +checksum = "0dd16308b7b0291c77f2d7acf428bc8254ec3db88a430a26cf3d3b0b63ae2d46" dependencies = [ "burn-common", "burn-cubecl-fusion", @@ -409,9 +409,9 @@ dependencies = [ [[package]] name = "burn-cubecl-fusion" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50398855bd40bfa74e10667c312fe7422986cfc230e854d4512fb709a28f4a81" +checksum = "cc21cf88201dfbf242cadb638a0cc924010727fc37d6a719f7e10548b339c63a" dependencies = [ "burn-common", "burn-fusion", @@ -426,9 +426,9 @@ dependencies = [ [[package]] name = "burn-cuda" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7adaf3f97a129c2394eb3170040410ada7b58d9f1aa28f5ec6cd6deb6f37f1" +checksum = "1e104dcf07eac70c7b5864b51d792df3360b11b00febb60543b4283bb414bb61" dependencies = [ "burn-cubecl", "burn-tensor", @@ -441,9 +441,9 @@ dependencies = [ [[package]] name = "burn-dataset" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cda16435acac1c6ea9c3f9bed5a4210a4cebbdb14abc30a1aeeff45460e3b6e" +checksum = "534d4398fd6aaec32f8caeb3f20ddffcd8a059bdefc01cc2794b91b4e984e8ea" dependencies = [ "csv", "derive-new", @@ -460,9 +460,9 @@ dependencies = [ [[package]] name = "burn-derive" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16277c15ea0eeeab321e8f7251be786483d6e7755e579ab39d7dd15043f7c3e1" +checksum = "3bcf49261de086b8206de6c8962d2adf23feb476119a18e384f5b2c9af07c0cf" dependencies = [ "derive-new", "proc-macro2", @@ -472,9 +472,9 @@ dependencies = [ [[package]] name = "burn-fusion" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b706d24d5dba3517ad0239e7591cd41c79dd5331a828ad736988b7735aa3e94a" +checksum = "662bf2679c04be34a0c3f1b11f77f6ff49456af1620d1eca311bc2562bbb56c9" dependencies = [ "burn-common", "burn-ir", @@ -489,9 +489,9 @@ dependencies = [ [[package]] name = "burn-ir" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69150047c338878c0fc1a4d8e26c399d1e2d9c97c4f87eecc4130dd03c2472a1" +checksum = "9161239d5691c4ab6f470f2c65aaec5c0a7c1f0b0da390700bcd59f5a77d1d7b" dependencies = [ "burn-tensor", "hashbrown 0.15.5", @@ -501,9 +501,9 @@ dependencies = [ [[package]] name = "burn-ndarray" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9b60c14a706bf2ddc9e1a49e9eb48eb1828b11c719785de676ee7372603de6" +checksum = "b78bcf4a3508043342f918e796dc79108b5f3252398403eb73952847e7683374" dependencies = [ "atomic_float", "burn-autodiff", @@ -528,9 +528,9 @@ dependencies = [ [[package]] name = "burn-nn" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b73072f9c77798f641a7f66dc6fa3e1092021cf39322e8ea68ff7f572c17ae" +checksum = "dc7829c87c4dd6c7929b50fd981e7e8d1b77414323da30ce2067a3e8b7ea422b" dependencies = [ "burn-core", "num-traits", @@ -538,9 +538,9 @@ dependencies = [ [[package]] name = "burn-optim" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f89c10e95d5d4691e5bba54d510e7148687cda9b2a2394eef29683a981cbdd" +checksum = "31758c02e50247f12457fca1905ed8684ac1b1c5292e10cbbfffb9fa0048d4bd" dependencies = [ "burn-core", "derive-new", @@ -552,9 +552,9 @@ dependencies = [ [[package]] name = "burn-rocm" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f08f8fea5ef76b67731c79529d9346f8623091832789dcc9e47aa85b50fe9d5f" +checksum = "5e1ceb87b6e7349b42d7995477c9a69d0e6c458c64eafa10af3b8b9070f260aa" dependencies = [ "burn-cubecl", "burn-tensor", @@ -567,9 +567,9 @@ dependencies = [ [[package]] name = "burn-router" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9342581c5ca083db421a1c51d2c4c4b3326f5d86dae3d21a81420ac564be4057" +checksum = "45f40403c500b5df380bee47aa0f23032350bdfde5402812d6fcec4d6ff6fbad" dependencies = [ "burn-common", "burn-ir", @@ -581,9 +581,9 @@ dependencies = [ [[package]] name = "burn-store" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c742ebf6d46a092c7324237f6f89a9cff8502c8c48db3961b435e6ee00bc316d" +checksum = "0a2a163486242fcb0c6e2cb89c5a803ab8588673652bb46ecd7af6378d06152f" dependencies = [ "burn-core", "burn-nn", @@ -598,9 +598,9 @@ dependencies = [ [[package]] name = "burn-tensor" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ff77a13d211fbf7a861e07eefc93bd1fb07a84da34e913c6e13ec1271208cc" +checksum = "df8861f7c21d3b07a2b19d028f6eb8903990949708b2ec825559b5200786877c" dependencies = [ "burn-common", "bytemuck", @@ -619,9 +619,9 @@ dependencies = [ [[package]] name = "burn-train" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c35107c50d97b73f2fc96d9c1beaab732e0f27bf5f9b847b8e3f6619ee85873" +checksum = "b0f1553197d50668823a4bafc187c62439df49b218973f0ca79e034b57ce38d6" dependencies = [ "async-channel", "burn-core", @@ -638,9 +638,9 @@ dependencies = [ [[package]] name = "burn-wgpu" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f2361ddac1bdc98e4f10dd8f62ded836537c316ca78ca4f41577ced0de4f21f" +checksum = "c17aeaa2eadaa4831a64672b99f62ffcdf4874fe4757080633d8a6c4452e2b38" dependencies = [ "burn-cubecl", "burn-tensor", @@ -1139,9 +1139,9 @@ dependencies = [ [[package]] name = "cubecl" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f2f345e729b90e342089acfbecb2818d170ff7ecccb2eba441903283f583fe" +checksum = "b8b7c74ecaca9356c9ae79d0ebf1db04f02bd98be09eea61f51d73373dffe758" dependencies = [ "cubecl-convolution", "cubecl-core", @@ -1159,9 +1159,9 @@ dependencies = [ [[package]] name = "cubecl-common" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d733d3437e87635378a16dd6d26062b7f184e5a4dd3437486ed953514bf1efd" +checksum = "4556981155bffc057a8effcd4549b52b51df3e9edec43af6ccae2dd03fc8fbff" dependencies = [ "bytemuck", "cfg-if", @@ -1193,9 +1193,9 @@ dependencies = [ [[package]] name = "cubecl-convolution" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a77dce74132f4c44c7557a01375beb92a25eed242c9082563cb9b6172832b5" +checksum = "27c624ec400b7203673bf2db86d7ff30d1384839d497d2dd029c19b1b7371e0d" dependencies = [ "bytemuck", "cubecl-common", @@ -1212,9 +1212,9 @@ dependencies = [ [[package]] name = "cubecl-core" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "850dbdac9cedfa367248e4eae7f26c2011c1ec4b6f4e1d88028a1f947d0d73c2" +checksum = "0ffc10af538ee74535cda260e581f5a177c243803dd30b698934a515f0114b55" dependencies = [ "bitflags 2.10.0", "bytemuck", @@ -1237,9 +1237,9 @@ dependencies = [ [[package]] name = "cubecl-cpp" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8732743cd1167eca4cf4045d3e9f9e219520d92dae17d8aa9bd18d6c2eb49663" +checksum = "d630e4d10cdd3af268ac753914ca79b48f01d1e36c5b5039970a817acc925fea" dependencies = [ "bytemuck", "cubecl-common", @@ -1254,9 +1254,9 @@ dependencies = [ [[package]] name = "cubecl-cpu" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baaa77f85342f7ccf6d33b5707e789a1beab339b00145b5868f7d590c319f4a4" +checksum = "ac1693555277d74152afb61a23e30d1f17d72cebd317a648faf50a8e69380f08" dependencies = [ "bytemuck", "cubecl-common", @@ -1278,9 +1278,9 @@ dependencies = [ [[package]] name = "cubecl-cuda" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "839ba85c69d3158b8d11f54e887f794507067be7f5be8547ac53b833f4a58784" +checksum = "67215fcd552a9e8bc68494a71cf2979f2e2bbcbda60f0695f56f86705b89ed5f" dependencies = [ "bytemuck", "cubecl-common", @@ -1296,9 +1296,9 @@ dependencies = [ [[package]] name = "cubecl-hip" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fa7d3f53a8a07656a1caa6414cf94700e25d876a2ff33dff9dea3190736b5b" +checksum = "d5e2e6a257f702fb2eb6f24e640e228a94695e4a4c73a4c549578cbb02ad4ec5" dependencies = [ "bytemuck", "cubecl-common", @@ -1326,9 +1326,9 @@ dependencies = [ [[package]] name = "cubecl-ir" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d8c468ca6c904ad97ed562fb61f77f3cdc929bc322f54b7c39e33cc49b20646" +checksum = "bf5d3aa7857e6aee1622aef128d6ad8d9289ed57362b4e65d10cc182aafc585f" dependencies = [ "cubecl-common", "cubecl-macros-internal", @@ -1346,9 +1346,9 @@ dependencies = [ [[package]] name = "cubecl-macros" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0ac86afe6b0a48569cc839605cf62d1d46e482f3476a87130d5166dafcc5c0" +checksum = "5200fb619be424749901e3c6e8e66ae71146c8f83636a74f171bd980cba379d7" dependencies = [ "cubecl-common", "darling 0.21.0", @@ -1374,9 +1374,9 @@ dependencies = [ [[package]] name = "cubecl-matmul" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdcca3ffa29b2f1abffab6b27046b2f86a9ebedfed597c405e4179984a004f8d" +checksum = "d1cf0a00609a249d5357c27cafea477f35218579db2ab00582d8d5800be4a5a3" dependencies = [ "bytemuck", "cubecl-common", @@ -1392,9 +1392,9 @@ dependencies = [ [[package]] name = "cubecl-opt" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c32810f85959e6c76dd336bf85d5a85b2752edb27310f0993d5b6b2243a308" +checksum = "870ca4b52f9eebd358c9b360b89cdc9f82bde05682db63f0e90c666b3c85a04d" dependencies = [ "cubecl-common", "cubecl-core", @@ -1410,9 +1410,9 @@ dependencies = [ [[package]] name = "cubecl-quant" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880705a7c0254b619ec30465db8dc8943c3ddba5ea81d644d21e95084f68a4f4" +checksum = "9be3e1202c219078d85dbad7f30d1195fe4f9d42cbfad2c94ab0ea1a6d9f01f6" dependencies = [ "cubecl-common", "cubecl-core", @@ -1424,9 +1424,9 @@ dependencies = [ [[package]] name = "cubecl-random" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7632ca58b1080ac883dd9313f7374757476b3e9c741a32353c71ec5765fa7f9b" +checksum = "9a293a05caa68663675823bab66205bca094a21a2c0f6686ad9f20b392516179" dependencies = [ "cubecl-common", "cubecl-core", @@ -1440,9 +1440,9 @@ dependencies = [ [[package]] name = "cubecl-reduce" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b759b19145c2e3a9d270200745c0fdbb9129c269f33f0b2ceab5e29f794e27" +checksum = "53306ace81f6262f7ae794370f47e6b5019842b27e8800240e5b039386b3ac3a" dependencies = [ "cubecl-core", "cubecl-runtime", @@ -1456,9 +1456,9 @@ dependencies = [ [[package]] name = "cubecl-runtime" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b04e944b2097b7c8931498a57615b290cdda755197d7a24d272c306af6e1edd9" +checksum = "91b823bb5899a6fa8809bf7aa36f93f72ced6de58ab9d6edea2c730b235eeda3" dependencies = [ "async-channel", "bytemuck", @@ -1484,9 +1484,9 @@ dependencies = [ [[package]] name = "cubecl-std" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83ca684200a5c77231460ec613a654ccb7cd46a9ec22d13cc94bc6c78d1a102" +checksum = "24536998f9fff84f9a1dd2a90f981d5aa4d15eb35cddec5021c4fcf977d2e75e" dependencies = [ "cubecl-common", "cubecl-core", @@ -1501,9 +1501,9 @@ dependencies = [ [[package]] name = "cubecl-wgpu" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08ec904606258323e0cf56e05935989121cf40f344970023e783682ed757439c" +checksum = "d59a7d737259a784247595e2f0cc5a97d3e50f45cdaefbd4cc7d7fd2126f7a58" dependencies = [ "async-channel", "bytemuck", @@ -1523,9 +1523,9 @@ dependencies = [ [[package]] name = "cudarc" -version = "0.17.5" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7342f14f265a572a93e6c2f26a566f5f9341d6bee7a8a72ce77bf328c917199f" +checksum = "ff0da1a70ec91e66731c1752deb9fda3044f1154fe4ceb5873e3f96ed34cafa3" dependencies = [ "libloading", ] diff --git a/harper-pos-utils/Cargo.toml b/harper-pos-utils/Cargo.toml index 0f6bc8ef..458e1eca 100644 --- a/harper-pos-utils/Cargo.toml +++ b/harper-pos-utils/Cargo.toml @@ -14,7 +14,7 @@ strum_macros = "0.27.2" serde = { version = "1.0.228", features = ["derive"] } is-macro = "0.3.7" rand = { version = "0.9.1", optional = true } -burn = { version = "0.19.0", default-features = false, features = ["std"] } +burn = { version = "0.19.1", default-features = false, features = ["std"] } burn-ndarray = { version = "0.19.0", default-features = false } serde_json = "1.0.145" lru = "0.16.2" From 3162a60b8197550d979e9fadd195c95df77a75c6 Mon Sep 17 00:00:00 2001 From: Andrew Dunbar Date: Tue, 11 Nov 2025 00:50:49 +0800 Subject: [PATCH 005/178] =?UTF-8?q?fix:=20CloudFlare=E2=86=92Cloudflare=20?= =?UTF-8?q?(#2153)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: CloudFlare→Cloudflare * fix: fix all the problems found by PR #2136 * test(core): from issue to avoid regressions --------- Co-authored-by: Elijah Potter --- harper-core/dictionary.dict | 17 ++++++++--------- harper-core/tests/run_tests.rs | 2 ++ harper-core/tests/test_sources/issue_2151.md | 1 + 3 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 harper-core/tests/test_sources/issue_2151.md diff --git a/harper-core/dictionary.dict b/harper-core/dictionary.dict index 7a58b68b..74cc2be5 100644 --- a/harper-core/dictionary.dict +++ b/harper-core/dictionary.dict @@ -2229,7 +2229,6 @@ Clorets/g Clorox/NgV Closure/g Clotho/Og -Cloudflare/Og Clouseau/Og Clovis/Og Clyde/Og @@ -12469,7 +12468,7 @@ anesthetize/VGdS aneurysm/~NSg anew/~ angel/~NgSV -angelfish/N9gS09 # singular and plural, also has a plural in -s +angelfish/N09gS # singular and plural, also has a plural in -s angelic/~J angelica/~Ng angelical/JY @@ -15066,7 +15065,7 @@ blueberry/~NwSgJV bluebird/~NgS bluebonnet/NSg bluebottle/NSg -bluefish/N9gS09 # singular and plural, also has a plural in -s +bluefish/N09gS # singular and plural, also has a plural in -s bluegill/NgS bluegrass/~Ng blueish/J @@ -16744,7 +16743,7 @@ catercorner/J caterer/Ng caterpillar/~NgSV caterwaul/VdGNSg -catfish/~N9gS09V # singular and plural, also has a plural in -s +catfish/~N09gSV # singular and plural, also has a plural in -s catgut/Ng catharses/N9 catharsis/Nm0g @@ -20139,7 +20138,7 @@ cutout/NSg cutter/~NSg cutthroat/~NSgJ cutting/~NgSJYV -cuttlefish/N9gS09 # singular and plural, also has a plural in -s +cuttlefish/N09gS # singular and plural, also has a plural in -s cutup/NSg cutworm/NgS cw/~ @@ -23690,7 +23689,7 @@ exaction/Ng exactitude/Nmg exactness/Nmgi exaggerate/VdSGJXn -exaggerated/~JYtTVtT +exaggerated/~JYVtT exaggeration/~NmgS exaggerator/NgS exajoule/NS @@ -50770,7 +50769,7 @@ vaporizer/NgS vaporous/J vaporware/Nmg< vapory/J< -vapour/NmgSgV!@_ +vapour/NmgSV!@_ vapoury/J!@_ vaquero/NgS var/~NS @@ -52873,7 +52872,7 @@ CVE/NSg # Common Vulnerabilities and Exposures Captcha/OgS ChatGPT/Og # OpenAI product ClickHouse/g -CloudFlare/Og +Cloudflare/Og Codeberg/Og # GitHub alternative Colemak/NgJ CommonMark/g @@ -53839,7 +53838,7 @@ self-indulgent/J self-obsessed/J self-tapping/J shark-infested/J -short circuit/NgSSVdG +short circuit/NgSVdG short-circuit/lSdG short-lived/J short-term/JR diff --git a/harper-core/tests/run_tests.rs b/harper-core/tests/run_tests.rs index 1857ddfe..96b6e14c 100644 --- a/harper-core/tests/run_tests.rs +++ b/harper-core/tests/run_tests.rs @@ -88,6 +88,8 @@ create_test!(hex_basic_dirty.md, 1, Dialect::American); create_test!(misc_closed_compound_clean.md, 0, Dialect::American); create_test!(yogurt_british_clean.md, 0, Dialect::British); create_test!(issue_1581.md, 0, Dialect::British); +// It just matters that it is > 1 +create_test!(issue_2151.md, 4, Dialect::British); // Make sure it doesn't panic create_test!(lukas_homework.md, 3, Dialect::American); diff --git a/harper-core/tests/test_sources/issue_2151.md b/harper-core/tests/test_sources/issue_2151.md new file mode 100644 index 00000000..60539651 --- /dev/null +++ b/harper-core/tests/test_sources/issue_2151.md @@ -0,0 +1 @@ +Are cloudflare and CloudFlare incorrect? From c38185015b18d899753d4b5bace78327a33302b6 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Mon, 10 Nov 2025 12:48:03 -0700 Subject: [PATCH 006/178] fix(core): add JetBrains as a proper noun (#2167) --- harper-core/dictionary.dict | 2 +- harper-core/src/linting/phrasal_verb_as_compound_noun.rs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/harper-core/dictionary.dict b/harper-core/dictionary.dict index 74cc2be5..f9eb4806 100644 --- a/harper-core/dictionary.dict +++ b/harper-core/dictionary.dict @@ -53009,7 +53009,7 @@ JWT/Ng # JSON Web Token Jacoco/Sg JavaDoc/Sg JavaScript/ONSg # programming language -JetBrains +JetBrains/Og Jetpack/Og Jira/Og # issue tracker Joomla/Sg diff --git a/harper-core/src/linting/phrasal_verb_as_compound_noun.rs b/harper-core/src/linting/phrasal_verb_as_compound_noun.rs index 1f962f72..0a8e84f7 100644 --- a/harper-core/src/linting/phrasal_verb_as_compound_noun.rs +++ b/harper-core/src/linting/phrasal_verb_as_compound_noun.rs @@ -732,4 +732,12 @@ mod tests { fn dont_flag_plugin_interface() { assert_no_lints("[Plugin interface]", PhrasalVerbAsCompoundNoun::default()); } + + #[test] + fn issue_1918() { + assert_no_lints( + "Boost your productivity with our JetBrains plugin!", + PhrasalVerbAsCompoundNoun::default(), + ); + } } From 1e1bbfdebac4ebc591f7bf91ab552d439e4001b1 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Mon, 10 Nov 2025 13:12:53 -0700 Subject: [PATCH 007/178] fix(core): problems with `EffectAffect` linter (#2171) --- .../effect_affect/affect_to_effect.rs | 12 ++++++++++++ .../effect_affect/effect_to_affect.rs | 6 ++++++ .../src/linting/noun_verb_confusion/mod.rs | 18 +++++++++++++++++- 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/harper-core/src/linting/noun_verb_confusion/effect_affect/affect_to_effect.rs b/harper-core/src/linting/noun_verb_confusion/effect_affect/affect_to_effect.rs index 73e95992..914b9933 100644 --- a/harper-core/src/linting/noun_verb_confusion/effect_affect/affect_to_effect.rs +++ b/harper-core/src/linting/noun_verb_confusion/effect_affect/affect_to_effect.rs @@ -85,6 +85,18 @@ impl ExprLinter for AffectToEffect { let offending_index = *self.map.lookup(0, matched_tokens, source)?; let target = &matched_tokens[offending_index]; + let preceding = matched_tokens[..offending_index] + .iter() + .rfind(|tok| !tok.kind.is_whitespace()); + + if preceding.is_some_and(|tok| { + (tok.kind.is_pronoun() || tok.kind.is_upos(UPOS::PRON)) + && !tok.kind.is_possessive_pronoun() + }) { + // Pronouns like "it" or "they" almost always introduce the verb form ("it affects"). + return None; + } + let token_text = target.span.get_content_string(source); let lower = token_text.to_lowercase(); let replacement = match lower.as_str() { diff --git a/harper-core/src/linting/noun_verb_confusion/effect_affect/effect_to_affect.rs b/harper-core/src/linting/noun_verb_confusion/effect_affect/effect_to_affect.rs index b122ae7c..0ef120f8 100644 --- a/harper-core/src/linting/noun_verb_confusion/effect_affect/effect_to_affect.rs +++ b/harper-core/src/linting/noun_verb_confusion/effect_affect/effect_to_affect.rs @@ -125,6 +125,12 @@ impl ExprLinter for EffectToAffect { let token_text = target.span.get_content_string(source); let lower = token_text.to_lowercase(); + if lower.as_str() == "effects" && preceding.is_some_and(|tok| tok.kind.is_upos(UPOS::VERB)) + { + // Imperative phrases like "Avoid effects" legitimately use the noun. + return None; + } + let replacement = match lower.as_str() { "effect" => "affect", "effects" => "affects", diff --git a/harper-core/src/linting/noun_verb_confusion/mod.rs b/harper-core/src/linting/noun_verb_confusion/mod.rs index b8a592e6..199711e0 100644 --- a/harper-core/src/linting/noun_verb_confusion/mod.rs +++ b/harper-core/src/linting/noun_verb_confusion/mod.rs @@ -30,7 +30,7 @@ merge_linters! { #[cfg(test)] mod tests { use super::NounVerbConfusion; - use crate::linting::tests::{assert_lint_count, assert_suggestion_result}; + use crate::linting::tests::{assert_lint_count, assert_no_lints, assert_suggestion_result}; #[test] fn corrects_good_advise() { @@ -1337,4 +1337,20 @@ mod tests { 0, ); } + + #[test] + fn issue_1997() { + assert_no_lints( + "It depends on which sources it affects, what parameters it uses, etc.", + NounVerbConfusion::default(), + ); + } + + #[test] + fn issue_1996() { + assert_no_lints( + "Avoid effects outside of functions.", + NounVerbConfusion::default(), + ); + } } From dc751b7aae05912146ac0d101a1415f74d0b6802 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Mon, 10 Nov 2025 13:13:09 -0700 Subject: [PATCH 008/178] fix(core): address #2003 (#2170) --- harper-core/src/linting/quite_quiet.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/harper-core/src/linting/quite_quiet.rs b/harper-core/src/linting/quite_quiet.rs index 28966203..e0a0f200 100644 --- a/harper-core/src/linting/quite_quiet.rs +++ b/harper-core/src/linting/quite_quiet.rs @@ -36,7 +36,7 @@ impl Default for QuiteQuiet { let adverb_quite = SequenceExpr::default() .then_kind_except( TokenKind::is_adverb, - &["actually", "never", "not", "really"], + &["actually", "never", "not", "really", "generally"], ) .t_ws() .t_aco("quite"); @@ -212,4 +212,12 @@ mod tests { QuiteQuiet::default(), ); } + + #[test] + fn issue_2003() { + assert_no_lints( + "The namespaces are generally quite short", + QuiteQuiet::default(), + ); + } } From 08675f287db434d3c5c4905d065d8b97a2bf819b Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Mon, 10 Nov 2025 13:36:58 -0700 Subject: [PATCH 009/178] test(core): ensure #2054 is no longer a problem (#2168) --- .../src/linting/orthographic_consistency.rs | 2 +- harper-core/tests/run_tests.rs | 2 ++ harper-core/tests/test_sources/issue_2054.md | 2 ++ .../tests/test_sources/issue_2054_clean.md | 2 ++ .../Alice's Adventures in Wonderland.snap.yml | 4 ++-- .../text/linters/Difficult sentences.snap.yml | 4 ++-- ... Constitution of the United States.snap.yml | 10 +++++----- .../text/linters/The Great Gatsby.snap.yml | 18 +++++++++--------- 8 files changed, 25 insertions(+), 19 deletions(-) create mode 100644 harper-core/tests/test_sources/issue_2054.md create mode 100644 harper-core/tests/test_sources/issue_2054_clean.md diff --git a/harper-core/src/linting/orthographic_consistency.rs b/harper-core/src/linting/orthographic_consistency.rs index aa7fa3bd..c5655643 100644 --- a/harper-core/src/linting/orthographic_consistency.rs +++ b/harper-core/src/linting/orthographic_consistency.rs @@ -82,7 +82,7 @@ impl ExprLinter for OrthographicConsistency { "The canonical dictionary spelling is `{}`.", canonical.iter().collect::() ), - priority: 127, + priority: 31, }); } diff --git a/harper-core/tests/run_tests.rs b/harper-core/tests/run_tests.rs index 96b6e14c..1206d2c9 100644 --- a/harper-core/tests/run_tests.rs +++ b/harper-core/tests/run_tests.rs @@ -88,6 +88,8 @@ create_test!(hex_basic_dirty.md, 1, Dialect::American); create_test!(misc_closed_compound_clean.md, 0, Dialect::American); create_test!(yogurt_british_clean.md, 0, Dialect::British); create_test!(issue_1581.md, 0, Dialect::British); +create_test!(issue_2054.md, 6, Dialect::British); +create_test!(issue_2054_clean.md, 0, Dialect::British); // It just matters that it is > 1 create_test!(issue_2151.md, 4, Dialect::British); diff --git a/harper-core/tests/test_sources/issue_2054.md b/harper-core/tests/test_sources/issue_2054.md new file mode 100644 index 00000000..a7053c79 --- /dev/null +++ b/harper-core/tests/test_sources/issue_2054.md @@ -0,0 +1,2 @@ +I really enjoy a good ui or gui. +When developer put time and effort into a good ux, I feel so much more productive. diff --git a/harper-core/tests/test_sources/issue_2054_clean.md b/harper-core/tests/test_sources/issue_2054_clean.md new file mode 100644 index 00000000..edf8bc9a --- /dev/null +++ b/harper-core/tests/test_sources/issue_2054_clean.md @@ -0,0 +1,2 @@ +I really enjoy a good UI or GUI. +When developer put time and effort into a good UX, I feel so much more productive. diff --git a/harper-core/tests/text/linters/Alice's Adventures in Wonderland.snap.yml b/harper-core/tests/text/linters/Alice's Adventures in Wonderland.snap.yml index aadc922c..3b48111f 100644 --- a/harper-core/tests/text/linters/Alice's Adventures in Wonderland.snap.yml +++ b/harper-core/tests/text/linters/Alice's Adventures in Wonderland.snap.yml @@ -1223,7 +1223,7 @@ Message: | -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 692 | below!” (a loud crash)—“Now, who did that?—It was Bill, I fancy—Who’s to go down | ^~~~~ The canonical dictionary spelling is `who's`. @@ -1983,7 +1983,7 @@ Suggest: -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 1582 | “Who’s making personal remarks now?” the Hatter asked triumphantly. | ^~~~~ The canonical dictionary spelling is `who's`. diff --git a/harper-core/tests/text/linters/Difficult sentences.snap.yml b/harper-core/tests/text/linters/Difficult sentences.snap.yml index cfba7975..c459abbd 100644 --- a/harper-core/tests/text/linters/Difficult sentences.snap.yml +++ b/harper-core/tests/text/linters/Difficult sentences.snap.yml @@ -56,7 +56,7 @@ Suggest: -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 126 | Who's for ice-cream? | ^~~~~ The canonical dictionary spelling is `who's`. @@ -74,7 +74,7 @@ Suggest: -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 166 | You can't get all your news from the Internet. | ^~~~ The canonical dictionary spelling is `news`. diff --git a/harper-core/tests/text/linters/The Constitution of the United States.snap.yml b/harper-core/tests/text/linters/The Constitution of the United States.snap.yml index a3140e7d..9a840d11 100644 --- a/harper-core/tests/text/linters/The Constitution of the United States.snap.yml +++ b/harper-core/tests/text/linters/The Constitution of the United States.snap.yml @@ -882,7 +882,7 @@ Suggest: -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 338 | #### SubSection. 1. | ^~~~~~~~~~ The canonical dictionary spelling is `subsection`. @@ -1003,7 +1003,7 @@ Suggest: -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 388 | #### SubSection. 2 | ^~~~~~~~~~ The canonical dictionary spelling is `subsection`. @@ -1057,7 +1057,7 @@ Message: | -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 406 | #### SubSection 3. | ^~~~~~~~~~ The canonical dictionary spelling is `subsection`. @@ -1226,7 +1226,7 @@ Message: | -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 446 | #### SubSection 4. | ^~~~~~~~~~ The canonical dictionary spelling is `subsection`. @@ -1297,7 +1297,7 @@ Suggest: -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 460 | #### SubSection 5. | ^~~~~~~~~~ The canonical dictionary spelling is `subsection`. diff --git a/harper-core/tests/text/linters/The Great Gatsby.snap.yml b/harper-core/tests/text/linters/The Great Gatsby.snap.yml index a0576e06..ac791958 100644 --- a/harper-core/tests/text/linters/The Great Gatsby.snap.yml +++ b/harper-core/tests/text/linters/The Great Gatsby.snap.yml @@ -279,7 +279,7 @@ Message: | -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 98 | of very solemn and obvious editorials for the Yale News—and now I was going to | ^~~~ The canonical dictionary spelling is `news`. @@ -998,7 +998,7 @@ Suggest: -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 747 | “I’ll meet you by the news-stand on the lower level.” | ^~~~ The canonical dictionary spelling is `news`. @@ -1040,7 +1040,7 @@ Suggest: -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 772 | her rather wide hips as Tom helped her to the platform in New York. At the 773 | news-stand she bought a copy of Town Tattle and a moving-picture magazine, and @@ -1583,7 +1583,7 @@ Suggest: -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 1210 | rhythm obligingly for her, and there is a burst of chatter as the erroneous news | ^~~~ The canonical dictionary spelling is `news`. @@ -4185,7 +4185,7 @@ Suggest: -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 2776 | ecstatic patron of recurrent light, and repeated the news to Daisy. “What do you | ^~~~ The canonical dictionary spelling is `news`. @@ -4367,7 +4367,7 @@ Message: | -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 2905 | “Who’s this?” | ^~~~~ The canonical dictionary spelling is `who's`. @@ -4467,7 +4467,7 @@ Suggest: -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 3019 | become authorities upon his past, had increased all summer until he fell just 3020 | short of being news. Contemporary legends such as the “underground pipe-line to @@ -4923,7 +4923,7 @@ Suggest: -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 3409 | Her glance left me and sought the lighted top of the steps, where “Three o’Clock | ^~~~~~~ The canonical dictionary spelling is `o'clock`. @@ -7274,7 +7274,7 @@ Message: | -Lint: Capitalization (127 priority) +Lint: Capitalization (31 priority) Message: | 5251 | Gatsby’s side, and alone. From the moment I telephoned news of the catastrophe | ^~~~ The canonical dictionary spelling is `news`. From a6c7b34c0429e842582a0e16446bc0b7611e1ebc Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Tue, 11 Nov 2025 08:26:56 -0700 Subject: [PATCH 010/178] feat(core): add new linter to detect `call as ` (#2172) --- harper-core/src/linting/call_them.rs | 335 +++++++++++++++++++++++ harper-core/src/linting/mod.rs | 1 + harper-core/src/patterns/derived_from.rs | 45 +++ harper-core/src/patterns/mod.rs | 2 + 4 files changed, 383 insertions(+) create mode 100644 harper-core/src/linting/call_them.rs create mode 100644 harper-core/src/patterns/derived_from.rs diff --git a/harper-core/src/linting/call_them.rs b/harper-core/src/linting/call_them.rs new file mode 100644 index 00000000..16f85a28 --- /dev/null +++ b/harper-core/src/linting/call_them.rs @@ -0,0 +1,335 @@ +use std::{ops::Range, sync::Arc}; + +use crate::expr::{Expr, ExprMap, SequenceExpr}; +use crate::patterns::{DerivedFrom, WordSet}; +use crate::{Token, TokenStringExt}; + +use super::{ExprLinter, Lint, LintKind, Suggestion}; + +pub struct CallThem { + expr: Box, + map: Arc>>, +} + +impl Default for CallThem { + fn default() -> Self { + let mut map = ExprMap::default(); + + let post_exception = Arc::new( + SequenceExpr::default() + .t_ws() + .then(WordSet::new(&["if", "it"])), + ); + + map.insert( + SequenceExpr::default() + .then(DerivedFrom::new_from_str("call")) + .t_ws() + .then_pronoun() + .t_ws() + .t_aco("as") + .then_unless(post_exception.clone()), + 3..5, + ); + + map.insert( + SequenceExpr::default() + .then(DerivedFrom::new_from_str("call")) + .t_ws() + .t_aco("as") + .t_ws() + .then_pronoun() + .then_unless(post_exception.clone()), + 1..3, + ); + + let map = Arc::new(map); + + Self { + expr: Box::new(map.clone()), + map, + } + } +} + +impl ExprLinter for CallThem { + fn expr(&self) -> &dyn Expr { + self.expr.as_ref() + } + + fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option { + let removal_range = self.map.lookup(0, matched_tokens, source)?.clone(); + let offending_tokens = matched_tokens.get(removal_range)?; + + Some(Lint { + span: offending_tokens.span()?, + lint_kind: LintKind::Redundancy, + suggestions: vec![Suggestion::Remove], + message: "`as` is redundant in this context.".to_owned(), + ..Default::default() + }) + } + + fn description(&self) -> &'static str { + "Addresses the non-idiomatic phrases `call them as`." + } +} + +#[cfg(test)] +mod tests { + #[allow(unused_imports)] + use crate::Document; + use crate::linting::tests::{assert_no_lints, assert_suggestion_result}; + + use super::CallThem; + + #[test] + fn prefer_plug_and_receptacle() { + assert_suggestion_result( + r#"I prefer to call them as Plug (male) and Receptacle (female). Receptacles are seen in laptops, mobile phones etc.."#, + CallThem::default(), + r#"I prefer to call them Plug (male) and Receptacle (female). Receptacles are seen in laptops, mobile phones etc.."#, + ); + } + + #[test] + fn builtins_id() { + assert_suggestion_result( + r#"I’d categorically ignore *id* as a builtin, and when you do need it in a module, make it super explicit and `import builtins` and call it as `builtins.id`."#, + CallThem::default(), + r#"I’d categorically ignore *id* as a builtin, and when you do need it in a module, make it super explicit and `import builtins` and call it `builtins.id`."#, + ); + } + + #[test] + fn non_modal_dialogue() { + assert_suggestion_result( + r#"We usually call it as non-modal dialogue e.g. when hit Gmail compose button, a nonmodal dialogue opens."#, + CallThem::default(), + r#"We usually call it non-modal dialogue e.g. when hit Gmail compose button, a nonmodal dialogue opens."#, + ); + } + + #[test] + fn prefer_to_call_them() { + assert_suggestion_result( + r#"So, how do you typically prefer to call them as?"#, + CallThem::default(), + r#"So, how do you typically prefer to call them?"#, + ); + } + + #[test] + fn called_them_allies() { + assert_suggestion_result( + r#"Yes as tribes or nomads you called them as allies but you didn’t get their levies as your own."#, + CallThem::default(), + r#"Yes as tribes or nomads you called them allies but you didn’t get their levies as your own."#, + ); + } + + #[test] + fn character_development() { + assert_suggestion_result( + r#"I call this as character development."#, + CallThem::default(), + r#"I call this character development."#, + ); + } + + #[test] + fn fate_or_time() { + assert_suggestion_result( + r#"Should I Call It As Fate Or Time"#, + CallThem::default(), + r#"Should I Call It Fate Or Time"#, + ); + } + + #[test] + fn abstract_latte_art() { + assert_suggestion_result( + r#"Can we just call it as abstract latte art."#, + CallThem::default(), + r#"Can we just call it abstract latte art."#, + ); + } + + #[test] + fn sounding_boards() { + assert_suggestion_result( + r#"I call them as my ‘sounding boards’"#, + CallThem::default(), + r#"I call them my ‘sounding boards’"#, + ); + } + + #[test] + fn calling_them_disaster() { + assert_suggestion_result( + r#"I totally disagree with your point listed and calling them as disaster."#, + CallThem::default(), + r#"I totally disagree with your point listed and calling them disaster."#, + ); + } + + #[test] + fn battle_of_boxes() { + assert_suggestion_result( + r#"Windows Sandbox and VirtualBox or I would like to call this as “Battle of Boxes.”"#, + CallThem::default(), + r#"Windows Sandbox and VirtualBox or I would like to call this “Battle of Boxes.”"#, + ); + } + + #[test] + fn called_her_shinnasan() { + assert_suggestion_result( + r#"Nice meeting a follower from reddit I called her as Shinna-san, welcome again to Toram!!"#, + CallThem::default(), + r#"Nice meeting a follower from reddit I called her Shinna-san, welcome again to Toram!!"#, + ); + } + + #[test] + fn calling_it_otp() { + assert_suggestion_result( + r#"Calling it as OTP in this case misleading"#, + CallThem::default(), + r#"Calling it OTP in this case misleading"#, + ); + } + + #[test] + fn call_it_procrastination() { + assert_suggestion_result( + r#"To summarise it in just one word I would call it as procrastination."#, + CallThem::default(), + r#"To summarise it in just one word I would call it procrastination."#, + ); + } + + #[test] + fn call_her_important() { + assert_suggestion_result( + r#"Liked the article overall but to call her as important to rap as Jay or Dre is a bold overstatement."#, + CallThem::default(), + r#"Liked the article overall but to call her important to rap as Jay or Dre is a bold overstatement."#, + ); + } + + #[test] + fn call_him_kindles() { + assert_suggestion_result( + r#"The days when I had my first best friend, I would rather call him as human version of kindle audiobook, who keeps on talking about everything under the umbrella."#, + CallThem::default(), + r#"The days when I had my first best friend, I would rather call him human version of kindle audiobook, who keeps on talking about everything under the umbrella."#, + ); + } + + #[test] + fn call_them_defenders() { + assert_suggestion_result( + r#"Declaring war challenging land of a vassal should call them as defenders!"#, + CallThem::default(), + r#"Declaring war challenging land of a vassal should call them defenders!"#, + ); + } + + #[test] + fn call_it_magical() { + assert_suggestion_result( + r#"I would like to call it as magical."#, + CallThem::default(), + r#"I would like to call it magical."#, + ); + } + + #[test] + fn forward_lateral() { + assert_suggestion_result( + r#"Surprised the refs didn’t call this as a forward lateral."#, + CallThem::default(), + r#"Surprised the refs didn’t call this a forward lateral."#, + ); + } + + #[test] + fn calling_best_friend() { + assert_suggestion_result( + r#"Meet my buddy! I love calling him as my best friend, because he never failed to bring some cheer in me!"#, + CallThem::default(), + r#"Meet my buddy! I love calling him my best friend, because he never failed to bring some cheer in me!"#, + ); + } + + #[test] + fn calling_everyone_titles() { + assert_suggestion_result( + r#"Currently, I’m teaching in Asia and the students have the local custom of calling everyone as Mr. Givenname or Miss Givenname"#, + CallThem::default(), + r#"Currently, I’m teaching in Asia and the students have the local custom of calling everyone Mr. Givenname or Miss Givenname"#, + ); + } + + #[test] + fn called_as_he() { + assert_suggestion_result( + r#"I prefer to be called as he when referred in 3rd person and I’m sure that everyone would be ok to call me as he."#, + CallThem::default(), + r#"I prefer to be called he when referred in 3rd person and I’m sure that everyone would be ok to call me he."#, + ); + } + + #[test] + fn calls_him_bob() { + assert_suggestion_result( + r#"In Twelve Monkeys, Cole hears someone who calls him as “Bob”"#, + CallThem::default(), + r#"In Twelve Monkeys, Cole hears someone who calls him “Bob”"#, + ); + } + + #[test] + fn pliny_called_it() { + assert_suggestion_result( + r#"Pliny the Elder called it as lake of Gennesaret or Taricheae in his encyclopedia, Natural History."#, + CallThem::default(), + r#"Pliny the Elder called it lake of Gennesaret or Taricheae in his encyclopedia, Natural History."#, + ); + } + + #[test] + fn students_call_you() { + assert_suggestion_result( + r#"In the same way your students will call you as ~先生 even after they graduated/move to higher education."#, + CallThem::default(), + r#"In the same way your students will call you ~先生 even after they graduated/move to higher education."#, + ); + } + + #[test] + fn paradoxical_reaction() { + assert_suggestion_result( + r#"We can call it as Paradoxical Reaction which means a medicine which is used to reduce pain increases the pain when it is"#, + CallThem::default(), + r#"We can call it Paradoxical Reaction which means a medicine which is used to reduce pain increases the pain when it is"#, + ); + } + + #[test] + fn rust_module() { + assert_no_lints( + "I want to call them as if they were just another Rust module", + CallThem::default(), + ); + } + + #[test] + fn want_to_do() { + assert_no_lints( + "however its a design choice to not call it as it does things I don't want to do.", + CallThem::default(), + ); + } +} diff --git a/harper-core/src/linting/mod.rs b/harper-core/src/linting/mod.rs index 952bf909..6cacb687 100644 --- a/harper-core/src/linting/mod.rs +++ b/harper-core/src/linting/mod.rs @@ -21,6 +21,7 @@ mod be_allowed; mod best_of_all_time; mod boring_words; mod bought; +mod call_them; mod cant; mod capitalize_personal_pronouns; mod cautionary_tale; diff --git a/harper-core/src/patterns/derived_from.rs b/harper-core/src/patterns/derived_from.rs new file mode 100644 index 00000000..f1d81866 --- /dev/null +++ b/harper-core/src/patterns/derived_from.rs @@ -0,0 +1,45 @@ +use crate::spell::WordId; + +use super::Pattern; + +/// A [Pattern] that looks for Word tokens that are either derived from a given word, or the word +/// itself. +/// +/// For example, this will match "call" as well as "recall", "calling", etc. +pub struct DerivedFrom { + word_id: WordId, +} + +impl DerivedFrom { + pub fn new_from_str(word: &str) -> DerivedFrom { + Self::new(WordId::from_word_str(word)) + } + + pub fn new_from_chars(word: &[char]) -> DerivedFrom { + Self::new(WordId::from_word_chars(word)) + } + + pub fn new(word_id: WordId) -> Self { + Self { word_id } + } +} + +impl Pattern for DerivedFrom { + fn matches(&self, tokens: &[crate::Token], source: &[char]) -> Option { + let tok = tokens.first()?; + let metadata = tok.kind.as_word()?.as_ref()?; + + if metadata.derived_from == Some(self.word_id) { + return Some(1); + } + + let chars = tok.span.get_content(source); + let word_id = WordId::from_word_chars(chars); + + if word_id == self.word_id { + return Some(1); + } + + None + } +} diff --git a/harper-core/src/patterns/mod.rs b/harper-core/src/patterns/mod.rs index d9cf0d7a..4346c6ab 100644 --- a/harper-core/src/patterns/mod.rs +++ b/harper-core/src/patterns/mod.rs @@ -8,6 +8,7 @@ use crate::{Document, LSend, Span, Token}; mod any_pattern; +mod derived_from; mod implies_quantity; mod indefinite_article; mod inflection_of_be; @@ -22,6 +23,7 @@ mod word; mod word_set; pub use any_pattern::AnyPattern; +pub use derived_from::DerivedFrom; pub use implies_quantity::ImpliesQuantity; pub use indefinite_article::IndefiniteArticle; pub use inflection_of_be::InflectionOfBe; From 68add530af09a26b08ae69f6a37a02c4ad891782 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Tue, 11 Nov 2025 13:02:56 -0700 Subject: [PATCH 011/178] feat(web): mention that we don't use generative AI --- packages/web/src/routes/+page.svelte | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/web/src/routes/+page.svelte b/packages/web/src/routes/+page.svelte index 086131dc..dcd6df89 100644 --- a/packages/web/src/routes/+page.svelte +++ b/packages/web/src/routes/+page.svelte @@ -177,6 +177,9 @@ const testimonials = [ That means you have 100% certainty we don't violate your copyright by training large language models.

+

+ Harper also intentionally avoids including any kind of generative AI in any part of our processing pipeline. +

Date: Tue, 11 Nov 2025 14:32:02 -0700 Subject: [PATCH 012/178] fix(core): use subject pronouns instead (#2178) --- harper-core/src/linting/didnt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harper-core/src/linting/didnt.rs b/harper-core/src/linting/didnt.rs index 1a0f459c..11498e65 100644 --- a/harper-core/src/linting/didnt.rs +++ b/harper-core/src/linting/didnt.rs @@ -9,7 +9,7 @@ pub struct Didnt { impl Default for Didnt { fn default() -> Self { let pattern = SequenceExpr::default() - .then_personal_pronoun() + .then_subject_pronoun() .t_ws() .t_aco("dint"); From 33e06c9b0f49b694ad875526231d2575e2fc93bc Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Tue, 11 Nov 2025 14:38:09 -0700 Subject: [PATCH 013/178] feat(chrome-ext): add buttons to reset and toggle all rules (#2174) --- .../chrome-plugin/src/options/Options.svelte | 41 ++++++++++++++++++- .../lint-framework/src/lint/LintFramework.ts | 13 ++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/packages/chrome-plugin/src/options/Options.svelte b/packages/chrome-plugin/src/options/Options.svelte index c31be964..22e9d5fa 100644 --- a/packages/chrome-plugin/src/options/Options.svelte +++ b/packages/chrome-plugin/src/options/Options.svelte @@ -13,6 +13,7 @@ let dialect = $state(Dialect.American); let defaultEnabled = $state(false); let activationKey: ActivationKey = $state(ActivationKey.Off); let userDict = $state(''); +let anyRulesEnabled = $derived(Object.values(lintConfig ?? {}).some((value) => value !== false)); $effect(() => { ProtocolClient.setLintConfig($state.snapshot(lintConfig)); @@ -97,6 +98,34 @@ export function dictToString(values: string[]): string { return values.map((v) => v.trim()).join('\n'); } +function resetRulesToDefaults(): void { + const keys = Object.keys(lintConfig ?? {}); + if (keys.length === 0) return; + + const nextConfig: LintConfig = { ...lintConfig }; + for (const key of keys) { + nextConfig[key] = null; + } + lintConfig = nextConfig; +} + +function updateAllRules(enabled: boolean): void { + const keys = Object.keys(lintConfig ?? {}); + if (keys.length === 0) { + return; + } + + const nextConfig: LintConfig = { ...lintConfig }; + for (const key of keys) { + nextConfig[key] = enabled; + } + lintConfig = nextConfig; +} + +function toggleAllRules(): void { + updateAllRules(!anyRulesEnabled); +} + async function exportEnabledDomainsCSV() { try { const enabledDomains = await ProtocolClient.getEnabledDomains(); @@ -197,8 +226,18 @@ async function exportEnabledDomainsCSV() {

Rules

+
+ + +
- {#each Object.entries(lintConfig).filter(([key]) => lintDescriptions[key].toLowerCase().includes(searchQueryLower) || key.toLowerCase().includes(searchQueryLower)) as [key, value]} + {#each Object.entries(lintConfig).filter( + ([key]) => + (lintDescriptions[key] ?? '').toLowerCase().includes(searchQueryLower) || + key.toLowerCase().includes(searchQueryLower) + ) as [key, value]}
diff --git a/packages/lint-framework/src/lint/LintFramework.ts b/packages/lint-framework/src/lint/LintFramework.ts index 34fdf113..624609a5 100644 --- a/packages/lint-framework/src/lint/LintFramework.ts +++ b/packages/lint-framework/src/lint/LintFramework.ts @@ -146,6 +146,19 @@ export default class LintFramework { } } + /** Get the individual lints produced the last time the text on the screen was scanned. */ + public getLastLints(): UnpackedLint[] { + const lints = []; + + for (const group of this.lastLints) { + for (const groupLints of Object.values(group.lints)) { + lints.push(...groupLints); + } + } + + return lints; + } + private attachTargetListeners(target: Node) { for (const event of INPUT_EVENTS) { target.addEventListener(event, this.updateEventCallback); From e27bab5bc2bd901ec9b6d5dacb23a51de59ef86b Mon Sep 17 00:00:00 2001 From: Andrew Dunbar Date: Wed, 12 Nov 2025 05:35:12 +0800 Subject: [PATCH 014/178] feat: add suggestions to mass plurals linter (#2162) --- harper-core/src/linting/mass_plurals.rs | 74 +++++++++++++++++-------- 1 file changed, 51 insertions(+), 23 deletions(-) diff --git a/harper-core/src/linting/mass_plurals.rs b/harper-core/src/linting/mass_plurals.rs index b4507de4..8eca69b6 100644 --- a/harper-core/src/linting/mass_plurals.rs +++ b/harper-core/src/linting/mass_plurals.rs @@ -3,7 +3,7 @@ use hashbrown::HashSet; use crate::{ CharStringExt, Token, TokenStringExt, expr::{All, Expr, FirstMatchOf, FixedPhrase, SequenceExpr}, - linting::{ExprLinter, Lint, LintKind}, + linting::{ExprLinter, Lint, LintKind, Suggestion}, spell::Dictionary, }; @@ -24,16 +24,15 @@ where }); let oov_looks_plural = All::new(vec![Box::new(oov), Box::new(looks_plural)]); - // let source_codes = FixedPhrase::from_phrase("source codes"); let phrases = FirstMatchOf::new(vec![ Box::new(FixedPhrase::from_phrase("real estates")), Box::new(FixedPhrase::from_phrase("source codes")), + Box::new(FixedPhrase::from_phrase("wear and tears")), ]); Self { expr: Box::new(FirstMatchOf::new(vec![ Box::new(oov_looks_plural), - // Box::new(source_codes), Box::new(phrases), ])), dict, @@ -62,21 +61,25 @@ where } fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option { - let mistake_toks = toks; + let invalid_plural_toks = toks; - let mut legit_words_found: HashSet> = HashSet::new(); + let mut valid_singulars: HashSet> = HashSet::new(); - if mistake_toks.len() == 1 { - let mistake_tok = &mistake_toks[0]; + if invalid_plural_toks.len() != 1 { + // Multiple tokens means we matched a fixed phrase + let phrase = invalid_plural_toks.span()?.get_content(src); + valid_singulars.insert(phrase[..phrase.len() - 1].into()); + } else { + let invalid_plural_tok = &invalid_plural_toks[0]; // Not a fixed phrase, so it's a single word that's not in the dictionary and ends with -s - let mut remaining_chars = mistake_tok.span.get_content(src); + let mut remaining_chars = invalid_plural_tok.span.get_content(src); // -s if remaining_chars.ends_with(&['s']) { remaining_chars = &remaining_chars[..remaining_chars.len() - 1]; if self.is_mass_noun_in_dictionary(remaining_chars) { - legit_words_found.insert(remaining_chars.into()); + valid_singulars.insert(remaining_chars.into()); } // -es @@ -84,7 +87,7 @@ where remaining_chars = &remaining_chars[..remaining_chars.len() - 1]; if self.is_mass_noun_in_dictionary(remaining_chars) { - legit_words_found.insert(remaining_chars.into()); + valid_singulars.insert(remaining_chars.into()); } // -ies -> -y @@ -95,41 +98,46 @@ where if self.is_mass_noun_in_dictionary_str(&y_singular) { let y_singular_chars: Box<[char]> = y_singular.chars().collect::>().into_boxed_slice(); - legit_words_found.insert(y_singular_chars.clone()); + valid_singulars.insert(y_singular_chars.clone()); } } } } - } else { - // Multiple tokens means we matched a fixed phrase - let phrase = mistake_toks.span()?.get_content(src); - legit_words_found.insert(phrase[..phrase.len() - 1].into()); } - if legit_words_found.is_empty() { + if valid_singulars.is_empty() { return None; } let message = format!( "The {} `{}` is a mass noun and should not be pluralized.", - if mistake_toks.len() == 1 { + if invalid_plural_toks.len() == 1 { "word" } else { "term" }, - legit_words_found + valid_singulars .iter() .map(|s| s.to_string()) .collect::>() .join("`, `") ); + let span = invalid_plural_toks.span()?; + + let suggestions: Vec = valid_singulars + .iter() + .map(|sing| { + Suggestion::replace_with_match_case(sing.clone().into(), span.get_content(src)) + }) + .collect(); + Some(Lint { - span: mistake_toks.span()?, + span, lint_kind: LintKind::Grammar, - suggestions: vec![], + suggestions, message, - priority: 31, + ..Default::default() }) } @@ -140,7 +148,10 @@ where #[cfg(test)] mod tests { - use crate::{linting::tests::assert_lint_count, spell::FstDictionary}; + use crate::{ + linting::tests::{assert_lint_count, assert_suggestion_result}, + spell::FstDictionary, + }; use super::MassPlurals; @@ -171,7 +182,6 @@ mod tests { ); } - // instead of giving any of her many luxury real estates or multi-million dollar fortune ... #[test] fn flag_real_estates() { assert_lint_count( @@ -180,4 +190,22 @@ mod tests { 1, ); } + + #[test] + fn flag_wear_and_tears() { + assert_lint_count( + "Transit costs were high in terms of time, finances, and vehicle wear and tears, which posed significant obstacles to international commerce", + MassPlurals::new(FstDictionary::curated()), + 1, + ); + } + + #[test] + fn fix_wear_and_tears() { + assert_suggestion_result( + "Transit costs were high in terms of time, finances, and vehicle wear and tears, which posed significant obstacles to international commerce", + MassPlurals::new(FstDictionary::curated()), + "Transit costs were high in terms of time, finances, and vehicle wear and tear, which posed significant obstacles to international commerce", + ); + } } From 19832424d6541c1c520045e679d1e6ad3499687f Mon Sep 17 00:00:00 2001 From: Andrew Dunbar Date: Wed, 12 Nov 2025 05:39:41 +0800 Subject: [PATCH 015/178] feat: `harper-cli audit-dictionary` (#2136) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: `harper-cli audit-dictionary` * feat: `just auditdictionary` and `just auditdict` alias * fix: end file with linefeed * feat: add dictionary audit to `check-rust` recipe Also: - improve audit counts and warnings - Add the `♂`, `♀`, and `ª` flags to `annotations.json` with comments that they're not implemented yet --- harper-cli/src/main.rs | 131 ++++++++++++++++++++++++++++++++++- harper-core/annotations.json | 18 +++++ justfile | 8 ++- 3 files changed, 154 insertions(+), 3 deletions(-) diff --git a/harper-cli/src/main.rs b/harper-cli/src/main.rs index a4e63685..f1af636d 100644 --- a/harper-cli/src/main.rs +++ b/harper-cli/src/main.rs @@ -28,6 +28,7 @@ use harper_python::PythonParser; use harper_stats::Stats; use serde::Serialize; +use serde_json::Value; mod input; use input::Input; @@ -161,6 +162,11 @@ enum Args { /// The directory containing the dictionary and affixes. dir: PathBuf, }, + /// Audit the `dictionary.dict` file. + AuditDictionary { + /// The directory containing the dictionary and affixes. + dir: PathBuf, + }, /// Emit a decompressed, line-separated list of the compounds in Harper's dictionary. /// As long as there's either an open or hyphenated spelling. Compounds, @@ -548,8 +554,6 @@ fn main() -> anyhow::Result<()> { Ok(()) } Args::RenameFlag { old, new, dir } => { - use serde_json::Value; - let dict_path = dir.join("dictionary.dict"); let affixes_path = dir.join("annotations.json"); @@ -668,6 +672,129 @@ fn main() -> anyhow::Result<()> { Ok(()) } + Args::AuditDictionary { dir } => { + let annotations_path = dir.join("annotations.json"); + let annotations_content = fs::read_to_string(&annotations_path) + .map_err(|e| anyhow!("Failed to read annotations: {e}"))?; + let annotations_json: Value = serde_json::from_str(&annotations_content) + .map_err(|e| anyhow!("Failed to parse annotations.json: {e}"))?; + + let annotations = annotations_json + .as_object() + .ok_or_else(|| anyhow!("annotations.json is not an object"))?; + + let (affixes, properties) = ["affixes", "properties"] + .iter() + .map(|key| { + annotations + .get(*key) + .and_then(Value::as_object) + .ok_or_else(|| { + anyhow!("Missing or invalid '{key}' key in annotations.json") + }) + }) + .collect::, _>>() + .map(|v| (v[0], v[1]))?; + + let all_keys = affixes.keys().chain(properties.keys()).collect::>(); + + let mut annotation_flag_count: HashMap = all_keys + .iter() + .filter_map(|key| key.chars().next()) // Get first char of each key + .map(|c| (c, 0)) + .collect(); + + // let mut duplicate_flag_total = 0; + let mut duplicate_flags = std::collections::HashMap::new(); + let mut unknown_flags = std::collections::HashMap::new(); + let mut unused_flag_total = 0; + + let dict_path = dir.join("dictionary.dict"); + let dict_content = fs::read_to_string(&dict_path) + .map_err(|e| anyhow!("Failed to read dictionary: {e}"))?; + + for (line_num, line) in dict_content.lines().enumerate() { + if line.is_empty() + || line.starts_with('#') + || line.chars().all(|c| c.is_ascii_digit()) + { + continue; + } + + let (entry_part, _comment_part) = + line.split_once('#').map_or((line, ""), |(e, c)| (e, c)); + + if let Some((lexeme, rest)) = entry_part.split_once('/') { + let (annotation, _whitespace) = match rest.split_once([' ', '\t']) { + Some((a, _)) => (a, &rest[a.len()..]), + None => (rest, ""), + }; + + let mut seen_flags = hashbrown::HashSet::new(); + + for flag in annotation.chars() { + if !seen_flags.insert(flag) { + eprintln!( + "Warning: Line {}: Duplicate annotation flag '{}' in entry: {}/{}", + line_num + 1, + flag, + lexeme, + annotation + ); + // duplicate_flag_total += 1; + *duplicate_flags.entry(flag).or_insert(0) += 1; + } + if !annotation_flag_count.contains_key(&flag) { + eprintln!( + "Warning: Line {}: Unknown annotation flag '{}' in entry: {}/{}", + line_num + 1, + flag, + lexeme, + annotation + ); + *unknown_flags.entry(flag).or_insert(0) += 1; + } else { + *annotation_flag_count.get_mut(&flag).unwrap() += 1; + } + } + } + } + + for (flag, count) in annotation_flag_count { + if count == 0 { + eprintln!("Warning: Unused annotation flag '{}'", flag); + unused_flag_total += 1; + } + } + + let duplicate_flag_total = duplicate_flags.values().sum::(); + let unknown_flag_total = unknown_flags.values().sum::(); + + if duplicate_flag_total > 0 || unknown_flag_total > 0 || unused_flag_total > 0 { + eprintln!("\nAudit found issues:"); + if duplicate_flag_total > 0 { + eprintln!( + " - {} duplicate flags found in {} entries", + duplicate_flags.len(), + duplicate_flag_total + ); + } + if !unknown_flags.is_empty() { + let total_unknown = unknown_flags.values().sum::(); + eprintln!( + " - {} unknown flags found in {} entries", + unknown_flags.len(), + total_unknown + ); + } + if unused_flag_total > 0 { + eprintln!(" - {} unused flags found", unused_flag_total); + } + std::process::exit(1); + } + + Ok(()) + } Args::Compounds => { let mut compound_map: HashMap> = HashMap::new(); diff --git a/harper-core/annotations.json b/harper-core/annotations.json index a2303121..2d874bef 100644 --- a/harper-core/annotations.json +++ b/harper-core/annotations.json @@ -942,6 +942,24 @@ "verb_form": "THIRD_PERSON_SINGULAR" } } + }, + "♂": { + "#": "masculine property", + "metadata": { + "//": "not yet implemented" + } + }, + "♀": { + "#": "feminine property", + "metadata": { + "//": "not yet implemented" + } + }, + "ª": { + "#": "animate property", + "metadata": { + "//": "not yet implemented" + } } } } diff --git a/justfile b/justfile index b6291429..ff555167 100644 --- a/justfile +++ b/justfile @@ -260,7 +260,7 @@ update-vscode-linters: just format # Run Rust formatting and linting. -check-rust: +check-rust: auditdictionary #!/usr/bin/env bash set -eo pipefail @@ -659,3 +659,9 @@ suggestannotation input: console.log(`None of the characters of "${input}" are available to use for new annotations, and none of them are OK to be moved to make way for new annotations.`); } } + +# Audit the curated dictionary for any issues. +alias auditdict := auditdictionary + +auditdictionary DIR="harper-core": + cargo run --bin harper-cli -- audit-dictionary {{DIR}} From a125a397efaf5e6b710dc3dbf1eb511ccd01d50f Mon Sep 17 00:00:00 2001 From: Andrew Voynov <37143421+Andrew15-5@users.noreply.github.com> Date: Wed, 12 Nov 2025 02:10:07 +0300 Subject: [PATCH 016/178] chore: add & update several TeX-related proper nouns (#1889) * chore: add & update several TeX-related proper nouns Updated: TeX, LyX Added: ConTeXt, LaTeX, pdfTeX * test(core): fix failures and ensure #1873 is fixed --------- Co-authored-by: Elijah Potter --- harper-core/dictionary.dict | 13 ++++++++----- harper-core/tests/run_tests.rs | 1 + harper-core/tests/test_sources/issue_1873.md | 2 ++ 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 harper-core/tests/test_sources/issue_1873.md diff --git a/harper-core/dictionary.dict b/harper-core/dictionary.dict index f9eb4806..c1fb126f 100644 --- a/harper-core/dictionary.dict +++ b/harper-core/dictionary.dict @@ -6167,8 +6167,8 @@ Luxembourgian/NJ Luz/Og Luzon/Og Lviv/Og -Lvov/Og # old name of Lviv -LyX/g +Lvov/Og +LyX/Og # GUI document processor based on LaTeX Lyallpur/O Lycra/Ng Lycurgus/Og @@ -9940,8 +9940,6 @@ Tbilisi/Og Tc/g Tchaikovsky/Og Te/Og -TeX/O -TeXes Teasdale/Og Technicolor/NgJ Tecumseh/Og @@ -9998,10 +9996,12 @@ Teuton/NgSJ Teutonic/JNg Tevet/Og Tex/Og +TeX/Og # Typesetting engine Texaco/g # company Texan/JNgS Texarkana/Og Texas/Og +TeXes Th/NOg # thorium Thackeray/Og Thad/Og @@ -53041,6 +53041,7 @@ LVM/Sg Labriola/OgS LanguageTool/g # grammar checker Laravel/g +LaTeX/Og # Typesetting system, based on TeX engine Lato/ONg LeetCode/Og # programming contest Lenovo/Og # computer manufacturer @@ -53052,7 +53053,7 @@ Logotherapy/Sg Longreads/Og LoRA/NwSg # low-rank adaptation Lua/Og # programming language -LuaTeX/Og +LuaTeX/Og # TeX-based typesetting engine MCP/Ng # model context protocol MMO/NgS # massively multiplayer online MMX/Ng # multimedia extension @@ -53380,6 +53381,7 @@ nullish/J olds/~ # common in phrasing such as "twenty year olds" pandoc/Sg # !! please check and comment !! elsewhere we have Pandoc pastebin/NgS +pdfTeX/Og # Program, extension for TeX engine pentest/VSdG pentester/NSg # penetration tester pentesting/NmgV6 @@ -53425,6 +53427,7 @@ waitlist/NgS watchOS/Og whitespace/~NSg # dictionaries prefer: white space wikilink/~NSg # !! please check and comment !! elsewhere we have Wikilink +XeTeX/Og # TeX typesetting engine z-buffer/NgS # Experimental multi-word entries diff --git a/harper-core/tests/run_tests.rs b/harper-core/tests/run_tests.rs index 1206d2c9..a8a9110b 100644 --- a/harper-core/tests/run_tests.rs +++ b/harper-core/tests/run_tests.rs @@ -90,6 +90,7 @@ create_test!(yogurt_british_clean.md, 0, Dialect::British); create_test!(issue_1581.md, 0, Dialect::British); create_test!(issue_2054.md, 6, Dialect::British); create_test!(issue_2054_clean.md, 0, Dialect::British); +create_test!(issue_1873.md, 0, Dialect::British); // It just matters that it is > 1 create_test!(issue_2151.md, 4, Dialect::British); diff --git a/harper-core/tests/test_sources/issue_1873.md b/harper-core/tests/test_sources/issue_1873.md new file mode 100644 index 00000000..ee50ae28 --- /dev/null +++ b/harper-core/tests/test_sources/issue_1873.md @@ -0,0 +1,2 @@ +TeX is a typesetting system. + From 6714b73d156467fd7adcf9fa71d92cba0fa3ae91 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Wed, 12 Nov 2025 08:39:01 -0700 Subject: [PATCH 017/178] feat(chrome-ext): Quill is a supported editor --- packages/chrome-plugin/src/background/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/chrome-plugin/src/background/index.ts b/packages/chrome-plugin/src/background/index.ts index 3a3c764d..b5d7d80e 100644 --- a/packages/chrome-plugin/src/background/index.ts +++ b/packages/chrome-plugin/src/background/index.ts @@ -96,6 +96,7 @@ async function enableDefaultDomains() { 'www.upwork.com', 'news.ycombinator.com', 'classroom.google.com', + 'quilljs.com', ]; for (const item of defaultEnabledDomains) { From 10592c8fc5da5861cc6fe6594e7f3bd083fff844 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Wed, 12 Nov 2025 15:31:25 -0700 Subject: [PATCH 018/178] fix(chrome-ext): it does not need to be enabled on Google --- packages/chrome-plugin/src/background/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/chrome-plugin/src/background/index.ts b/packages/chrome-plugin/src/background/index.ts index b5d7d80e..1e7792da 100644 --- a/packages/chrome-plugin/src/background/index.ts +++ b/packages/chrome-plugin/src/background/index.ts @@ -70,7 +70,6 @@ async function enableDefaultDomains() { 'playground.lexical.dev', 'discord.com', 'www.youtube.com', - 'www.google.com', 'www.instagram.com', 'web.whatsapp.com', 'outlook.live.com', From 64e95d801002e8603050d0e798630a76d90eabe6 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Thu, 13 Nov 2025 10:04:34 -0700 Subject: [PATCH 019/178] feat(web): use Quill instead of a ` +
+ {@html content.replace(/\n\n/g, '
')} +
- +
diff --git a/packages/web/src/lib/components/LazyEditor.svelte b/packages/web/src/lib/components/LazyEditor.svelte new file mode 100644 index 00000000..d7c1ea35 --- /dev/null +++ b/packages/web/src/lib/components/LazyEditor.svelte @@ -0,0 +1,23 @@ + + +{#await editor then { default: Editor}} +
+ +
+{/await} + +{#if loading} +
+ +
+{/if} diff --git a/packages/web/src/lib/components/LintKindChart.svelte b/packages/web/src/lib/components/LintKindChart.svelte index e2d56889..008b44cf 100644 --- a/packages/web/src/lib/components/LintKindChart.svelte +++ b/packages/web/src/lib/components/LintKindChart.svelte @@ -1,7 +1,7 @@ + + diff --git a/packages/web/src/lib/lintKindColor.ts b/packages/web/src/lib/lintKindColor.ts deleted file mode 100644 index 2c0cbda6..00000000 --- a/packages/web/src/lib/lintKindColor.ts +++ /dev/null @@ -1,38 +0,0 @@ -// First, define the color map as a constant -const LINT_KIND_COLORS = { - Agreement: '#228B22', // Forest green - BoundaryError: '#8B4513', // Saddle brown - Capitalization: '#540D6E', // Deep purple - Eggcorn: '#FF8C00', // Dark orange - Enhancement: '#0EAD69', // Green - Formatting: '#7D3C98', // Amethyst purple - Grammar: '#9B59B6', // Medium purple - Malapropism: '#C71585', // Medium violet red - Miscellaneous: '#3BCEAC', // Turquoise - Nonstandard: '#008B8B', // Dark cyan - Punctuation: '#D4850F', // Dark orange - Readability: '#2E8B57', // Sea green - Redundancy: '#4682B4', // Steel blue - Regionalism: '#C061CB', // Vibrant purple - Repetition: '#00A67C', // Green-cyan - Spelling: '#EE4266', // Pink-red - Style: '#FFD23F', // Yellow - Typo: '#FF6B35', // Vibrant orange-red - Usage: '#1E90FF', // Dodger blue - WordChoice: '#228B22', // Forest green -} as const; - -// Export the type for the lint kind keys -export type LintKind = keyof typeof LINT_KIND_COLORS; - -// Export the array of all lint kind names -export const LINT_KINDS = Object.keys(LINT_KIND_COLORS) as LintKind[]; - -// The main function that uses the map -export default function lintKindColor(lintKindKey: string): string { - const color = LINT_KIND_COLORS[lintKindKey as LintKind]; - if (!color) { - throw new Error(`Unexpected lint kind: ${lintKindKey}`); - } - return color; -} diff --git a/packages/web/src/routes/+page.svelte b/packages/web/src/routes/+page.svelte index dcd6df89..3a6c6be5 100644 --- a/packages/web/src/routes/+page.svelte +++ b/packages/web/src/routes/+page.svelte @@ -7,7 +7,7 @@ export const frontmatter = { -
+
{@html content.replace(/\n\n/g, '
')} diff --git a/packages/web/src/lib/components/LazyEditor.svelte b/packages/web/src/lib/components/LazyEditor.svelte index d7c1ea35..00f53cc4 100644 --- a/packages/web/src/lib/components/LazyEditor.svelte +++ b/packages/web/src/lib/components/LazyEditor.svelte @@ -11,7 +11,7 @@ function onReady() { {#await editor then { default: Editor}} -
+
{/await} From e4955aeb544d2ea0211cd7cc0726e12fee400bd9 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Thu, 13 Nov 2025 15:22:19 -0700 Subject: [PATCH 025/178] fix(core): adjust spell check suggestion score function (#2189) --- harper-core/dictionary.dict | 1 - harper-core/src/char_string.rs | 7 + .../src/linting/phrase_corrections/mod.rs | 2 +- harper-core/src/linting/spell_check.rs | 17 +- harper-core/src/spell/merged_dictionary.rs | 4 + harper-core/src/spell/mod.rs | 29 ++- .../Alice's Adventures in Wonderland.snap.yml | 20 +- .../text/linters/Computer science.snap.yml | 32 +-- .../text/linters/Difficult sentences.snap.yml | 2 +- .../linters/Part-of-speech tagging.snap.yml | 20 +- .../tests/text/linters/Spell.US.snap.yml | 2 +- harper-core/tests/text/linters/Spell.snap.yml | 2 +- ...Constitution of the United States.snap.yml | 10 +- .../text/linters/The Great Gatsby.snap.yml | 200 +++++++++--------- 14 files changed, 194 insertions(+), 154 deletions(-) diff --git a/harper-core/dictionary.dict b/harper-core/dictionary.dict index 556a48cd..0925c831 100644 --- a/harper-core/dictionary.dict +++ b/harper-core/dictionary.dict @@ -9939,7 +9939,6 @@ Tb/g Tbilisi/Og Tc/g Tchaikovsky/Og -Te/Og Teasdale/Og Technicolor/NgJ Tecumseh/Og diff --git a/harper-core/src/char_string.rs b/harper-core/src/char_string.rs index 08170a81..72978461 100644 --- a/harper-core/src/char_string.rs +++ b/harper-core/src/char_string.rs @@ -37,6 +37,9 @@ pub trait CharStringExt { /// Case-insensitive check if the string ends with the given ASCII suffix. /// The suffix is assumed to be lowercase. fn ends_with_ignore_ascii_case_str(&self, suffix: &str) -> bool; + + /// Check if the string contains any vowels + fn contains_vowel(&self) -> bool; } impl CharStringExt for [char] { @@ -119,6 +122,10 @@ impl CharStringExt for [char] { .zip(suffix.iter()) .all(|(a, b)| a.to_ascii_lowercase() == *b) } + + fn contains_vowel(&self) -> bool { + self.iter().any(|c| c.is_vowel()) + } } macro_rules! char_string { diff --git a/harper-core/src/linting/phrase_corrections/mod.rs b/harper-core/src/linting/phrase_corrections/mod.rs index 61066677..81e9ff65 100644 --- a/harper-core/src/linting/phrase_corrections/mod.rs +++ b/harper-core/src/linting/phrase_corrections/mod.rs @@ -1217,7 +1217,7 @@ pub fn lint_group() -> LintGroup { "Fixes incorrect use of `to worried about`." ), "The" => ( - ["teh"], + ["teh", "te"], ["the"], "Did you mean the definite article?", "Fixes especially common misspellings of the word `the`", diff --git a/harper-core/src/linting/spell_check.rs b/harper-core/src/linting/spell_check.rs index a098c133..fbf97fc2 100644 --- a/harper-core/src/linting/spell_check.rs +++ b/harper-core/src/linting/spell_check.rs @@ -42,7 +42,7 @@ impl SpellCheck { // Back off until we find a match. for dist in 2..5 { let suggestions: Vec = - suggest_correct_spelling(word, 100, dist, &self.dictionary) + suggest_correct_spelling(word, 200, dist, &self.dictionary) .into_iter() .filter(|v| { // Ignore entries outside the configured dialect @@ -465,4 +465,19 @@ mod tests { ); } } + + #[test] + fn issue_2026() { + assert_top3_suggestion_result( + "'Tere' is supposed to be 'There'", + SpellCheck::new(FstDictionary::curated(), Dialect::British), + "'There' is supposed to be 'There'", + ); + + assert_top3_suggestion_result( + "'fll' is supposed to be 'fill'", + SpellCheck::new(FstDictionary::curated(), Dialect::British), + "'fill' is supposed to be 'fill'", + ); + } } diff --git a/harper-core/src/spell/merged_dictionary.rs b/harper-core/src/spell/merged_dictionary.rs index be70b061..3c144e17 100644 --- a/harper-core/src/spell/merged_dictionary.rs +++ b/harper-core/src/spell/merged_dictionary.rs @@ -129,6 +129,8 @@ impl Dictionary for MergedDictionary { self.children .iter() .flat_map(|d| d.fuzzy_match(word, max_distance, max_results)) + .sorted_by_key(|r| r.word) + .dedup_by(|a, b| a.word == b.word) .sorted_by_key(|r| r.edit_distance) .take(max_results) .collect() @@ -143,6 +145,8 @@ impl Dictionary for MergedDictionary { self.children .iter() .flat_map(|d| d.fuzzy_match_str(word, max_distance, max_results)) + .sorted_by_key(|r| r.word) + .dedup_by(|a, b| a.word == b.word) .sorted_by_key(|r| r.edit_distance) .take(max_results) .collect() diff --git a/harper-core/src/spell/mod.rs b/harper-core/src/spell/mod.rs index 9e183867..fd5f2341 100644 --- a/harper-core/src/spell/mod.rs +++ b/harper-core/src/spell/mod.rs @@ -1,6 +1,8 @@ //! Contains the relevant code for performing dictionary lookups and spellchecking (i.e. fuzzy //! dictionary lookups). +use itertools::Itertools; + use crate::{CharString, CharStringExt, DictWordMetadata}; pub use self::dictionary::Dictionary; @@ -188,6 +190,13 @@ pub(crate) fn is_ll_misspelling(a: &[char], b: &[char]) -> bool { } } +pub fn is_th_h_missing(a: &[char], b: &[char]) -> bool { + a.iter().any(|c| c.eq_ignore_ascii_case(&'t')) + && b.iter() + .tuple_windows() + .any(|(a, b)| a.eq_ignore_ascii_case(&'t') && b.eq_ignore_ascii_case(&'h')) +} + /// Returns whether the two words are the same, except that one is written /// with 'ay' and the other with 'ey'. /// @@ -314,15 +323,25 @@ fn score_suggestion(misspelled_word: &[char], sug: &FuzzyMatchResult) -> i32 { score -= 5; } + if is_th_h_missing(misspelled_word, sug.word) { + score -= 6; + } + + if !misspelled_word.contains_vowel() && !sug.word.contains_vowel() { + score += 10; + } + // Detect dialect-specific variations if sug.edit_distance == 1 && (is_cksz_misspelling(misspelled_word, sug.word) || is_ou_misspelling(misspelled_word, sug.word) || is_ll_misspelling(misspelled_word, sug.word) - || is_ay_ey_misspelling(misspelled_word, sug.word)) + || is_ay_ey_misspelling(misspelled_word, sug.word) + || is_th_h_missing(misspelled_word, sug.word)) { score -= 6; } + if sug.edit_distance == 2 { if is_ei_ie_misspelling(misspelled_word, sug.word) { score -= 11; @@ -340,7 +359,7 @@ fn order_suggestions<'b>( misspelled_word: &[char], mut matches: Vec>, ) -> Vec<&'b [char]> { - matches.sort_by_key(|v| score_suggestion(misspelled_word, v)); + matches.sort_by_cached_key(|v| score_suggestion(misspelled_word, v)); matches.into_iter().map(|v| v.word).collect() } @@ -390,7 +409,7 @@ mod tests { use super::{FstDictionary, suggest_correct_spelling_str}; - const RESULT_LIMIT: usize = 100; + const RESULT_LIMIT: usize = 200; const MAX_EDIT_DIST: u8 = 2; #[test] @@ -442,8 +461,6 @@ mod tests { &FstDictionary::curated(), ); - dbg!(&results); - assert!(results.iter().all_unique()) } @@ -461,8 +478,6 @@ mod tests { &FstDictionary::curated(), ); - dbg!(&results); - assert!(results.iter().take(3).contains(&"hello".to_string())); } diff --git a/harper-core/tests/text/linters/Alice's Adventures in Wonderland.snap.yml b/harper-core/tests/text/linters/Alice's Adventures in Wonderland.snap.yml index 7654b98f..fe4228a2 100644 --- a/harper-core/tests/text/linters/Alice's Adventures in Wonderland.snap.yml +++ b/harper-core/tests/text/linters/Alice's Adventures in Wonderland.snap.yml @@ -915,8 +915,8 @@ Message: | 412 | advisable to go with Edgar Atheling to meet William and offer him the crown. | ^~~~~~~~ Did you mean to spell `Atheling` this way? Suggest: - - Replace with: “Steeling” - Replace with: “Theming” + - Replace with: “Steeling” @@ -1028,7 +1028,7 @@ Message: | Suggest: - Replace with: “We” - Replace with: “WA” - - Replace with: “WC” + - Replace with: “WI” @@ -2364,9 +2364,9 @@ Message: | 1985 | “’Tis so,” said the Duchess: “and the moral of that is—‘Oh, ’tis love, ’tis | ^~~ Did you mean to spell `Tis` this way? Suggest: + - Replace with: “This” - Replace with: “Tic” - Replace with: “T's” - - Replace with: “This” @@ -2376,8 +2376,8 @@ Message: | | ^~~ Did you mean to spell `tis` this way? 1986 | love, that makes the world go round!’” Suggest: - - Replace with: “tic” - Replace with: “this” + - Replace with: “tic” - Replace with: “ti's” @@ -2388,8 +2388,8 @@ Message: | | ^~~ Did you mean to spell `tis` this way? 1986 | love, that makes the world go round!’” Suggest: - - Replace with: “tic” - Replace with: “this” + - Replace with: “tic” - Replace with: “ti's” @@ -3085,9 +3085,9 @@ Message: | 2418 | “Stand up and repeat ‘’Tis the voice of the sluggard,’” said the Gryphon. | ^~~ Did you mean to spell `Tis` this way? Suggest: + - Replace with: “This” - Replace with: “Tic” - Replace with: “T's” - - Replace with: “This” @@ -3112,9 +3112,9 @@ Message: | 2425 | > “’Tis the voice of the Lobster; I heard him declare, “You have baked me too | ^~~ Did you mean to spell `Tis` this way? Suggest: + - Replace with: “This” - Replace with: “Tic” - Replace with: “T's” - - Replace with: “This” @@ -3207,9 +3207,9 @@ Message: | 2477 | eagerly that the Gryphon said, in a rather offended tone, “Hm! No accounting for | ^~ Did you mean to spell `Hm` this way? Suggest: - - Replace with: “H'm” - - Replace with: “Ha” - - Replace with: “Hem” + - Replace with: “Him” + - Replace with: “Ho” + - Replace with: “Am” diff --git a/harper-core/tests/text/linters/Computer science.snap.yml b/harper-core/tests/text/linters/Computer science.snap.yml index 096f2570..b4534dc1 100644 --- a/harper-core/tests/text/linters/Computer science.snap.yml +++ b/harper-core/tests/text/linters/Computer science.snap.yml @@ -56,9 +56,9 @@ Message: | 50 | de Colmar launched the mechanical calculator industry[note 1] when he invented | ^~ Did you mean to spell `de` this way? Suggest: + - Replace with: “den” - Replace with: “db” - Replace with: “dc” - - Replace with: “dd” @@ -242,8 +242,8 @@ Message: | 110 | and those of others such as numerical analyst George Forsythe, were rewarded: | ^~~~~~~~ Did you mean to spell `Forsythe` this way? Suggest: - - Replace with: “Forster” - Replace with: “Forsythia” + - Replace with: “Forster” - Replace with: “Porsche” @@ -338,8 +338,8 @@ Message: | 127 | the ACM—turingineer, turologist, flow-charts-man, applied meta-mathematician, | ^~~~~~~~~~ Did you mean to spell `turologist` this way? Suggest: - - Replace with: “urologist” - Replace with: “theologist” + - Replace with: “urologist” - Replace with: “neurologist” @@ -635,8 +635,8 @@ Message: | | ^~ Did you mean to spell `A.` this way? Suggest: - Replace with: “Ax” + - Replace with: “A” - Replace with: “Ab” - - Replace with: “Ac” @@ -688,7 +688,7 @@ Message: | Suggest: - Replace with: “We” - Replace with: “WA” - - Replace with: “WC” + - Replace with: “WI” @@ -765,9 +765,9 @@ Message: | 216 | that they are theory, abstraction (modeling), and design. Amnon H. Eden | ^~ Did you mean to spell `H.` this way? Suggest: - - Replace with: “Ht” - Replace with: “He” - - Replace with: “Hf” + - Replace with: “Hi” + - Replace with: “Ht” @@ -988,9 +988,9 @@ Message: | 393 | term "architecture" in computer literature can be traced to the work of Lyle R. | ^~ Did you mean to spell `R.` this way? Suggest: - - Replace with: “Rm” - - Replace with: “R” - - Replace with: “Rd” + - Replace with: “Re” + - Replace with: “RI” + - Replace with: “Ra” @@ -999,9 +999,9 @@ Message: | 394 | Johnson and Frederick P. Brooks Jr., members of the Machine Organization | ^~ Did you mean to spell `P.` this way? Suggest: - - Replace with: “Pm” - - Replace with: “P” - - Replace with: “Pd” + - Replace with: “Pa” + - Replace with: “Po” + - Replace with: “PE” @@ -1012,8 +1012,8 @@ Message: | | ^~~~~ Did you mean to spell `Petri` this way? Suggest: - Replace with: “Petra” + - Replace with: “Perth” - Replace with: “Perry” - - Replace with: “Patti” @@ -1074,9 +1074,9 @@ Message: | | ^~ Did you mean to spell `de` this way? 445 | > "high-voltage/low-voltage", etc.). Suggest: + - Replace with: “den” - Replace with: “db” - Replace with: “dc” - - Replace with: “dd” @@ -1107,7 +1107,7 @@ Message: | Suggest: - Replace with: “Baum” - Replace with: “Bohr” - - Replace with: “Chm” + - Replace with: “Ohm” diff --git a/harper-core/tests/text/linters/Difficult sentences.snap.yml b/harper-core/tests/text/linters/Difficult sentences.snap.yml index de9d216a..3dd2647c 100644 --- a/harper-core/tests/text/linters/Difficult sentences.snap.yml +++ b/harper-core/tests/text/linters/Difficult sentences.snap.yml @@ -341,9 +341,9 @@ Message: | 443 | With their reputation on the line, they decided to fire their PR team. | ^~ Did you mean to spell `PR` this way? Suggest: - - Replace with: “Pt” - Replace with: “Pa” - Replace with: “Par” + - Replace with: “Pre” diff --git a/harper-core/tests/text/linters/Part-of-speech tagging.snap.yml b/harper-core/tests/text/linters/Part-of-speech tagging.snap.yml index 964c2f20..132e8910 100644 --- a/harper-core/tests/text/linters/Part-of-speech tagging.snap.yml +++ b/harper-core/tests/text/linters/Part-of-speech tagging.snap.yml @@ -38,9 +38,9 @@ Message: | 18 | two distinctive groups: rule-based and stochastic. E. Brill's tagger, one of the | ^~ Did you mean to spell `E.` this way? Suggest: - - Replace with: “Em” - - Replace with: “Eh” - - Replace with: “EC” + - Replace with: “E” + - Replace with: “Ea” + - Replace with: “Ed” @@ -78,7 +78,7 @@ Message: | Suggest: - Replace with: “Nun” - Replace with: “Non” - - Replace with: “NV” + - Replace with: “Na” @@ -88,9 +88,9 @@ Message: | | ^~~ Did you mean to spell `NNS` this way? 50 | for singular proper nouns (see the POS tags used in the Brown Corpus). Other Suggest: - - Replace with: “NBS” - Replace with: “NES” - - Replace with: “NS” + - Replace with: “Ens” + - Replace with: “INS” @@ -238,7 +238,7 @@ Message: | Suggest: - Replace with: “We” - Replace with: “WA” - - Replace with: “WC” + - Replace with: “WI” @@ -248,9 +248,9 @@ Message: | | ^ Did you mean to spell `s` this way? 99 | the tagging was nearly perfect (allowing for some cases on which even human Suggest: - - Replace with: “sf” - - Replace with: “sh” - Replace with: “so” + - Replace with: “as” + - Replace with: “is” @@ -398,7 +398,7 @@ Message: | Suggest: - Replace with: “We” - Replace with: “WA” - - Replace with: “WC” + - Replace with: “WI” diff --git a/harper-core/tests/text/linters/Spell.US.snap.yml b/harper-core/tests/text/linters/Spell.US.snap.yml index 1234c982..641c6de0 100644 --- a/harper-core/tests/text/linters/Spell.US.snap.yml +++ b/harper-core/tests/text/linters/Spell.US.snap.yml @@ -135,8 +135,8 @@ Message: | | ^~~~~~~ Did you mean to spell `Theatre` this way? Suggest: - Replace with: “Theater” + - Replace with: “Sheathe” - Replace with: “Heater” - - Replace with: “Cheater” diff --git a/harper-core/tests/text/linters/Spell.snap.yml b/harper-core/tests/text/linters/Spell.snap.yml index 70053227..7ab547c5 100644 --- a/harper-core/tests/text/linters/Spell.snap.yml +++ b/harper-core/tests/text/linters/Spell.snap.yml @@ -110,9 +110,9 @@ Message: | 13 | At the centre of the theatre I dropped a litre of coke. | ^~~~~ Did you mean to spell `litre` this way? Suggest: + - Replace with: “lithe” - Replace with: “lire” - Replace with: “lite” - - Replace with: “liter” diff --git a/harper-core/tests/text/linters/The Constitution of the United States.snap.yml b/harper-core/tests/text/linters/The Constitution of the United States.snap.yml index e457df7c..6a0e9832 100644 --- a/harper-core/tests/text/linters/The Constitution of the United States.snap.yml +++ b/harper-core/tests/text/linters/The Constitution of the United States.snap.yml @@ -42,8 +42,8 @@ Message: | | ^~ Did you mean to spell `I.` this way? Suggest: - Replace with: “In” + - Replace with: “Id” - Replace with: “IC” - - Replace with: “IE” @@ -736,7 +736,7 @@ Message: | Suggest: - Replace with: “fact” - Replace with: “factor” - - Replace with: “facts” + - Replace with: “faith” @@ -802,7 +802,7 @@ Message: | Suggest: - Replace with: “fact” - Replace with: “factor” - - Replace with: “facts” + - Replace with: “faith” @@ -1727,9 +1727,9 @@ Message: | 663 | ## Article. V. | ^~ Did you mean to spell `V.` this way? Suggest: - - Replace with: “Vs.” - - Replace with: “Vb” - Replace with: “Vi” + - Replace with: “Va” + - Replace with: “Vs.” diff --git a/harper-core/tests/text/linters/The Great Gatsby.snap.yml b/harper-core/tests/text/linters/The Great Gatsby.snap.yml index 86f97286..942b92ca 100644 --- a/harper-core/tests/text/linters/The Great Gatsby.snap.yml +++ b/harper-core/tests/text/linters/The Great Gatsby.snap.yml @@ -3,9 +3,9 @@ Message: | 3 | BY F. SCOTT FITZGERALD | ^~ Did you mean to spell `F.` this way? Suggest: - - Replace with: “Fa” + - Replace with: “Fe” - Replace with: “F” - - Replace with: “F1” + - Replace with: “Ff” @@ -343,9 +343,9 @@ Message: | 120 | was a factual imitation of some Hôtel de Ville in Normandy, with a tower on one | ^~ Did you mean to spell `de` this way? Suggest: + - Replace with: “den” - Replace with: “db” - Replace with: “dc” - - Replace with: “dd” @@ -818,8 +818,8 @@ Message: | | ^~ Did you mean to spell `T.` this way? Suggest: - Replace with: “Ta” - - Replace with: “Tb” - - Replace with: “Tn” + - Replace with: “Ti” + - Replace with: “Th” @@ -828,8 +828,8 @@ Message: | 664 | it, you perceive, after a moment, the eyes of Doctor T. J. Eckleburg. The eyes | ^~ Did you mean to spell `J.` this way? Suggest: - - Replace with: “Jg” - - Replace with: “J” + - Replace with: “Jo” + - Replace with: “Jr” - Replace with: “JD” @@ -852,8 +852,8 @@ Message: | | ^~ Did you mean to spell `T.` this way? Suggest: - Replace with: “Ta” - - Replace with: “Tb” - - Replace with: “Tn” + - Replace with: “Ti” + - Replace with: “Th” @@ -862,8 +862,8 @@ Message: | 665 | of Doctor T. J. Eckleburg are blue and gigantic—their retinas are one yard high. | ^~ Did you mean to spell `J.` this way? Suggest: - - Replace with: “Jg” - - Replace with: “J” + - Replace with: “Jo” + - Replace with: “Jr” - Replace with: “JD” @@ -930,7 +930,7 @@ Message: | Suggest: - Replace with: “Bu” - Replace with: “Be” - - Replace with: “Bf” + - Replace with: “Bi” @@ -980,9 +980,9 @@ Message: | 729 | spotted dress of dark blue crêpe-de-chine, contained no facet or gleam of | ^~ Did you mean to spell `de` this way? Suggest: + - Replace with: “den” - Replace with: “db” - Replace with: “dc” - - Replace with: “dd” @@ -1055,9 +1055,9 @@ Message: | 784 | We backed up to a gray old man who bore an absurd resemblance to John D. | ^~ Did you mean to spell `D.` this way? Suggest: - - Replace with: “Dc” - Replace with: “DA” - Replace with: “DE” + - Replace with: “Di” @@ -1247,7 +1247,7 @@ Message: | Suggest: - Replace with: “Bu” - Replace with: “Be” - - Replace with: “Bf” + - Replace with: “Bi” @@ -2088,7 +2088,7 @@ Message: | Suggest: - Replace with: “outta” - Replace with: “out” - - Replace with: “outs” + - Replace with: “oath” @@ -2353,8 +2353,8 @@ Message: | | ^~~~ Did you mean to spell `Etty` this way? Suggest: - Replace with: “Etta” + - Replace with: “Thy” - Replace with: “Betty” - - Replace with: “Getty” @@ -2386,9 +2386,9 @@ Message: | 1858 | farther out on the Island came the Cheadles and the O. R. P. Schraeders, and the | ^~ Did you mean to spell `R.` this way? Suggest: - - Replace with: “Rm” - - Replace with: “R” - - Replace with: “Rd” + - Replace with: “Re” + - Replace with: “RI” + - Replace with: “Ra” @@ -2397,9 +2397,9 @@ Message: | 1858 | farther out on the Island came the Cheadles and the O. R. P. Schraeders, and the | ^~ Did you mean to spell `P.` this way? Suggest: - - Replace with: “Pm” - - Replace with: “P” - - Replace with: “Pd” + - Replace with: “Pa” + - Replace with: “Po” + - Replace with: “PE” @@ -2441,9 +2441,9 @@ Message: | 1861 | the gravel drive that Mrs. Ulysses Swett’s automobile ran over his right hand. | ^~~~~~~ Did you mean to spell `Swett’s` this way? Suggest: + - Replace with: “Seth's” - Replace with: “Scott's” - Replace with: “Sept's” - - Replace with: “Seth's” @@ -2463,9 +2463,9 @@ Message: | 1862 | The Dancies came, too, and S. B. Whitebait, who was well over sixty, and Maurice | ^~ Did you mean to spell `S.` this way? Suggest: - - Replace with: “Sf” - - Replace with: “Sh” - - Replace with: “Sq” + - Replace with: “So” + - Replace with: “SA” + - Replace with: “Si” @@ -2476,7 +2476,7 @@ Message: | Suggest: - Replace with: “Bu” - Replace with: “Be” - - Replace with: “Bf” + - Replace with: “Bi” @@ -2487,8 +2487,8 @@ Message: | | ^~ Did you mean to spell `A.` this way? Suggest: - Replace with: “Ax” + - Replace with: “A” - Replace with: “Ab” - - Replace with: “Ac” @@ -2566,9 +2566,9 @@ Message: | 1868 | Excellence, and Eckhaust and Clyde Cohen and Don S. Schwartze (the son) and | ^~ Did you mean to spell `S.` this way? Suggest: - - Replace with: “Sf” - - Replace with: “Sh” - - Replace with: “Sq” + - Replace with: “So” + - Replace with: “SA” + - Replace with: “Si” @@ -2610,9 +2610,9 @@ Message: | 1870 | Catlips and the Bembergs and G. Earl Muldoon, brother to that Muldoon who | ^~ Did you mean to spell `G.` this way? Suggest: - - Replace with: “Gr” - Replace with: “Go” - - Replace with: “GB” + - Replace with: “GI” + - Replace with: “GU” @@ -2654,9 +2654,9 @@ Message: | 1871 | afterward strangled his wife. Da Fontano the promoter came there, and Ed Legros | ^~ Did you mean to spell `Da` this way? Suggest: - - Replace with: “Dab” - Replace with: “Dad” - Replace with: “Dam” + - Replace with: “Day” @@ -2702,7 +2702,7 @@ Message: | Suggest: - Replace with: “Bu” - Replace with: “Be” - - Replace with: “Bf” + - Replace with: “Bi” @@ -2724,7 +2724,7 @@ Message: | Suggest: - Replace with: “Db” - Replace with: “Dc” - - Replace with: “Def” + - Replace with: “Dd” @@ -2874,9 +2874,9 @@ Message: | 1881 | Scullys and S. W. Belcher and the Smirkes and the young Quinns, divorced now, | ^~ Did you mean to spell `S.` this way? Suggest: - - Replace with: “Sf” - - Replace with: “Sh” - - Replace with: “Sq” + - Replace with: “So” + - Replace with: “SA” + - Replace with: “Si” @@ -2887,7 +2887,7 @@ Message: | Suggest: - Replace with: “We” - Replace with: “WA” - - Replace with: “WC” + - Replace with: “WI” @@ -2932,9 +2932,9 @@ Message: | 1882 | and Henry L. Palmetto, who killed himself by jumping in front of a subway train | ^~ Did you mean to spell `L.` this way? Suggest: - - Replace with: “Lb” - - Replace with: “L” - - Replace with: “LC” + - Replace with: “Le” + - Replace with: “Li” + - Replace with: “Lu” @@ -3054,9 +3054,9 @@ Message: | 1896 | Fitz-Peters and Mr. P. Jewett, once head of the American Legion, and Miss | ^~ Did you mean to spell `P.` this way? Suggest: - - Replace with: “Pm” - - Replace with: “P” - - Replace with: “Pd” + - Replace with: “Pa” + - Replace with: “Po” + - Replace with: “PE” @@ -3136,9 +3136,9 @@ Message: | 1982 | de Boulogne. | ^~ Did you mean to spell `de` this way? Suggest: + - Replace with: “den” - Replace with: “db” - Replace with: “dc” - - Replace with: “dd” @@ -4507,9 +4507,9 @@ Message: | 3024 | a source of satisfaction to James Gatz of North Dakota, isn’t easy to say. | ^~~~ Did you mean to spell `Gatz` this way? Suggest: + - Replace with: “Garth” + - Replace with: “Goth” - Replace with: “Ga's” - - Replace with: “Gate” - - Replace with: “GHz” @@ -4518,9 +4518,9 @@ Message: | 3026 | James Gatz—that was really, or at least legally, his name. He had changed it at | ^~~~ Did you mean to spell `Gatz` this way? Suggest: + - Replace with: “Garth” + - Replace with: “Goth” - Replace with: “Ga's” - - Replace with: “Gate” - - Replace with: “GHz” @@ -4542,9 +4542,9 @@ Message: | 3029 | on Lake Superior. It was James Gatz who had been loafing along the beach that | ^~~~ Did you mean to spell `Gatz` this way? Suggest: + - Replace with: “Garth” + - Replace with: “Goth” - Replace with: “Ga's” - - Replace with: “Gate” - - Replace with: “GHz” @@ -4596,9 +4596,9 @@ Message: | | ^~ Did you mean to spell `de` this way? 3075 | sent him to sea in a yacht, were common property of the turgid journalism Suggest: + - Replace with: “den” - Replace with: “db” - Replace with: “dc” - - Replace with: “dd” @@ -4610,7 +4610,7 @@ Message: | Suggest: - Replace with: “Maintenance” - Replace with: “Maintop” - - Replace with: “Maine's” + - Replace with: “Matheson” @@ -4621,8 +4621,8 @@ Message: | | ^~~~~~ Did you mean to spell `Gatz’s` this way? Suggest: - Replace with: “Gate's” - - Replace with: “Ga's” - - Replace with: “Gaea's” + - Replace with: “Garth's” + - Replace with: “Goth's” @@ -4631,9 +4631,9 @@ Message: | 3079 | To young Gatz, resting on his oars and looking up at the railed deck, that yacht | ^~~~ Did you mean to spell `Gatz` this way? Suggest: + - Replace with: “Garth” + - Replace with: “Goth” - Replace with: “Ga's” - - Replace with: “Gate” - - Replace with: “GHz” @@ -5308,9 +5308,9 @@ Message: | 3647 | up now, and say—How-de-do.” | ^~ Did you mean to spell `de` this way? Suggest: + - Replace with: “den” - Replace with: “db” - Replace with: “dc” - - Replace with: “dd” @@ -5468,8 +5468,8 @@ Message: | | ^~ Did you mean to spell `T.` this way? Suggest: - Replace with: “Ta” - - Replace with: “Tb” - - Replace with: “Tn” + - Replace with: “Ti” + - Replace with: “Th” @@ -5478,8 +5478,8 @@ Message: | 3874 | while in silence. Then as Doctor T. J. Eckleburg’s faded eyes came into sight | ^~ Did you mean to spell `J.` this way? Suggest: - - Replace with: “Jg” - - Replace with: “J” + - Replace with: “Jo” + - Replace with: “Jr” - Replace with: “JD” @@ -5537,8 +5537,8 @@ Message: | | ^~ Did you mean to spell `T.` this way? Suggest: - Replace with: “Ta” - - Replace with: “Tb” - - Replace with: “Tn” + - Replace with: “Ti” + - Replace with: “Th” @@ -5547,8 +5547,8 @@ Message: | 3948 | behind. Over the ashheaps the giant eyes of Doctor T. J. Eckleburg kept their | ^~ Did you mean to spell `J.` this way? Suggest: - - Replace with: “Jg” - - Replace with: “J” + - Replace with: “Jo” + - Replace with: “Jr” - Replace with: “JD” @@ -5741,9 +5741,9 @@ Message: | 4073 | “We're getting old,” said Daisy. “lf we were young we’d rise and dance.” | ^~ Did you mean to spell `lf` this way? Suggest: - - Replace with: “l” - - Replace with: “lb” - - Replace with: “lg” + - Replace with: “la” + - Replace with: “lo” + - Replace with: “elf” @@ -7026,9 +7026,9 @@ Message: | 5122 | ghost of a superior “Hm!” | ^~ Did you mean to spell `Hm` this way? Suggest: - - Replace with: “H'm” - - Replace with: “Ha” - - Replace with: “Hem” + - Replace with: “Him” + - Replace with: “Ho” + - Replace with: “Am” @@ -7093,8 +7093,8 @@ Message: | | ^~ Did you mean to spell `T.` this way? Suggest: - Replace with: “Ta” - - Replace with: “Tb” - - Replace with: “Tn” + - Replace with: “Ti” + - Replace with: “Th” @@ -7103,8 +7103,8 @@ Message: | 5156 | of Doctor T. J. Eckleburg, which had just emerged, pale and enormous, from the | ^~ Did you mean to spell `J.` this way? Suggest: - - Replace with: “Jg” - - Replace with: “J” + - Replace with: “Jo” + - Replace with: “Jr” - Replace with: “JD” @@ -7421,7 +7421,7 @@ Message: | Suggest: - Replace with: “eta” - Replace with: “etc.” - - Replace with: “etch” + - Replace with: “ethic” @@ -7500,9 +7500,9 @@ Message: | 5352 | I think it was on the third day that a telegram signed Henry C. Gatz arrived | ^~ Did you mean to spell `C.` this way? Suggest: - - Replace with: “Cw” - - Replace with: “Cc” - - Replace with: “Ch” + - Replace with: “Cu” + - Replace with: “CI” + - Replace with: “Ce” @@ -7512,9 +7512,9 @@ Message: | | ^~~~ Did you mean to spell `Gatz` this way? 5353 | from a town in Minnesota. It said only that the sender was leaving immediately Suggest: + - Replace with: “Garth” + - Replace with: “Goth” - Replace with: “Ga's” - - Replace with: “Gate” - - Replace with: “GHz” @@ -7543,9 +7543,9 @@ Message: | 5385 | After a little while Mr. Gatz opened the door and came out, his mouth ajar, his | ^~~~ Did you mean to spell `Gatz` this way? Suggest: + - Replace with: “Garth” + - Replace with: “Goth” - Replace with: “Ga's” - - Replace with: “Gate” - - Replace with: “GHz” @@ -7578,9 +7578,9 @@ Message: | 5396 | “Gatz is my name.” | ^~~~ Did you mean to spell `Gatz` this way? Suggest: + - Replace with: “Garth” + - Replace with: “Goth” - Replace with: “Ga's” - - Replace with: “Gate” - - Replace with: “GHz” @@ -7589,9 +7589,9 @@ Message: | 5398 | “—Mr. Gatz. I thought you might want to take the body West.” | ^~~~ Did you mean to spell `Gatz` this way? Suggest: + - Replace with: “Garth” + - Replace with: “Goth” - Replace with: “Ga's” - - Replace with: “Gate” - - Replace with: “GHz” @@ -7607,8 +7607,8 @@ Message: | 5412 | “If he’d of lived, he’d of been a great man. A man like James J. Hill. He’d of | ^~ Did you mean to spell `J.` this way? Suggest: - - Replace with: “Jg” - - Replace with: “J” + - Replace with: “Jo” + - Replace with: “Jr” - Replace with: “JD” @@ -7637,7 +7637,7 @@ Message: | Suggest: - Replace with: “Bu” - Replace with: “Be” - - Replace with: “Bf” + - Replace with: “Bi” @@ -7646,9 +7646,9 @@ Message: | 5455 | and I’m sort of helpless without them. My address is care of B. F.———” | ^~ Did you mean to spell `F.` this way? Suggest: - - Replace with: “Fa” + - Replace with: “Fe” - Replace with: “F” - - Replace with: “F1” + - Replace with: “Ff” @@ -7835,9 +7835,9 @@ Message: | | ^~~~ Did you mean to spell `Gatz` this way? 5560 | up and down excitedly in the hall. His pride in his son and in his son’s Suggest: + - Replace with: “Garth” + - Replace with: “Goth” - Replace with: “Ga's” - - Replace with: “Gate” - - Replace with: “GHz” @@ -7950,9 +7950,9 @@ Message: | 5629 | then Mr. Gatz and the minister and I in the limousine, and a little later four | ^~~~ Did you mean to spell `Gatz` this way? Suggest: + - Replace with: “Garth” + - Replace with: “Goth” - Replace with: “Ga's” - - Replace with: “Gate” - - Replace with: “GHz” From 9526d55d17ec56f0f8777331c307d5fdfefd609b Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Thu, 13 Nov 2025 15:30:03 -0700 Subject: [PATCH 026/178] fix: remove redundant check --- .github/workflows/just_checks.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/just_checks.yml b/.github/workflows/just_checks.yml index d31a6c7c..67f51760 100644 --- a/.github/workflows/just_checks.yml +++ b/.github/workflows/just_checks.yml @@ -26,7 +26,6 @@ jobs: test-chrome-plugin, test-firefox-plugin, test-obsidian, - build-obsidian, ] steps: - uses: actions/checkout@v4 From d323c39b50a559464d187dfb8b249bf9109e3868 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Thu, 13 Nov 2025 15:32:54 -0700 Subject: [PATCH 027/178] feat(core): create rule to detect incorrect verbs in nominal phrase (#2176) --- harper-core/annotations.json | 8 + harper-core/dictionary.dict | 10 +- harper-core/src/expr/sequence_expr.rs | 4 +- harper-core/src/linting/lint_group.rs | 2 + harper-core/src/linting/mod.rs | 2 + harper-core/src/linting/verb_to_adjective.rs | 129 +++++++ harper-core/src/token_kind.rs | 2 + .../text/linters/The Great Gatsby.snap.yml | 8 + .../Alice's Adventures in Wonderland.md | 26 +- .../tests/text/tagged/Computer science.md | 28 +- .../tests/text/tagged/Difficult sentences.md | 14 +- .../text/tagged/Part-of-speech tagging.md | 4 +- .../The Constitution of the United States.md | 14 +- .../tests/text/tagged/The Great Gatsby.md | 314 +++++++++--------- test.md | 1 + 15 files changed, 359 insertions(+), 207 deletions(-) create mode 100644 harper-core/src/linting/verb_to_adjective.rs create mode 100644 test.md diff --git a/harper-core/annotations.json b/harper-core/annotations.json index 7e8e18e6..b72f0eaf 100644 --- a/harper-core/annotations.json +++ b/harper-core/annotations.json @@ -648,6 +648,14 @@ "verb": {} } }, + "j": { + "#": "verb past property", + "metadata": { + "verb": { + "verb_form": "PAST" + } + } + }, "J": { "#": "adjective property", "metadata": { diff --git a/harper-core/dictionary.dict b/harper-core/dictionary.dict index 0925c831..7341aae8 100644 --- a/harper-core/dictionary.dict +++ b/harper-core/dictionary.dict @@ -23946,7 +23946,7 @@ exploiter/NgV exploration/~NwgS exploratory/~JN explore/~VGd>NSZ -explored/~VU +explored/~jU explorer/~Ng explosion/~NSg explosive/~JYNSgwp @@ -35391,7 +35391,7 @@ nevermore/~ nevertheless/~R nevi/N nevus/Ng -new/~J^YNSgV>p +new/~J^YNSg>p newbie/~NgS newborn/~JNSg newcomer/~NSg @@ -44262,7 +44262,7 @@ shiver/VdGNg shivery/J shoal/~JNgSVGd shoat/NgS -shock/~NwgSJVGd>Z +shock/~NwgSJGd>Z shocker/~Ng shocking/~JYV6N shockproof/JV @@ -46855,7 +46855,7 @@ submission/~NgSr submissive/~JYNp submissiveness/Ng submit/~VSr -submitted/~Vr +submitted/~jr submitter/N submitting/~VNr submodule/NgS @@ -48808,7 +48808,7 @@ tokenism/Nmg tokenization/Nmg tokenize/VdSG tokenizer/NgS -told/~VtTrU +told/~jtTrU tole/NgV tolerable/Ji tolerably/Ryi diff --git a/harper-core/src/expr/sequence_expr.rs b/harper-core/src/expr/sequence_expr.rs index 0a4867ba..eca33279 100644 --- a/harper-core/src/expr/sequence_expr.rs +++ b/harper-core/src/expr/sequence_expr.rs @@ -292,7 +292,7 @@ impl SequenceExpr { } /// Match a token where either of the two token kind predicates returns true. - /// For instance, an adjetive or an adverb. + /// For instance, an adjective or an adverb. pub fn then_kind_either(self, pred_is_1: F1, pred_is_2: F2) -> Self where F1: Fn(&TokenKind) -> bool + Send + Sync + 'static, @@ -374,7 +374,6 @@ impl SequenceExpr { { self.then(move |tok: &Token, src: &[char]| { preds.iter().any(|pred| pred(&tok.kind)) - // && !words || words .iter() .any(|&word| tok.span.get_content(src).eq_ignore_ascii_case_str(word)) @@ -463,6 +462,7 @@ impl SequenceExpr { gen_then_from_is!(possessive_determiner); gen_then_from_is!(quantifier); gen_then_from_is!(non_quantifier_determiner); + gen_then_from_is!(non_demonstrative_determiner); /// Push an [`IndefiniteArticle`] to the end of the operation list. pub fn then_indefinite_article(self) -> Self { diff --git a/harper-core/src/linting/lint_group.rs b/harper-core/src/linting/lint_group.rs index be6c0491..a1206890 100644 --- a/harper-core/src/linting/lint_group.rs +++ b/harper-core/src/linting/lint_group.rs @@ -158,6 +158,7 @@ use super::touristic::Touristic; use super::unclosed_quotes::UnclosedQuotes; use super::update_place_names::UpdatePlaceNames; use super::use_genitive::UseGenitive; +use super::verb_to_adjective::VerbToAdjective; use super::very_unique::VeryUnique; use super::vice_versa::ViceVersa; use super::was_aloud::WasAloud; @@ -473,6 +474,7 @@ impl LintGroup { insert_expr_rule!(CautionaryTale, true); insert_expr_rule!(ChangeTack, true); insert_expr_rule!(ChockFull, true); + insert_expr_rule!(VerbToAdjective, true); insert_struct_rule!(CommaFixes, true); insert_struct_rule!(CompoundNouns, true); insert_expr_rule!(CompoundSubjectI, true); diff --git a/harper-core/src/linting/mod.rs b/harper-core/src/linting/mod.rs index 6cacb687..c9c92153 100644 --- a/harper-core/src/linting/mod.rs +++ b/harper-core/src/linting/mod.rs @@ -174,6 +174,7 @@ mod touristic; mod unclosed_quotes; mod update_place_names; mod use_genitive; +mod verb_to_adjective; mod very_unique; mod vice_versa; mod was_aloud; @@ -341,6 +342,7 @@ pub use touristic::Touristic; pub use unclosed_quotes::UnclosedQuotes; pub use update_place_names::UpdatePlaceNames; pub use use_genitive::UseGenitive; +pub use verb_to_adjective::VerbToAdjective; pub use very_unique::VeryUnique; pub use vice_versa::ViceVersa; pub use was_aloud::WasAloud; diff --git a/harper-core/src/linting/verb_to_adjective.rs b/harper-core/src/linting/verb_to_adjective.rs new file mode 100644 index 00000000..da0f2d9a --- /dev/null +++ b/harper-core/src/linting/verb_to_adjective.rs @@ -0,0 +1,129 @@ +use harper_brill::UPOS; + +use crate::expr::All; +use crate::expr::Expr; +use crate::expr::SequenceExpr; +use crate::patterns::UPOSSet; +use crate::patterns::WordSet; +use crate::{Token, TokenStringExt}; + +use super::{ExprLinter, Lint, LintKind}; + +pub struct VerbToAdjective { + expr: Box, +} + +impl Default for VerbToAdjective { + fn default() -> Self { + let expr = SequenceExpr::default() + .then(WordSet::new(&["the", "a", "an"])) + .t_ws() + .then(|tok: &Token, _: &[char]| { + (tok.kind.is_verb() + && !tok.kind.is_verb_past_form() + && !tok.kind.is_adjective() + && !tok.kind.is_noun()) + || tok.kind.is_degree_adverb() + }) + .t_ws() + .then(UPOSSet::new(&[UPOS::NOUN, UPOS::PROPN])); + + let exceptions = SequenceExpr::default() + .t_any() + .t_any() + .then_unless(WordSet::new(&["very"])); + + Self { + expr: Box::new(All::new(vec![Box::new(expr), Box::new(exceptions)])), + } + } +} + +impl ExprLinter for VerbToAdjective { + fn expr(&self) -> &dyn Expr { + self.expr.as_ref() + } + + fn match_to_lint(&self, matched_tokens: &[Token], _source: &[char]) -> Option { + Some(Lint { + span: matched_tokens.span()?, + lint_kind: LintKind::Typo, + suggestions: vec![], + message: "Did you intend to use an adjective here?".to_owned(), + priority: 31, + }) + } + + fn description(&self) -> &'static str { + "Looks for situations where a verb was written where an adjective is often intended." + } +} + +#[cfg(test)] +mod tests { + use crate::linting::tests::{assert_lint_count, assert_no_lints}; + + use super::VerbToAdjective; + + #[test] + fn fully_accounting() { + assert_lint_count( + "By scheduling time to do a fully accounting of where your CPU cycles are going, you can preemptively save yourself (and your contributors) a lot of time.", + VerbToAdjective::default(), + 1, + ); + } + + #[test] + fn new_car_is_valid() { + assert_no_lints("I really like my new car.", VerbToAdjective::default()); + } + + #[test] + fn new_sentence_is_valid() { + assert_no_lints( + "I want you to write a new sentence for me.", + VerbToAdjective::default(), + ); + } + + #[test] + fn correct_term_is_valid() { + assert_no_lints( + "Ensure the correct term is used for individuals residing abroad.", + VerbToAdjective::default(), + ); + } + + #[test] + fn causes_amazement_is_valid() { + assert_no_lints( + "It is something that causes amazement.", + VerbToAdjective::default(), + ); + } + + #[test] + fn correct_auxiliary_is_valid() { + assert_no_lints( + "Can you suggest a correct auxiliary?", + VerbToAdjective::default(), + ); + } + + #[test] + fn submitted_form_data_is_valid() { + assert_no_lints( + "This is the email address that will receive the submitted form data.", + VerbToAdjective::default(), + ); + } + + #[test] + fn the_unexplored_territories_is_valid() { + assert_no_lints( + "Not the unexplored territories, ripe for discovery, but the areas actively erased, obscured, or simply deemed unworthy of representation?", + VerbToAdjective::default(), + ); + } +} diff --git a/harper-core/src/token_kind.rs b/harper-core/src/token_kind.rs index 7ed31c25..ee1e4958 100644 --- a/harper-core/src/token_kind.rs +++ b/harper-core/src/token_kind.rs @@ -93,6 +93,7 @@ impl TokenKind { is_auxiliary_verb, is_linking_verb, is_verb_lemma, + is_verb_past_form, is_verb_simple_past_form, is_verb_past_participle_form, is_verb_progressive_form, @@ -116,6 +117,7 @@ impl TokenKind { is_possessive_determiner, is_quantifier, is_non_quantifier_determiner, + is_non_demonstrative_determiner, // Conjunction methods is_conjunction, diff --git a/harper-core/tests/text/linters/The Great Gatsby.snap.yml b/harper-core/tests/text/linters/The Great Gatsby.snap.yml index 942b92ca..e4d66b40 100644 --- a/harper-core/tests/text/linters/The Great Gatsby.snap.yml +++ b/harper-core/tests/text/linters/The Great Gatsby.snap.yml @@ -3687,6 +3687,14 @@ Suggest: +Lint: Typo (31 priority) +Message: | + 2286 | (said Jordan Baker that afternoon, sitting up very straight on a straight chair + | ^~~~~~~~~~~~~~~~ Did you intend to use an adjective here? + 2287 | in the tea-garden at the Plaza Hotel) + + + Lint: Readability (127 priority) Message: | 2291 | with rubber nobs on the soles that bit into the soft ground. I had on a new diff --git a/harper-core/tests/text/tagged/Alice's Adventures in Wonderland.md b/harper-core/tests/text/tagged/Alice's Adventures in Wonderland.md index 9521c341..128a4f46 100644 --- a/harper-core/tests/text/tagged/Alice's Adventures in Wonderland.md +++ b/harper-core/tests/text/tagged/Alice's Adventures in Wonderland.md @@ -132,8 +132,8 @@ # Nᴹ/Vg/J . I/Ddem N🅪Sg/VB/J+ . NSg/R NPr/ISg+ VB N🅪Sg/VB/J+ NSg/P NSg/I/J/C/Dq D NPr/VB/J NSg/VB+ . . . NSg/C/P ISg/#r+ VXB > have to ask them what the name of the country is , you know . Please , Ma’am , is # NSg/VXB P NSg/VB NSg/IPl+ NSg/I+ D NSg/VB P D NSg/J+ VL3 . ISgPl+ NSg/VB . VB . NSg/VB . VL3 -> this New Zealand or Australia ? ” ( and she tried to curtsey as she spoke — fancy -# I/Ddem NSg/VB/J NPr NPr/C NPr+ . . . VB/C ISg+ VP/J P ? NSg/R ISg+ NSg/VPt . NSg/VB/J +> this New Zealand or Australia ? ” ( and she tried to curtsey as she spoke — fancy +# I/Ddem NSg/J NPr NPr/C NPr+ . . . VB/C ISg+ VP/J P ? NSg/R ISg+ NSg/VPt . NSg/VB/J > curtseying as you’re falling through the air ! Do you think you could manage it ? ) # ? NSg/R K Nᴹ/Vg/J NSg/J/P D N🅪Sg/VB+ . NSg/VXB ISgPl+ NSg/VB ISgPl+ NSg/VXB NSg/VB NPr/ISg+ . . > “ And what an ignorant little girl she’ll think me for asking ! No , it’ll never do @@ -390,8 +390,8 @@ # R NSg/VB/J NSg/VB/J/P P N🅪Sg/VB ISg+ J/P ISgPl+ . ISgPl+ NSg/VB NSg/VB D+ NPr/VXB/JS+ NSg/J+ ISgPl+ > can ; — but I must be kind to them , ” thought Alice , “ or perhaps they won’t walk the # NPr/VXB . . NSg/C/P ISg/#r+ NSg/VB NSg/VXB NSg/J P NSg/IPl+ . . N🅪Sg/VP NPr+ . . NPr/C NSg/R IPl+ VB NSg/VB D -> way I want to go ! Let me see : I’ll give them a new pair of boots every -# NSg/J+ ISg/#r+ NSg/VB P NSg/VB/J . NSg/VBP NPr/ISg+ NSg/VB . K NSg/VB NSg/IPl+ D/P NSg/VB/J NSg/VB P NPl/V3+ Dq +> way I want to go ! Let me see : I’ll give them a new pair of boots every +# NSg/J+ ISg/#r+ NSg/VB P NSg/VB/J . NSg/VBP NPr/ISg+ NSg/VB . K NSg/VB NSg/IPl+ D/P NSg/J NSg/VB P NPl/V3+ Dq > Christmas . ” # NPr/VB/J+ . . > @@ -773,7 +773,7 @@ > driest thing I know . Silence all round , if you please ! ‘ William the Conqueror , # JS NSg ISg/#r+ NSg/VB . NSg/VB+ NSg/I/J/C/Dq NSg/VB/J/P . NSg/C ISgPl+ VB . Unlintable NPr+ D NSg . > whose cause was favoured by the pope , was soon submitted to by the English , who -# I+ N🅪Sg/VB/C+ VPt VP/J/Comm NSg/J/P D NPr/VB+ . VPt J/R VB P NSg/J/P D NPr🅪Sg/VB/J+ . NPr/I+ +# I+ N🅪Sg/VB/C+ VPt VP/J/Comm NSg/J/P D NPr/VB+ . VPt J/R VP P NSg/J/P D NPr🅪Sg/VB/J+ . NPr/I+ > wanted leaders , and had been of late much accustomed to usurpation and conquest . # VP/J NPl+ . VB/C VB NSg/VPp P NSg/J NSg/I/J/R/Dq VP/J P NSg VB/C NSg/VB+ . > Edwin and Morcar , the earls of Mercia and Northumbria — ’ ” @@ -2058,8 +2058,8 @@ # P NSg/VB+ . NSg$ NSg/I/J/C/Dq ISg/#r+ NPr/VXB NSg/VB . . > # -> This was such a new idea to Alice , that she was quite silent for a minute or -# I/Ddem+ VPt NSg/I D/P NSg/VB/J NSg P NPr+ . NSg/I/C/Ddem ISg+ VPt R NSg/J C/P D/P+ NSg/VB/J+ NPr/C +> This was such a new idea to Alice , that she was quite silent for a minute or +# I/Ddem+ VPt NSg/I D/P NSg/J NSg P NPr+ . NSg/I/C/Ddem ISg+ VPt R NSg/J C/P D/P+ NSg/VB/J+ NPr/C > two , which gave the Pigeon the opportunity of adding , “ You’re looking for eggs , # NSg . I/C+ VPt D NSg/VB+ D N🅪Sg P Nᴹ/Vg/J+ . . K Nᴹ/Vg/J C/P NPl/V3+ . > I know that well enough ; and what does it matter to me whether you’re a little @@ -3922,8 +3922,8 @@ # . ISg/#r+ VB NSg/VXB I/R/Dq N🅪Sg/VB+ NPr/J/P D$+ NSg/VB+ NSg/P NSg/I/J/C/Dq . N🅪Sg/VB+ NPl/V3 J/R NSg/VB/J/R C/P . NSg/J/R > it’s always pepper that makes people hot - tempered , ” she went on , very much # K R N🅪Sg/VB NSg/I/C/Ddem+ NPl/V3 NPl/VB+ NSg/VB/J . VP/J . . ISg+ NSg/VPt J/P . J/R NSg/I/J/R/Dq -> pleased at having found out a new kind of rule , “ and vinegar that makes them -# VP/J NSg/P Nᴹ/Vg/J NSg/VB NSg/VB/J/R/P D/P NSg/VB/J NSg/J P NSg/VB+ . . VB/C NSg/VB+ NSg/I/C/Ddem+ NPl/V3 NSg/IPl+ +> pleased at having found out a new kind of rule , “ and vinegar that makes them +# VP/J NSg/P Nᴹ/Vg/J NSg/VB NSg/VB/J/R/P D/P NSg/J NSg/J P NSg/VB+ . . VB/C NSg/VB+ NSg/I/C/Ddem+ NPl/V3 NSg/IPl+ > sour — and camomile that makes them bitter — and — and barley - sugar and such things # NSg/VB/J . VB/C ? NSg/I/C/Ddem+ NPl/V3 NSg/IPl+ NSg/VB/J . VB/C . VB/C Nᴹ . N🅪Sg/VB VB/C NSg/I NPl+ > that make children sweet - tempered . I only wish people knew that : then they @@ -4466,8 +4466,8 @@ # VB/C P NPr🅪Sg+ P NPr🅪Sg . . > # -> This was quite a new idea to Alice , and she thought it over a little before she -# I/Ddem+ VPt R D/P NSg/VB/J NSg P NPr+ . VB/C ISg+ N🅪Sg/VP NPr/ISg+ NSg/J/P D/P NPr/I/J/Dq C/P ISg+ +> This was quite a new idea to Alice , and she thought it over a little before she +# I/Ddem+ VPt R D/P NSg/J NSg P NPr+ . VB/C ISg+ N🅪Sg/VP NPr/ISg+ NSg/J/P D/P NPr/I/J/Dq C/P ISg+ > made her next remark . “ Then the eleventh day must have been a holiday ? ” # VB ISg/D$+ NSg/J/P+ NSg/VB+ . . NSg/J/C D+ NSg/J+ NPr🅪Sg+ NSg/VB NSg/VXB NSg/VPp D/P+ NPr/VB+ . . > @@ -5536,8 +5536,8 @@ # VXB NSg/VXB R NSg/R NSg/I/J/R/Dq N🅪Sg/VB NPr/J/P D+ NSg/VB/J+ NSg/I/VB/J+ NSg/J+ NSg/VB/J/P NSg/R D NSg/VB/J . . > # -> As soon as the jury had a little recovered from the shock of being upset , and -# NSg/R J/R NSg/R D+ NSg/VB/J+ VB D/P NPr/I/J/Dq VP/J+ P D N🅪Sg/VB/J P N🅪Sg/Vg/J/C NSg/VB/J . VB/C +> As soon as the jury had a little recovered from the shock of being upset , and +# NSg/R J/R NSg/R D+ NSg/VB/J+ VB D/P NPr/I/J/Dq VP/J+ P D N🅪Sg/J P N🅪Sg/Vg/J/C NSg/VB/J . VB/C > their slates and pencils had been found and handed back to them , they set to # D$+ NPl/V3 VB/C NPl/V3+ VB NSg/VPp NSg/VB VB/C VP/J NSg/VB/J P NSg/IPl+ . IPl+ NPr/VBP/J P > work very diligently to write out a history of the accident , all except the diff --git a/harper-core/tests/text/tagged/Computer science.md b/harper-core/tests/text/tagged/Computer science.md index e9aa0144..8eaf3c0f 100644 --- a/harper-core/tests/text/tagged/Computer science.md +++ b/harper-core/tests/text/tagged/Computer science.md @@ -158,8 +158,8 @@ # NPr/ISg+ NSg/R . NSg$ NSg/VB/J+ NSg/VBPp/P NSg/VB/J . . > # -> During the 1940s , with the development of new and more powerful computing -# VB/P D+ #d . P D N🅪Sg P NSg/VB/J VB/C NPr/I/VB/J/R/Dq J Nᴹ/Vg/J+ +> During the 1940s , with the development of new and more powerful computing +# VB/P D+ #d . P D N🅪Sg P NSg/J VB/C NPr/I/VB/J/R/Dq J Nᴹ/Vg/J+ > machines such as the Atanasoff – Berry computer and ENIAC , the term computer came # NPl/V3 NSg/I NSg/R D ? . NPr🅪Sg/VB+ NSg/VB+ VB/C ? . D NSg/VB/J+ NSg/VB+ NSg/VPt/P > to refer to the machines rather than their human predecessors . As it became @@ -170,16 +170,16 @@ # D NSg/VB P NSg/VB+ N🅪Sg/VB+ VP/J P NSg/VB NSg NPr/J/P NSg/VB/J . NPr/J/P > 1945 , IBM founded the Watson Scientific Computing Laboratory at Columbia # # . NPr+ VP/J D+ NPr+ J+ Nᴹ/Vg/J+ NSg+ NSg/P NPr+ -> University in New York City . The renovated fraternity house on Manhattan's West -# NSg NPr/J/P NSg/VB/J+ NPr+ NSg+ . D VP/J NSg+ NPr/VB J/P NSg$ NPr/VB/J+ +> University in New York City . The renovated fraternity house on Manhattan's West +# NSg NPr/J/P NSg/J+ NPr+ NSg+ . D VP/J NSg+ NPr/VB J/P NSg$ NPr/VB/J+ > Side was IBM's first laboratory devoted to pure science . The lab is the # NSg/VB/J+ VPt NSg$ NSg/VB/J NSg+ VP/J P NSg/VB/J N🅪Sg/VB+ . D+ NPr+ VL3 D > forerunner of IBM's Research Division , which today operates research facilities # NSg P NSg$ Nᴹ/VB+ NSg+ . I/C+ NSg/J+ V3 Nᴹ/VB+ NPl+ > around the world . Ultimately , the close relationship between IBM and Columbia # J/P D NSg/VB+ . R . D NSg/VB/J NSg NSg/P NPr VB/C NPr+ -> University was instrumental in the emergence of a new scientific discipline , -# NSg+ VPt NSg/J NPr/J/P D Nᴹ P D/P NSg/VB/J J NSg/VB+ . +> University was instrumental in the emergence of a new scientific discipline , +# NSg+ VPt NSg/J NPr/J/P D Nᴹ P D/P NSg/J J NSg/VB+ . > with Columbia offering one of the first academic - credit courses in computer # P NPr+ N🅪Sg/Vg/J NSg/I/VB/J P D NSg/VB/J NSg/J . NSg/VB+ NPl/V3 NPr/J/P NSg/VB+ > science in 1946 . Computer science began to be established as a distinct academic @@ -368,8 +368,8 @@ # NSg/J+ N🅪Sg/VB+ . NSg/C/P NSg/VB/J/C/P Nᴹ+ . Nᴹ+ . VB/C NSg . I/J/R/Dq P ISg/D$+ > unique forms of observation and experience do not fit a narrow stereotype of # NSg/J NPl/V3 P N🅪Sg VB/C N🅪Sg/VB+ NSg/VXB NSg/R/C NSg/VBP/J D/P NSg/VB/J NSg/VB P -> the experimental method . Nonetheless , they are experiments . Each new machine -# D NSg/J NSg/VB+ . R . IPl+ VB NPl/V3+ . Dq+ NSg/VB/J+ NSg/VB+ +> the experimental method . Nonetheless , they are experiments . Each new machine +# D NSg/J NSg/VB+ . R . IPl+ VB NPl/V3+ . Dq+ NSg/J+ NSg/VB+ > that is built is an experiment . Actually constructing the machine poses a # NSg/I/C/Ddem+ VL3 NSg/VB/J VL3 D/P+ NSg/VB+ . R Nᴹ/Vg/J D+ NSg/VB+ NPl/V3+ D/P+ > question to nature ; and we listen for the answer by observing the machine in @@ -674,8 +674,8 @@ # NPl . P NSg/I/J/Dq NPl/V3+ . NSg/J NPl/V3+ VB N🅪Sg P NSg/I > designs as complete aircraft . Notable in electrical and electronic circuit # NPl/V3+ NSg/R NSg/VB/J+ NSgPl+ . J NPr/J/P NSg/J VB/C J+ NSg/VB+ -> design are SPICE , as well as software for physical realization of new ( or -# N🅪Sg/VB+ VB N🅪Sg/VB+ . NSg/R NSg/VB/J/R NSg/R Nᴹ C/P NSg/J NSg/NoAm P NSg/VB/J . NPr/C +> design are SPICE , as well as software for physical realization of new ( or +# N🅪Sg/VB+ VB N🅪Sg/VB+ . NSg/R NSg/VB/J/R NSg/R Nᴹ C/P NSg/J NSg/NoAm P NSg/J . NPr/C > modified ) designs . The latter includes essential design software for integrated # NSg/VP/J . NPl/V3+ . D NSg/J NPl/V3 NSg/J+ N🅪Sg/VB+ Nᴹ+ C/P VP/J > circuits . @@ -712,8 +712,8 @@ # NSg P Nᴹ/Vg/J+ NPl/V3+ P Nᴹ . Nᴹ+ Nᴹ/Vg/J+ NPl/V3+ > with the organizing and analyzing of software — it does not just deal with the # P D Nᴹ/Vg/J VB/C Nᴹ/Vg/J P Nᴹ+ . NPr/ISg+ NPl/V3 NSg/R/C VB/J NSg/VB/J P D+ -> creation or manufacture of new software , but its internal arrangement and -# NSg+ NPr/C NSg/VB P NSg/VB/J+ Nᴹ+ . NSg/C/P ISg/D$+ J NSg VB/C +> creation or manufacture of new software , but its internal arrangement and +# NSg+ NPr/C NSg/VB P NSg/J+ Nᴹ+ . NSg/C/P ISg/D$+ J NSg VB/C > maintenance . For example software testing , systems engineering , technical debt # Nᴹ+ . C/P NSg/VB+ Nᴹ+ Nᴹ/Vg/J+ . NPl+ Nᴹ/Vg/J+ . NSg/J N🅪Sg > and software development processes . @@ -1058,7 +1058,7 @@ # D Nᴹ/VB/J P NSg/VB+ NPl/V3+ VL3 JC C/P NSg/I/C/Ddem P NSg/VB/J+ NPl+ . > One proposed explanation for this is the quick development of this relatively # NSg/I/VB/J VP/J N🅪Sg+ C/P I/Ddem+ VL3 D NSg/VB/J N🅪Sg P I/Ddem R -> new field requires rapid review and distribution of results , a task better -# NSg/VB/J NSg/VB+ NPl/V3 NSg/J NSg/VB VB/C NSg P NPl/V3+ . D/P+ NSg/VB+ NSg/VXB/JC +> new field requires rapid review and distribution of results , a task better +# NSg/J NSg/VB+ NPl/V3 NSg/J NSg/VB VB/C NSg P NPl/V3+ . D/P+ NSg/VB+ NSg/VXB/JC > handled by conferences than by journals . # VP/J NSg/J/P NPl/V3+ C/P NSg/J/P NPl/V3+ . diff --git a/harper-core/tests/text/tagged/Difficult sentences.md b/harper-core/tests/text/tagged/Difficult sentences.md index 6226350f..e6b555ea 100644 --- a/harper-core/tests/text/tagged/Difficult sentences.md +++ b/harper-core/tests/text/tagged/Difficult sentences.md @@ -234,8 +234,8 @@ # D+ NSg+ VPt D/P N🅪Sg/VB C/P D+ NPr+ NSg/J+ . > If having to bag the groceries correctly is more than you can handle , then this isn't the job for you . # NSg/C Nᴹ/Vg/J P NSg/VB D+ NPl/V3+ R VL3 NPr/I/VB/J/R/Dq C/P ISgPl+ NPr/VXB NSg/VB . NSg/J/C I/Ddem NSg/VB D NPr/VB+ C/P ISgPl+ . -> This is a new bell for my bicycle . -# I/Ddem+ VL3 D/P NSg/VB/J NPr/VB C/P D$+ NSg/VB+ . +> This is a new bell for my bicycle . +# I/Ddem+ VL3 D/P NSg/J NPr/VB C/P D$+ NSg/VB+ . > The cake is for Tom and Helen's anniversary . # D+ N🅪Sg/VB+ VL3 C/P NPr/VB VB/C NSg$ NSg+ . > This medicine is for your cough . @@ -256,8 +256,8 @@ # NSg VP/J C/P . VB/C NSg C/P . . P VP/J NSg/VB+ . > Make way for the president ! # NSg/VB NSg/J C/P D+ NSg/VB+ . -> Clear the shelves for our new Christmas stock ! -# NSg/VB/J D NPl/V3 C/P D$+ NSg/VB/J+ NPr/VB/J+ N🅪Sg/VB/J+ . +> Clear the shelves for our new Christmas stock ! +# NSg/VB/J D NPl/V3 C/P D$+ NSg/J+ NPr/VB/J+ N🅪Sg/VB/J+ . > Stand by for your cue . # NSg/VB NSg/J/P C/P D$+ NSg/VB+ . > Prepare for battle . @@ -324,12 +324,12 @@ # P > # -> Paul is from New Zealand . -# NPr+ VL3 P NSg/VB/J+ NPr+ . +> Paul is from New Zealand . +# NPr+ VL3 P NSg/J+ NPr+ . > I got a letter from my brother . # ISg/#r+ VP D/P NSg/VB+ P D$+ NSg/VB/J+ . > You can't get all your news from the Internet . -# ISgPl+ VXB NSg/VB NSg/I/J/C/Dq D$+ Nᴹ/V3+ P D NPrᴹ/VB+ . +# ISgPl+ VXB NSg/VB NSg/I/J/C/Dq D$+ Nᴹ/VB+ P D NPrᴹ/VB+ . > He had books piled from floor to ceiling . # NPr/ISg+ VB NPl/V3+ VP/J P NSg/VB+ P NSg/VB . > He departed yesterday from Chicago . diff --git a/harper-core/tests/text/tagged/Part-of-speech tagging.md b/harper-core/tests/text/tagged/Part-of-speech tagging.md index 11adcadb..859bbd80 100644 --- a/harper-core/tests/text/tagged/Part-of-speech tagging.md +++ b/harper-core/tests/text/tagged/Part-of-speech tagging.md @@ -380,8 +380,8 @@ # N🅪Sg . NSg NPl/V3 P NPl/V3+ NSg/VB NSg/I/C/Ddem+ VB R NSg/J P > those human linguists would expect ; and the differences themselves sometimes # I/Ddem NSg/VB/J NPl+ VXB VB . VB/C D NPl/VB+ IPl+ R -> suggest valuable new insights . -# VB NSg/J NSg/VB/J NPl+ . +> suggest valuable new insights . +# VB NSg/J NSg/J NPl+ . > # > These two categories can be further subdivided into rule - based , stochastic , and diff --git a/harper-core/tests/text/tagged/The Constitution of the United States.md b/harper-core/tests/text/tagged/The Constitution of the United States.md index df1777d5..67fcc48e 100644 --- a/harper-core/tests/text/tagged/The Constitution of the United States.md +++ b/harper-core/tests/text/tagged/The Constitution of the United States.md @@ -121,11 +121,11 @@ > not exceed one for every thirty Thousand , but each State shall have at Least # NSg/R/C VB NSg/I/VB/J C/P Dq NSg NSg . NSg/C/P Dq+ N🅪Sg/VB+ VXB NSg/VXB NSg/P NSg/J+ > one Representative ; and until such enumeration shall be made , the State of New -# NSg/I/VB/J+ NSg/J+ . VB/C C/P NSg/I N🅪Sg VXB NSg/VXB VB . D N🅪Sg/VB P NSg/VB/J +# NSg/I/VB/J+ NSg/J+ . VB/C C/P NSg/I N🅪Sg VXB NSg/VXB VB . D N🅪Sg/VB P NSg/J > Hampshire shall be entitled to chuse three , Massachusetts eight , Rhode - Island # NPr+ VXB NSg/VXB VP/J P ? NSg . NPr+ NSg/J . NPr . NSg/VB -> and Providence Plantations one , Connecticut five , New - York six , New Jersey -# VB/C NPr+ NPl NSg/I/VB/J . NPr+ NSg . NSg/VB/J . NPr+ NSg . NSg/VB/J NPr+ +> and Providence Plantations one , Connecticut five , New - York six , New Jersey +# VB/C NPr+ NPl NSg/I/VB/J . NPr+ NSg . NSg/J . NPr+ NSg . NSg/J NPr+ > four , Pennsylvania eight , Delaware one , Maryland six , Virginia ten , North # NSg . NPr+ NSg/J . NPr NSg/I/VB/J . NPr NSg . NPr+ NSg . NPr/VB/J+ > Carolina five , South Carolina five , and Georgia three . @@ -1318,10 +1318,10 @@ # NSg/VB+ . # . > # -> New States may be admitted by the Congress into this Union ; but -# NSg/VB/J+ NPrPl/V3+ NPr/VXB NSg/VXB VP/J NSg/J/P D+ NPr/VB+ P I/Ddem+ NPr/VB/J+ . NSg/C/P -> no new State shall be formed or erected within the Jurisdiction of any other -# NPr/P+ NSg/VB/J+ N🅪Sg/VB+ VXB NSg/VXB VP/J NPr/C VP/J NSg/J/P D N🅪Sg P I/R/Dq+ NSg/VB/J+ +> New States may be admitted by the Congress into this Union ; but +# NSg/J+ NPrPl/V3+ NPr/VXB NSg/VXB VP/J NSg/J/P D+ NPr/VB+ P I/Ddem+ NPr/VB/J+ . NSg/C/P +> no new State shall be formed or erected within the Jurisdiction of any other +# NPr/P+ NSg/J+ N🅪Sg/VB+ VXB NSg/VXB VP/J NPr/C VP/J NSg/J/P D N🅪Sg P I/R/Dq+ NSg/VB/J+ > State ; nor any State be formed by the Junction of two or more States , or Parts # N🅪Sg/VB+ . NSg/C I/R/Dq+ N🅪Sg/VB+ NSg/VXB VP/J NSg/J/P D NSg/VB P NSg NPr/C NPr/I/VB/J/R/Dq NPrPl/V3+ . NPr/C NPl/V3 > of States , without the Consent of the Legislatures of the States concerned as diff --git a/harper-core/tests/text/tagged/The Great Gatsby.md b/harper-core/tests/text/tagged/The Great Gatsby.md index 06b27004..db3f3444 100644 --- a/harper-core/tests/text/tagged/The Great Gatsby.md +++ b/harper-core/tests/text/tagged/The Great Gatsby.md @@ -112,8 +112,8 @@ # ISg/#r+ R NSg/VPt I/Ddem NSg/J . NSg/VB+ . NSg/C/P K VP/J P NSg/VB NSg/VB/J/C/P ISg+ . P NSg/VB/J > reference to the rather hard - boiled painting that hangs in father’s office . I # NSg/VB+ P D NPr/VB/J/R N🅪Sg/J/R . VP/J N🅪Sg/Vg/J+ NSg/I/C/Ddem+ NPl/V3 NPr/J/P NSg$ NSg/VB+ . ISg/#r+ -> graduated from New Haven in 1915 , just a quarter of a century after my father , -# VP/J P NSg/VB/J+ NSg/VB+ NPr/J/P # . VB/J D/P NSg/VB/J P D/P NSg+ P D$+ NPr/VB+ . +> graduated from New Haven in 1915 , just a quarter of a century after my father , +# VP/J P NSg/J+ NSg/VB+ NPr/J/P # . VB/J D/P NSg/VB/J P D/P NSg+ P D$+ NPr/VB+ . > and a little later I participated in that delayed Teutonic migration known as # VB/C D/P NPr/I/J/Dq JC ISg/#r+ VP/J NPr/J/P NSg/I/C/Ddem+ VP/J NSg/J NSg+ VPp/J NSg/R > the Great War . I enjoyed the counter - raid so thoroughly that I came back @@ -186,14 +186,14 @@ # N🅪Sg/VB/J/P NSg/VB/J/R/P P D+ NPr/VB/J+ N🅪Sg/VB/J+ . Nᴹ/Vg/J N🅪Sg/VB+ . ISg/#r+ NSg/VP D/P NSg NPl/V3 J/P Nᴹ/Vg/J VB/C > credit and investment securities , and they stood on my shelf in red and gold # NSg/VB VB/C N🅪Sg+ NPl+ . VB/C IPl+ VB J/P D$+ NSg+ NPr/J/P N🅪Sg/J VB/C Nᴹ/VB/J -> like new money from the mint , promising to unfold the shining secrets that only -# NSg/VB/J/C/P NSg/VB/J+ N🅪Sg/J+ P D NSg/VB/J . Nᴹ/Vg/J P NSg/VB D Nᴹ/Vg/J NPl/V3+ NSg/I/C/Ddem J/R/C +> like new money from the mint , promising to unfold the shining secrets that only +# NSg/VB/J/C/P NSg/J+ N🅪Sg/J+ P D NSg/VB/J . Nᴹ/Vg/J P NSg/VB D Nᴹ/Vg/J NPl/V3+ NSg/I/C/Ddem J/R/C > Midas and Morgan and Mæcenas knew . And I had the high intention of reading many # NPr VB/C NPr+ VB/C ? VPt . VB/C ISg/#r+ VB D NSg/VB/J/R NSg/VB P NPrᴹ/Vg/J NSg/I/J/Dq+ > other books besides . I was rather literary in college — one year I wrote a series # NSg/VB/J+ NPl/V3+ R/P . ISg/#r+ VPt NPr/VB/J/R J NPr/J/P NSg . NSg/I/VB/J+ NSg+ ISg/#r+ VPt D/P NSgPl > of very solemn and obvious editorials for the Yale News — and now I was going to -# P J/R J VB/C J NPl C/P D+ NPr+ Nᴹ/V3+ . VB/C NPr/VB/J/C ISg/#r+ VPt Nᴹ/Vg/J P +# P J/R J VB/C J NPl C/P D+ NPr+ Nᴹ/VB+ . VB/C NPr/VB/J/C ISg/#r+ VPt Nᴹ/Vg/J P > bring back all such things into my life and become again that most limited of # VB NSg/VB/J NSg/I/J/C/Dq NSg/I NPl+ P D$+ N🅪Sg/VB+ VB/C VBPp P NSg/I/C/Ddem NSg/I/J/R/Dq NSg/VP/J P > all specialists , the “ well - rounded man . ” This isn’t just an epigram — life is much @@ -206,8 +206,8 @@ # NPr/ISg+ VPt D/P N🅪Sg/VB P NPr/VB/J+ NSg/I/C/Ddem+ ISg/#r+ VXB NSg/VXB VP/J D/P+ NPr/VB+ NPr/J/P NSg/I/VB/J P D > strangest communities in North America . It was on that slender riotous island # JS NPl NPr/J/P NPr/VB/J+ NPr+ . NPr/ISg+ VPt J/P NSg/I/C/Ddem+ J J NSg/VB+ -> which extends itself due east of New York — and where there are , among other -# I/C+ NPl/V3 ISg+ NSg/J NPr/J P NSg/VB/J NPr+ . VB/C NSg/C R+ VB . P NSg/VB/J +> which extends itself due east of New York — and where there are , among other +# I/C+ NPl/V3 ISg+ NSg/J NPr/J P NSg/J NPr+ . VB/C NSg/C R+ VB . P NSg/VB/J > natural curiosities , two unusual formations of land . Twenty miles from the city # NSg/J NPl . NSg NSg/J NPl P NPr🅪Sg/VB+ . NSg NPrPl+ P D+ NSg+ > a pair of enormous eggs , identical in contour and separated only by a courtesy @@ -238,8 +238,8 @@ # NSg+ D/P+ NSg/VB+ . D NSg/I/VB/J J/P D$+ NPr/VB/J VPt D/P J NSg NSg/J/P I/R/Dq NSg/J . NPr/ISg+ > was a factual imitation of some Hôtel de Ville in Normandy , with a tower on one # VPt D/P NSg/J N🅪Sg P I/J/R/Dq ? NPr+ ? NPr/J/P NPr . P D/P NSg/VB+ J/P NSg/I/VB/J -> side , spanking new under a thin beard of raw ivy , and a marble swimming pool , -# NSg/VB/J+ . Nᴹ/Vg/J NSg/VB/J NSg/J/P D/P NSg/VB/J NPr/VB P NSg/VB/J NPr+ . VB/C D/P NSg/VB/J+ NSg/VB NSg/VB+ . +> side , spanking new under a thin beard of raw ivy , and a marble swimming pool , +# NSg/VB/J+ . Nᴹ/Vg/J NSg/J NSg/J/P D/P NSg/VB/J NPr/VB P NSg/VB/J NPr+ . VB/C D/P NSg/VB/J+ NSg/VB NSg/VB+ . > and more than forty acres of lawn and garden . It was Gatsby’s mansion . Or , # VB/C NPr/I/VB/J/R/Dq C/P NSg/J NPl P NSg/VB VB/C NSg/VB/J+ . NPr/ISg+ VPt NSg$ NSg+ . NPr/C . > rather , as I didn’t know Mr . Gatsby , it was a mansion inhabited by a gentleman @@ -268,8 +268,8 @@ # > Her husband , among various physical accomplishments , had been one of the most # ISg/D$+ NSg/VB+ . P J+ NSg/J+ NPl+ . VB NSg/VPp NSg/I/VB/J P D NSg/I/J/R/Dq -> powerful ends that ever played football at New Haven — a national figure in a way , -# J NPl/V3+ NSg/I/C/Ddem+ J VP/J NSg/VB+ NSg/P NSg/VB/J+ NSg/VB+ . D/P NSg/J NSg/VB+ NPr/J/P D/P+ NSg/J+ . +> powerful ends that ever played football at New Haven — a national figure in a way , +# J NPl/V3+ NSg/I/C/Ddem+ J VP/J NSg/VB+ NSg/P NSg/J+ NSg/VB+ . D/P NSg/J NSg/VB+ NPr/J/P D/P+ NSg/J+ . > one of those men who reach such an acute limited excellence at twenty - one that # NSg/I/VB/J P I/Ddem+ NPl+ NPr/I+ NSg/VB NSg/I D/P+ NSg/VB/J+ NSg/VP/J+ NSg+ NSg/P NSg . NSg/I/VB/J NSg/I/C/Ddem > everything afterward savors of anti - climax . His family were enormously @@ -320,8 +320,8 @@ # J/P D+ NSg/VB/J+ NSg+ . > # -> He had changed since his New Haven years . Now he was a sturdy straw - haired man -# NPr/ISg+ VB VP/J C/P ISg/D$+ NSg/VB/J+ NSg/VB+ NPl+ . NPr/VB/J/C NPr/ISg+ VPt D/P NSg/J N🅪Sg/VB/J . VP/J NPr/VB/J +> He had changed since his New Haven years . Now he was a sturdy straw - haired man +# NPr/ISg+ VB VP/J C/P ISg/D$+ NSg/J+ NSg/VB+ NPl+ . NPr/VB/J/C NPr/ISg+ VPt D/P NSg/J N🅪Sg/VB/J . VP/J NPr/VB/J > of thirty with a rather hard mouth and a supercilious manner . Two shining # P NSg P D/P NPr/VB/J/R N🅪Sg/J/R NSg/VB+ VB/C D/P J NSg+ . NSg Nᴹ/Vg/J > arrogant eyes had established dominance over his face and gave him the @@ -342,8 +342,8 @@ # ISg/D$+ Nᴹ/Vg/J NSg/VB+ . D/P NSg/VB/J NSg/J NSg/J . VP/J P D NSg/VB+ P > fractiousness he conveyed . There was a touch of paternal contempt in it , even # NSg NPr/ISg+ VP/J . R+ VPt D/P N🅪Sg/VB P J Nᴹ NPr/J/P NPr/ISg+ . NSg/VB/J -> toward people he liked — and there were men at New Haven who had hated his guts . -# J/P NPl/VB+ NPr/ISg+ VP/J . VB/C R+ NSg/VPt NPl+ NSg/P NSg/VB/J+ NSg/VB+ NPr/I+ VB VP/J ISg/D$+ NPl/V3+ . +> toward people he liked — and there were men at New Haven who had hated his guts . +# J/P NPl/VB+ NPr/ISg+ VP/J . VB/C R+ NSg/VPt NPl+ NSg/P NSg/J+ NSg/VB+ NPr/I+ VB VP/J ISg/D$+ NPl/V3+ . > # > “ Now , don’t think my opinion on these matters is final , ” he seemed to say , “ just @@ -580,8 +580,8 @@ # NSg/VB . . > # -> “ Don’t look at me , ” Daisy retorted , “ ‘ I’ve been trying to get you to New York -# . VB NSg/VB NSg/P NPr/ISg+ . . NPr+ VP/J . . Unlintable K NSg/VPp Nᴹ/Vg/J P NSg/VB ISgPl+ P NSg/VB/J NPr+ +> “ Don’t look at me , ” Daisy retorted , “ ‘ I’ve been trying to get you to New York +# . VB NSg/VB NSg/P NPr/ISg+ . . NPr+ VP/J . . Unlintable K NSg/VPp Nᴹ/Vg/J P NSg/VB ISgPl+ P NSg/J NPr+ > all afternoon . ” # NSg/I/J/C/Dq N🅪Sg+ . . > @@ -812,8 +812,8 @@ # > “ Well , he wasn’t always a butler ; he used to be the silver polisher for some # . NSg/VB/J/R . NPr/ISg+ VB R D/P NPr/VB . NPr/ISg+ VP/J P NSg/VXB D Nᴹ/VB/J+ NSg/JC C/P I/J/R/Dq -> people in New York that had a silver service for two hundred people . He had to -# NPl/VB+ NPr/J/P NSg/VB/J NPr+ NSg/I/C/Ddem+ VB D/P Nᴹ/VB/J+ NSg/VB+ C/P NSg NSg NPl/VB+ . NPr/ISg+ VB P +> people in New York that had a silver service for two hundred people . He had to +# NPl/VB+ NPr/J/P NSg/J NPr+ NSg/I/C/Ddem+ VB D/P Nᴹ/VB/J+ NSg/VB+ C/P NSg NSg NPl/VB+ . NPr/ISg+ VB P > polish it from morning till night , until finally it began to affect his nose — — — ” # NSg/VB/J NPr/ISg+ P N🅪Sg/Vg/J+ NSg/VB/C/P N🅪Sg/VB+ . C/P R NPr/ISg+ VPt P NSg/VB ISg/D$+ NSg/VB+ . . . . > @@ -900,8 +900,8 @@ # . ISg/#r+ VB . . > # -> “ Why — ” she said hesitantly , ‘ ‘ Tom’s got some woman in New York . ” -# . NSg/VB . . ISg+ VP/J R . Unlintable Unlintable NSg$ VP I/J/R/Dq NSg/VB+ NPr/J/P NSg/VB/J NPr+ . . +> “ Why — ” she said hesitantly , ‘ ‘ Tom’s got some woman in New York . ” +# . NSg/VB . . ISg+ VP/J R . Unlintable Unlintable NSg$ VP I/J/R/Dq NSg/VB+ NPr/J/P NSg/J NPr+ . . > # > “ Got some woman ? ” I repeated blankly . @@ -1170,8 +1170,8 @@ # NPr VB/C NPr/VB+ VP/J NSg/P Dq NSg/VB/J C/P D/P NSg NPr/J/P NSg/VB+ . > # -> “ Is she from New York ? ” I asked quickly . -# . VL3 ISg+ P NSg/VB/J+ NPr+ . . ISg/#r+ VP/J R . +> “ Is she from New York ? ” I asked quickly . +# . VL3 ISg+ P NSg/J+ NPr+ . . ISg/#r+ VP/J R . > # > “ From Louisville . Our white girlhood was passed together there . Our beautiful @@ -1244,8 +1244,8 @@ # NSg+ C/P NPr+ P NSg/VXB VPt P NPr/VB/J NSg/VB/J/R/P P D+ NPr/VB+ . NSg/VB+ NPr/J/P NPl/V3+ . NSg/C/P R > there were no such intentions in her head . As for Tom , the fact that he “ had # R+ NSg/VPt NPr/P NSg/I NPl/V3+ NPr/J/P ISg/D$+ NPr/VB/J+ . NSg/R C/P NPr/VB+ . D+ NSg+ NSg/I/C/Ddem+ NPr/ISg+ . VB -> some woman in New York ” was really less surprising than that he had been -# I/J/R/Dq NSg/VB NPr/J/P NSg/VB/J+ NPr+ . VPt R VB/J/R/C/P Nᴹ/Vg/J C/P NSg/I/C/Ddem NPr/ISg+ VB NSg/VPp +> some woman in New York ” was really less surprising than that he had been +# I/J/R/Dq NSg/VB NPr/J/P NSg/J+ NPr+ . VPt R VB/J/R/C/P Nᴹ/Vg/J C/P NSg/I/C/Ddem NPr/ISg+ VB NSg/VPp > depressed by a book . Something was making him nibble at the edge of stale ideas # VP/J NSg/J/P D/P+ NSg/VB+ . NSg/I/VB/J+ VPt Nᴹ/Vg/J ISg+ NSg/VB NSg/P D NSg/VB P NSg/VB/J+ NPl+ > as if his sturdy physical egotism no longer nourished his peremptory heart . @@ -1254,8 +1254,8 @@ # > Already it was deep summer on roadhouse roofs and in front of wayside garages , # R NPr/ISg+ VPt NSg/J NPr🅪Sg/VB J/P NSg+ NPl/V3+ VB/C NPr/J/P NSg/VB/J+ P NSg/J NPl/V3 . -> where new red gaspumps sat out in pools of light , and when I reached my estate -# NSg/C NSg/VB/J N🅪Sg/J ? NSg/VP/J NSg/VB/J/R/P NPr/J/P NPl/V3 P N🅪Sg/VB/J+ . VB/C NSg/I/C ISg/#r+ VP/J D$+ NSg/VB/J+ +> where new red gaspumps sat out in pools of light , and when I reached my estate +# NSg/C NSg/J N🅪Sg/J ? NSg/VP/J NSg/VB/J/R/P NPr/J/P NPl/V3 P N🅪Sg/VB/J+ . VB/C NSg/I/C ISg/#r+ VP/J D$+ NSg/VB/J+ > at West Egg I ran the car under its shed and sat for a while on an abandoned # NSg/P NPr/VB/J+ N🅪Sg/VB+ ISg/#r+ NSg/VPt D NSg+ NSg/J/P ISg/D$+ NSg/VP+ VB/C NSg/VP/J C/P D/P NSg/VB/C/P+ J/P D/P VP/J > grass roller in the yard . The wind had blown off , leaving a loud , bright night , @@ -1300,8 +1300,8 @@ # NSg/VB+ #r > # -> About half way between West Egg and New York the motor road hastily joins the -# J/P N🅪Sg/VB/J/P+ NSg/J NSg/P NPr/VB/J+ N🅪Sg/VB VB/C NSg/VB/J+ NPr+ D+ NSg/VB/J+ N🅪Sg/J+ R NPl/V3 D+ +> About half way between West Egg and New York the motor road hastily joins the +# J/P N🅪Sg/VB/J/P+ NSg/J NSg/P NPr/VB/J+ N🅪Sg/VB VB/C NSg/J+ NPr+ D+ NSg/VB/J+ N🅪Sg/J+ R NPl/V3 D+ > railroad and runs beside it for a quarter of a mile , so as to shrink away from a # NSg/VB+ VB/C NPl/V3 P NPr/ISg+ C/P D/P NSg/VB/J P D/P NSg+ . NSg/I/J/C NSg/R P NSg/VB VB/J P D/P > certain desolate area of land . This is a valley of ashes — a fantastic farm where @@ -1362,8 +1362,8 @@ # Nᴹ/Vg/J ISg/D$+ NSg/P D/P NSg/VB+ . VP/J J/P . NSg/Vg P I NPr/ISg+ VPt . > Though I was curious to see her , I had no desire to meet her — but I did . I went # VB/C ISg/#r+ VPt J P NSg/VB ISg/D$+ . ISg/#r+ VB NPr/P+ N🅪Sg/VB+ P NSg/VB/J ISg/D$+ . NSg/C/P ISg/#r+ VPt . ISg/#r+ NSg/VPt -> up to New York with Tom on the train one afternoon , and when we stopped by the -# NSg/VB/J/P P NSg/VB/J NPr+ P NPr/VB+ J/P D+ NSg/VB+ NSg/I/VB/J+ N🅪Sg+ . VB/C NSg/I/C IPl+ VB/J NSg/J/P D +> up to New York with Tom on the train one afternoon , and when we stopped by the +# NSg/VB/J/P P NSg/J NPr+ P NPr/VB+ J/P D+ NSg/VB+ NSg/I/VB/J+ N🅪Sg+ . VB/C NSg/I/C IPl+ VB/J NSg/J/P D > ashheaps he jumped to his feet and , taking hold of my elbow , literally forced me # ? NPr/ISg+ VP/J P ISg/D$+ NPl+ VB/C . NSg/Vg/J NSg/VB/J P D$+ NSg/VB+ . R VP/J NPr/ISg+ > from the car . @@ -1491,7 +1491,7 @@ > # > “ I’ll meet you by the news - stand on the lower level . ” -# . K NSg/VB/J ISgPl+ NSg/J/P D Nᴹ/V3+ . NSg/VB J/P D NSg/VB/JC NSg/VB/J+ . . +# . K NSg/VB/J ISgPl+ NSg/J/P D Nᴹ/VB+ . NSg/VB J/P D NSg/VB/JC NSg/VB/J+ . . > # > She nodded and moved away from him just as George Wilson emerged with two chairs @@ -1524,14 +1524,14 @@ # . VB ISg/D$+ NSg/VB+ NSg/VB+ . . > # -> “ Wilson ? He thinks she goes to see her sister in New York . He’s so dumb he -# . NPr+ . NPr/ISg+ NPl/V3 ISg+ NPl/VB P NSg/VB ISg/D$+ NSg/VB NPr/J/P NSg/VB/J+ NPr+ . NSg$ NSg/I/J/C VB/J NPr/ISg+ +> “ Wilson ? He thinks she goes to see her sister in New York . He’s so dumb he +# . NPr+ . NPr/ISg+ NPl/V3 ISg+ NPl/VB P NSg/VB ISg/D$+ NSg/VB NPr/J/P NSg/J+ NPr+ . NSg$ NSg/I/J/C VB/J NPr/ISg+ > doesn’t know he’s alive . ” # VB NSg/VB NSg$ J . . > # -> So Tom Buchanan and his girl and I went up together to New York — or not quite -# NSg/I/J/C NPr/VB+ NPr+ VB/C ISg/D$+ NSg/VB+ VB/C ISg/#r+ NSg/VPt NSg/VB/J/P J P NSg/VB/J+ NPr+ . NPr/C NSg/R/C R +> So Tom Buchanan and his girl and I went up together to New York — or not quite +# NSg/I/J/C NPr/VB+ NPr+ VB/C ISg/D$+ NSg/VB+ VB/C ISg/#r+ NSg/VPt NSg/VB/J/P J P NSg/J+ NPr+ . NPr/C NSg/R/C R > together , for Mrs . Wilson sat discreetly in another car . Tom deferred that much # J . C/P NPl+ . NPr+ NSg/VP/J R NPr/J/P I/D NSg+ . NPr/VB+ NSg/VB/J NSg/I/C/Ddem+ NSg/I/J/R/Dq > to the sensibilities of those East Eggers who might be on the train . @@ -1540,16 +1540,16 @@ # > She had changed her dress to a brown figured muslin , which stretched tight over # ISg+ VB VP/J ISg/D$+ NSg/VB+ P D/P+ NPr🅪Sg/VB/J+ VP/J+ NSg+ . I/C+ VP/J VB/J NSg/J/P -> her rather wide hips as Tom helped her to the platform in New York . At the -# ISg/D$+ NPr/VB/J/R NSg/J+ NPl/V3+ NSg/R NPr/VB+ VP/J ISg/D$+ P D NSg/VB NPr/J/P NSg/VB/J+ NPr+ . NSg/P D+ +> her rather wide hips as Tom helped her to the platform in New York . At the +# ISg/D$+ NPr/VB/J/R NSg/J+ NPl/V3+ NSg/R NPr/VB+ VP/J ISg/D$+ P D NSg/VB NPr/J/P NSg/J+ NPr+ . NSg/P D+ > news - stand she bought a copy of Town Tattle and a moving - picture magazine , and -# Nᴹ/V3+ . NSg/VB ISg+ NSg/VP D/P NSg/VB P NSg+ NSg/VB VB/C D/P Nᴹ/Vg/J+ . NSg/VB+ NSg+ . VB/C +# Nᴹ/VB+ . NSg/VB ISg+ NSg/VP D/P NSg/VB P NSg+ NSg/VB VB/C D/P Nᴹ/Vg/J+ . NSg/VB+ NSg+ . VB/C > in the station drug - store some cold cream and a small flask of perfume . # NPr/J/P D NSg/VB+ NSg/VB+ . NSg/VB+ I/J/R/Dq NSg/J N🅪Sg/VB/J+ VB/C D/P NPr/VB/J NSg/VB P N🅪Sg/VB+ . > Up - stairs , in the solemn echoing drive she let four taxicabs drive away before # NSg/VB/J/P . NPl+ . NPr/J/P D+ J+ Nᴹ/Vg/J+ N🅪Sg/VB ISg+ NSg/VBP NSg NPl/V3 N🅪Sg/VB VB/J C/P -> she selected a new one , lavender - colored with gray upholstery , and in this we -# ISg+ VP/J D/P NSg/VB/J NSg/I/VB/J+ . Nᴹ/VB/J . NSg/VP/J/Am P NPr🅪Sg/VB/J/Am NSg . VB/C NPr/J/P I/Ddem IPl+ +> she selected a new one , lavender - colored with gray upholstery , and in this we +# ISg+ VP/J D/P NSg/J NSg/I/VB/J+ . Nᴹ/VB/J . NSg/VP/J/Am P NPr🅪Sg/VB/J/Am NSg . VB/C NPr/J/P I/Ddem IPl+ > slid out from the mass of the station into the glowing sunshine . But immediately # VP NSg/VB/J/R/P P D NPr🅪Sg/VB/J P D NSg/VB+ P D Nᴹ/Vg/J Nᴹ/J+ . NSg/C/P R > she turned sharply from the window and , leaning forward , tapped on the front @@ -2019,7 +2019,7 @@ > # > Daisy was not a Catholic , and I was a little shocked at the elaborateness of the -# NPr+ VPt NSg/R/C D/P NSg/J . VB/C ISg/#r+ VPt D/P NPr/I/J/Dq VP/J NSg/P D NSg P D+ +# NPr+ VPt NSg/R/C D/P NSg/J . VB/C ISg/#r+ VPt D/P NPr/I/J/Dq J NSg/P D NSg P D+ > lie . # NPr/VB+ . > @@ -2176,8 +2176,8 @@ # > “ It was on the two little seats facing each other that are always the last ones # . NPr/ISg+ VPt J/P D+ NSg+ NPr/I/J/Dq+ NPl/V3+ Nᴹ/Vg/J Dq+ NSg/VB/J+ NSg/I/C/Ddem+ VB R D NSg/VB/J NPl/V3 -> left on the train . I was going up to New York to see my sister and spend the -# NPr/VB/J J/P D+ NSg/VB+ . ISg/#r+ VPt Nᴹ/Vg/J NSg/VB/J/P P NSg/VB/J+ NPr+ P NSg/VB D$+ NSg/VB+ VB/C NSg/VB D+ +> left on the train . I was going up to New York to see my sister and spend the +# NPr/VB/J J/P D+ NSg/VB+ . ISg/#r+ VPt Nᴹ/Vg/J NSg/VB/J/P P NSg/J+ NPr+ P NSg/VB D$+ NSg/VB+ VB/C NSg/VB D+ > night . He had on a dress suit and patent leather shoes , and I couldn’t keep my # N🅪Sg/VB+ . NPr/ISg+ VB J/P D/P NSg/VB+ NSg/VB VB/C NSg/VB/J+ N🅪Sg/VB/J+ NPl/V3+ . VB/C ISg/#r+ VB NSg/VB D$+ > eyes off him , but every time he looked at me I had to pretend to be looking at @@ -2339,7 +2339,7 @@ > # > Every Friday five crates of oranges and lemons arrived from a fruiterer in New -# Dq+ NSg+ NSg NPl/V3 P NPl/V3 VB/C NPl/V3+ VP/J P D/P NSg NPr/J/P NSg/VB/J +# Dq+ NSg+ NSg NPl/V3 P NPl/V3 VB/C NPl/V3+ VP/J P D/P NSg NPr/J/P NSg/J > York — every Monday these same oranges and lemons left his back door in a pyramid # NPr+ . Dq NSg+ I/Ddem I/J NPl/V3 VB/C NPl/V3+ NPr/VB/J ISg/D$+ NSg/VB/J NSg/VB+ NPr/J/P D/P NSg/VB > of pulpless halves . There was a machine in the kitchen which could extract the @@ -2374,12 +2374,12 @@ # NSg/J ? P NPl VB/C NPl/V3 VB/C NPl/V3 VB/C NPl/V3 VB/C NPl VB/C > piccolos , and low and high drums . The last swimmers have come in from the beach # NPl . VB/C NSg/VB/J/R VB/C NSg/VB/J/R NPl/V3+ . D NSg/VB/J NPl NSg/VXB NSg/VBPp/P NPr/J/P P D NPr/VB+ -> now and are dressing up - stairs ; the cars from New York are parked five deep in -# NPr/VB/J/C VB/C VB Nᴹ/Vg/J NSg/VB/J/P . NPl+ . D NPl+ P NSg/VB/J NPr+ VB VP/J NSg NSg/J NPr/J/P +> now and are dressing up - stairs ; the cars from New York are parked five deep in +# NPr/VB/J/C VB/C VB Nᴹ/Vg/J NSg/VB/J/P . NPl+ . D NPl+ P NSg/J NPr+ VB VP/J NSg NSg/J NPr/J/P > the drive , and already the halls and salons and verandas are gaudy with primary # D N🅪Sg/VB+ . VB/C R D NPl VB/C NPl+ VB/C NPl/NoAm/Br VB NSg/J P NSg/VB/J -> colors , and hair bobbed in strange new ways , and shawls beyond the dreams of -# NPl/V3/Am+ . VB/C N🅪Sg/VB+ VP/J NPr/J/P NSg/VB/J NSg/VB/J NPl+ . VB/C NPl/V3 NSg/P D NPl/V3+ P +> colors , and hair bobbed in strange new ways , and shawls beyond the dreams of +# NPl/V3/Am+ . VB/C N🅪Sg/VB+ VP/J NPr/J/P NSg/VB/J NSg/J NPl+ . VB/C NPl/V3 NSg/P D NPl/V3+ P > Castile . The bar is in full swing , and floating rounds of cocktails permeate the # ? . D+ NSg/VB/P+ VL3 NPr/J/P NSg/VB/J+ NSg/VB+ . VB/C Nᴹ/Vg/J NPl/V3 P NPl/V3+ NSg/VB D+ > garden outside , until the air is alive with chatter and laughter , and casual @@ -2397,7 +2397,7 @@ > key higher . Laughter is easier minute by minute , spilled with prodigality , # NPr/VB/J NSg/JC . Nᴹ+ VL3 NSg/JC NSg/VB/J NSg/J/P NSg/VB/J+ . VP/J P Nᴹ . > tipped out at a cheerful word . The groups change more swiftly , swell with new -# VP NSg/VB/J/R/P NSg/P D/P J NSg/VB+ . D+ NPl/V3+ N🅪Sg/VB+ NPr/I/VB/J/R/Dq R . NSg/VB/J+ P NSg/VB/J +# VP NSg/VB/J/R/P NSg/P D/P J NSg/VB+ . D+ NPl/V3+ N🅪Sg/VB+ NPr/I/VB/J/R/Dq R . NSg/VB/J+ P NSg/J > arrivals , dissolve and form in the same breath ; already there are wanderers , # NPl . NSg/VB VB/C N🅪Sg/VB+ NPr/J/P D I/J N🅪Sg/VB/J+ . R R+ VB NPl . > confident girls who weave here and there among the stouter and more stable , @@ -2417,7 +2417,7 @@ > alone on the canvas platform . A momentary hush ; the orchestra leader varies his # J J/P D NSg/VB+ NSg/VB+ . D/P+ J+ NSg/VB+ . D+ NSg+ NSg/JC+ NPl/V3 ISg/D$+ > rhythm obligingly for her , and there is a burst of chatter as the erroneous news -# N🅪Sg/VB+ R C/P ISg/D$+ . VB/C R+ VL3 D/P NSg/VB P NSg/VB+ NSg/R D J Nᴹ/V3+ +# N🅪Sg/VB+ R C/P ISg/D$+ . VB/C R+ VL3 D/P NSg/VB P NSg/VB+ NSg/R D J Nᴹ/VB+ > goes around that she is Gilda Gray’s understudy from the Follies . The party has # NPl/VB J/P NSg/I/C/Ddem ISg+ VL3 NPr NSg$/Am NSg/VB P D NPl/V3 . D+ NSg/VB/J+ V3 > begun . @@ -2570,8 +2570,8 @@ # . ISg/#r+ NSg/VB/J/C/P P NSg/VBPp/P . . NPr+ VP/J . . ISg/#r+ R N🅪Sg/VB NSg/I+ ISg/#r+ NSg/VXB . NSg/I/J/C ISg/#r+ R NSg/VXB D/P+ NPr/VB/J+ > time . When I was here last I tore my gown on a chair , and he asked me my name # N🅪Sg/VB/J+ . NSg/I/C ISg/#r+ VPt NSg/J/R NSg/VB/J ISg/#r+ NSg/VB/J D$+ NSg/VB+ J/P D/P+ NSg/VB+ . VB/C NPr/ISg+ VP/J NPr/ISg+ D$+ NSg/VB -> and address — inside of a week I got a package from Croirier’s with a new evening -# VB/C NSg/VB+ . NSg/J/P P D/P+ NSg/J+ ISg/#r+ VP D/P+ NSg/VB+ P ? P D/P NSg/VB/J N🅪Sg/Vg/J+ +> and address — inside of a week I got a package from Croirier’s with a new evening +# VB/C NSg/VB+ . NSg/J/P P D/P+ NSg/J+ ISg/#r+ VP D/P+ NSg/VB+ P ? P D/P NSg/J N🅪Sg/Vg/J+ > gown in it . ” # NSg/VB+ NPr/J/P NPr/ISg+ . . > @@ -2888,8 +2888,8 @@ # . Nᴹ/Vg/J D/P+ NPr/VB/J+ N🅪Sg/VB/J+ NPr/VB/J/C . . ISg+ VP/J . > # -> “ Much better . ” I turned again to my new acquaintance . “ This is an unusual party -# . NSg/I/J/R/Dq NSg/VXB/JC . . ISg/#r+ VP/J P P D$+ NSg/VB/J+ NSg+ . . I/Ddem+ VL3 D/P NSg/J NSg/VB/J +> “ Much better . ” I turned again to my new acquaintance . “ This is an unusual party +# . NSg/I/J/R/Dq NSg/VXB/JC . . ISg/#r+ VP/J P P D$+ NSg/J+ NSg+ . . I/Ddem+ VL3 D/P NSg/J NSg/VB/J > for me . I haven’t even seen the host . I live over there — ” I waved my hand at the # C/P NPr/ISg+ . ISg/#r+ VB NSg/VB/J NSg/VPp D NSg/VB+ . ISg/#r+ VB/J NSg/J/P R . . ISg/#r+ VP/J D$+ NSg/VB+ NSg/P D > invisible hedge in the distance , “ and this man Gatsby sent over his chauffeur @@ -3002,8 +3002,8 @@ # VB/C VB D NSg/VB P Nᴹ/Vg/J D$+ NSg+ . ISg/#r+ VXB NSg/VXB VP/J C/P > question the information that Gatsby sprang from the swamps of Louisiana or from # NSg/VB+ D+ Nᴹ+ NSg/I/C/Ddem+ NPr VB P D NPl/V3 P NPr+ NPr/C P -> the lower East Side of New York . That was comprehensible . But young men -# D NSg/VB/JC NPr/J+ NSg/VB/J P NSg/VB/J NPr+ . NSg/I/C/Ddem+ VPt J . NSg/C/P NPr/VB/J+ NPl+ +> the lower East Side of New York . That was comprehensible . But young men +# D NSg/VB/JC NPr/J+ NSg/VB/J P NSg/J NPr+ . NSg/I/C/Ddem+ VPt J . NSg/C/P NPr/VB/J+ NPl+ > didn’t — at least in my provincial inexperience I believed they didn’t — drift # VB . NSg/P NSg/J NPr/J/P D$+ NSg/J Nᴹ ISg/#r+ VP/J IPl+ VB . NSg/VB > coolly out of nowhere and buy a palace on Long Island Sound . @@ -3282,8 +3282,8 @@ # NPl P D+ NSg/VB+ D/P NSg NPl VP/J D/P J VB/C J > scene . In the ditch beside the road , right side up , but violently shorn of one # NSg/VB+ . NPr/J/P D+ NSg/VB+ P D+ N🅪Sg/J+ . NPr/VB/J+ NSg/VB/J+ NSg/VB/J/P . NSg/C/P R ? P NSg/I/VB/J+ -> wheel , rested a new coupé which had left Gatsby’s drive not two minutes before . -# NSg/VB+ . VP/J D/P NSg/VB/J ? I/C+ VB NPr/VB/J NSg$ N🅪Sg/VB NSg/R/C NSg NPl/V3+ C/P . +> wheel , rested a new coupé which had left Gatsby’s drive not two minutes before . +# NSg/VB+ . VP/J D/P NSg/J ? I/C+ VB NPr/VB/J NSg$ N🅪Sg/VB NSg/R/C NSg NPl/V3+ C/P . > The sharp jut of a wall accounted for the detachment of the wheel , which was now # D NPr/VB/J NSg/VB P D/P NPr/VB+ VP/J C/P D N🅪Sg P D NSg/VB+ . I/C+ VPt NPr/VB/J/C > getting considerable attention from half a dozen curious chauffeurs . However , as @@ -3362,8 +3362,8 @@ # I/D NPr/VB/J+ NPr/J/P D NSg+ . . > # -> The shock that followed this declaration found voice in a sustained “ Ah - h - h ! ” as -# D+ N🅪Sg/VB/J+ NSg/I/C/Ddem+ VP/J I/Ddem+ NSg+ NSg/VB NSg/VB+ NPr/J/P D/P VP/J . NSg/I/VB . NSg/VB/J . NSg/VB/J+ . . NSg/R +> The shock that followed this declaration found voice in a sustained “ Ah - h - h ! ” as +# D+ N🅪Sg/J+ NSg/I/C/Ddem+ VP/J I/Ddem+ NSg+ NSg/VB NSg/VB+ NPr/J/P D/P VP/J . NSg/I/VB . NSg/VB/J . NSg/VB/J+ . . NSg/R > the door of the coupé swung slowly open . The crowd — it was now a crowd — stepped # D NSg/VB P D ? VPp R NSg/VB/J . D+ NSg/VB+ . NPr/ISg+ VPt NPr/VB/J/C D/P NSg/VB+ . J > back involuntarily , and when the door had opened wide there was a ghostly pause . @@ -3470,8 +3470,8 @@ # > Most of the time I worked . In the early morning the sun threw my shadow westward # NSg/I/J/R/Dq P D+ N🅪Sg/VB/J+ ISg/#r+ VP/J . NPr/J/P D+ NSg/J/R+ N🅪Sg/Vg/J+ D+ NPr/VB+ VB D$+ NSg/VB/J+ NSg/J -> as I hurried down the white chasms of lower New York to the Probity Trust . I -# NSg/R ISg/#r+ VP/J N🅪Sg/VB/J/P D NPr🅪Sg/VB/J NPl P NSg/VB/JC NSg/VB/J NPr+ P D Nᴹ N🅪Sg/VB/J . ISg/#r+ +> as I hurried down the white chasms of lower New York to the Probity Trust . I +# NSg/R ISg/#r+ VP/J N🅪Sg/VB/J/P D NPr🅪Sg/VB/J NPl P NSg/VB/JC NSg/J NPr+ P D Nᴹ N🅪Sg/VB/J . ISg/#r+ > knew the other clerks and young bond - salesmen by their first names , and lunched # VPt D NSg/VB/J NPl/V3 VB/C NPr/VB/J+ NPr/VB/J+ . NPl NSg/J/P D$+ NSg/VB/J NPl/V3+ . VB/C VP/J > with them in dark , crowded restaurants on little pig sausages and mashed @@ -3500,8 +3500,8 @@ # NPr+ NPr/VB+ NSg+ . VB/C NSg/J/P # NPr/J/#r+ NSg/VB/J+ P D+ NPr+ NSg/VB+ . > # -> I began to like New York , the racy , adventurous feel of it at night , and the -# ISg/#r+ VPt P NSg/VB/J/C/P NSg/VB/J+ NPr+ . D J . J NSg/I/VB P NPr/ISg+ NSg/P N🅪Sg/VB+ . VB/C D +> I began to like New York , the racy , adventurous feel of it at night , and the +# ISg/#r+ VPt P NSg/VB/J/C/P NSg/J+ NPr+ . D J . J NSg/I/VB P NPr/ISg+ NSg/P N🅪Sg/VB+ . VB/C D > satisfaction that the constant flicker of men and women and machines gives to # Nᴹ+ NSg/I/C/Ddem D NSg/J NSg/VB P NPl VB/C NPl+ VB/C NPl/V3+ NPl/V3 P > the restless eye . I liked to walk up Fifth Avenue and pick out romantic women @@ -3754,8 +3754,8 @@ # . D NSg+ . . ISg/#r+ N🅪Sg/VB NSg/C NPr/ISg+ VB I/R/Dq NSg/VB/J NSg/VB/J+ . P NSg/J+ NPl/VB+ R+ NSg/VPt > Gus Waize and Horace O’Donavan and Lester Myer and George Duckweed and Francis # NPr+ ? VB/C NPr+ ? VB/C NPr ? VB/C NPr+ Nᴹ VB/C NPr+ -> Bull . Also from New York were the Chromes and the Backhyssons and the Dennickers -# NSg/VB/J+ . R/C P NSg/VB/J+ NPr+ NSg/VPt D NPl/V3 VB/C D ? VB/C D ? +> Bull . Also from New York were the Chromes and the Backhyssons and the Dennickers +# NSg/VB/J+ . R/C P NSg/J+ NPr+ NSg/VPt D NPl/V3 VB/C D ? VB/C D ? > and Russel Betty and the Corrigans and the Kellehers and the Dewars and the # VB/C NPr NPr+ VB/C D ? VB/C D ? VB/C D ? VB/C D > Scullys and S. W. Belcher and the Smirkes and the young Quinns , divorced now , @@ -4214,8 +4214,8 @@ # > Gatsby took an arm of each of us and moved forward into the restaurant , # NPr VPt D/P NSg/VB/J P Dq P NPr/IPl+ VB/C VP/J NSg/VB/J P D NSg+ . -> whereupon Mr . Wolfshiem swallowed a new sentence he was starting and lapsed into -# C NSg+ . ? VP/J D/P NSg/VB/J NSg/VB+ NPr/ISg+ VPt Nᴹ/Vg/J VB/C VP/J P +> whereupon Mr . Wolfshiem swallowed a new sentence he was starting and lapsed into +# C NSg+ . ? VP/J D/P NSg/J NSg/VB+ NPr/ISg+ VPt Nᴹ/Vg/J VB/C VP/J P > a somnambulatory abstraction . # D/P ? N🅪Sg+ . > @@ -4466,8 +4466,8 @@ # > “ He becomes very sentimental sometimes , ” explained Gatsby . “ This is one of his # . NPr/ISg+ V3 J/R J R . . VP/J NPr . . I/Ddem+ VL3 NSg/I/VB/J P ISg/D$+ -> sentimental days . He’s quite a character around New York — a denizen of Broadway . ” -# J+ NPl+ . NSg$ R D/P N🅪Sg/VB+ J/P NSg/VB/J NPr+ . D/P NSg/VB P NPr/J+ . . +> sentimental days . He’s quite a character around New York — a denizen of Broadway . ” +# J+ NPl+ . NSg$ R D/P N🅪Sg/VB+ J/P NSg/J NPr+ . D/P NSg/VB P NPr/J+ . . > # > “ Who is he , anyhow , an actor ? ” @@ -4579,7 +4579,7 @@ > on the lawns . I was happier on the lawns because I had on shoes from England # J/P D NPl/V3 . ISg/#r+ VPt NSg/JC J/P D NPl/V3 C/P ISg/#r+ VB J/P NPl/V3+ P NPr > with rubber nobs on the soles that bit into the soft ground . I had on a new -# P NSg/VB/J+ NPl/V3 J/P D NPl/V3+ NSg/I/C/Ddem+ NSg/VPt P D NSg/J N🅪Sg/VB/J+ . ISg/#r+ VB J/P D/P NSg/VB/J +# P NSg/VB/J+ NPl/V3 J/P D NPl/V3+ NSg/I/C/Ddem+ NSg/VPt P D NSg/J N🅪Sg/VB/J+ . ISg/#r+ VB J/P D/P NSg/J > plaid skirt also that blew a little in the wind , and whenever this happened the # NSg/VB/J NSg/VB R/C NSg/I/C/Ddem+ NSg/VPt/J D/P NPr/I/J/Dq NPr/J/P D N🅪Sg/VB+ . VB/C C I/Ddem VP/J D > red , white , and blue banners in front of all the houses stretched out stiff and @@ -4642,8 +4642,8 @@ # R JC+ NSg/VB+ . NSg/I/C ISg+ NSg/VPt P NSg/I+ NSg/P NSg/I/J/C/Dq . NSg/VB/J+ NPl/V3/Am+ NSg/VPt > circulating about her — how her mother had found her packing her bag one winter # Nᴹ/Vg/J J/P ISg/D$+ . NSg/C ISg/D$+ NSg/VB/J+ VB NSg/VB ISg/D$+ Nᴹ/Vg/J ISg/D$+ NSg/VB+ NSg/I/VB/J+ N🅪Sg/VB+ -> night to go to New York and say good - by to a soldier who was going overseas . She -# N🅪Sg/VB+ P NSg/VB/J P NSg/VB/J+ NPr+ VB/C NSg/VB NPr/VB/J . NSg/J/P P D/P+ NSg/VB/J+ NPr/I+ VPt Nᴹ/Vg/J J/R . ISg+ +> night to go to New York and say good - by to a soldier who was going overseas . She +# N🅪Sg/VB+ P NSg/VB/J P NSg/J+ NPr+ VB/C NSg/VB NPr/VB/J . NSg/J/P P D/P+ NSg/VB/J+ NPr/I+ VPt Nᴹ/Vg/J J/R . ISg+ > was effectually prevented , but she wasn’t on speaking terms with her family for # VPt R VP/J . NSg/C/P ISg+ VB J/P Nᴹ/Vg/J NPl/V3+ P ISg/D$+ N🅪Sg/J+ C/P > several weeks . After that she didn’t play around with the soldiers any more , but @@ -4656,8 +4656,8 @@ # > By the next autumn she was gay again , gay as ever . She had a début after the # NSg/J/P D+ NSg/J/P+ NPr🅪Sg/VB+ ISg+ VPt NPr/VB/J P . NPr/VB/J NSg/R J . ISg+ VB D/P ? P D -> armistice , and in February she was presumably engaged to a man from New Orleans . -# NPr🅪Sg . VB/C NPr/J/P NPr+ ISg+ VPt R VP/J P D/P NPr/VB/J+ P NSg/VB/J NPr+ . +> armistice , and in February she was presumably engaged to a man from New Orleans . +# NPr🅪Sg . VB/C NPr/J/P NPr+ ISg+ VPt R VP/J P D/P NPr/VB/J+ P NSg/J NPr+ . > In June she married Tom Buchanan of Chicago , with more pomp and circumstance # NPr/J/P NPr+ ISg+ NSg/VP/J NPr/VB+ NPr P NPr+ . P NPr/I/VB/J/R/Dq NSg/VB VB/C NSg/VB > than Louisville ever knew before . He came down with a hundred people in four @@ -4878,8 +4878,8 @@ # VPt ISg/D$+ . VB/C ISg/#r+ VPt D NSg/VB/J NSg/I/VB/J NPr/ISg+ NSg/VB . NPr/ISg+ VPt NSg/I/C/Ddem N🅪Sg/VB NPr/ISg+ NSg/VB C/P NPr/ISg+ NSg/P > his dance , and you should have heard the elaborate way he worked up to it . Of # ISg/D$+ N🅪Sg/VB+ . VB/C ISgPl+ VXB NSg/VXB VP/J D+ VB/J+ NSg/J+ NPr/ISg+ VP/J NSg/VB/J/P P NPr/ISg+ . P -> course , I immediately suggested a luncheon in New York — and I thought he’d go -# NSg/VB+ . ISg/#r+ R VP/J D/P NSg/VB NPr/J/P NSg/VB/J+ NPr+ . VB/C ISg/#r+ N🅪Sg/VP K NSg/VB/J +> course , I immediately suggested a luncheon in New York — and I thought he’d go +# NSg/VB+ . ISg/#r+ R VP/J D/P NSg/VB NPr/J/P NSg/J+ NPr+ . VB/C ISg/#r+ N🅪Sg/VP K NSg/VB/J > mad : # NSg/VB/J . > @@ -5510,8 +5510,8 @@ # R P D NSg/VB/J+ . NPr/ISg+ VPt N🅪Sg/VB/J ISg/#r+ NSg/VPt NSg/VB/J . NSg/VB/C/P D+ N🅪Sg/VB+ VP/J > it had seemed like the murmur of their voices , rising and swelling a little now # NPr/ISg+ VB VP/J NSg/VB/J/C/P D NSg/VB P D$+ NPl/V3+ . Nᴹ/Vg/J/P VB/C Nᴹ/Vg/J D/P NPr/I/J/Dq NPr/VB/J/C -> and then with gusts of emotion . But in the new silence I felt that silence had -# VB/C NSg/J/C P NPl/V3 P N🅪Sg+ . NSg/C/P NPr/J/P D+ NSg/VB/J+ NSg/VB+ ISg/#r+ N🅪Sg/VB/J NSg/I/C/Ddem NSg/VB+ VB +> and then with gusts of emotion . But in the new silence I felt that silence had +# VB/C NSg/J/C P NPl/V3 P N🅪Sg+ . NSg/C/P NPr/J/P D+ NSg/J+ NSg/VB+ ISg/#r+ N🅪Sg/VB/J NSg/I/C/Ddem NSg/VB+ VB > fallen within the house too . # VPp/J NSg/J/P D+ NPr/VB+ R . > @@ -5530,8 +5530,8 @@ # NSg/P NPr/ISg+ P ISg/D$+ NSg+ C/P D/P NSg/VB+ . NSg/C/P R+ VPt D/P+ N🅪Sg/VB+ NPr/J/P NPr > that was simply confounding . He literally glowed ; without a word or a gesture of # NSg/I/C/Ddem+ VPt R Nᴹ/Vg/J . NPr/ISg+ R VP/J . C/P D/P NSg/VB+ NPr/C D/P NSg/VB P -> exultation a new well - being radiated from him and filled the little room . -# NSg D/P NSg/VB/J NSg/VB/J/R . N🅪Sg/Vg/J/C VP/J P ISg+ VB/C VP/J D NPr/I/J/Dq N🅪Sg/VB/J+ . +> exultation a new well - being radiated from him and filled the little room . +# NSg D/P NSg/J NSg/VB/J/R . N🅪Sg/Vg/J/C VP/J P ISg+ VB/C VP/J D NPr/I/J/Dq N🅪Sg/VB/J+ . > # > “ Oh , hello , old sport , ” he said , as if he hadn’t seen me for years . I thought @@ -5549,7 +5549,7 @@ > twinkle - bells of sunshine in the room , he smiled like a weather man , like an # NSg/VB . NPl/V3 P Nᴹ/J+ NPr/J/P D N🅪Sg/VB/J+ . NPr/ISg+ VP/J NSg/VB/J/C/P D/P Nᴹ/VB/J+ NPr/VB/J+ . NSg/VB/J/C/P D/P > ecstatic patron of recurrent light , and repeated the news to Daisy . “ What do you -# NSg/J NSg/VB P NSg/J N🅪Sg/VB/J+ . VB/C VP/J D Nᴹ/V3+ P NPr . . NSg/I+ NSg/VXB ISgPl+ +# NSg/J NSg/VB P NSg/J N🅪Sg/VB/J+ . VB/C VP/J D Nᴹ/VB+ P NPr . . NSg/I+ NSg/VXB ISgPl+ > think of that ? It’s stopped raining . ” # NSg/VB P NSg/I/C/Ddem+ . K VB/J Nᴹ/Vg/J . . > @@ -5678,8 +5678,8 @@ # > We went up - stairs , through period bedrooms swathed in rose and lavender silk and # IPl+ NSg/VPt NSg/VB/J/P . NPl+ . NSg/J/P NSg/VB/J+ NPl+ J/Am NPr/J/P NPr/VPt/J VB/C Nᴹ/VB/J N🅪Sg/VB+ VB/C -> vivid with new flowers , through dressing - rooms and poolrooms , and bathrooms with -# NSg/J P NSg/VB/J NPrPl/V3+ . NSg/J/P Nᴹ/Vg/J+ . NPl/V3+ VB/C NPl . VB/C NPl/V3+ P +> vivid with new flowers , through dressing - rooms and poolrooms , and bathrooms with +# NSg/J P NSg/J NPrPl/V3+ . NSg/J/P Nᴹ/Vg/J+ . NPl/V3+ VB/C NPl . VB/C NPl/V3+ P > sunken baths — intruding into one chamber where a dishevelled man in pajamas was # VPp/J NSg/VB+ . Nᴹ/Vg/J P NSg/I/VB/J NSg/VB+ NSg/C D/P VP/J/Comm NPr/VB/J+ NPr/J/P NPl VPt > doing liver exercises on the floor . It was Mr . Klipspringer , the ‘ ‘ boarder . ” I @@ -5950,8 +5950,8 @@ # Nᴹ/VB/J/P D+ N🅪Sg/VB+ VPt NSg/J VB/C R+ VPt D/P NSg/VB/J NSg/VB P NSg/VB+ P D+ N🅪Sg/VB/J+ . > All the lights were going on in West Egg now ; the electric trains , men - carrying , # NSg/I/J/C/Dq+ D+ NPl/V3+ NSg/VPt Nᴹ/Vg/J J/P NPr/J/P NPr/VB/J+ N🅪Sg/VB+ NPr/VB/J/C . D NSg/J NPl/V3 . NPl+ . Nᴹ/Vg/J . -> were plunging home through the rain from New York . It was the hour of a profound -# NSg/VPt Nᴹ/Vg/J NSg/VB/J+ NSg/J/P D N🅪Sg/VB+ P NSg/VB/J NPr+ . NPr/ISg+ VPt D NSg P D/P+ NSg/VB/J+ +> were plunging home through the rain from New York . It was the hour of a profound +# NSg/VPt Nᴹ/Vg/J NSg/VB/J+ NSg/J/P D N🅪Sg/VB+ P NSg/J NPr+ . NPr/ISg+ VPt D NSg P D/P+ NSg/VB/J+ > human change , and excitement was generating on the air . # NSg/VB/J+ N🅪Sg/VB+ . VB/C NSg+ VPt Nᴹ/Vg/J J/P D+ N🅪Sg/VB+ . > @@ -6006,8 +6006,8 @@ # NSg/VB+ NPr/#r > # -> About this time an ambitious young reporter from New York arrived one morning at -# J/P I/Ddem+ N🅪Sg/VB/J+ D/P J NPr/VB/J NSg/VB P NSg/VB/J+ NPr+ VP/J NSg/I/VB/J N🅪Sg/Vg/J+ NSg/P +> About this time an ambitious young reporter from New York arrived one morning at +# J/P I/Ddem+ N🅪Sg/VB/J+ D/P J NPr/VB/J NSg/VB P NSg/J+ NPr+ VP/J NSg/I/VB/J N🅪Sg/Vg/J+ NSg/P > Gatsby’s door and asked him if he had anything to say . # NSg$ NSg/VB+ VB/C VP/J ISg+ NSg/C NPr/ISg+ VB NSg/I/VB+ P NSg/VB . > @@ -6037,7 +6037,7 @@ > become authorities upon his past , had increased all summer until he fell just # VBPp NPl+ P ISg/D$+ NSg/VB/J/P . VB VP/J NSg/I/J/C/Dq NPr🅪Sg/VB+ C/P NPr/ISg+ NSg/VPt/J VB/J > short of being news . Contemporary legends such as the “ underground pipe - line to -# NPr/VB/J/P P N🅪Sg/Vg/J/C Nᴹ/V3+ . NSg/J NPl/V3 NSg/I NSg/R D . NSg/VB/J NSg/VB+ . NSg/VB P +# NPr/VB/J/P P N🅪Sg/Vg/J/C Nᴹ/VB+ . NSg/J NPl/V3 NSg/I NSg/R D . NSg/VB/J NSg/VB+ . NSg/VB P > Canada ” attached themselves to him , and there was one persistent story that he # NPr+ . VP/J IPl+ P ISg+ . VB/C R+ VPt NSg/I/VB/J J NSg/VB+ NSg/I/C/Ddem+ NPr/ISg+ > didn’t live in a house at all , but in a boat that looked like a house and was @@ -6160,8 +6160,8 @@ # VP/J NSg/I/J/C/Dq D N🅪Sg/VB/J VB/C NSg/VB+ NPr/J/P D NSg/VB+ . ISg/#r+ VB NPr/ISg+ VP/J NSg/P > Cody — he had probably discovered that people liked him when he smiled . At any # NPr . NPr/ISg+ VB R VP/J NSg/I/C/Ddem NPl/VB+ VP/J ISg+ NSg/I/C NPr/ISg+ VP/J . NSg/P I/R/Dq+ -> rate Cody asked him a few questions ( one of them elicited the brand new name ) -# NSg/VB+ NPr VP/J ISg+ D/P NSg/I/Dq NPl/V3+ . NSg/I/VB/J P NSg/IPl+ VP/J D NSg/VB+ NSg/VB/J NSg/VB+ . +> rate Cody asked him a few questions ( one of them elicited the brand new name ) +# NSg/VB+ NPr VP/J ISg+ D/P NSg/I/Dq NPl/V3+ . NSg/I/VB/J P NSg/IPl+ VP/J D NSg/VB+ NSg/J NSg/VB+ . > and found that he was quick and extravagantly ambitious . A few days later he # VB/C NSg/VB NSg/I/C/Ddem NPr/ISg+ VPt NSg/VB/J VB/C R J . D/P+ NSg/I/Dq+ NPl+ JC NPr/ISg+ > took him to Duluth and bought him a blue coat , six pair of white duck trousers , @@ -6230,8 +6230,8 @@ # > It was a halt , too , in my association with his affairs . For several weeks I # NPr/ISg+ VPt D/P NSg/VB/J . R . NPr/J/P D$+ N🅪Sg+ P ISg/D$+ NPl+ . C/P J/Dq+ NPrPl+ ISg/#r+ -> didn’t see him or hear his voice on the phone — mostly I was in New York , trotting -# VB NSg/VB ISg+ NPr/C VB ISg/D$+ NSg/VB+ J/P D NSg/VB+ . R ISg/#r+ VPt NPr/J/P NSg/VB/J NPr+ . NSg/Vg/J +> didn’t see him or hear his voice on the phone — mostly I was in New York , trotting +# VB NSg/VB ISg+ NPr/C VB ISg/D$+ NSg/VB+ J/P D NSg/VB+ . R ISg/#r+ VPt NPr/J/P NSg/J NPr+ . NSg/Vg/J > around with Jordan and trying to ingratiate myself with her senile aunt — but # J/P P NPr+ VB/C Nᴹ/Vg/J P VB ISg+ P ISg/D$+ NSg/J NSg+ . NSg/C/P > finally I went over to his house one Sunday afternoon . I hadn’t been there two @@ -6366,8 +6366,8 @@ # . VB VB NSg/VB+ . . NPr VP/J NSg/IPl+ . NPr/ISg+ VB N🅪Sg/VB P ISg+ NPr/VB/J/C . VB/C NPr/ISg+ > wanted to see more of Tom . “ Why don’t you — why don’t you stay for supper ? I # VP/J P NSg/VB NPr/I/VB/J/R/Dq P NPr/VB+ . . NSg/VB VB ISgPl+ . NSg/VB VB ISgPl+ NSg/VB/J C/P NSg/VB+ . ISg/#r+ -> wouldn’t be surprised if some other people dropped in from New York . ” -# VXB NSg/VXB VP/J NSg/C I/J/R/Dq NSg/VB/J NPl/VB+ VP/J NPr/J/P P NSg/VB/J NPr+ . . +> wouldn’t be surprised if some other people dropped in from New York . ” +# VXB NSg/VXB VP/J NSg/C I/J/R/Dq NSg/VB/J NPl/VB+ VP/J NPr/J/P P NSg/J NPr+ . . > # > “ You come to supper with me , ” said the lady enthusiastically . “ Both of you . ” @@ -6480,8 +6480,8 @@ # P ISg/D$+ NSg/VB/J NPl VB/C ISg/D$+ NSg/VB/J+ NSg/J+ NPl/V3+ . NSg/VB/J P NSg/I/J+ C/P NPr/ISg+ > had no consciousness of being so , and now I was looking at it again , through # VB NPr/P Nᴹ P N🅪Sg/Vg/J/C NSg/I/J/C . VB/C NPr/VB/J/C ISg/#r+ VPt Nᴹ/Vg/J NSg/P NPr/ISg+ P . NSg/J/P -> Daisy’s eyes . It is invariably saddening to look through new eyes at things upon -# NSg$ NPl/V3+ . NPr/ISg+ VL3 R Nᴹ/Vg/J P NSg/VB NSg/J/P NSg/VB/J NPl/V3+ NSg/P NPl+ P +> Daisy’s eyes . It is invariably saddening to look through new eyes at things upon +# NSg$ NPl/V3+ . NPr/ISg+ VL3 R Nᴹ/Vg/J P NSg/VB NSg/J/P NSg/J NPl/V3+ NSg/P NPl+ P > which you have expended your own powers of adjustment . # I/C+ ISgPl+ NSg/VXB VP/J D$+ NSg/VB/J NPrPl/V3 P NSg+ . > @@ -6662,8 +6662,8 @@ # > “ Anything I hate is to get my head stuck in a pool , ” mumbled Miss Baedeker . # . NSg/I/VB+ ISg/#r+ N🅪Sg/VB VL3 P NSg/VB D$+ NPr/VB/J+ NSg/VB/J NPr/J/P D/P+ NSg/VB+ . . VP/J NSg/VB NPr . -> “ They almost drowned me once over in New Jersey . ” -# . IPl+ R VP/J NPr/ISg+ NSg/C NSg/J/P NPr/J/P NSg/VB/J+ NPr+ . . +> “ They almost drowned me once over in New Jersey . ” +# . IPl+ R VP/J NPr/ISg+ NSg/C NSg/J/P NPr/J/P NSg/J+ NPr+ . . > # > “ Then you ought to leave it alone , ” countered Doctor Civet . @@ -7040,8 +7040,8 @@ # NSg+ P NSg/VXB VP/J NSg/J/P D NPl . NSg/C/P VP/J NSg/VB/J NPl/V3+ NSg/J/P D > telephone . The grocery boy reported that the kitchen looked like a pigsty , and # NSg/VB+ . D+ NSg/VB+ NSg/VB+ VP/J NSg/I/C/Ddem D+ NSg/VB+ VP/J NSg/VB/J/C/P D/P+ NSg+ . VB/C -> the general opinion in the village was that the new people weren’t servants at -# D NSg/VB/J N🅪Sg NPr/J/P D+ NSg+ VPt NSg/I/C/Ddem D NSg/VB/J NPl/VB+ VB NPl/V3+ NSg/P +> the general opinion in the village was that the new people weren’t servants at +# D NSg/VB/J N🅪Sg NPr/J/P D+ NSg+ VPt NSg/I/C/Ddem D NSg/J NPl/VB+ VB NPl/V3+ NSg/P > all . # NSg/I/J/C/Dq . > @@ -7728,8 +7728,8 @@ # . R NSg$ D/P+ NPr+ NPr/VB/J+ . . > # -> “ Oxford , New Mexico , ” snorted Tom contemptuously , “ or something like that . ” -# . NPr+ . NSg/VB/J+ NPr+ . . VP/J NPr/VB+ R . . NPr/C NSg/I/VB/J+ NSg/VB/J/C/P NSg/I/C/Ddem+ . . +> “ Oxford , New Mexico , ” snorted Tom contemptuously , “ or something like that . ” +# . NPr+ . NSg/J+ NPr+ . . VP/J NPr/VB+ R . . NPr/C NSg/I/VB/J+ NSg/VB/J/C/P NSg/I/C/Ddem+ . . > # > “ Listen , Tom . If you’re such a snob , why did you invite him to lunch ? ” demanded @@ -7870,8 +7870,8 @@ # R+ C/P ISg/#r+ VP/J/NoAm NSg/I/C/Ddem NSg/I/J/C NSg/VB/J ISg/D$+ NPl/V3 VB VP/J J/P NPr/VB+ . NPr/ISg+ > had discovered that Myrtle had some sort of life apart from him in another # VB VP/J NSg/I/C/Ddem NPr VB I/J/R/Dq NSg/VB P N🅪Sg/VB+ J P ISg+ NPr/J/P I/D -> world , and the shock had made him physically sick . I stared at him and then at -# NSg/VB+ . VB/C D N🅪Sg/VB/J+ VB VB ISg+ R NSg/VB/J . ISg/#r+ VP/J NSg/P ISg+ VB/C NSg/J/C NSg/P +> world , and the shock had made him physically sick . I stared at him and then at +# NSg/VB+ . VB/C D N🅪Sg/J+ VB VB ISg+ R NSg/VB/J . ISg/#r+ VP/J NSg/P ISg+ VB/C NSg/J/C NSg/P > Tom , who had made a parallel discovery less than an hour before — and it occurred # NPr/VB+ . NPr/I+ VB VB D/P NSg/VB/J+ N🅪Sg+ VB/J/R/C/P C/P D/P+ NSg+ C/P . VB/C NPr/ISg+ VB > to me that there was no difference between men , in intelligence or race , so @@ -7936,8 +7936,8 @@ # > “ Those big movies around Fiftieth Street are cool , ” suggested Jordan . “ I love # . I/Ddem NSg/J NPl+ J/P NSg/J NSg/VB/J+ VB NSg/VB/J . . VP/J NPr+ . . ISg/#r+ NPr🅪Sg/VB -> New York on summer afternoons when every one’s away . There’s something very -# NSg/VB/J NPr+ J/P NPr🅪Sg/VB+ NPl NSg/I/C Dq NSg$ VB/J . K NSg/I/VB/J+ J/R +> New York on summer afternoons when every one’s away . There’s something very +# NSg/J NPr+ J/P NPr🅪Sg/VB+ NPl NSg/I/C Dq NSg$ VB/J . K NSg/I/VB/J+ J/R > sensuous about it — overripe , as if all sorts of funny fruits were going to fall # J J/P NPr/ISg+ . NSg/J . NSg/R NSg/C NSg/I/J/C/Dq NPl/V3 P NSg/J NPl/V3+ NSg/VPt Nᴹ/Vg/J P N🅪Sg/VB > into your hands . ” @@ -8214,8 +8214,8 @@ # D/P+ NSg/VB+ . NSg/J/C NSg$ NSg/VB+ . J VB/C Nᴹ/Vg/J . > # -> “ You must have gone there about the time Biloxi went to New Haven . ” -# . ISgPl+ NSg/VB NSg/VXB VPp/J/P R J/P D+ N🅪Sg/VB/J+ ? NSg/VPt P NSg/VB/J+ NSg/VB+ . . +> “ You must have gone there about the time Biloxi went to New Haven . ” +# . ISgPl+ NSg/VB NSg/VXB VPp/J/P R J/P D+ N🅪Sg/VB/J+ ? NSg/VPt P NSg/J+ NSg/VB+ . . > # > Another pause . A waiter knocked and came in with crushed mint and ice but the @@ -8628,8 +8628,8 @@ # > “ And you left him in the lurch , didn’t you ? You let him go to jail for a month # . VB/C ISgPl+ NPr/VB/J ISg+ NPr/J/P D+ NSg/VB+ . VB ISgPl+ . ISgPl+ NSg/VBP ISg+ NSg/VB/J P N🅪Sg/VB C/P D/P+ NSg/J+ -> over in New Jersey . God ! You ought to hear Walter on the subject of you . ” -# NSg/J/P NPr/J/P NSg/VB/J+ NPr+ . NPr/VB+ . ISgPl+ NSg/I/VXB P VB NPr+ J/P D NSg/VB/J P ISgPl+ . . +> over in New Jersey . God ! You ought to hear Walter on the subject of you . ” +# NSg/J/P NPr/J/P NSg/J+ NPr+ . NPr/VB+ . ISgPl+ NSg/I/VXB P VB NPr+ J/P D NSg/VB/J P ISgPl+ . . > # > “ He came to us dead broke . He was very glad to pick up some money , old sport . ” @@ -8746,8 +8746,8 @@ # . NPr/P . . . ISg/#r+ VB/J VP/J NSg/I/C/Ddem+ P . NSg$ D$+ NSg/VB+ . . > # -> I was thirty . Before me stretched the portentous , menacing road of a new decade . -# ISg/#r+ VPt NSg . C/P NPr/ISg+ VP/J D J . Nᴹ/Vg/J N🅪Sg/J P D/P NSg/VB/J NSg+ . +> I was thirty . Before me stretched the portentous , menacing road of a new decade . +# ISg/#r+ VPt NSg . C/P NPr/ISg+ VP/J D J . Nᴹ/Vg/J N🅪Sg/J P D/P NSg/J NSg+ . > # > It was seven o’clock when we got into the coupé with him and started for Long @@ -8850,8 +8850,8 @@ # Nᴹ/Vg/J Nᴹ+ . VP/J R C/P D/P NSg+ . VB/C NSg/J/C VP/J J/P > the next bend . Mavromichaelis wasn’t even sure of its color — he told the first # D NSg/J/P NPr/VB+ . ? VB NSg/VB/J J P ISg/D$+ N🅪Sg/VB/J/Am+ . NPr/ISg+ VP D NSg/VB/J -> policeman that it was light green . The other car , the one going toward New York , -# NSg+ NSg/I/C/Ddem+ NPr/ISg+ VPt N🅪Sg/VB/J+ NPr🅪Sg/VB/J . D+ NSg/VB/J+ NSg+ . D+ NSg/I/VB/J+ Nᴹ/Vg/J J/P NSg/VB/J+ NPr+ . +> policeman that it was light green . The other car , the one going toward New York , +# NSg+ NSg/I/C/Ddem+ NPr/ISg+ VPt N🅪Sg/VB/J+ NPr🅪Sg/VB/J . D+ NSg/VB/J+ NSg+ . D+ NSg/I/VB/J+ Nᴹ/Vg/J J/P NSg/J+ NPr+ . > came to rest a hundred yards beyond , and it’s driver hurried back to where # NSg/VPt/P P NSg/VB D/P+ NSg+ NPl/V3+ NSg/P . VB/C K NSg+ VP/J NSg/VB/J P NSg/C > Myrtle Wilson , her life violently extinguished , knelt in the road and mingled @@ -8922,8 +8922,8 @@ # > The circle closed up again with a running murmur of expostulation ; it was a # D+ NSg/VB+ VP/J NSg/VB/J/P P P D/P Nᴹ/Vg/J/P NSg/VB P NSg+ . NPr/ISg+ VPt D/P -> minute before I could see anything at all . Then new arrivals deranged the line , -# NSg/VB/J+ C/P ISg/#r+ NSg/VXB NSg/VB NSg/I/VB+ NSg/P NSg/I/J/C/Dq . NSg/J/C NSg/VB/J NPl VP/J D NSg/VB+ . +> minute before I could see anything at all . Then new arrivals deranged the line , +# NSg/VB/J+ C/P ISg/#r+ NSg/VXB NSg/VB NSg/I/VB+ NSg/P NSg/I/J/C/Dq . NSg/J/C NSg/J NPl VP/J D NSg/VB+ . > and Jordan and I were pushed suddenly inside . # VB/C NPr+ VB/C ISg/#r+ NSg/VPt VP/J R NSg/J/P . > @@ -9034,8 +9034,8 @@ # D/P NSg/VB/J NSg/VB/J/R . VP/J NSg/J J NSg/VB/J/P . > # -> “ It was a yellow car , ” he said , “ big yellow car . New . ” -# . NPr/ISg+ VPt D/P NSg/VB/J NSg . . NPr/ISg+ VP/J . . NSg/J+ NSg/VB/J+ NSg+ . NSg/VB/J . . +> “ It was a yellow car , ” he said , “ big yellow car . New . ” +# . NPr/ISg+ VPt D/P NSg/VB/J NSg . . NPr/ISg+ VP/J . . NSg/J+ NSg/VB/J+ NSg+ . NSg/J . . > # > “ See the accident ? ” asked the policeman . @@ -9054,8 +9054,8 @@ # > Some words of this conversation must have reached Wilson , swaying in the office # I/J/R/Dq NPl/V3 P I/Ddem+ N🅪Sg/VB+ NSg/VB NSg/VXB VP/J NPr+ . Nᴹ/Vg/J NPr/J/P D+ NSg/VB+ -> door , for suddenly a new theme found voice among his gasping cries : -# NSg/VB+ . C/P R D/P+ NSg/VB/J+ NSg/VB+ NSg/VB NSg/VB+ P ISg/D$+ Nᴹ/Vg/J NPl/V3 . +> door , for suddenly a new theme found voice among his gasping cries : +# NSg/VB+ . C/P R D/P+ NSg/J+ NSg/VB+ NSg/VB NSg/VB+ P ISg/D$+ Nᴹ/Vg/J NPl/V3 . > # > “ You don’t have to tell me what kind of car it was ! I know what kind of car it @@ -9084,8 +9084,8 @@ # > “ Listen , ” said Tom , shaking him a little . “ I just got here a minute ago , from # . NSg/VB . . VP/J NPr/VB+ . Nᴹ/Vg/J ISg+ D/P NPr/I/J/Dq . . ISg/#r+ VB/J VP NSg/J/R D/P+ NSg/VB/J+ J/P . P -> New York . I was bringing you that coupé we’ve been talking about . That yellow -# NSg/VB/J+ NPr+ . ISg/#r+ VPt Nᴹ/Vg/J ISgPl+ NSg/I/C/Ddem ? K NSg/VPp Nᴹ/Vg/J J/P . NSg/I/C/Ddem+ NSg/VB/J+ +> New York . I was bringing you that coupé we’ve been talking about . That yellow +# NSg/J+ NPr+ . ISg/#r+ VPt Nᴹ/Vg/J ISgPl+ NSg/I/C/Ddem ? K NSg/VPp Nᴹ/Vg/J J/P . NSg/I/C/Ddem+ NSg/VB/J+ > car I was driving this afternoon wasn’t mine — do you hear ? I haven’t seen it all # NSg+ ISg/#r+ VPt Nᴹ/Vg/J I/Ddem+ N🅪Sg+ VB NSg/I/VB+ . NSg/VXB ISgPl+ VB . ISg/#r+ VB NSg/VPp NPr/ISg+ NSg/I/J/C/Dq > afternoon . ” @@ -9120,8 +9120,8 @@ # . K D/P+ N🅪Sg/VB/J+ NSg+ . D/P ? . . > # -> “ We've come straight from New York , ” I said . -# . K NSg/VBPp/P NSg/VB/J/R P NSg/VB/J NPr+ . . ISg/#r+ VP/J . +> “ We've come straight from New York , ” I said . +# . K NSg/VBPp/P NSg/VB/J/R P NSg/J NPr+ . . ISg/#r+ VP/J . > # > Some one who had been driving a little behind us confirmed this , and the @@ -9292,8 +9292,8 @@ # . NPl/VB . . > # -> “ I thought so ; I told Daisy I thought so . It’s better that the shock should all -# . ISg/#r+ N🅪Sg/VP NSg/I/J/C . ISg/#r+ VP NPr+ ISg/#r+ N🅪Sg/VP NSg/I/J/C . K NSg/VXB/JC NSg/I/C/Ddem D N🅪Sg/VB/J+ VXB NSg/I/J/C/Dq +> “ I thought so ; I told Daisy I thought so . It’s better that the shock should all +# . ISg/#r+ N🅪Sg/VP NSg/I/J/C . ISg/#r+ VP NPr+ ISg/#r+ N🅪Sg/VP NSg/I/J/C . K NSg/VXB/JC NSg/I/C/Ddem D N🅪Sg/J+ VXB NSg/I/J/C/Dq > come at once . She stood it pretty well . ” # NSg/VBPp/P+ NSg/P NSg/C . ISg+ VB NPr/ISg+ NSg/VB/J/R NSg/VB/J/R . . > @@ -9334,8 +9334,8 @@ # > “ Yes , ” he said after a moment , “ but of course I'll say I was . You see , when we # . NPl/VB . . NPr/ISg+ VP/J P D/P+ NSg+ . . NSg/C/P P NSg/VB+ K NSg/VB ISg/#r+ VPt . ISgPl+ NSg/VB . NSg/I/C IPl+ -> left New York she was very nervous and she thought it would steady her to -# NPr/VB/J NSg/VB/J+ NPr+ ISg+ VPt J/R J VB/C ISg+ N🅪Sg/VP NPr/ISg+ VXB NSg/VB/J ISg/D$+ P +> left New York she was very nervous and she thought it would steady her to +# NPr/VB/J NSg/J+ NPr+ ISg+ VPt J/R J VB/C ISg+ N🅪Sg/VP NPr/ISg+ VXB NSg/VB/J ISg/D$+ P > drive — and this woman rushed out at us just as we were passing a car coming the # N🅪Sg/VB . VB/C I/Ddem+ NSg/VB+ VP/J NSg/VB/J/R/P NSg/P NPr/IPl+ VB/J NSg/R IPl+ NSg/VPt Nᴹ/Vg/J D/P+ NSg+ Nᴹ/Vg/J D+ > other way . It all happened in a minute , but it seemed to me that she wanted to @@ -9344,8 +9344,8 @@ # NSg/VB P NPr/IPl+ . N🅪Sg/VP IPl+ NSg/VPt NSg/I+ ISg+ VPt . NSg/VB/J/R . NSg/VB/J+ NPr+ VP/J VB/J > from the woman toward the other car , and then she lost her nerve and turned # P D+ NSg/VB+ J/P D+ NSg/VB/J+ NSg+ . VB/C NSg/J/C ISg+ VP/J ISg/D$+ NSg/VB+ VB/C VP/J -> back . The second my hand reached the wheel I felt the shock — it must have killed -# NSg/VB/J . D+ NSg/VB/J+ D$+ NSg/VB+ VP/J D+ NSg/VB+ ISg/#r+ N🅪Sg/VB/J D+ N🅪Sg/VB/J+ . NPr/ISg+ NSg/VB NSg/VXB VP/J +> back . The second my hand reached the wheel I felt the shock — it must have killed +# NSg/VB/J . D+ NSg/VB/J+ D$+ NSg/VB+ VP/J D+ NSg/VB+ ISg/#r+ N🅪Sg/VB/J D+ N🅪Sg/J+ . NPr/ISg+ NSg/VB NSg/VXB VP/J > her instantly . ” # ISg/D$+ R . . > @@ -9388,8 +9388,8 @@ # . NSg/I/J/C/Dq+ N🅪Sg/VB+ . NSg/C NSg/J . J . NSg/VB/C/P IPl+ NSg/I/J/C/Dq NSg/VB/J P NSg/VBP/J . . > # -> A new point of view occurred to me . Suppose Tom found out that Daisy had been -# D/P NSg/VB/J NSg/VB P NSg/VB+ VB P NPr/ISg+ . VB NPr/VB+ NSg/VB NSg/VB/J/R/P NSg/I/C/Ddem NPr+ VB NSg/VPp +> A new point of view occurred to me . Suppose Tom found out that Daisy had been +# D/P NSg/J NSg/VB P NSg/VB+ VB P NPr/ISg+ . VB NPr/VB+ NSg/VB NSg/VB/J/R/P NSg/I/C/Ddem NPr+ VB NSg/VPp > driving . He might think he saw a connection in it — he might think anything . I # Nᴹ/Vg/J . NPr/ISg+ Nᴹ/VXB/J NSg/VB NPr/ISg+ NSg/VPt D/P+ N🅪Sg+ NPr/J/P NPr/ISg+ . NPr/ISg+ Nᴹ/VXB/J NSg/VB NSg/I/VB+ . ISg/#r+ > looked at the house ; there were two or three bright windows down - stairs and the @@ -9694,8 +9694,8 @@ # C/P NPr+ VPt NPr/VB/J VB/C ISg/D$+ J+ NSg/VB+ VPt J P NPl VB/C > pleasant , cheerful snobbery and orchestras which set the rhythm of the year , # NSg/J . J Nᴹ VB/C NPl+ I/C+ NPr/VBP/J D N🅪Sg/VB P D NSg+ . -> summing up the sadness and suggestiveness of life in new tunes . All night the -# NSg/Vg NSg/VB/J/P D Nᴹ+ VB/C NSg P N🅪Sg/VB+ NPr/J/P NSg/VB/J NPl/V3 . NSg/I/J/C/Dq+ N🅪Sg/VB+ D +> summing up the sadness and suggestiveness of life in new tunes . All night the +# NSg/Vg NSg/VB/J/P D Nᴹ+ VB/C NSg P N🅪Sg/VB+ NPr/J/P NSg/J NPl/V3 . NSg/I/J/C/Dq+ N🅪Sg/VB+ D > saxophones wailed the hopeless comment of the “ Beale Street Blues ” while a # NPl/V3 VP/J D J NSg/VB P D . ? NSg/VB/J+ NPl/V3+ . NSg/VB/C/P D/P > hundred pairs of golden and silver slippers shuffled the shining dust . At the @@ -10174,8 +10174,8 @@ # ? VP/J D+ NSg+ JS+ ISg/D$+ NSg/VB+ . R+ VPt NSg/I/J+ NPr/J/P NPr/ISg+ NSg/C/P D/P > small , expensive dog - leash , made of leather and braided silver . It was # NPr/VB/J . J NSg/VB/J+ . NSg/VB+ . VB P N🅪Sg/VB/J VB/C VP/J Nᴹ/VB/J+ . NPr/ISg+ VPt -> apparently new . -# R NSg/VB/J . +> apparently new . +# R NSg/J . > # > “ This ? ” he inquired , holding it up . @@ -10306,8 +10306,8 @@ # NSg/VB/J NPr/ISg+ . NSg/C/P ISgPl+ VXB NSg/VB/J NPr/VB+ . . . > # -> Standing behind him , Michaelis saw with a shock that he was looking at the eyes -# Nᴹ/Vg/J NSg/J/P ISg+ . ? NSg/VPt P D/P+ N🅪Sg/VB/J+ NSg/I/C/Ddem+ NPr/ISg+ VPt Nᴹ/Vg/J NSg/P D NPl/V3 +> Standing behind him , Michaelis saw with a shock that he was looking at the eyes +# Nᴹ/Vg/J NSg/J/P ISg+ . ? NSg/VPt P D/P+ N🅪Sg/J+ NSg/I/C/Ddem+ NPr/ISg+ VPt Nᴹ/Vg/J NSg/P D NPl/V3 > of Doctor T. J. Eckleburg , which had just emerged , pale and enormous , from the # P NSg/VB+ ? ? ? . I/C+ VB VB/J VP/J . NSg/VB/J VB/C J . P D > dissolving night . @@ -10402,8 +10402,8 @@ # NPr/ISg+ NSg/VB NSg/VXB VP/J NSg/VB/J/P NSg/P D/P+ NSg/J+ N🅪Sg/VB+ NSg/J/P Nᴹ/Vg/J+ NPl/V3+ VB/C > shivered as he found what a grotesque thing a rose is and how raw the sunlight # VP/J NSg/R NPr/ISg+ NSg/VB NSg/I+ D/P+ NSg/J+ NSg+ D/P+ NPr/VPt/J+ VL3 VB/C NSg/C NSg/VB/J D+ NSg/VB+ -> was upon the scarcely created grass . A new world , material without being real , -# VPt P D R VP/J NPr🅪Sg/VB+ . D/P+ NSg/VB/J NSg/VB+ . N🅪Sg/VB/J+ C/P N🅪Sg/Vg/J/C NSg/J . +> was upon the scarcely created grass . A new world , material without being real , +# VPt P D R VP/J NPr🅪Sg/VB+ . D/P+ NSg/J NSg/VB+ . N🅪Sg/VB/J+ C/P N🅪Sg/Vg/J/C NSg/J . > where poor ghosts , breathing dreams like air , drifted fortuitously about . . . # NSg/C NSg/VB/J+ NPl/V3+ . Nᴹ/Vg/J NPl/V3+ NSg/VB/J/C/P N🅪Sg/VB+ . VP/J R J/P . . . > like that ashen , fantastic figure gliding toward him through the amorphous @@ -10499,7 +10499,7 @@ > But all this part of it seemed remote and unessential . I found myself on # NSg/C/P NSg/I/J/C/Dq I/Ddem NSg/VB/J P NPr/ISg+ VP/J NSg/VB/J VB/C J . ISg/#r+ NSg/VB ISg+ J/P > Gatsby’s side , and alone . From the moment I telephoned news of the catastrophe -# NSg$ NSg/VB/J+ . VB/C J . P D+ NSg+ ISg/#r+ VP/J Nᴹ/V3 P D NSg+ +# NSg$ NSg/VB/J+ . VB/C J . P D+ NSg+ ISg/#r+ VP/J Nᴹ/VB P D NSg+ > to West Egg village , every surmise about him , and every practical question , was # P NPr/VB/J+ N🅪Sg/VB+ NSg+ . Dq NSg/VB J/P ISg+ . VB/C Dq NSg/J NSg/VB+ . VPt > referred to me . At first I was surprised and confused ; then , as he lay in his @@ -10583,7 +10583,7 @@ > visitors , all these official people who suddenly filled it . But , though they # NPl+ . NSg/I/J/C/Dq I/Ddem+ NSg/J+ NPl/VB+ NPr/I+ R VP/J NPr/ISg+ . NSg/C/P . VB/C IPl+ > drew back the sheet and looked at Gatsby with shocked eyes , his protest -# NPr/VB NSg/VB/J D+ NSg/VB+ VB/C VP/J NSg/P NPr P VP/J NPl/V3+ . ISg/D$+ N🅪Sg/VB+ +# NPr/VB NSg/VB/J D+ NSg/VB+ VB/C VP/J NSg/P NPr P J NPl/V3+ . ISg/D$+ N🅪Sg/VB+ > continued in my brain : # VP/J NPr/J/P D$+ NPr🅪Sg/VB+ . > @@ -10604,8 +10604,8 @@ # NPr+ NPr . D/P NSg/VB/J P NSg/VPp/J Nᴹ/VB+ . Nᴹ/Vg/J N🅪Sg/VB/J/P P D NPr/VB+ . > # -> Next morning I sent the butler to New York with a letter to Wolfshiem , which -# NSg/J/P+ N🅪Sg/Vg/J+ ISg/#r+ NSg/VB D NPr/VB P NSg/VB/J NPr+ P D/P NSg/VB+ P ? . I/C+ +> Next morning I sent the butler to New York with a letter to Wolfshiem , which +# NSg/J/P+ N🅪Sg/Vg/J+ ISg/#r+ NSg/VB D NPr/VB P NSg/J NPr+ P D/P NSg/VB+ P ? . I/C+ > asked for information and urged him to come out on the next train . That request # VP/J C/P Nᴹ+ VB/C VP/J ISg+ P NSg/VBPp/P NSg/VB/J/R/P J/P D NSg/J/P NSg/VB+ . NSg/I/C/Ddem+ NSg/VB+ > seemed superfluous when I wrote it . I was sure he’d start when he saw the @@ -10623,7 +10623,7 @@ > # > Dear Mr . Carraway . This has been one of the most terrible shocks of my life to -# NSg/VB/J+ NSg+ . ? . I/Ddem+ V3 NSg/VPp NSg/I/VB/J P D NSg/I/J/R/Dq J NPl/V3 P D$+ N🅪Sg/VB+ P +# NSg/VB/J+ NSg+ . ? . I/Ddem+ V3 NSg/VPp NSg/I/VB/J P D NSg/I/J/R/Dq J NPl P D$+ N🅪Sg/VB+ P > me I hardly can believe it that it is true at all . Such a mad act as that man # NPr/ISg+ ISg/#r+ R NPr/VXB VB NPr/ISg+ NSg/I/C/Ddem NPr/ISg+ VL3 NSg/VB/J NSg/P NSg/I/J/C/Dq . NSg/I D/P NSg/VB/J NPr/VB+ NSg/R NSg/I/C/Ddem NPr/VB/J+ > did should make us all think . I cannot come down now as I am tied up in some @@ -10680,8 +10680,8 @@ # > “ Young Parke’s in trouble , ” he said rapidly . “ They picked him up when he handed # . NPr/VB/J ? NPr/J/P N🅪Sg/VB+ . . NPr/ISg+ VP/J R . . IPl+ VP/J ISg+ NSg/VB/J/P NSg/I/C NPr/ISg+ VP/J -> the bonds over the counter . They got a circular from New York giving ’ em the -# D+ NPl/V3+ NSg/J/P D+ NSg/VB/J+ . IPl+ VP D/P NSg/VB/J P NSg/VB/J+ NPr+ Nᴹ/Vg/J . NSg/I/J+ D+ +> the bonds over the counter . They got a circular from New York giving ’ em the +# D+ NPl/V3+ NSg/J/P D+ NSg/VB/J+ . IPl+ VP D/P NSg/VB/J P NSg/J+ NPr+ Nᴹ/Vg/J . NSg/I/J+ D+ > numbers just five minutes before . What d’you know about that , hey ? You never can # NPrPl/V3+ VB/J NSg+ NPl/V3+ C/P . NSg/I+ ? NSg/VB J/P NSg/I/C/Ddem+ . NSg . ISgPl+ R NPr/VXB > tell in these hick towns — — — ” @@ -10924,8 +10924,8 @@ # NSg$ N🅪Sg/VB+ . VB/C ISg/#r+ VXB NSg/VXB VPp/J NSg/VXB/JC C/P P NSg/VB ISg+ . > # -> The morning of the funeral I went up to New York to see Meyer Wolfshiem ; I -# D N🅪Sg/Vg/J P D+ NSg/J+ ISg/#r+ NSg/VPt NSg/VB/J/P P NSg/VB/J+ NPr+ P NSg/VB NPr ? . ISg/#r+ +> The morning of the funeral I went up to New York to see Meyer Wolfshiem ; I +# D N🅪Sg/Vg/J P D+ NSg/J+ ISg/#r+ NSg/VPt NSg/VB/J/P P NSg/J+ NPr+ P NSg/VB NPr ? . ISg/#r+ > couldn’t seem to reach him any other way . The door that I pushed open , on the # VB VB P NSg/VB ISg+ I/R/Dq NSg/VB/J NSg/J+ . D+ NSg/VB+ NSg/I/C/Ddem+ ISg/#r+ VP/J NSg/VB/J . J/P D > advice of an elevator boy , was marked “ The Swastika Holding Company , ” and at @@ -11438,8 +11438,8 @@ # > “ Nevertheless you did throw me over , ” said Jordan suddenly . ‘ ‘ You threw me over # . R ISgPl+ VPt NSg/VB NPr/ISg+ NSg/J/P . . VP/J NPr+ R . Unlintable Unlintable ISgPl+ VB NPr/ISg+ NSg/J/P -> on the telephone . I don’t give a damn about you now , but it was a new experience -# J/P D+ NSg/VB+ . ISg/#r+ VB NSg/VB D/P NSg/VB/J J/P ISgPl+ NPr/VB/J/C . NSg/C/P NPr/ISg+ VPt D/P NSg/VB/J N🅪Sg/VB +> on the telephone . I don’t give a damn about you now , but it was a new experience +# J/P D+ NSg/VB+ . ISg/#r+ VB NSg/VB D/P NSg/VB/J J/P ISgPl+ NPr/VB/J/C . NSg/C/P NPr/ISg+ VPt D/P NSg/J N🅪Sg/VB > for me , and I felt a little dizzy for a while . ” # C/P NPr/ISg+ . VB/C ISg/#r+ N🅪Sg/VB/J D/P NPr/I/J/Dq NSg/VB/J C/P D/P NSg/VB/C/P+ . . > @@ -11588,8 +11588,8 @@ # VP/J ISg+ NSg/I/C ISg/#r+ VP NSg/VB/J/P D NSg/VB+ . > # -> I spent my Saturday nights in New York because those gleaming , dazzling parties -# ISg/#r+ VB/J D$+ NSg/VB+ NPl/V3+ NPr/J/P NSg/VB/J+ NPr+ C/P I/Ddem+ Nᴹ/Vg/J+ . Nᴹ/Vg/J NPl/V3 +> I spent my Saturday nights in New York because those gleaming , dazzling parties +# ISg/#r+ VB/J D$+ NSg/VB+ NPl/V3+ NPr/J/P NSg/J+ NPr+ C/P I/Ddem+ Nᴹ/Vg/J+ . Nᴹ/Vg/J NPl/V3 > of his were with me so vividly that I could still hear the music and the # P ISg/D$+ NSg/VPt P NPr/ISg+ NSg/I/J/C R NSg/I/C/Ddem ISg/#r+ NSg/VXB NSg/VB/J VB D N🅪Sg/VB/J VB/C D+ > laughter , faint and incessant , from his garden , and the cars going up and down @@ -11622,8 +11622,8 @@ # NPr/VPt/J NSg/JC D NSg/J NPl/V3+ VPt P NSg/VB VB/J C/P R ISg/#r+ VPt > aware of the old island here that flowered once for Dutch sailors ’ eyes — a fresh , # VB/J P D NSg/J NSg/VB+ NSg/J/R NSg/I/C/Ddem VP/J NSg/C C/P NPrᴹ/VB/J NPl+ . NPl/V3+ . D/P NSg/VB/J . -> green breast of the new world . Its vanished trees , the trees that had made way -# NPr🅪Sg/VB/J NSg/VB P D NSg/VB/J NSg/VB+ . ISg/D$+ VP/J NPl/V3+ . D+ NPl/V3+ NSg/I/C/Ddem+ VB VB NSg/J +> green breast of the new world . Its vanished trees , the trees that had made way +# NPr🅪Sg/VB/J NSg/VB P D NSg/J NSg/VB+ . ISg/D$+ VP/J NPl/V3+ . D+ NPl/V3+ NSg/I/C/Ddem+ VB VB NSg/J > for Gatsby’s house , had once pandered in whispers to the last and greatest of # C/P NSg$ NPr/VB+ . VB NSg/C VP/J NPr/J/P NPl/V3 P D NSg/VB/J VB/C JS P > all human dreams ; for a transitory enchanted moment man must have held his diff --git a/test.md b/test.md new file mode 100644 index 00000000..d4f2d8bc --- /dev/null +++ b/test.md @@ -0,0 +1 @@ +Flags sentences that begin with a discourse marker but omit the required following comma. From 636770640880c26d69b0f86c99e3040f129121c9 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Thu, 13 Nov 2025 15:37:27 -0700 Subject: [PATCH 028/178] fix(core): make nominal phrases an exception for `HowTo` (#2190) --- harper-core/src/linting/how_to.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/harper-core/src/linting/how_to.rs b/harper-core/src/linting/how_to.rs index 56b4bfe3..9ecc07d6 100644 --- a/harper-core/src/linting/how_to.rs +++ b/harper-core/src/linting/how_to.rs @@ -26,7 +26,7 @@ impl Default for HowTo { let exceptions = SequenceExpr::default() .then_unless(UPOSSet::new(&[UPOS::PART])) .then_anything() - .then_anything() + .then_unless(|tok: &Token, _: &[char]| tok.kind.is_np_member()) .then_anything() .then_unless( InflectionOfBe::new().or(SequenceExpr::default().then_kind_any_or_words( @@ -40,7 +40,7 @@ impl Default for HowTo { )), ); - pattern.add(SequenceExpr::default().then(exceptions)); + pattern.add(exceptions); Self { expr: Box::new(pattern), @@ -296,4 +296,16 @@ mod tests { HowTo::default(), ); } + + #[test] + fn issue_2124() { + assert_no_lints( + "I like how discord shows Spotify status on your profile.", + HowTo::default(), + ); + assert_no_lints( + "To be determined based on how error handling is done in new paradigm.", + HowTo::default(), + ); + } } From ed21fe2d128893b2e960afa8b78b92f40aaa5677 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Fri, 14 Nov 2025 09:56:55 -0700 Subject: [PATCH 029/178] fix(core): improve the initialisms linter for all-caps inputs (#2184) --- harper-core/src/linting/initialism_linter.rs | 18 ++++++------------ harper-core/src/linting/initialisms.rs | 9 +++++++++ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/harper-core/src/linting/initialism_linter.rs b/harper-core/src/linting/initialism_linter.rs index b29d7df5..27020121 100644 --- a/harper-core/src/linting/initialism_linter.rs +++ b/harper-core/src/linting/initialism_linter.rs @@ -37,20 +37,14 @@ impl ExprLinter for InitialismLinter { let tok = matched_tokens.first()?; let source = tok.span.get_content(source); - let caps = source - .iter() - .map(char::is_ascii_uppercase) - .chain([false].into_iter().cycle()); - let mut expansion_lower = self.expansion_lower.to_owned(); + let first_letter = &mut expansion_lower[0][0]; - for (word, cap) in expansion_lower.iter_mut().zip(caps) { - word[0] = if cap { - word[0].to_ascii_uppercase() - } else { - word[0].to_ascii_lowercase() - } - } + *first_letter = if source[0].is_ascii_uppercase() { + first_letter.to_ascii_uppercase() + } else { + first_letter.to_ascii_lowercase() + }; let phrase = Itertools::intersperse_with(expansion_lower.into_iter(), || vec![' ']) .reduce(|mut left, mut right| { diff --git a/harper-core/src/linting/initialisms.rs b/harper-core/src/linting/initialisms.rs index 69d154d4..36668ab4 100644 --- a/harper-core/src/linting/initialisms.rs +++ b/harper-core/src/linting/initialisms.rs @@ -118,4 +118,13 @@ mod tests { "Really excited for this.", ); } + + #[test] + fn issue_2181() { + assert_suggestion_result( + "AFAIK, we don't currently have an issue for it.", + lint_group(), + "As far as i know, we don't currently have an issue for it.", + ); + } } From 2514f881c8969eb197abe2ce4093bf28859ac625 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Fri, 14 Nov 2025 10:02:45 -0700 Subject: [PATCH 030/178] feat(chrome-ext): add dark mode to options and popup pages (#2192) --- packages/chrome-plugin/app.css | 9 ++++ .../chrome-plugin/src/options/Options.svelte | 54 ++++++++++++------- packages/chrome-plugin/src/options/index.ts | 2 + packages/chrome-plugin/src/popup/Main.svelte | 2 +- .../chrome-plugin/src/popup/Onboarding.svelte | 8 +-- packages/chrome-plugin/src/popup/Popup.svelte | 8 +-- .../src/popup/ReportProblematicLint.svelte | 27 +++++++--- packages/chrome-plugin/src/popup/index.ts | 2 + packages/chrome-plugin/src/theme.ts | 29 ++++++++++ .../install-browser-extension/+page.svelte | 2 +- 10 files changed, 107 insertions(+), 36 deletions(-) create mode 100644 packages/chrome-plugin/src/theme.ts diff --git a/packages/chrome-plugin/app.css b/packages/chrome-plugin/app.css index 64ebcae2..70e9f43a 100644 --- a/packages/chrome-plugin/app.css +++ b/packages/chrome-plugin/app.css @@ -25,3 +25,12 @@ code { @apply bg-primary-100 rounded p-1; } + +body { + @apply min-h-screen bg-white text-gray-900 transition-colors duration-150; +} + +.dark body, +body.dark { + @apply bg-slate-950 text-slate-100; +} diff --git a/packages/chrome-plugin/src/options/Options.svelte b/packages/chrome-plugin/src/options/Options.svelte index 22e9d5fa..3f0923f5 100644 --- a/packages/chrome-plugin/src/options/Options.svelte +++ b/packages/chrome-plugin/src/options/Options.svelte @@ -149,21 +149,26 @@ async function exportEnabledDomainsCSV() { -
-
- Harper logo +
+
+ Harper logo Harper
-
+
-

General

+

General

English Dialect - @@ -176,7 +181,7 @@ async function exportEnabledDomainsCSV() {
Enable on New Sites by Default - Can make some apps behave abnormally. + Can make some apps behave abnormally.
@@ -186,7 +191,7 @@ async function exportEnabledDomainsCSV() {
Export Enabled Domains - Downloads JSON of domains explicitly enabled. + Downloads JSON of domains explicitly enabled.
@@ -198,9 +203,14 @@ async function exportEnabledDomainsCSV() {
Activation Key - If you're finding that you're accidentally triggering Harper. + If you're finding that you're accidentally triggering Harper.
- @@ -212,9 +222,12 @@ async function exportEnabledDomainsCSV() {
User Dictionary - Each word should be on its own line. + Each word should be on its own line.
- +
@@ -223,8 +236,13 @@ async function exportEnabledDomainsCSV() {
-

Rules

- +

Rules

+
@@ -240,11 +258,11 @@ async function exportEnabledDomainsCSV() { ) as [key, value]}
-
+
-

{key}

-

{@html lintDescriptions[key]}

+

{key}

+

{@html lintDescriptions[key]}

+
-
+ -
+
-

Rules

+

Rules

- - +
@@ -256,21 +256,19 @@ async function exportEnabledDomainsCSV() { (lintDescriptions[key] ?? '').toLowerCase().includes(searchQueryLower) || key.toLowerCase().includes(searchQueryLower) ) as [key, value]} -
+
-
-

{key}

-

{@html lintDescriptions[key]}

+

{key}

+

{@html lintDescriptions[key]}

-
{/each} -
-
+ +
diff --git a/packages/chrome-plugin/src/popup/Main.svelte b/packages/chrome-plugin/src/popup/Main.svelte index 76a5d38b..b85cbe6f 100644 --- a/packages/chrome-plugin/src/popup/Main.svelte +++ b/packages/chrome-plugin/src/popup/Main.svelte @@ -1,5 +1,5 @@
-
+
Harper logo Harper
{#if popupState.page != "main"} - + }}> {/if}
@@ -48,9 +48,9 @@ function openSettings() { {/if}
diff --git a/packages/chrome-plugin/src/popup/ReportProblematicLint.svelte b/packages/chrome-plugin/src/popup/ReportProblematicLint.svelte index c6e15e57..7ff482fc 100644 --- a/packages/chrome-plugin/src/popup/ReportProblematicLint.svelte +++ b/packages/chrome-plugin/src/popup/ReportProblematicLint.svelte @@ -1,5 +1,5 @@ + +{#if href} + + + +{:else} + +{/if} diff --git a/packages/components/src/lib/Card.svelte b/packages/components/src/lib/Card.svelte new file mode 100644 index 00000000..49fcfe2e --- /dev/null +++ b/packages/components/src/lib/Card.svelte @@ -0,0 +1,16 @@ + + +
+ +
diff --git a/packages/components/src/lib/Collapsible.svelte b/packages/components/src/lib/Collapsible.svelte new file mode 100644 index 00000000..f8b5ef89 --- /dev/null +++ b/packages/components/src/lib/Collapsible.svelte @@ -0,0 +1,17 @@ + + +
+ {title} +
+ +
+
diff --git a/packages/components/src/lib/Input.svelte b/packages/components/src/lib/Input.svelte new file mode 100644 index 00000000..127254d8 --- /dev/null +++ b/packages/components/src/lib/Input.svelte @@ -0,0 +1,35 @@ + + + diff --git a/packages/components/src/lib/Link.svelte b/packages/components/src/lib/Link.svelte new file mode 100644 index 00000000..ca57911c --- /dev/null +++ b/packages/components/src/lib/Link.svelte @@ -0,0 +1,34 @@ + + + + + diff --git a/packages/components/src/lib/Select.svelte b/packages/components/src/lib/Select.svelte new file mode 100644 index 00000000..c2300d17 --- /dev/null +++ b/packages/components/src/lib/Select.svelte @@ -0,0 +1,45 @@ + + + diff --git a/packages/components/src/lib/Textarea.svelte b/packages/components/src/lib/Textarea.svelte new file mode 100644 index 00000000..c24b00b0 --- /dev/null +++ b/packages/components/src/lib/Textarea.svelte @@ -0,0 +1,22 @@ + + + diff --git a/packages/components/src/lib/index.ts b/packages/components/src/lib/index.ts new file mode 100644 index 00000000..3f90d0ed --- /dev/null +++ b/packages/components/src/lib/index.ts @@ -0,0 +1,23 @@ +export { + Badge, + Checkbox, + Fileupload, + Label, + Radio, + Spinner, + Table, + TableBody, + TableBodyCell, + TableBodyRow, + TableHead, + TableHeadCell, + Toggle, +} from 'flowbite-svelte'; + +export { default as Button } from './Button.svelte'; +export { default as Card } from './Card.svelte'; +export { default as Collapsible } from './Collapsible.svelte'; +export { default as Input } from './Input.svelte'; +export { default as Link } from './Link.svelte'; +export { default as Select } from './Select.svelte'; +export { default as Textarea } from './Textarea.svelte'; diff --git a/packages/components/src/lib/styles.css b/packages/components/src/lib/styles.css new file mode 100644 index 00000000..0b35f5a9 --- /dev/null +++ b/packages/components/src/lib/styles.css @@ -0,0 +1,74 @@ +@import "tailwindcss"; + +@plugin "flowbite/plugin"; + +@custom-variant dark (&:where(.dark, .dark *)); + +@source "./src/lib/**/*.{svelte,ts}"; +@source "./node_modules/flowbite-svelte/**/*.{svelte,ts,js}"; + +@theme { + --color-primary-50: #fef4e7; /* honey bronze */ + --color-primary-100: #fce9cf; + --color-primary-200: #f9d49f; + --color-primary-300: #f7be6e; + --color-primary-400: #f4a83e; + --color-primary: #f1920e; + --color-primary-600: #c1750b; + --color-primary-700: #915808; + --color-primary-800: #603b06; + --color-primary-900: #301d03; + --color-primary-950: #221402; + + --color-accent-50: #fee7e9; /* hot fuchsia */ + --color-accent-100: #fccfd3; + --color-accent-200: #f99fa6; + --color-accent-300: #f76e7a; + --color-accent-400: #f43e4d; + --color-accent: #f10e21; + --color-accent-600: #c10b1a; + --color-accent-700: #910814; + --color-accent-800: #60060d; + --color-accent-900: #300307; + --color-accent-950: #220205; + + --color-cream: #fef4e7; /* simple cream */ + --color-cream-100: #fce9cf; + --color-cream-200: #f9d49f; + --color-cream-300: #f7be6e; + --color-cream-400: #f4a83e; + --color-cream-500: #f1920e; + --color-cream-600: #c1750b; + --color-cream-700: #915808; + --color-cream-800: #603b06; + --color-cream-900: #301d03; + --color-cream-950: #221402; + + --color-champagne-mist-50: #fef4e7; + --color-champagne-mist-100: #fce9cf; + --color-champagne-mist-200: #fad49e; + --color-champagne-mist-300: #f7be6e; + --color-champagne-mist-400: #f5a83d; + --color-champagne-mist-500: #f2930d; + --color-champagne-mist-600: #c2750a; + --color-champagne-mist-700: #915808; + --color-champagne-mist-800: #613b05; + --color-champagne-mist-900: #301d03; + --color-champagne-mist-950: #221502; + + --color-white: #fffdfa; + --color-white-100: #fceacf; + --color-white-200: #fad59e; + --color-white-300: #f7c06e; + --color-white-400: #f5ab3d; + --color-white-500: #f2960d; + --color-white-600: #c2780a; + --color-white-700: #915a08; + --color-white-800: #613c05; + --color-white-900: #301e03; + --color-white-950: #221502; +} + +body { + @apply bg-white dark:bg-white-900 dark:text-white; +} diff --git a/packages/components/src/routes/+layout.svelte b/packages/components/src/routes/+layout.svelte new file mode 100644 index 00000000..b4059508 --- /dev/null +++ b/packages/components/src/routes/+layout.svelte @@ -0,0 +1,7 @@ + + +{@render children()} diff --git a/packages/components/src/routes/+page.svelte b/packages/components/src/routes/+page.svelte new file mode 100644 index 00000000..da127aeb --- /dev/null +++ b/packages/components/src/routes/+page.svelte @@ -0,0 +1,7 @@ + + +

Welcome to your library project

+

Create your package using @sveltejs/package and preview/showcase your work with SvelteKit

+

Visit svelte.dev/docs/kit to read the documentation

diff --git a/packages/components/src/routes/layout.css b/packages/components/src/routes/layout.css new file mode 100644 index 00000000..f1d8c73c --- /dev/null +++ b/packages/components/src/routes/layout.css @@ -0,0 +1 @@ +@import "tailwindcss"; diff --git a/packages/components/static/favicon.svg b/packages/components/static/favicon.svg new file mode 100644 index 00000000..cc5dc66a --- /dev/null +++ b/packages/components/static/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/packages/components/svelte.config.js b/packages/components/svelte.config.js new file mode 100644 index 00000000..3ad145a2 --- /dev/null +++ b/packages/components/svelte.config.js @@ -0,0 +1,18 @@ +import adapter from '@sveltejs/adapter-auto'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://svelte.dev/docs/kit/integrations + // for more information about preprocessors + preprocess: vitePreprocess(), + + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter(), + }, +}; + +export default config; diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json new file mode 100644 index 00000000..371777ed --- /dev/null +++ b/packages/components/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowImportingTsExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "module": "NodeNext", + "moduleResolution": "NodeNext" + } +} diff --git a/packages/components/vite.config.ts b/packages/components/vite.config.ts new file mode 100644 index 00000000..da1f9251 --- /dev/null +++ b/packages/components/vite.config.ts @@ -0,0 +1,7 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import tailwindcss from '@tailwindcss/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [tailwindcss(), sveltekit()], +}); diff --git a/packages/lint-framework/package.json b/packages/lint-framework/package.json index 64f72de1..26e7b072 100644 --- a/packages/lint-framework/package.json +++ b/packages/lint-framework/package.json @@ -24,6 +24,7 @@ "@fortawesome/fontawesome-svg-core": "^7.1.0", "@fortawesome/free-solid-svg-icons": "^7.1.0", "bowser": "^2.12.1", + "colorjs.io": "^0.5.2", "virtual-dom": "^2.1.1" }, "peerDependencies": { diff --git a/packages/lint-framework/src/index.ts b/packages/lint-framework/src/index.ts index a4f1e2b5..5356c178 100644 --- a/packages/lint-framework/src/index.ts +++ b/packages/lint-framework/src/index.ts @@ -5,7 +5,6 @@ export * from './lint/editorUtils'; export { default as Highlights } from './lint/Highlights'; export { default as LintFramework } from './lint/LintFramework'; export * from './lint/lintKindColor'; -export { default as lintKindColor } from './lint/lintKindColor'; export { default as PopupHandler } from './lint/PopupHandler'; export { default as RenderBox } from './lint/RenderBox'; export * from './lint/unpackLint'; diff --git a/packages/lint-framework/src/lint/Highlights.ts b/packages/lint-framework/src/lint/Highlights.ts index 13933d2b..e7373d8b 100644 --- a/packages/lint-framework/src/lint/Highlights.ts +++ b/packages/lint-framework/src/lint/Highlights.ts @@ -17,7 +17,7 @@ import { getSlateRoot, getTrixRoot, } from './editorUtils'; -import lintKindColor, { type LintKind } from './lintKindColor'; +import { type LintKind, lintKindColor } from './lintKindColor'; import RenderBox from './RenderBox'; import type SourceElement from './SourceElement'; import type { UnpackedLint } from './unpackLint'; diff --git a/packages/lint-framework/src/lint/SuggestionBox.ts b/packages/lint-framework/src/lint/SuggestionBox.ts index adf73ed7..e46f5013 100644 --- a/packages/lint-framework/src/lint/SuggestionBox.ts +++ b/packages/lint-framework/src/lint/SuggestionBox.ts @@ -5,7 +5,7 @@ import type { VNode } from 'virtual-dom'; import h from 'virtual-dom/h'; import bookDownSvg from '../assets/bookDownSvg'; import type { IgnorableLintBox, LintBox } from './Box'; -import lintKindColor from './lintKindColor'; +import { type LintKind, lintKindColor, lintKindTextColor } from './lintKindColor'; // Decoupled: actions passed in by framework consumer import type { UnpackedLint, UnpackedSuggestion } from './unpackLint'; @@ -190,6 +190,7 @@ function addToDictionary( } function suggestions( + lintKind: LintKind, suggestions: UnpackedSuggestion[], apply: (s: UnpackedSuggestion) => void, ): any { @@ -197,7 +198,13 @@ function suggestions( const label = s.replacement_text !== '' ? s.replacement_text : String(s.kind); const desc = `Replace with "${label}"`; const props = i === 0 ? { hook: new FocusHook() } : {}; - return button(label, { background: '#2DA44E', color: '#FFFFFF' }, () => apply(s), desc, props); + return button( + label, + { background: lintKindColor(lintKind), color: lintKindTextColor(lintKind) }, + () => apply(s), + desc, + props, + ); }); } @@ -221,10 +228,10 @@ function reportProblemButton(reportError?: () => Promise): any { ); } -function styleTag() { +function styleTag(lintKind: LintKind) { return h('style', { id: 'harper-suggestion-style' }, [ `code{ - background-color:#e3eccf; + text-decoration: underline solid ${lintKindColor(lintKind)} 2px; padding:0.125rem; border-radius:0.25rem } @@ -351,10 +358,16 @@ function styleTag() { animation: fadeIn 100ms ease-in-out forwards; } - @keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } - } + @keyframes fadeIn { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } + } @media (prefers-color-scheme:dark){ code{background-color:#1f2d3d;color:#c9d1d9} @@ -437,6 +450,7 @@ export default function SuggestionBox( top: bottom ? '' : `${top}px`, bottom: bottom ? `${bottom}px` : '', left: `${left}px`, + transformOrigin: `${bottom ? 'bottom' : 'top'} left`, }; return h( @@ -447,7 +461,7 @@ export default function SuggestionBox( 'harper-close-on-escape': new CloseOnEscapeHook(close), }, [ - styleTag(), + styleTag(box.lint.lint_kind), header( box.lint.lint_kind_pretty, lintKindColor(box.lint.lint_kind), @@ -458,7 +472,7 @@ export default function SuggestionBox( ), body(box.lint.message_html), footer( - suggestions(box.lint.suggestions, (v) => { + suggestions(box.lint.lint_kind, box.lint.suggestions, (v) => { box.applySuggestion(v); close(); }), diff --git a/packages/lint-framework/src/lint/lintKindColor.ts b/packages/lint-framework/src/lint/lintKindColor.ts index 2c0cbda6..49c64749 100644 --- a/packages/lint-framework/src/lint/lintKindColor.ts +++ b/packages/lint-framework/src/lint/lintKindColor.ts @@ -1,3 +1,5 @@ +import { getContrastingTextColor } from './utils'; + // First, define the color map as a constant const LINT_KIND_COLORS = { Agreement: '#228B22', // Forest green @@ -29,10 +31,15 @@ export type LintKind = keyof typeof LINT_KIND_COLORS; export const LINT_KINDS = Object.keys(LINT_KIND_COLORS) as LintKind[]; // The main function that uses the map -export default function lintKindColor(lintKindKey: string): string { +export function lintKindColor(lintKindKey: string): string { const color = LINT_KIND_COLORS[lintKindKey as LintKind]; if (!color) { throw new Error(`Unexpected lint kind: ${lintKindKey}`); } return color; } + +export function lintKindTextColor(lintKindKeyOrColor: string): 'black' | 'white' { + const color = LINT_KIND_COLORS[lintKindKeyOrColor as LintKind] ?? lintKindKeyOrColor; + return getContrastingTextColor(color); +} diff --git a/packages/lint-framework/src/lint/utils.ts b/packages/lint-framework/src/lint/utils.ts new file mode 100644 index 00000000..9d73e511 --- /dev/null +++ b/packages/lint-framework/src/lint/utils.ts @@ -0,0 +1,13 @@ +import Color from 'colorjs.io'; + +/** Get the text color that best contrasts with a background of the provided color. */ +export function getContrastingTextColor(color: string): 'black' | 'white' { + const c = new Color(color); + const luminance = c.luminance; + + if (luminance > 0.5) { + return 'black'; + } else { + return 'white'; + } +} diff --git a/packages/web/package.json b/packages/web/package.json index 12fd0c4b..f8934350 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -19,7 +19,6 @@ "autoprefixer": "^10.4.21", "drizzle-kit": "^0.31.5", "flowbite": "^3.1.2", - "flowbite-svelte": "^0.44.18", "svelte": "^5.15.0", "svelte-check": "^4.1.5", "tailwindcss": "^4.1.16", @@ -35,6 +34,7 @@ "@sveltepress/theme-default": "^5.0.7", "@sveltepress/vite": "^1.1.5", "chart.js": "^4.4.8", + "components": "workspace:*", "drizzle-orm": "^0.44.6", "drizzle-zod": "^0.8.3", "harper.js": "workspace:*", diff --git a/packages/web/src/app.css b/packages/web/src/app.css index dd148a09..3370b8f0 100644 --- a/packages/web/src/app.css +++ b/packages/web/src/app.css @@ -1,53 +1,118 @@ @import "tailwindcss"; +@import "components/components.css"; -@layer base { - body { - @apply bg-white text-black dark:bg-gray-900 dark:text-white; - } +@custom-variant dark (&:where(.dark, .dark *)); - ul { - @apply list-disc pl-4; - } +@theme { + --color-primary-50: #fef4e7; /* honey bronze */ + --color-primary-100: #fce9cf; + --color-primary-200: #f9d49f; + --color-primary-300: #f7be6e; + --color-primary-400: #f4a83e; + --color-primary: #f1920e; + --color-primary-600: #c1750b; + --color-primary-700: #915808; + --color-primary-800: #603b06; + --color-primary-900: #301d03; + --color-primary-950: #221402; - ol { - @apply list-decimal pl-4; - } + --color-accent-50: #fee7e9; /* hot fuchsia */ + --color-accent-100: #fccfd3; + --color-accent-200: #f99fa6; + --color-accent-300: #f76e7a; + --color-accent-400: #f43e4d; + --color-accent: #f10e21; + --color-accent-600: #c10b1a; + --color-accent-700: #910814; + --color-accent-800: #60060d; + --color-accent-900: #300307; + --color-accent-950: #220205; - h1 { - @apply text-4xl font-extrabold tracking-tight lg:text-5xl py-4; - } + --color-cream: #fef4e7; /* simple cream */ + --color-cream-100: #fce9cf; + --color-cream-200: #f9d49f; + --color-cream-300: #f7be6e; + --color-cream-400: #f4a83e; + --color-cream-500: #f1920e; + --color-cream-600: #c1750b; + --color-cream-700: #915808; + --color-cream-800: #603b06; + --color-cream-900: #301d03; + --color-cream-950: #221402; - h2 { - @apply text-3xl font-semibold tracking-tight py-4; - } + --color-champagne-mist-50: #fef4e7; + --color-champagne-mist-100: #fce9cf; + --color-champagne-mist-200: #fad49e; + --color-champagne-mist-300: #f7be6e; + --color-champagne-mist-400: #f5a83d; + --color-champagne-mist-500: #f2930d; + --color-champagne-mist-600: #c2750a; + --color-champagne-mist-700: #915808; + --color-champagne-mist-800: #613b05; + --color-champagne-mist-900: #301d03; + --color-champagne-mist-950: #221502; - h3 { - @apply text-2xl font-semibold tracking-tight py-4; - } - - h4 { - @apply text-xl font-semibold tracking-tight; - } - - p { - @apply leading-7 [&:not(:first-child)]:mt-6; - } - - a { - @apply underline-offset-4 underline; - } - - blockquote { - @apply mt-6 border-l-2 border-gray-200 pl-6 italic dark:border-gray-700; - } + --color-white: #fffdfa; + --color-white-100: #fceacf; + --color-white-200: #fad59e; + --color-white-300: #f7c06e; + --color-white-400: #f5ab3d; + --color-white-500: #f2960d; + --color-white-600: #c2780a; + --color-white-700: #915a08; + --color-white-800: #613c05; + --color-white-900: #301e03; + --color-white-950: #221502; } -* { +body { + @apply bg-white text-black dark:bg-black dark:text-white; + font-family: Atkinson Hyperlegible, sans-serif; } +ul { + @apply list-disc pl-4; +} + +ol { + @apply list-decimal pl-4; +} + +h1 { + @apply text-4xl font-extrabold tracking-tight lg:text-5xl py-4; + font-family: Domine, serif; +} + +h2 { + @apply text-3xl font-semibold tracking-tight py-4; + font-family: Domine, serif; +} + +h3 { + @apply text-2xl font-semibold tracking-tight py-4; + font-family: Domine, serif; +} + +h4 { + @apply text-xl font-semibold tracking-tight; + font-family: Domine, serif; +} + +p { + @apply leading-7 [&:not(:first-child)]:mt-6; +} + +a { + @apply underline-offset-4 text-black dark:text-white; +} + +blockquote { + @apply mt-6 border-l-2 border-gray-200 pl-6 italic dark:border-gray-700; +} + code { font-family: "JetBrains Mono", monospace; word-break: keep-all; diff --git a/packages/web/src/app.html b/packages/web/src/app.html index 58bc9add..dcff643f 100644 --- a/packages/web/src/app.html +++ b/packages/web/src/app.html @@ -70,10 +70,22 @@ + + + + + + %sveltekit.head% - +
%sveltekit.body%
diff --git a/packages/web/src/lib/components/DefaultNeovimConfig.svelte b/packages/web/src/lib/components/DefaultNeovimConfig.svelte index 758f6105..4a59cf17 100644 --- a/packages/web/src/lib/components/DefaultNeovimConfig.svelte +++ b/packages/web/src/lib/components/DefaultNeovimConfig.svelte @@ -1,5 +1,5 @@ -
- +
+
{@html content.replace(/\n\n/g, '
')}
diff --git a/packages/web/src/lib/components/LazyEditor.svelte b/packages/web/src/lib/components/LazyEditor.svelte index 00f53cc4..c5735d29 100644 --- a/packages/web/src/lib/components/LazyEditor.svelte +++ b/packages/web/src/lib/components/LazyEditor.svelte @@ -1,5 +1,5 @@ -