From 5d3beaad4f93dc17978615ffcf1aad2221d6e138 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Fri, 28 Nov 2025 13:25:20 -0700 Subject: [PATCH 01/84] fix: ignore test dataset files from source code line count --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index 6313b56c..8e79ade1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,3 @@ * text=auto eol=lf +quill_simple.html linguist-generated +github_textarea.html linguist-generated From 8fff5e9a56087bbc333faa9ad6f3a9a33931b0da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 08:41:10 -0700 Subject: [PATCH 02/84] build(deps): bump tracing from 0.1.41 to 0.1.43 (#2271) Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.41 to 0.1.43. - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.41...tracing-0.1.43) --- updated-dependencies: - dependency-name: tracing dependency-version: 0.1.43 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 | 12 ++++++------ harper-ls/Cargo.toml | 2 +- harper-wasm/Cargo.toml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0bb57ba8..c8a186e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5636,9 +5636,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -5659,9 +5659,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -5670,9 +5670,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" dependencies = [ "once_cell", "valuable", diff --git a/harper-ls/Cargo.toml b/harper-ls/Cargo.toml index dcfa0e17..fdb9d5ec 100644 --- a/harper-ls/Cargo.toml +++ b/harper-ls/Cargo.toml @@ -24,7 +24,7 @@ dirs = "6.0.0" anyhow = "1.0.100" serde_json = "1.0.145" itertools = "0.14.0" -tracing = { version = "0.1.41", default-features = false, features = ["std"] } +tracing = { version = "0.1.43", default-features = false, features = ["std"] } tracing-subscriber = { version = "0.3.20", default-features = false, features = ["fmt", "std"] } resolve-path = "0.1.0" open = "5.3.3" diff --git a/harper-wasm/Cargo.toml b/harper-wasm/Cargo.toml index 1c4f1029..996c3287 100644 --- a/harper-wasm/Cargo.toml +++ b/harper-wasm/Cargo.toml @@ -10,7 +10,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] console_error_panic_hook = "0.1.7" -tracing = "0.1.41" +tracing = "0.1.43" tracing-wasm = "0.2.1" wasm-bindgen = "0.2.97" harper-core = { path = "../harper-core", version = "1.0.0", features = ["concurrent"] } From 538b21fb0c4828b4314b3a3d27f20736c97558d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 08:41:29 -0700 Subject: [PATCH 03/84] build(deps): bump indexmap from 2.12.0 to 2.12.1 (#2274) Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.12.0 to 2.12.1. - [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md) - [Commits](https://github.com/indexmap-rs/indexmap/compare/2.12.0...2.12.1) --- updated-dependencies: - dependency-name: indexmap dependency-version: 2.12.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 | 4 ++-- harper-ls/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c8a186e3..3681746b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3235,9 +3235,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown 0.16.1", diff --git a/harper-ls/Cargo.toml b/harper-ls/Cargo.toml index fdb9d5ec..1e58257b 100644 --- a/harper-ls/Cargo.toml +++ b/harper-ls/Cargo.toml @@ -30,7 +30,7 @@ resolve-path = "0.1.0" open = "5.3.3" futures = "0.3.31" serde = { version = "1.0.228", features = ["derive"] } -indexmap = { version = "2.12.0", features = ["serde"] } +indexmap = { version = "2.12.1", features = ["serde"] } globset = "0.4.18" harper-ink = { version = "1.0.0", path = "../harper-ink" } From 5ac4c0ad2e46a8c6afa9a06d277599537af74e26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 08:41:44 -0700 Subject: [PATCH 04/84] build(deps): bump criterion from 0.7.0 to 0.8.0 (#2272) Bumps [criterion](https://github.com/criterion-rs/criterion.rs) from 0.7.0 to 0.8.0. - [Release notes](https://github.com/criterion-rs/criterion.rs/releases) - [Changelog](https://github.com/criterion-rs/criterion.rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/criterion-rs/criterion.rs/compare/criterion-plot-v0.7.0...criterion-v0.8.0) --- updated-dependencies: - dependency-name: criterion dependency-version: 0.8.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 29 +++++++++++++++++++++++++---- harper-core/Cargo.toml | 2 +- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3681746b..05a43974 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -1008,10 +1017,11 @@ dependencies = [ [[package]] name = "criterion" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +checksum = "a0dfe5e9e71bdcf4e4954f7d14da74d1cdb92a3a07686452d1509652684b1aab" dependencies = [ + "alloca", "anes", "cast", "ciborium", @@ -1020,6 +1030,7 @@ dependencies = [ "itertools 0.13.0", "num-traits", "oorandom", + "page_size", "regex", "serde", "serde_json", @@ -1029,9 +1040,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +checksum = "5de36c2bee19fba779808f92bf5d9b0fa5a40095c277aba10c458a12b35d21d6" dependencies = [ "cast", "itertools 0.13.0", @@ -3970,6 +3981,16 @@ dependencies = [ "serde", ] +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "parking" version = "2.2.1" diff --git a/harper-core/Cargo.toml b/harper-core/Cargo.toml index 5650a6d3..0aa29ba4 100644 --- a/harper-core/Cargo.toml +++ b/harper-core/Cargo.toml @@ -36,7 +36,7 @@ bitflags = { version = "2.10.0", features = ["serde"] } trie-rs = "0.4.2" [dev-dependencies] -criterion = { version = "0.7.0", default-features = false } +criterion = { version = "0.8.0", default-features = false } rand = "0.8.5" quickcheck = "1.0.3" quickcheck_macros = "1.1.0" From 4c539bc75fb2bd402a6eaad5d5af98b1b7a172a9 Mon Sep 17 00:00:00 2001 From: Andrew Dunbar Date: Mon, 1 Dec 2025 16:58:09 +0000 Subject: [PATCH 05/84] refactor: upgrade `Expatriate` to `phrase_set_corrections` (#2265) --- .../src/linting/phrase_corrections/mod.rs | 7 -- .../src/linting/phrase_corrections/tests.rs | 3 - .../src/linting/phrase_set_corrections/mod.rs | 20 +++++ .../linting/phrase_set_corrections/tests.rs | 77 +++++++++++++++++++ 4 files changed, 97 insertions(+), 10 deletions(-) diff --git a/harper-core/src/linting/phrase_corrections/mod.rs b/harper-core/src/linting/phrase_corrections/mod.rs index f8202ae9..6469f698 100644 --- a/harper-core/src/linting/phrase_corrections/mod.rs +++ b/harper-core/src/linting/phrase_corrections/mod.rs @@ -426,13 +426,6 @@ pub fn lint_group() -> LintGroup { "Expands the abbreviation `w/o` to the full word `without` for clarity.", LintKind::Style ), - "Expatriate" => ( - ["ex-patriot"], - ["expatriate"], - "Use the correct term for someone living abroad.", - "Fixes the misinterpretation of `expatriate`, ensuring the correct term is used for individuals residing abroad.", - LintKind::Eggcorn - ), "FaceFirst" => ( ["face first into"], ["face-first into"], diff --git a/harper-core/src/linting/phrase_corrections/tests.rs b/harper-core/src/linting/phrase_corrections/tests.rs index 89305819..00901ac7 100644 --- a/harper-core/src/linting/phrase_corrections/tests.rs +++ b/harper-core/src/linting/phrase_corrections/tests.rs @@ -770,9 +770,6 @@ fn expand_cuz() { // ExpandWithout // -none- -// Expatriate -// -none- - // FaceFirst // -none- diff --git a/harper-core/src/linting/phrase_set_corrections/mod.rs b/harper-core/src/linting/phrase_set_corrections/mod.rs index d7f99152..6a2c0027 100644 --- a/harper-core/src/linting/phrase_set_corrections/mod.rs +++ b/harper-core/src/linting/phrase_set_corrections/mod.rs @@ -352,6 +352,26 @@ pub fn lint_group() -> LintGroup { "Suggests using either `await` or `wait for` but not both, as they express the same meaning.", LintKind::Redundancy ), + "Expat" => ( + &[ + (&["ex-pat", "ex pat"], &["expat"]), + (&["ex-pats", "ex pats"], &["expats"]), + (&["ex-pat's", "ex pat's"], &["expat's"]), + ], + "The correct spelling is `expat` with no hyphen or space.", + "Corrects the mistake of writing `expat` as two words.", + LintKind::Spelling + ), + "Expatriate" => ( + &[ + (&["ex-patriot", "expatriot", "ex patriot"], &["expatriate"]), + (&["ex-patriots", "expatriots", "ex patriots"], &["expatriates"]), + (&["ex-patriot's", "expatriot's", "ex patriot's"], &["expatriate's"]), + ], + "Use the correct term for someone living abroad.", + "Fixes the misinterpretation of `expatriate`, ensuring the correct term is used for individuals residing abroad.", + LintKind::Eggcorn + ), "GetRidOf" => ( &[ (&["get rid off", "get ride of", "get ride off"], &["get rid of"]), diff --git a/harper-core/src/linting/phrase_set_corrections/tests.rs b/harper-core/src/linting/phrase_set_corrections/tests.rs index dcb114fa..b4d86649 100644 --- a/harper-core/src/linting/phrase_set_corrections/tests.rs +++ b/harper-core/src/linting/phrase_set_corrections/tests.rs @@ -888,6 +888,83 @@ fn correct_awaited_for() { ); } +// Expat + +#[test] +fn correct_ex_pat_hyphen() { + assert_suggestion_result( + "It seems ex-pat means the person will be in a foreign country temporarily", + lint_group(), + "It seems expat means the person will be in a foreign country temporarily", + ); +} + +#[test] +fn correct_ex_pats_hyphen() { + assert_suggestion_result( + "So, it might be correct to call most Brits ex-pats.", + lint_group(), + "So, it might be correct to call most Brits expats.", + ); +} + +#[test] +fn correct_ex_pat_space() { + assert_suggestion_result( + "For me, the term ex pat embodies the exquisite hypocrisy of certain people feeling entitled", + lint_group(), + "For me, the term expat embodies the exquisite hypocrisy of certain people feeling entitled", + ); +} + +#[test] +#[ignore = "replace_with_match_case results in ExPats"] +fn correct_ex_pats_space() { + assert_suggestion_result( + "Why are Brits who emigrate \"Ex Pats\" but people who come here \"immigrants\"?", + lint_group(), + "Why are Brits who emigrate \"Expats\" but people who come here \"immigrants\"?", + ); +} + +// Expatriate + +#[test] +fn correct_expatriot() { + assert_suggestion_result( + "Another expatriot of the era, James Joyce, also followed Papa's writing and drinking schedule.", + lint_group(), + "Another expatriate of the era, James Joyce, also followed Papa's writing and drinking schedule.", + ); +} + +#[test] +fn correct_expatriots() { + assert_suggestion_result( + "Expatriots, upon discovering the delightful nuances of Dutch pronunciation, often find themselves in stitches.", + lint_group(), + "Expatriates, upon discovering the delightful nuances of Dutch pronunciation, often find themselves in stitches.", + ); +} + +#[test] +fn correct_ex_patriot_hyphen() { + assert_suggestion_result( + "Then I added we should all be using the word 移民 immigrant, not ex-patriot, not 外国人 gaikokujin, and definitely not 外人 gaijin", + lint_group(), + "Then I added we should all be using the word 移民 immigrant, not expatriate, not 外国人 gaikokujin, and definitely not 外人 gaijin", + ); +} + +#[test] +fn correct_ex_patriots_hyphen() { + assert_suggestion_result( + "Ex-patriots who move to Hong Kong to seek greener pastures and to experience a new culture seem to bring their own cultural baggage with them.", + lint_group(), + "Expatriates who move to Hong Kong to seek greener pastures and to experience a new culture seem to bring their own cultural baggage with them.", + ); +} + // GetRidOf #[test] From 63914aa23e28b03cee4c7fe6003f8f9d38c00079 Mon Sep 17 00:00:00 2001 From: Matthew Espino <65783406+mcecode@users.noreply.github.com> Date: Mon, 1 Dec 2025 18:30:25 +0000 Subject: [PATCH 06/84] fix(dict): update webpack entry (#2278) --- harper-core/dictionary.dict | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/harper-core/dictionary.dict b/harper-core/dictionary.dict index 43e458ec..d0d7ab8e 100644 --- a/harper-core/dictionary.dict +++ b/harper-core/dictionary.dict @@ -53305,14 +53305,12 @@ WSL/Og # windows subsystem for linux Waymo/Og WeChat/Og WebAssembly/g # see WASM -Webpack/Og WebDAV/Og WebGL/Og WebGPU/Sg WebKit/Og WebRTC/Og WebSocket/Sg -Webpack/Og WhatsApp/NOgVG # messaging app Wikilink/NSg # !! please check and comment !! elsewhere we have wikilink Wikimedia/Og # foundation that runs Wikipedia etc. @@ -53456,6 +53454,7 @@ uptime/NwgS vid/NgS # video waitlist/NgS watchOS/Og +webpack/Og whitespace/~NSg # dictionaries prefer: white space wikilink/~NSg # !! please check and comment !! elsewhere we have Wikilink XeTeX/Og # TeX typesetting engine From 212508e1d9209c6420a8f24a59725926f0679d39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:30:58 -0700 Subject: [PATCH 07/84] build(deps): bump tracing-subscriber from 0.3.20 to 0.3.22 (#2273) Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.3.20 to 0.3.22. - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.20...tracing-subscriber-0.3.22) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-version: 0.3.22 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 | 4 ++-- harper-ls/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 05a43974..97c62edd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5712,9 +5712,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "nu-ansi-term", "sharded-slab", diff --git a/harper-ls/Cargo.toml b/harper-ls/Cargo.toml index 1e58257b..60b57a4d 100644 --- a/harper-ls/Cargo.toml +++ b/harper-ls/Cargo.toml @@ -25,7 +25,7 @@ anyhow = "1.0.100" serde_json = "1.0.145" itertools = "0.14.0" tracing = { version = "0.1.43", default-features = false, features = ["std"] } -tracing-subscriber = { version = "0.3.20", default-features = false, features = ["fmt", "std"] } +tracing-subscriber = { version = "0.3.22", default-features = false, features = ["fmt", "std"] } resolve-path = "0.1.0" open = "5.3.3" futures = "0.3.31" From 611475176d635dca654e2c1ab50a13a93136fc35 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Tue, 2 Dec 2025 08:35:30 -0700 Subject: [PATCH 08/84] fix(core): address edge cases in `title_case` module (#2284) --- harper-core/dictionary.dict | 2 ++ harper-core/src/title_case.rs | 24 +++++++++++++++++++ .../text/linters/Computer science.snap.yml | 11 --------- .../tests/text/tagged/Computer science.md | 2 +- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/harper-core/dictionary.dict b/harper-core/dictionary.dict index d0d7ab8e..d740f1ee 100644 --- a/harper-core/dictionary.dict +++ b/harper-core/dictionary.dict @@ -7820,6 +7820,7 @@ PRC/ONg # mainland China PRO/N PS/ONgJ # postscript; multiple meanings as implied by the attributes? P.S. # postscript +U.S./Og # United States PST/ONg PSU/Sg # power supply unit PT/NOJ @@ -54136,3 +54137,4 @@ topicalization/Nmg typological/JQ volitive/NSgJ worldbuild/Vd>G +WordCamp/gO diff --git a/harper-core/src/title_case.rs b/harper-core/src/title_case.rs index 453825ab..264ca2ed 100644 --- a/harper-core/src/title_case.rs +++ b/harper-core/src/title_case.rs @@ -243,4 +243,28 @@ mod tests { "United States" ) } + + #[test] + fn keeps_decimal() { + assert_eq!( + make_title_case_str( + "harper turns 1.0 today", + &PlainEnglish, + &FstDictionary::curated() + ), + "Harper Turns 1.0 Today" + ) + } + + #[test] + fn fixes_odd_capitalized_proper_nouns() { + assert_eq!( + make_title_case_str( + "i spoke at wordcamp u.s. in 2025", + &PlainEnglish, + &FstDictionary::curated() + ), + "I Spoke at WordCamp U.S. in 2025", + ); + } } diff --git a/harper-core/tests/text/linters/Computer science.snap.yml b/harper-core/tests/text/linters/Computer science.snap.yml index b20ba6cc..9e5c6d2d 100644 --- a/harper-core/tests/text/linters/Computer science.snap.yml +++ b/harper-core/tests/text/linters/Computer science.snap.yml @@ -491,17 +491,6 @@ Message: | -Lint: Spelling (63 priority) -Message: | - 137 | "In the U.S., however, informatics is linked with applied computing, or - | ^~~~ Did you mean to spell `U.S.` this way? -Suggest: - - Replace with: “P.S.” - - Replace with: “UBS” - - Replace with: “USA” - - - Lint: Spelling (63 priority) Message: | 140 | A folkloric quotation, often attributed to—but almost certainly not first diff --git a/harper-core/tests/text/tagged/Computer science.md b/harper-core/tests/text/tagged/Computer science.md index 1b88867c..7d570c31 100644 --- a/harper-core/tests/text/tagged/Computer science.md +++ b/harper-core/tests/text/tagged/Computer science.md @@ -269,7 +269,7 @@ > adopted in the UK ( as in the School of Informatics , University of Edinburgh ) . # VP/J NPr/J/R/P D+ NPr+ . NSg/R NPr/J/R/P D N🅪Sg/VB P Nᴹ . NSg P NPr+ . . > " In the U.S. , however , informatics is linked with applied computing , or -# . NPr/J/R/P D+ ? . C . Nᴹ VL3 VP/J P VP/J Nᴹ/Vg/J+ . NPr/C +# . NPr/J/R/P D+ NPr+ . C . Nᴹ VL3 VP/J P VP/J Nᴹ/Vg/J+ . NPr/C > computing in the context of another domain . " # Nᴹ/Vg/J+ NPr/J/R/P D N🅪Sg/VB P I/D NSg+ . . > From edff925df0ab5d46c73dfa0dc990b3486f33e385 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Tue, 2 Dec 2025 08:38:55 -0700 Subject: [PATCH 09/84] fix(chrome-ext): return focus to source element when `SuggestionBox` is closed (#2282) * fix(chrome-ext): return focus to source element when `SuggestionBox` is closed * test(chrome-ext): make sure focus ends up in the right places * fix(chrome-ext): ignoring a suggestion should result in the right focus as well --- .../tests/simple_textarea.spec.ts | 3 +++ packages/chrome-plugin/tests/testUtils.ts | 17 ++++++++++++++ .../lint-framework/src/lint/SuggestionBox.ts | 23 +++++++++++++++---- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/packages/chrome-plugin/tests/simple_textarea.spec.ts b/packages/chrome-plugin/tests/simple_textarea.spec.ts index b9b83a26..1e482da3 100644 --- a/packages/chrome-plugin/tests/simple_textarea.spec.ts +++ b/packages/chrome-plugin/tests/simple_textarea.spec.ts @@ -1,6 +1,7 @@ import { test } from './fixtures'; import { assertHarperHighlightBoxes, + assertLocatorIsFocused, clickHarperHighlight, getTextarea, replaceEditorContent, @@ -73,4 +74,6 @@ test('Can dismiss with escape key', async ({ page }) => { await page.keyboard.press('Escape'); await page.locator('.harper-container').waitFor({ state: 'hidden' }); + + await assertLocatorIsFocused(page, editor); }); diff --git a/packages/chrome-plugin/tests/testUtils.ts b/packages/chrome-plugin/tests/testUtils.ts index efd47466..7c9b895a 100644 --- a/packages/chrome-plugin/tests/testUtils.ts +++ b/packages/chrome-plugin/tests/testUtils.ts @@ -38,6 +38,20 @@ export function getHarperHighlights(page: Page): Locator { return page.locator('#harper-highlight'); } +export async function assertLocatorIsFocused(page: Page, loc: Locator) { + await assertLocatorsResolveEqually(page, loc, page.locator(':focus')); +} + +/** Checks that the two provided locators resolve to the same element. */ +export async function assertLocatorsResolveEqually(page: Page, a: Locator, b: Locator) { + const areSame = await page.evaluate( + ([a, b]) => a === b, + [await a.elementHandle(), await b.elementHandle()], + ); + + expect(areSame).toBe(true); +} + /** Locates the first Harper highlight on the page and clicks it. * It should result in the popup opening. * Returns whether the highlight was found. */ @@ -97,6 +111,7 @@ export async function testBasicSuggestionTextarea(testPageUrl: TestPageUrlProvid await page.waitForTimeout(3000); expect(editor).toHaveValue('This is a test'); + await assertLocatorIsFocused(page, editor); }); } @@ -126,6 +141,7 @@ export async function testCanIgnoreTextareaSuggestion(testPageUrl: TestPageUrlPr // Nothing should change. expect(editor).toHaveValue(cacheSalt); expect(await clickHarperHighlight(page)).toBe(false); + await assertLocatorIsFocused(page, editor); }); } @@ -146,6 +162,7 @@ export async function testCanBlockRuleTextareaSuggestion(testPageUrl: TestPageUr await page.waitForTimeout(1000); await assertHarperHighlightBoxes(page, []); + await assertLocatorIsFocused(page, editor); }); } diff --git a/packages/lint-framework/src/lint/SuggestionBox.ts b/packages/lint-framework/src/lint/SuggestionBox.ts index e46f5013..c7f50543 100644 --- a/packages/lint-framework/src/lint/SuggestionBox.ts +++ b/packages/lint-framework/src/lint/SuggestionBox.ts @@ -16,6 +16,8 @@ function iconSvg(definition: IconDefinition): string { const settingsIconSvg = iconSvg(faGear); const disableIconSvg = iconSvg(faBan); +let previouslyActiveElement: null | HTMLElement = null; + var FocusHook: any = function () {}; FocusHook.prototype.hook = function (node: any, _propertyName: any, _previousValue: any) { if ((node as any).__harperAutofocused) { @@ -23,6 +25,10 @@ FocusHook.prototype.hook = function (node: any, _propertyName: any, _previousVal } requestAnimationFrame(() => { + if (document.activeElement?.tagName.toLowerCase() != 'harper-render-box') { + previouslyActiveElement = document.activeElement as HTMLElement; + } + node.focus(); Object.defineProperty(node, '__harperAutofocused', { value: true, @@ -453,19 +459,26 @@ export default function SuggestionBox( transformOrigin: `${bottom ? 'bottom' : 'top'} left`, }; + const ignoreLintCallback = box.ignoreLint; + + const refocusClose = () => { + previouslyActiveElement?.focus(); + close(); + }; + return h( 'div', { className: 'harper-container fade-in', style: positionStyle, - 'harper-close-on-escape': new CloseOnEscapeHook(close), + 'harper-close-on-escape': new CloseOnEscapeHook(refocusClose), }, [ styleTag(box.lint.lint_kind), header( box.lint.lint_kind_pretty, lintKindColor(box.lint.lint_kind), - close, + refocusClose, actions.openOptions, box.rule, actions.setRuleEnabled, @@ -474,13 +487,15 @@ export default function SuggestionBox( footer( suggestions(box.lint.lint_kind, box.lint.suggestions, (v) => { box.applySuggestion(v); - close(); + refocusClose(); }), [ box.lint.lint_kind === 'Spelling' && actions.addToUserDictionary ? addToDictionary(box, actions.addToUserDictionary) : undefined, - box.ignoreLint ? ignoreLint(box.ignoreLint) : undefined, + ignoreLintCallback + ? ignoreLint(() => ignoreLintCallback().then(refocusClose)) + : undefined, ], ), hintDrawer(hint), From 5d7bbeefc9bb609abd11f919eb9d8eee93ae3330 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Tue, 2 Dec 2025 08:46:41 -0700 Subject: [PATCH 10/84] fix(core): ignore inflections of `be` in compound noun rule (#2281) --- harper-core/src/expr/mod.rs | 6 ++++++ .../compound_nouns/compound_noun_after_det_adj.rs | 4 +++- harper-core/tests/run_tests.rs | 1 + harper-core/tests/test_sources/issue_1988.md | 1 + .../The Constitution of the United States.snap.yml | 10 ---------- 5 files changed, 11 insertions(+), 11 deletions(-) create mode 100644 harper-core/tests/test_sources/issue_1988.md diff --git a/harper-core/src/expr/mod.rs b/harper-core/src/expr/mod.rs index 952ef503..51b8833c 100644 --- a/harper-core/src/expr/mod.rs +++ b/harper-core/src/expr/mod.rs @@ -155,6 +155,7 @@ where pub trait OwnedExprExt { fn or(self, other: impl Expr + 'static) -> FirstMatchOf; fn and(self, other: impl Expr + 'static) -> All; + fn and_not(self, other: impl Expr + 'static) -> All; fn or_longest(self, other: impl Expr + 'static) -> LongestMatchOf; } @@ -172,6 +173,11 @@ where All::new(vec![Box::new(self), Box::new(other)]) } + /// Returns an expression that matches only if the current one matches and the expression contained in `other` does not. + fn and_not(self, other: impl Expr + 'static) -> All { + self.and(UnlessStep::new(other, |_tok: &Token, _src: &[char]| true)) + } + /// Returns an expression that matches the longest of the current one or the expression contained in `other`. /// /// If you don't need the longest match, prefer using the short-circuiting [`Self::or()`] instead. diff --git a/harper-core/src/linting/compound_nouns/compound_noun_after_det_adj.rs b/harper-core/src/linting/compound_nouns/compound_noun_after_det_adj.rs index 56a47d47..6b2045a7 100644 --- a/harper-core/src/linting/compound_nouns/compound_noun_after_det_adj.rs +++ b/harper-core/src/linting/compound_nouns/compound_noun_after_det_adj.rs @@ -1,7 +1,9 @@ use crate::expr::All; use crate::expr::Expr; use crate::expr::MergeableWords; +use crate::expr::OwnedExprExt; use crate::expr::SequenceExpr; +use crate::patterns::InflectionOfBe; use crate::{CharStringExt, TokenStringExt, linting::ExprLinter}; use super::{Lint, LintKind, Suggestion, is_content_word, predicate}; @@ -31,7 +33,7 @@ impl Default for CompoundNounAfterDetAdj { .t_ws() .then(is_content_word) .t_ws() - .then(is_content_word); + .then(is_content_word.and_not(InflectionOfBe::default())); let split_expr = Lrc::new(MergeableWords::new(|meta_closed, meta_open| { predicate(meta_closed, meta_open) diff --git a/harper-core/tests/run_tests.rs b/harper-core/tests/run_tests.rs index 61b8df5e..ca46220a 100644 --- a/harper-core/tests/run_tests.rs +++ b/harper-core/tests/run_tests.rs @@ -89,6 +89,7 @@ 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_1988.md, 0, Dialect::American); create_test!(issue_2054_clean.md, 0, Dialect::British); create_test!(issue_1873.md, 0, Dialect::British); create_test!(issue_2233.md, 0, Dialect::American); diff --git a/harper-core/tests/test_sources/issue_1988.md b/harper-core/tests/test_sources/issue_1988.md new file mode 100644 index 00000000..187611b9 --- /dev/null +++ b/harper-core/tests/test_sources/issue_1988.md @@ -0,0 +1 @@ +When this test is run, it returns a result. 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 4df3d006..2dcd5fcc 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 @@ -225,16 +225,6 @@ Message: | -Lint: WordChoice (63 priority) -Message: | - 89 | third Class at the Expiration of the sixth Year, so that one third may be - | ^~~~~~ Did you mean the closed compound noun “maybe”? - 90 | chosen every second Year; and when vacancies happen in the representation of -Suggest: - - Replace with: “maybe” - - - Lint: Readability (127 priority) Message: | 96 | No Person shall be a Senator who shall not have attained to the Age of thirty From d31396e7a3ee02c8273f303fa6eba7e728d90a4f Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Tue, 2 Dec 2025 08:47:13 -0700 Subject: [PATCH 11/84] fix(core): add `bypassable` to the dictionary (#2280) --- harper-core/dictionary.dict | 2 +- harper-core/tests/run_tests.rs | 1 + harper-core/tests/test_sources/issue_2246.md | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 harper-core/tests/test_sources/issue_2246.md diff --git a/harper-core/dictionary.dict b/harper-core/dictionary.dict index d740f1ee..8765d69a 100644 --- a/harper-core/dictionary.dict +++ b/harper-core/dictionary.dict @@ -16086,7 +16086,7 @@ bye/~NSgJP bygone/JNSg bylaw/NSg byline/NSgV -bypass/~NgSVGd +bypass/~NgSVGdB bypath/Ng bypaths/N byplay/Ng diff --git a/harper-core/tests/run_tests.rs b/harper-core/tests/run_tests.rs index ca46220a..c9ad5efb 100644 --- a/harper-core/tests/run_tests.rs +++ b/harper-core/tests/run_tests.rs @@ -92,6 +92,7 @@ create_test!(issue_2054.md, 6, Dialect::British); create_test!(issue_1988.md, 0, Dialect::American); create_test!(issue_2054_clean.md, 0, Dialect::British); create_test!(issue_1873.md, 0, Dialect::British); +create_test!(issue_2246.md, 0, Dialect::American); create_test!(issue_2233.md, 0, Dialect::American); // It just matters that it is > 1 create_test!(issue_2151.md, 4, Dialect::British); diff --git a/harper-core/tests/test_sources/issue_2246.md b/harper-core/tests/test_sources/issue_2246.md new file mode 100644 index 00000000..ecb4694d --- /dev/null +++ b/harper-core/tests/test_sources/issue_2246.md @@ -0,0 +1 @@ +But current implementations will likely be bypassable. From 8dd02aad4e4fc29f49a0265d7bb6f3077ec55930 Mon Sep 17 00:00:00 2001 From: Andrew Dunbar Date: Tue, 2 Dec 2025 15:51:00 +0000 Subject: [PATCH 12/84] =?UTF-8?q?feat:=20copywritten=20=E2=86=92=20copyrig?= =?UTF-8?q?hted=20(#2276)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: copywritten → copyrighted * fix: `cargo fmt` --- harper-core/dictionary.dict | 8 ++-- .../src/linting/phrase_set_corrections/mod.rs | 11 +++++ .../linting/phrase_set_corrections/tests.rs | 47 +++++++++++++++++++ .../Alice's Adventures in Wonderland.md | 6 +-- .../tests/text/tagged/Difficult sentences.md | 2 +- .../tests/text/tagged/The Great Gatsby.md | 22 ++++----- 6 files changed, 78 insertions(+), 18 deletions(-) diff --git a/harper-core/dictionary.dict b/harper-core/dictionary.dict index 8765d69a..b1fbac3f 100644 --- a/harper-core/dictionary.dict +++ b/harper-core/dictionary.dict @@ -19184,8 +19184,10 @@ copycatting/V6 copyist/NgS copyleft/NV copyright/~NSgVGd -copywrite/VSdG +copywrite/VSG copywriter/NgS +copywritten/VT +copywrote/Vt coquetry/NSg coquette/NSgVdGJ coquettish/JY @@ -48487,7 +48489,7 @@ threnody/NSg thresh/Vd>SGgZ thresher/Ng threshold/~NSgG -threw/~V +threw/~Vt thrice/~ thrift/~NSgV thriftily/Ry @@ -48525,7 +48527,7 @@ throw/~V>GSNgZ throwaway/JNSg throwback/NSg thrower/~Ng -thrown/~VJ +thrown/~VTJ thru/~P thrum/NSgVJ thrummed/VtTJ diff --git a/harper-core/src/linting/phrase_set_corrections/mod.rs b/harper-core/src/linting/phrase_set_corrections/mod.rs index 6a2c0027..7d9233af 100644 --- a/harper-core/src/linting/phrase_set_corrections/mod.rs +++ b/harper-core/src/linting/phrase_set_corrections/mod.rs @@ -352,6 +352,17 @@ pub fn lint_group() -> LintGroup { "Suggests using either `await` or `wait for` but not both, as they express the same meaning.", LintKind::Redundancy ), + "Copyright" => ( + &[ + (&["copywrite"], &["copyright"]), + (&["copywrites"], &["copyrights"]), + (&["copywriting"], &["copyrighting"]), + (&["copywritten", "copywrited", "copywrote"], &["copyrighted"]), + ], + "Did you mean `copyright`? `Copywrite` means to write copy (advertising text), while `copyright` is the legal right to control use of creative works.", + "Corrects `copywrite` to `copyright`. `Copywrite` refers to writing copy, while `copyright` is the legal right to creative works.", + LintKind::WordChoice + ), "Expat" => ( &[ (&["ex-pat", "ex pat"], &["expat"]), diff --git a/harper-core/src/linting/phrase_set_corrections/tests.rs b/harper-core/src/linting/phrase_set_corrections/tests.rs index b4d86649..b014be93 100644 --- a/harper-core/src/linting/phrase_set_corrections/tests.rs +++ b/harper-core/src/linting/phrase_set_corrections/tests.rs @@ -888,6 +888,53 @@ fn correct_awaited_for() { ); } +// Copyright + +#[test] +fn copywritten() { + assert_suggestion_result( + "Including digital copies of copywritten artwork with the project isn't advised.", + lint_group(), + "Including digital copies of copyrighted artwork with the project isn't advised.", + ); +} + +#[test] +fn copywrites() { + assert_suggestion_result( + "Code is 99% copy/pasted from OpenSSH with an attempt to retain all copywrites", + lint_group(), + "Code is 99% copy/pasted from OpenSSH with an attempt to retain all copyrights", + ); +} + +#[test] +fn copywrited() { + assert_suggestion_result( + "Proprietary copywrited code", + lint_group(), + "Proprietary copyrighted code", + ); +} + +#[test] +fn copywrited_all_caps() { + assert_suggestion_result( + "URLS MAY CONTAIN COPYWRITED MATERIAL", + lint_group(), + "URLS MAY CONTAIN COPYRIGHTED MATERIAL", + ); +} + +#[test] +fn copywrote() { + assert_suggestion_result( + "How do you find out if someone copywrote a movie", + lint_group(), + "How do you find out if someone copyrighted a movie", + ); +} + // Expat #[test] 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 0ad2f112..0bd253b7 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 @@ -2429,7 +2429,7 @@ > the baby at her as she spoke . “ I must go and get ready to play croquet with the # D NSg/VB/J+ NSg/P ISg/D$+ NSg/R ISg+ NSg/VPt . . ISg/#r+ NSg/VB NSg/VB/J VB/C NSg/VB NSg/VB/J P N🅪Sg/VB NSg/VB P D+ > Queen , ” and she hurried out of the room . The cook threw a frying - pan after her -# NPr/VB/J+ . . VB/C ISg+ VP/J NSg/VB/J/R/P P D N🅪Sg/VB/J+ . D NPr/VB VB D/P Nᴹ/Vg/J . NPr/VB/J P ISg/D$+ +# NPr/VB/J+ . . VB/C ISg+ VP/J NSg/VB/J/R/P P D N🅪Sg/VB/J+ . D NPr/VB VPt D/P Nᴹ/Vg/J . NPr/VB/J P ISg/D$+ > as she went out , but it just missed her . # NSg/R ISg+ NSg/VPt NSg/VB/J/R/P . NSg/C/P NPr/ISg+ J VP/J ISg/D$+ . > @@ -3409,7 +3409,7 @@ > the garden , called out “ The Queen ! The Queen ! ” and the three gardeners instantly # D NSg/VB/J+ . VP/J NSg/VB/J/R/P . D NPr/VB/J+ . D+ NPr/VB/J+ . . VB/C D+ NSg+ NPl+ R > threw themselves flat upon their faces . There was a sound of many footsteps , and -# VB IPl+ NSg/VB/J P D$+ NPl/V3+ . R+ VPt D/P N🅪Sg/VB/J P NSg/I/J/Dq+ NPl+ . VB/C +# VPt IPl+ NSg/VB/J P D$+ NPl/V3+ . R+ VPt D/P N🅪Sg/VB/J P NSg/I/J/Dq+ NPl+ . VB/C > Alice looked round , eager to see the Queen . # NPr+ VP/J NSg/VB/J/P . NSg/VB/J P NSg/VB D+ NPr/VB/J+ . > @@ -4693,7 +4693,7 @@ > “ The reason is , ” said the Gryphon , “ that they would go with the lobsters to the # . D+ N🅪Sg/VB+ VL3 . . VP/J D ? . . NSg/I/C/Ddem IPl+ VXB NSg/VB/J P D NPl/V3 P D > dance . So they got thrown out to sea . So they had to fall a long way . So they -# N🅪Sg/VB+ . NSg/I/J/R/C IPl+ VP VB/J NSg/VB/J/R/P P NSg . NSg/I/J/R/C IPl+ VB P N🅪Sg/VB D/P+ NPr/VB/J+ NSg/J+ . NSg/I/J/R/C IPl+ +# N🅪Sg/VB+ . NSg/I/J/R/C IPl+ VP VPp/J NSg/VB/J/R/P P NSg . NSg/I/J/R/C IPl+ VB P N🅪Sg/VB D/P+ NPr/VB/J+ NSg/J+ . NSg/I/J/R/C IPl+ > got their tails fast in their mouths . So they couldn’t get them out again . # VP D$+ NPl/V3+ NSg/VB/J/R NPr/J/R/P D$+ NPl/V3+ . NSg/I/J/R/C IPl+ VB NSg/VB NSg/IPl+ NSg/VB/J/R/P P . > That’s all . ” diff --git a/harper-core/tests/text/tagged/Difficult sentences.md b/harper-core/tests/text/tagged/Difficult sentences.md index 9d15fb9c..5784b2e2 100644 --- a/harper-core/tests/text/tagged/Difficult sentences.md +++ b/harper-core/tests/text/tagged/Difficult sentences.md @@ -603,7 +603,7 @@ > Are we still on for tonight ? # VB IPl+ NSg/VB/J J/P R/C/P NSg+ . > Mike just threw coffee onto Paul's lap . It's on now . -# NPr/VB+ J VB N🅪Sg/VB/J+ J/P NSg$ NSg/VB/J+ . + J/P NSg/J/R/C . +# NPr/VB+ J VPt N🅪Sg/VB/J+ J/P NSg$ NSg/VB/J+ . + J/P NSg/J/R/C . > England need a hundred runs , with twenty - five overs remaining . Game on ! # NPr+ N🅪Sg/VXB D/P NSg NPl/V3 . P NSg . NSg NPl Nᴹ/Vg/J . NSg/VB/J+ J/P . > Your feet will soon warm up once your socks are on . diff --git a/harper-core/tests/text/tagged/The Great Gatsby.md b/harper-core/tests/text/tagged/The Great Gatsby.md index e73916ac..908b1d83 100644 --- a/harper-core/tests/text/tagged/The Great Gatsby.md +++ b/harper-core/tests/text/tagged/The Great Gatsby.md @@ -863,7 +863,7 @@ > you concealed in one of those breathless , thrilling words . Then suddenly she # ISgPl+ VP/J NPr/J/R/P NSg/I/J P I/Ddem J . Nᴹ/Vg/J NPl/V3+ . NSg/J/C R ISg+ > threw her napkin on the table and excused herself and went into the house . -# VB ISg/D$+ NSg+ J/P D+ NSg/VB+ VB/C VP/J ISg+ VB/C NSg/VPt P D+ NPr/VB+ . +# VPt ISg/D$+ NSg+ J/P D+ NSg/VB+ VB/C VP/J ISg+ VB/C NSg/VPt P D+ NPr/VB+ . > # > Miss Baker and I exchanged a short glance consciously devoid of meaning . I was @@ -3129,7 +3129,7 @@ > inky color , and pursued the rest of their way in slow black rivulets . A humorous # J N🅪Sg/VB/J/Am+ . VB/C VP/J D NSg/VB P D$+ NSg/J+ NPr/J/R/P NSg/VB/J N🅪Sg/VB/J NPl . D/P J > suggestion was made that she sing the notes on her face , whereupon she threw up -# N🅪Sg+ VPt VB NSg/I/C/Ddem ISg+ NSg/VB/J D NPl/V3+ J/P ISg/D$+ NSg/VB+ . C ISg+ VB NSg/VB/J/P +# N🅪Sg+ VPt VB NSg/I/C/Ddem ISg+ NSg/VB/J D NPl/V3+ J/P ISg/D$+ NSg/VB+ . C ISg+ VPt NSg/VB/J/P > her hands , sank into a chair , and went off into a deep vinous sleep . # ISg/D$+ NPl/V3+ . VPt P D/P NSg/VB+ . VB/C NSg/VPt NSg/VB/J/P P D/P NSg/J J N🅪Sg/VB+ . > @@ -3469,7 +3469,7 @@ > # > 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/R/P D+ NSg/J/R+ N🅪Sg/Vg/J+ D+ NPr/VB+ VB D$+ NSg/VB/J+ NSg/J +# NSg/I/J/R/Dq P D+ N🅪Sg/VB/J+ ISg/#r+ VP/J . NPr/J/R/P D+ NSg/J/R+ N🅪Sg/Vg/J+ D+ NPr/VB+ VPt 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/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 @@ -4963,7 +4963,7 @@ > At first I thought it was another party , a wild rout that had resolved itself # NSg/P NSg/VB/J ISg/#r+ N🅪Sg/VP NPr/ISg+ VPt I/D NSg/VB/J . D/P+ NSg/VB/J+ NSg/VB+ NSg/I/C/Ddem+ VB VP/J ISg+ > into “ hide - and - go - seek ” or “ sardines - in - the - box ” with all the house thrown open -# P . NSg/VB . VB/C . NSg/VB/J . NSg/VB . NPr/C . NPl/V3+ . NPr/J/R/P . D . NSg/VB . P NSg/I/J/C/Dq D+ NPr/VB+ VB/J NSg/VB/J +# P . NSg/VB . VB/C . NSg/VB/J . NSg/VB . NPr/C . NPl/V3+ . NPr/J/R/P . D . NSg/VB . P NSg/I/J/C/Dq D+ NPr/VB+ VPp/J NSg/VB/J > to the game . But there wasn’t a sound . Only wind in the trees , which blew the # P D+ NSg/VB/J+ . NSg/C/P R+ VB D/P N🅪Sg/VB/J+ . J/R/C N🅪Sg/VB+ NPr/J/R/P D+ NPl/V3+ . I/C+ NSg/VPt/J D+ > wires and made the lights go off and on again as if the house had winked into @@ -5825,7 +5825,7 @@ > There was a small picture of Gatsby , also in yachting costume , on the # R+ VPt D/P NPr/VB/J NSg/VB P NPr . R/C NPr/J/R/P Nᴹ/Vg/J NSg/VB . J/P D > bureau — Gatsby with his head thrown back defiantly — taken apparently when he was -# NSg+ . NPr P ISg/D$+ NPr/VB/J+ VB/J NSg/VB/J R . VPp/J R NSg/I/C NPr/ISg+ VPt +# NSg+ . NPr P ISg/D$+ NPr/VB/J+ VPp/J NSg/VB/J R . VPp/J R NSg/I/C NPr/ISg+ VPt > about eighteen . # J/P NSg . > @@ -5973,7 +5973,7 @@ > her own fault , but because of the colossal vitality of his illusion . It had gone # ISg/D$+ NSg/VB/J+ NSg/VB+ . NSg/C/P C/P P D J Nᴹ P ISg/D$+ N🅪Sg+ . NPr/ISg+ VB VPp/J/P > beyond her , beyond everything . He had thrown himself into it with a creative -# NSg/P ISg/D$+ . NSg/P NSg/I/VB+ . NPr/ISg+ VB VB/J ISg+ P NPr/ISg+ P D/P+ NSg/J+ +# NSg/P ISg/D$+ . NSg/P NSg/I/VB+ . NPr/ISg+ VB VPp/J ISg+ P NPr/ISg+ P D/P+ NSg/J+ > passion , adding to it all the time , decking it out with every bright feather # NPrᴹ/VB+ . Nᴹ/Vg/J P NPr/ISg+ NSg/I/J/C/Dq D+ N🅪Sg/VB/J+ . Nᴹ/Vg/J+ NPr/ISg+ NSg/VB/J/R/P P Dq+ NPr/VB/J+ NSg/VB+ > that drifted his way . No amount of fire or freshness can challenge what a man @@ -7761,7 +7761,7 @@ > # > Tom threw on both brakes impatiently , and we slid to an abrupt dusty spot under -# NPr/VB+ VB J/P I/C/Dq+ NPl/V3+ R . VB/C IPl+ VP P D/P NSg/VB/J NPr/J NSg/VB/J+ NSg/J/P +# NPr/VB+ VPt J/P I/C/Dq+ NPl/V3+ R . VB/C IPl+ VP P D/P NSg/VB/J NPr/J NSg/VB/J+ NSg/J/P > Wilson’s sign . After a moment the proprietor emerged from the interior of his # NSg$ NSg/VB+ . P D/P+ NSg+ D NSg VP/J P D NSg/J P ISg/D$+ > establishment and gazed hollow - eyed at the car . @@ -8503,7 +8503,7 @@ > at Gatsby . “ There , Jay , ” she said — but her hand as she tried to light a cigarette # NSg/P NPr . . R . NPr+ . . ISg+ VP/J . NSg/C/P ISg/D$+ NSg/VB+ NSg/R ISg+ VP/J P N🅪Sg/VB/J D/P+ NSg/VB+ > was trembling . Suddenly she threw the cigarette and the burning match on the -# VPt Nᴹ/Vg/J . R ISg+ VB D+ NSg/VB+ VB/C D Nᴹ/Vg/J NSg/VB J/P D+ +# VPt Nᴹ/Vg/J . R ISg+ VPt D+ NSg/VB+ VB/C D Nᴹ/Vg/J NSg/VB J/P D+ > carpet . # NSg/VB+ . > @@ -11355,7 +11355,7 @@ > the thrilling returning trains of my youth , and the street lamps and sleigh # D Nᴹ/Vg/J Nᴹ/Vg/J NPl/V3 P D$+ NSg+ . VB/C D NSg/VB/J+ NPl/V3 VB/C NSg/VB/J+ > bells in the frosty dark and the shadows of holly wreaths thrown by lighted -# NPl/V3 NPr/J/R/P D J NSg/VB/J VB/C D NPl/V3 P NPr+ NPl/VB VB/J NSg/J/P VP/J +# NPl/V3 NPr/J/R/P D J NSg/VB/J VB/C D NPl/V3 P NPr+ NPl/VB VPp/J NSg/J/P VP/J > windows on the snow . I am part of that , a little solemn with the feel of those # NPrPl/V3+ J/P D NPr🅪Sg/VB+ . ISg/#r+ NPr/VB/J NSg/VB/J P NSg/I/C/Ddem+ . D/P NPr/I/J/Dq J P D NSg/I/VB P I/Ddem+ > long winters , a little complacent from growing up in the Carraway house in a @@ -11437,7 +11437,7 @@ > # > “ 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 +# . R ISgPl+ VPt NSg/VB NPr/ISg+ NSg/J/P . . VP/J NPr+ R . Unlintable Unlintable ISgPl+ VPt 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+ NSg/J/R/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 . ” @@ -11531,7 +11531,7 @@ > house — ” He broke off defiantly . “ What if I did tell him ? That fellow had it # NPr/VB+ . . NPr/ISg+ NSg/VPt/J NSg/VB/J/P R . . NSg/I+ NSg/C ISg/#r+ VPt NPr/VB ISg+ . NSg/I/C/Ddem NSg/VB VB NPr/ISg+ > coming to him . He threw dust into your eyes just like he did in Daisy’s , but he -# Nᴹ/Vg/J P ISg+ . NPr/ISg+ VB Nᴹ/VB+ P D$+ NPl/V3+ J NSg/VB/J/C/P NPr/ISg+ VPt NPr/J/R/P NSg$ . NSg/C/P NPr/ISg+ +# Nᴹ/Vg/J P ISg+ . NPr/ISg+ VPt Nᴹ/VB+ P D$+ NPl/V3+ J NSg/VB/J/C/P NPr/ISg+ VPt NPr/J/R/P NSg$ . NSg/C/P NPr/ISg+ > was a tough one . He ran over Myrtle like you’d run over a dog and never even # VPt D/P NSg/VB/J NSg/I/J+ . NPr/ISg+ NSg/VPt NSg/J/P NPr NSg/VB/J/C/P K NSg/VBPp NSg/J/P D/P NSg/VB/J+ VB/C R NSg/VB/J > stopped his car . ” From 30ff6e1f221d8acfbba7db58b7622beef3044344 Mon Sep 17 00:00:00 2001 From: Julien Voisin Date: Tue, 2 Dec 2025 17:03:48 +0100 Subject: [PATCH 13/84] feat: add `chroot` to dictionary (#2270) Co-authored-by: Elijah Potter --- harper-core/dictionary.dict | 1 + 1 file changed, 1 insertion(+) diff --git a/harper-core/dictionary.dict b/harper-core/dictionary.dict index b1fbac3f..e57ca1e2 100644 --- a/harper-core/dictionary.dict +++ b/harper-core/dictionary.dict @@ -54139,4 +54139,5 @@ topicalization/Nmg typological/JQ volitive/NSgJ worldbuild/Vd>G +chroot/gNS WordCamp/gO From 70d5861b94709de9d03e961fb2a9322dc8fad948 Mon Sep 17 00:00:00 2001 From: Andrew Dunbar Date: Tue, 2 Dec 2025 16:14:33 +0000 Subject: [PATCH 14/84] refactor: new `SequenceExpr` and `CharStringExt` methods (#2269) * refactor: new `SequenceExpr` and `CharStringExt` methods * refactor: propagate new methods --- harper-core/src/char_string.rs | 10 +++ harper-core/src/expr/fixed_phrase.rs | 10 +-- harper-core/src/expr/sequence_expr.rs | 77 ++++++++++++++----- .../src/linting/adjective_double_degree.rs | 12 +-- .../src/linting/determiner_without_noun.rs | 2 +- harper-core/src/linting/everyday.rs | 56 +++++++------- harper-core/src/linting/i_am_agreement.rs | 4 +- harper-core/src/linting/it_looks_like_that.rs | 16 ++-- .../no_contraction_with_verb.rs | 15 ++-- harper-core/src/linting/mass_plurals.rs | 7 +- harper-core/src/linting/missing_to.rs | 2 +- harper-core/src/linting/modal_seem.rs | 6 +- harper-core/src/linting/months.rs | 4 +- harper-core/src/linting/need_to_noun.rs | 6 +- .../effect_affect/affect_to_effect.rs | 2 +- harper-core/src/linting/pique_interest.rs | 8 +- harper-core/src/linting/pronoun_are.rs | 12 +-- .../src/linting/pronoun_inflection_be.rs | 5 +- harper-core/src/linting/quite_quiet.rs | 6 +- harper-core/src/linting/theres.rs | 14 ++-- .../to_two_too/to_too_adjverb_ed_punct.rs | 5 +- harper-core/src/linting/verb_to_adjective.rs | 12 +-- 22 files changed, 165 insertions(+), 126 deletions(-) diff --git a/harper-core/src/char_string.rs b/harper-core/src/char_string.rs index 43007a65..8f8c75fc 100644 --- a/harper-core/src/char_string.rs +++ b/harper-core/src/char_string.rs @@ -46,6 +46,10 @@ pub trait CharStringExt { /// The suffix is assumed to be lowercase. fn ends_with_ignore_ascii_case_str(&self, suffix: &str) -> bool; + /// Case-insensitive check if the string ends with any of the given ASCII suffixes. + /// The suffixes are assumed to be lowercase. + fn ends_with_any_ignore_ascii_case_chars(&self, suffixes: &[&[char]]) -> bool; + /// Check if the string contains any vowels fn contains_vowel(&self) -> bool; } @@ -148,6 +152,12 @@ impl CharStringExt for [char] { .all(|(a, b)| a.to_ascii_lowercase() == *b) } + fn ends_with_any_ignore_ascii_case_chars(&self, suffixes: &[&[char]]) -> bool { + suffixes + .iter() + .any(|suffix| self.ends_with_ignore_ascii_case_chars(suffix)) + } + fn contains_vowel(&self) -> bool { self.iter().any(|c| c.is_vowel()) } diff --git a/harper-core/src/expr/fixed_phrase.rs b/harper-core/src/expr/fixed_phrase.rs index 6fc3c9ae..6a384454 100644 --- a/harper-core/src/expr/fixed_phrase.rs +++ b/harper-core/src/expr/fixed_phrase.rs @@ -42,17 +42,13 @@ impl FixedPhrase { phrase = phrase.then_whitespace(); } TokenKind::Punctuation(p) => { - phrase = phrase.then(move |t: &Token, _source: &[char]| { - t.kind.as_punctuation().cloned() == Some(p) - }) + phrase = phrase + .then_kind_where(move |kind| kind.as_punctuation().cloned() == Some(p)); } TokenKind::ParagraphBreak => { phrase = phrase.then_whitespace(); } - TokenKind::Number(n) => { - phrase = phrase - .then(move |tok: &Token, _source: &[char]| tok.kind == TokenKind::Number(n)) - } + TokenKind::Number(_) => phrase = phrase.then_kind_where(|kind| kind.is_number()), _ => panic!("Fell out of expected document formats."), } } diff --git a/harper-core/src/expr/sequence_expr.rs b/harper-core/src/expr/sequence_expr.rs index 88dd06a6..3a070ad1 100644 --- a/harper-core/src/expr/sequence_expr.rs +++ b/harper-core/src/expr/sequence_expr.rs @@ -19,8 +19,8 @@ macro_rules! gen_then_from_is { paste! { #[doc = concat!("Adds a step matching a token where [`TokenKind::is_", stringify!($quality), "()`] returns true.")] pub fn [< then_$quality >] (self) -> Self{ - self.then(|tok: &Token, _source: &[char]| { - tok.kind.[< is_$quality >]() + self.then_kind_where(|kind| { + kind.[< is_$quality >]() }) } @@ -40,12 +40,8 @@ macro_rules! gen_then_from_is { #[doc = concat!("Adds a step matching a token where [`TokenKind::is_", stringify!($quality), "()`] returns false.")] pub fn [< then_anything_but_$quality >] (self) -> Self{ - self.then(|tok: &Token, _source: &[char]| { - if tok.kind.[< is_$quality >](){ - false - }else{ - true - } + self.then_kind_where(|kind| { + !kind.[< is_$quality >]() }) } } @@ -170,11 +166,6 @@ impl SequenceExpr { self.then(WordSet::new(words)) } - /// Matches any token whose `Kind` exactly matches. - pub fn then_strict(self, kind: TokenKind) -> Self { - self.then(move |tok: &Token, _source: &[char]| tok.kind == kind) - } - /// Match against one or more whitespace tokens. pub fn then_whitespace(self) -> Self { self.then(WhitespacePattern) @@ -229,7 +220,7 @@ impl SequenceExpr { /// Matches any word. pub fn then_any_word(self) -> Self { - self.then(|tok: &Token, _source: &[char]| tok.kind.is_word()) + self.then_kind_where(|kind| kind.is_word()) } /// Match examples of `word` that have any capitalization. @@ -266,6 +257,23 @@ impl SequenceExpr { // One kind + /// Matches any token whose `Kind` exactly matches. + pub fn then_kind(self, kind: TokenKind) -> Self { + self.then_kind_where(move |k| kind == *k) + } + + /// Matches a token where the provided closure returns true for the token's kind. + pub fn then_kind_where(mut self, predicate: F) -> Self + where + F: Fn(&TokenKind) -> bool + Send + Sync + 'static, + { + self.exprs + .push(Box::new(move |tok: &Token, _source: &[char]| { + predicate(&tok.kind) + })); + self + } + /// Match a token of a given kind which is not in the list of words. pub fn then_kind_except(self, pred_is: F, ex: &'static [&'static str]) -> Self where @@ -288,7 +296,7 @@ impl SequenceExpr { F1: Fn(&TokenKind) -> bool + Send + Sync + 'static, F2: Fn(&TokenKind) -> bool + Send + Sync + 'static, { - self.then(move |tok: &Token, _source: &[char]| pred_is_1(&tok.kind) && pred_is_2(&tok.kind)) + self.then_kind_where(move |k| pred_is_1(k) && pred_is_2(k)) } /// Match a token where either of the two token kind predicates returns true. @@ -298,7 +306,17 @@ impl SequenceExpr { F1: Fn(&TokenKind) -> bool + Send + Sync + 'static, F2: Fn(&TokenKind) -> bool + Send + Sync + 'static, { - self.then(move |tok: &Token, _source: &[char]| pred_is_1(&tok.kind) || pred_is_2(&tok.kind)) + self.then_kind_where(move |k| pred_is_1(k) || pred_is_2(k)) + } + + /// Match a token where neither of the two token kind predicates returns true. + /// For instance, a word that can't be a verb or a noun. + pub fn then_kind_neither(self, pred_isnt_1: F1, pred_isnt_2: F2) -> Self + where + F1: Fn(&TokenKind) -> bool + Send + Sync + 'static, + F2: Fn(&TokenKind) -> bool + Send + Sync + 'static, + { + self.then_kind_where(move |k| !pred_isnt_1(k) && !pred_isnt_2(k)) } /// Match a token where the first token kind predicate returns true and the second returns false. @@ -308,7 +326,21 @@ impl SequenceExpr { F1: Fn(&TokenKind) -> bool + Send + Sync + 'static, F2: Fn(&TokenKind) -> bool + Send + Sync + 'static, { - self.then(move |tok: &Token, _source: &[char]| pred_is(&tok.kind) && !pred_not(&tok.kind)) + self.then_kind_where(move |k| pred_is(k) && !pred_not(k)) + } + + /// Match a token where the first token kind predicate returns true and all of the second return false. + /// For instance, a word that can be a verb but not a noun or an adjective. + pub fn then_kind_is_but_isnt_any_of( + self, + pred_is: F1, + preds_isnt: &'static [F2], + ) -> Self + where + F1: Fn(&TokenKind) -> bool + Send + Sync + 'static, + F2: Fn(&TokenKind) -> bool + Send + Sync + 'static, + { + self.then_kind_where(move |k| pred_is(k) && !preds_isnt.iter().any(|pred| pred(k))) } /// Match a token where the first token kind predicate returns true and the second returns false, @@ -341,7 +373,16 @@ impl SequenceExpr { where F: Fn(&TokenKind) -> bool + Send + Sync + 'static, { - self.then(move |tok: &Token, _source: &[char]| preds_is.iter().any(|pred| pred(&tok.kind))) + self.then_kind_where(move |k| preds_is.iter().any(|pred| pred(k))) + } + + /// Match a token where none of the token kind predicates returns true. + /// Like `then_kind_neither` but for more than two predicates. + pub fn then_kind_none_of(self, preds_isnt: &'static [F]) -> Self + where + F: Fn(&TokenKind) -> bool + Send + Sync + 'static, + { + self.then_kind_where(move |k| preds_isnt.iter().all(|pred| !pred(k))) } /// Match a token where any of the token kind predicates returns true, diff --git a/harper-core/src/linting/adjective_double_degree.rs b/harper-core/src/linting/adjective_double_degree.rs index cb19ebef..a603141e 100644 --- a/harper-core/src/linting/adjective_double_degree.rs +++ b/harper-core/src/linting/adjective_double_degree.rs @@ -12,11 +12,13 @@ pub struct AdjectiveDoubleDegree { impl Default for AdjectiveDoubleDegree { fn default() -> Self { Self { - expr: Box::new(SequenceExpr::word_set(&["more", "most"]).t_ws().then( - |tok: &Token, _src: &[char]| { - tok.kind.is_comparative_adjective() || tok.kind.is_superlative_adjective() - }, - )), + expr: Box::new( + SequenceExpr::word_set(&["more", "most"]) + .t_ws() + .then_kind_where(|kind| { + kind.is_comparative_adjective() || kind.is_superlative_adjective() + }), + ), } } } diff --git a/harper-core/src/linting/determiner_without_noun.rs b/harper-core/src/linting/determiner_without_noun.rs index f0e6e786..dcdaa91b 100644 --- a/harper-core/src/linting/determiner_without_noun.rs +++ b/harper-core/src/linting/determiner_without_noun.rs @@ -12,7 +12,7 @@ pub struct DeterminerWithoutNoun { impl Default for DeterminerWithoutNoun { fn default() -> Self { let expr = SequenceExpr::default() - .then(|tok: &Token, _: &[char]| tok.kind.is_determiner()) + .then_kind_where(|kind| kind.is_determiner()) .t_ws() .then_conjunction(); diff --git a/harper-core/src/linting/everyday.rs b/harper-core/src/linting/everyday.rs index f056a556..c05cac7b 100644 --- a/harper-core/src/linting/everyday.rs +++ b/harper-core/src/linting/everyday.rs @@ -15,22 +15,22 @@ impl Default for Everyday { let everyday = Word::new("everyday"); let every_day = Lrc::new(SequenceExpr::aco("every").t_ws().t_aco("day")); - let everyday_bad_after = - All::new(vec![ - Box::new( - SequenceExpr::default() - .then(everyday.clone()) - .t_ws() - .then_any_word(), - ), - Box::new(SequenceExpr::default().t_any().t_any().then( - |tok: &Token, _src: &[char]| { - !tok.kind.is_noun() - && !tok.kind.is_oov() - && !tok.kind.is_verb_progressive_form() - }, - )), - ]); + let everyday_bad_after = All::new(vec![ + Box::new( + SequenceExpr::default() + .then(everyday.clone()) + .t_ws() + .then_any_word(), + ), + Box::new( + SequenceExpr::default() + .t_any() + .t_any() + .then_kind_where(|kind| { + !kind.is_noun() && !kind.is_oov() && !kind.is_verb_progressive_form() + }), + ), + ]); let bad_before_every_day = All::new(vec![ Box::new( @@ -72,18 +72,14 @@ impl Default for Everyday { .then(everyday.clone()) .then_punctuation(), ), - Box::new( - SequenceExpr::default() - .t_any() - .then(|tok: &Token, _src: &[char]| { - matches!( - tok.kind, - TokenKind::Punctuation( - Punctuation::Question | Punctuation::Comma | Punctuation::Period - ) - ) - }), - ), + Box::new(SequenceExpr::default().t_any().then_kind_where(|kind| { + matches!( + kind, + TokenKind::Punctuation( + Punctuation::Question | Punctuation::Comma | Punctuation::Period + ) + ) + })), ]); // (However, the message goes far beyond) every day things. @@ -102,9 +98,9 @@ impl Default for Everyday { .t_any() .t_any() .t_any() - .then(|tok: &Token, _src: &[char]| { + .then_kind_where(|kind| { matches!( - tok.kind, + kind, TokenKind::Punctuation( Punctuation::Question | Punctuation::Comma | Punctuation::Period ) diff --git a/harper-core/src/linting/i_am_agreement.rs b/harper-core/src/linting/i_am_agreement.rs index d766f17d..ee0051c3 100644 --- a/harper-core/src/linting/i_am_agreement.rs +++ b/harper-core/src/linting/i_am_agreement.rs @@ -18,9 +18,7 @@ impl Default for IAmAgreement { .then(i_are.clone()); let non_and_word_before_i_are = SequenceExpr::default() - .then(|tok: &Token, src: &[char]| { - !tok.kind.is_word() || tok.span.get_content_string(src).to_lowercase() != "and" - }) + .then_word_except(&["and"]) .t_ws() .then(i_are); diff --git a/harper-core/src/linting/it_looks_like_that.rs b/harper-core/src/linting/it_looks_like_that.rs index 2e28691a..50ad966c 100644 --- a/harper-core/src/linting/it_looks_like_that.rs +++ b/harper-core/src/linting/it_looks_like_that.rs @@ -15,23 +15,23 @@ impl Default for ItLooksLikeThat { SequenceExpr::default() .then_fixed_phrase("it looks like that") .then_whitespace() - .then(|tok: &Token, _: &[char]| { + .then_kind_where(|kind| { // Heuristics on the word after "that" which show "that" was used // as a relative pronoun, which is a mistake - let is_subj = tok.kind.is_subject_pronoun(); - let is_ing = tok.kind.is_verb_progressive_form(); + let is_subj = kind.is_subject_pronoun(); + let is_ing = kind.is_verb_progressive_form(); let is_definitely_rel_pron = is_subj || is_ing; // Heuristics on the word after "that" which show "that" // could possibly be a legitimate demonstrative pronoun or determiner // as a demonstrative pronoun or a determiner // which would not be a mistake. - let is_v3psgpres = tok.kind.is_verb_third_person_singular_present_form(); + let is_v3psgpres = kind.is_verb_third_person_singular_present_form(); // NOTE: we don't have .is_modal_verb() but maybe we need it now! - let is_vmodal_or_aux = tok.kind.is_auxiliary_verb(); - let is_vpret = tok.kind.is_verb_simple_past_form(); - let is_noun = tok.kind.is_noun(); - let is_oov = tok.kind.is_oov(); + let is_vmodal_or_aux = kind.is_auxiliary_verb(); + let is_vpret = kind.is_verb_simple_past_form(); + let is_noun = kind.is_noun(); + let is_oov = kind.is_oov(); let maybe_demonstrative_or_determiner = is_v3psgpres || is_vmodal_or_aux || is_vpret || is_noun || is_oov; diff --git a/harper-core/src/linting/lets_confusion/no_contraction_with_verb.rs b/harper-core/src/linting/lets_confusion/no_contraction_with_verb.rs index fe60e45f..6cb16a07 100644 --- a/harper-core/src/linting/lets_confusion/no_contraction_with_verb.rs +++ b/harper-core/src/linting/lets_confusion/no_contraction_with_verb.rs @@ -27,13 +27,14 @@ impl Default for NoContractionWithVerb { .then(WordSet::new(&["lets", "let"])) .then_whitespace(); - // Match verbs that are only verbs (not also nouns/adjectives) and not in -ing form - let non_ing_verb = SequenceExpr::default().then(|tok: &Token, _src: &[char]| { - tok.kind.is_verb() - && !tok.kind.is_noun() - && !tok.kind.is_adjective() - && !tok.kind.is_verb_progressive_form() - }); + let non_ing_verb = SequenceExpr::default().then_kind_is_but_isnt_any_of( + TokenKind::is_verb, + &[ + TokenKind::is_noun, + TokenKind::is_adjective, + TokenKind::is_verb_progressive_form, + ] as &[_], + ); // Ambiguous word is a verb determined by heuristic of following word's part of speech // Tests the next two words after "let". diff --git a/harper-core/src/linting/mass_plurals.rs b/harper-core/src/linting/mass_plurals.rs index f9efce1e..40d87f1a 100644 --- a/harper-core/src/linting/mass_plurals.rs +++ b/harper-core/src/linting/mass_plurals.rs @@ -19,9 +19,10 @@ where { pub fn new(dict: D) -> Self { let oov = SequenceExpr::default().then_oov(); - let looks_plural = SequenceExpr::default().then(|tok: &Token, _src: &[char]| { - let lchars = tok.span.get_content(_src).to_lower(); - lchars.last().is_some_and(|c| *c == 's') + let looks_plural = SequenceExpr::default().then(|tok: &Token, src: &[char]| { + tok.span + .get_content(src) + .ends_with_ignore_ascii_case_chars(&['s']) }); let oov_looks_plural = All::new(vec![Box::new(oov), Box::new(looks_plural)]); diff --git a/harper-core/src/linting/missing_to.rs b/harper-core/src/linting/missing_to.rs index 3979de6c..5d44a762 100644 --- a/harper-core/src/linting/missing_to.rs +++ b/harper-core/src/linting/missing_to.rs @@ -203,7 +203,7 @@ impl Default for MissingTo { let pattern = SequenceExpr::default() .then(Self::controller_words()) .t_ws() - .then(|tok: &Token, _source: &[char]| tok.kind.is_verb_lemma()); + .then_kind_where(|kind| kind.is_verb_lemma()); map.insert(pattern, 0); diff --git a/harper-core/src/linting/modal_seem.rs b/harper-core/src/linting/modal_seem.rs index aea17bc9..f611bddb 100644 --- a/harper-core/src/linting/modal_seem.rs +++ b/harper-core/src/linting/modal_seem.rs @@ -29,15 +29,15 @@ impl ModalSeem { fn adjective_step() -> SequenceExpr { SequenceExpr::default() .t_ws() - .then(|tok: &Token, _source: &[char]| tok.kind.is_adjective()) + .then_kind_where(|kind| kind.is_adjective()) } fn adverb_then_adjective_step() -> SequenceExpr { SequenceExpr::default() .t_ws() - .then(|tok: &Token, _source: &[char]| tok.kind.is_adverb()) + .then_kind_where(|kind| kind.is_adverb()) .t_ws() - .then(|tok: &Token, _source: &[char]| tok.kind.is_adjective()) + .then_kind_where(|kind| kind.is_adjective()) } } diff --git a/harper-core/src/linting/months.rs b/harper-core/src/linting/months.rs index ab5962b7..19776cfe 100644 --- a/harper-core/src/linting/months.rs +++ b/harper-core/src/linting/months.rs @@ -54,8 +54,8 @@ impl Default for Months { "by", "during", "in", "last", "next", "of", "until", ]); - let year_or_day_of_month = SequenceExpr::default().then(|tok: &Token, _src: &[char]| { - if let TokenKind::Number(number) = &tok.kind { + let year_or_day_of_month = SequenceExpr::default().then_kind_where(|kind| { + if let TokenKind::Number(number) = &kind { let v = number.value.into_inner() as u32; (1500..=2500).contains(&v) || (1..=31).contains(&v) } else { diff --git a/harper-core/src/linting/need_to_noun.rs b/harper-core/src/linting/need_to_noun.rs index 2cd18948..b7eb9a84 100644 --- a/harper-core/src/linting/need_to_noun.rs +++ b/harper-core/src/linting/need_to_noun.rs @@ -30,12 +30,12 @@ impl Default for NeedToNoun { .then_word_set(&["be"]); let a = SequenceExpr::default() - .then(|tok: &Token, _: &[char]| tok.kind.is_nominal()) + .then_kind_where(|kind| kind.is_nominal()) .t_ws() .then_unless(postfix_exceptions); - let b = SequenceExpr::default() - .then(|tok: &Token, _: &[char]| tok.kind.is_nominal() && !tok.kind.is_verb()); + let b = + SequenceExpr::default().then_kind_where(|kind| kind.is_nominal() && !kind.is_verb()); let expr = SequenceExpr::default() .then(DerivedFrom::new_from_str("need")) 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 5a9b405e..d1036b9d 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 @@ -57,7 +57,7 @@ impl Default for AffectToEffect { .then(|tok: &Token, source: &[char]| matches_preceding_context(tok, source)) .t_ws() .then(|tok: &Token, source: &[char]| is_affect_word(tok, source)) - .then(|tok: &Token, _source: &[char]| matches!(tok.kind, TokenKind::Punctuation(_))); + .then_kind_where(|kind| kind.is_punctuation()); map.insert(punctuation_follow, 2); diff --git a/harper-core/src/linting/pique_interest.rs b/harper-core/src/linting/pique_interest.rs index 5aa3a9f7..c7d51d68 100644 --- a/harper-core/src/linting/pique_interest.rs +++ b/harper-core/src/linting/pique_interest.rs @@ -1,3 +1,4 @@ +use crate::TokenKind; use crate::expr::Expr; use crate::expr::SequenceExpr; use crate::{CharString, CharStringExt, Token, char_string::char_string, patterns::WordSet}; @@ -16,9 +17,10 @@ impl Default for PiqueInterest { "peak", "peaked", "peek", "peeked", "peeking", "peaking", ])) .then_whitespace() - .then(|tok: &Token, _: &[char]| { - tok.kind.is_non_plural_nominal() || tok.kind.is_possessive_determiner() - }) + .then_kind_either( + TokenKind::is_non_plural_nominal, + TokenKind::is_possessive_determiner, + ) .then_whitespace() .t_aco("interest"); diff --git a/harper-core/src/linting/pronoun_are.rs b/harper-core/src/linting/pronoun_are.rs index af7474aa..758c3817 100644 --- a/harper-core/src/linting/pronoun_are.rs +++ b/harper-core/src/linting/pronoun_are.rs @@ -13,12 +13,12 @@ pub struct PronounAre { impl Default for PronounAre { fn default() -> Self { let expr = SequenceExpr::default() - .then(|tok: &Token, _src: &[char]| { - tok.kind.is_pronoun() - && tok.kind.is_subject_pronoun() - && (tok.kind.is_second_person_pronoun() - || tok.kind.is_first_person_plural_pronoun() - || tok.kind.is_third_person_plural_pronoun()) + .then_kind_where(|kind| { + kind.is_pronoun() + && kind.is_subject_pronoun() + && (kind.is_second_person_pronoun() + || kind.is_first_person_plural_pronoun() + || kind.is_third_person_plural_pronoun()) }) .t_ws() .t_aco("r"); diff --git a/harper-core/src/linting/pronoun_inflection_be.rs b/harper-core/src/linting/pronoun_inflection_be.rs index 72934c2b..b76b7f0c 100644 --- a/harper-core/src/linting/pronoun_inflection_be.rs +++ b/harper-core/src/linting/pronoun_inflection_be.rs @@ -52,9 +52,8 @@ impl PronounInflectionBe { map.insert(arent, "isn't"); let is = SequenceExpr::default() - .then(|tok: &Token, _: &[char]| { - tok.kind - .as_word() + .then_kind_where(|kind| { + kind.as_word() .as_ref() .and_then(|m| m.as_ref().and_then(|m| m.np_member)) .unwrap_or_default() diff --git a/harper-core/src/linting/quite_quiet.rs b/harper-core/src/linting/quite_quiet.rs index 9afedc76..dc068a66 100644 --- a/harper-core/src/linting/quite_quiet.rs +++ b/harper-core/src/linting/quite_quiet.rs @@ -27,9 +27,9 @@ impl Default for QuiteQuiet { if !tok.kind.is_verb() || !tok.kind.is_apostrophized() { return false; } - let chars = tok.span.get_content(src); - chars.ends_with_ignore_ascii_case_chars(&['n', '\'', 't']) - || chars.ends_with_ignore_ascii_case_chars(&['n', '’', 't']) + tok.span + .get_content(src) + .ends_with_any_ignore_ascii_case_chars(&[&['n', '\'', 't'], &['n', '’', 't']]) }) .t_ws() .t_aco("quiet"); diff --git a/harper-core/src/linting/theres.rs b/harper-core/src/linting/theres.rs index da74437f..15985dc7 100644 --- a/harper-core/src/linting/theres.rs +++ b/harper-core/src/linting/theres.rs @@ -1,6 +1,6 @@ use crate::linting::expr_linter::Chunk; use crate::{ - CharStringExt, Token, + Token, TokenKind, expr::SequenceExpr, linting::{ExprLinter, Lint, LintKind, Suggestion}, }; @@ -11,14 +11,10 @@ pub struct Theres { impl Default for Theres { fn default() -> Self { - let expr = SequenceExpr::aco("their's") - .t_ws() - .then(|tok: &Token, src: &[char]| { - tok.kind.is_determiner() - || tok.kind.is_quantifier() - || tok.span.get_content(src).eq_ignore_ascii_case_str("no") - || tok.span.get_content(src).eq_ignore_ascii_case_str("enough") - }); + let expr = SequenceExpr::aco("their's").t_ws().then_kind_any_or_words( + &[TokenKind::is_determiner, TokenKind::is_quantifier] as &[_], + &["no", "enough"], + ); Self { expr: Box::new(expr), diff --git a/harper-core/src/linting/to_two_too/to_too_adjverb_ed_punct.rs b/harper-core/src/linting/to_two_too/to_too_adjverb_ed_punct.rs index 6579b48d..d313f9b5 100644 --- a/harper-core/src/linting/to_two_too/to_too_adjverb_ed_punct.rs +++ b/harper-core/src/linting/to_two_too/to_too_adjverb_ed_punct.rs @@ -23,10 +23,7 @@ impl Default for ToTooAdjVerbEdPunct { && tok .span .get_content(src) - .iter() - .collect::() - .to_lowercase() - .ends_with("ed") + .ends_with_ignore_ascii_case_chars(&['e', 'd']) }) .then_sentence_terminator(); diff --git a/harper-core/src/linting/verb_to_adjective.rs b/harper-core/src/linting/verb_to_adjective.rs index 93c39df5..969a046e 100644 --- a/harper-core/src/linting/verb_to_adjective.rs +++ b/harper-core/src/linting/verb_to_adjective.rs @@ -19,12 +19,12 @@ impl Default for VerbToAdjective { 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() + .then_kind_where(|kind| { + (kind.is_verb() + && !kind.is_verb_past_form() + && !kind.is_adjective() + && !kind.is_noun()) + || kind.is_degree_adverb() }) .t_ws() .then(UPOSSet::new(&[UPOS::NOUN, UPOS::PROPN])); From aa2cdd4029b93be19d3e86c08cfb8b05ae8f7cf4 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Tue, 2 Dec 2025 11:40:55 -0700 Subject: [PATCH 15/84] feat(ls): add `octo` as a supported filetype (#2279) I've started using `octo.nvim` for various code reviews. It seems to work well with Harper --- harper-ls/src/backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harper-ls/src/backend.rs b/harper-ls/src/backend.rs index d3838181..6bb793ab 100644 --- a/harper-ls/src/backend.rs +++ b/harper-ls/src/backend.rs @@ -359,7 +359,7 @@ impl Backend { Some(Box::new(ts_parser)) } } - "git-commit" | "gitcommit" => { + "git-commit" | "gitcommit" | "octo" => { Some(Box::new(GitCommitParser::new_markdown(markdown_options))) } "html" => Some(Box::new(HtmlParser::default())), From 150656ee427069e3454eb34fe45d7beef2db9dcd Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Tue, 2 Dec 2025 14:34:36 -0700 Subject: [PATCH 16/84] fix(core): issues with spell check (#2288) * fix(core): `SpellCheck` not properly correcting `Generaly` * refactor(core): move higher up * chore(core): `just format` * fix(core): appease Clippy --- harper-core/src/linting/spell_check.rs | 8 + harper-core/src/spell/fst_dictionary.rs | 36 +- harper-core/src/spell/mod.rs | 2 +- .../Alice's Adventures in Wonderland.snap.yml | 137 ++-- .../text/linters/Computer science.snap.yml | 142 ++-- .../text/linters/Difficult sentences.snap.yml | 24 +- .../linters/Part-of-speech tagging.snap.yml | 101 +-- .../tests/text/linters/Spell.US.snap.yml | 42 +- harper-core/tests/text/linters/Spell.snap.yml | 4 +- ...Constitution of the United States.snap.yml | 52 +- .../text/linters/The Great Gatsby.snap.yml | 747 ++++++++++-------- 11 files changed, 705 insertions(+), 590 deletions(-) diff --git a/harper-core/src/linting/spell_check.rs b/harper-core/src/linting/spell_check.rs index fbf97fc2..76de0839 100644 --- a/harper-core/src/linting/spell_check.rs +++ b/harper-core/src/linting/spell_check.rs @@ -480,4 +480,12 @@ mod tests { "'fill' is supposed to be 'fill'", ); } + #[test] + fn issue_2261() { + assert_top3_suggestion_result( + "Generaly", + SpellCheck::new(FstDictionary::curated(), Dialect::British), + "Generally", + ); + } } diff --git a/harper-core/src/spell/fst_dictionary.rs b/harper-core/src/spell/fst_dictionary.rs index d00ad25e..fc7aa991 100644 --- a/harper-core/src/spell/fst_dictionary.rs +++ b/harper-core/src/spell/fst_dictionary.rs @@ -1,5 +1,6 @@ use super::{MutableDictionary, WordId}; use fst::{IntoStreamer, Map as FstMap, Streamer, map::StreamWithState}; +use hashbrown::HashMap; use lazy_static::lazy_static; use levenshtein_automata::{DFA, LevenshteinAutomatonBuilder}; use std::borrow::Cow; @@ -150,28 +151,35 @@ impl Dictionary for FstDictionary { let upper_dists = stream_distances_vec(&mut word_indexes_stream, &dfa); let lower_dists = stream_distances_vec(&mut word_indexes_lowercase_stream, &dfa_lowercase); - let mut merged = Vec::with_capacity(upper_dists.len()); + // Merge the two results, keeping the smallest distance when both DFAs match. + // The uppercase and lowercase searches can return different result counts, so + // we can't simply zip the vectors without losing matches. + let mut merged = Vec::with_capacity(upper_dists.len().max(lower_dists.len())); + let mut best_distances = HashMap::::new(); - // Merge the two results - for ((i_u, dist_u), (i_l, dist_l)) in upper_dists.into_iter().zip(lower_dists.into_iter()) { - let (chosen_index, edit_distance) = if dist_u <= dist_l { - (i_u, dist_u) - } else { - (i_l, dist_l) - }; - - let (word, metadata) = &self.words[chosen_index as usize]; + for (idx, dist) in upper_dists.into_iter().chain(lower_dists.into_iter()) { + best_distances + .entry(idx) + .and_modify(|existing| *existing = (*existing).min(dist)) + .or_insert(dist); + } + for (index, edit_distance) in best_distances { + let (word, metadata) = &self.words[index as usize]; merged.push(FuzzyMatchResult { word, edit_distance, metadata: Cow::Borrowed(metadata), - }) + }); } - merged.sort_unstable_by_key(|v| v.word); - merged.dedup_by_key(|v| v.word); - merged.sort_unstable_by_key(|v| v.edit_distance); + // Ignore exact matches + merged.retain(|v| v.edit_distance > 0); + merged.sort_unstable_by(|a, b| { + a.edit_distance + .cmp(&b.edit_distance) + .then_with(|| a.word.cmp(b.word)) + }); merged.truncate(max_results); merged diff --git a/harper-core/src/spell/mod.rs b/harper-core/src/spell/mod.rs index fd5f2341..bb5fccd8 100644 --- a/harper-core/src/spell/mod.rs +++ b/harper-core/src/spell/mod.rs @@ -342,7 +342,7 @@ fn score_suggestion(misspelled_word: &[char], sug: &FuzzyMatchResult) -> i32 { score -= 6; } - if sug.edit_distance == 2 { + if sug.edit_distance <= 2 { if is_ei_ie_misspelling(misspelled_word, sug.word) { score -= 11; } 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 fe4228a2..e09a0759 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 @@ -103,8 +103,8 @@ Message: | | ^~~~~~ Did you mean to spell `centre` this way? Suggest: - Replace with: “center” - - Replace with: “central” - Replace with: “censure” + - Replace with: “cent” @@ -171,11 +171,9 @@ Suggest: Lint: Spelling (63 priority) Message: | 74 | again. “Dinah’ll miss me very much to-night, I should think!” (Dinah was the - | ^~~~~~~~ Did you mean to spell `Dinah’ll` this way? + | ^~~~~~~~ Did you mean `Dinah's`? Suggest: - Replace with: “Dinah's” - - Replace with: “Dina's” - - Replace with: “Dinah” @@ -668,9 +666,9 @@ Message: | 320 | she began again: “Où est ma chatte?” which was the first sentence in her French | ^~ Did you mean to spell `Où` this way? Suggest: - - Replace with: “Os” - - Replace with: “O” - - Replace with: “OD” + - Replace with: “Of” + - Replace with: “Oh” + - Replace with: “Oi” @@ -699,9 +697,9 @@ Message: | 320 | she began again: “Où est ma chatte?” which was the first sentence in her French | ^~~~~~ Did you mean to spell `chatte` this way? Suggest: - - Replace with: “chatty” - Replace with: “chaste” - Replace with: “chatted” + - Replace with: “chattel” @@ -761,9 +759,9 @@ Message: | | ^~~~ Did you mean to spell `Lory` this way? 363 | Eaglet, and several other curious creatures. Alice led the way, and the whole Suggest: - - Replace with: “Lori” - - Replace with: “Gory” - - Replace with: “Dory” + - Replace with: “Lord” + - Replace with: “Lore” + - Replace with: “Lorry” @@ -809,9 +807,9 @@ Message: | | ^~~~ Did you mean to spell `Lory` this way? 376 | and would only say, “I am older than you, and must know better;” and this Alice Suggest: - - Replace with: “Lori” - - Replace with: “Gory” - - Replace with: “Dory” + - Replace with: “Lord” + - Replace with: “Lore” + - Replace with: “Lorry” @@ -821,9 +819,9 @@ Message: | | ^~~~ Did you mean to spell `Lory` this way? 378 | refused to tell its age, there was no more to be said. Suggest: - - Replace with: “Lori” - - Replace with: “Gory” - - Replace with: “Dory” + - Replace with: “Lord” + - Replace with: “Lore” + - Replace with: “Lorry” @@ -845,8 +843,8 @@ Message: | | ^~~~~~ Did you mean to spell `Morcar` this way? Suggest: - Replace with: “Mortar” - - Replace with: “Molnar” - - Replace with: “Mopar” + - Replace with: “Mercer” + - Replace with: “Molar” @@ -862,9 +860,9 @@ Message: | 392 | “Ugh!” said the Lory, with a shiver. | ^~~~ Did you mean to spell `Lory` this way? Suggest: - - Replace with: “Lori” - - Replace with: “Gory” - - Replace with: “Dory” + - Replace with: “Lord” + - Replace with: “Lore” + - Replace with: “Lorry” @@ -873,9 +871,9 @@ Message: | 397 | “Not I!” said the Lory hastily. | ^~~~ Did you mean to spell `Lory` this way? Suggest: - - Replace with: “Lori” - - Replace with: “Gory” - - Replace with: “Dory” + - Replace with: “Lord” + - Replace with: “Lore” + - Replace with: “Lorry” @@ -886,8 +884,8 @@ Message: | 400 | of Mercia and Northumbria, declared for him: and even Stigand, the patriotic Suggest: - Replace with: “Mortar” - - Replace with: “Molnar” - - Replace with: “Mopar” + - Replace with: “Mercer” + - Replace with: “Molar” @@ -902,10 +900,12 @@ Message: | Lint: Spelling (63 priority) Message: | 400 | of Mercia and Northumbria, declared for him: and even Stigand, the patriotic - | ^~~~~~~ Did you mean `Brigand`? + | ^~~~~~~ Did you mean to spell `Stigand` this way? 401 | archbishop of Canterbury, found it advisable—’” Suggest: - - Replace with: “Brigand” + - Replace with: “Stand” + - Replace with: “Stipend” + - Replace with: “Strand” @@ -915,8 +915,9 @@ 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: “Theming” - - Replace with: “Steeling” + - Replace with: “Gathering” + - Replace with: “Adhering” + - Replace with: “Fathering” @@ -967,9 +968,9 @@ Message: | | ^~~~ Did you mean to spell `Lory` this way? 528 | sight; and an old Crab took the opportunity of saying to her daughter “Ah, my Suggest: - - Replace with: “Lori” - - Replace with: “Gory” - - Replace with: “Dory” + - Replace with: “Lord” + - Replace with: “Lore” + - Replace with: “Lorry” @@ -978,9 +979,9 @@ Message: | 536 | “And who is Dinah, if I might venture to ask the question?” said the Lory. | ^~~~ Did you mean to spell `Lory` this way? Suggest: - - Replace with: “Lori” - - Replace with: “Gory” - - Replace with: “Dory” + - Replace with: “Lord” + - Replace with: “Lore” + - Replace with: “Lorry” @@ -1035,11 +1036,9 @@ Suggest: Lint: Spelling (63 priority) Message: | 585 | I suppose Dinah’ll be sending me on messages next!” And she began fancying the - | ^~~~~~~~ Did you mean to spell `Dinah’ll` this way? + | ^~~~~~~~ Did you mean `Dinah's`? Suggest: - Replace with: “Dinah's” - - Replace with: “Dina's” - - Replace with: “Dinah” @@ -1152,8 +1151,8 @@ Message: | | ^~~~~ Did you mean to spell `arrum` this way? Suggest: - Replace with: “arum” - - Replace with: “atrium” - - Replace with: “argue” + - Replace with: “album” + - Replace with: “alum” @@ -1968,7 +1967,7 @@ Message: | Suggest: - Replace with: “Lace” - Replace with: “Lacier” - - Replace with: “Lacey” + - Replace with: “Lac” @@ -1977,9 +1976,9 @@ Message: | 1555 | great hurry; “and their names were Elsie, Lacie, and Tillie; and they lived at | ^~~~~~ Did you mean to spell `Tillie` this way? Suggest: - - Replace with: “Billie” - - Replace with: “Lillie” - - Replace with: “Millie” + - Replace with: “Tile” + - Replace with: “Till” + - Replace with: “Till's” @@ -2558,7 +2557,7 @@ Message: | | ^~~~~~~ Did you mean to spell `Hjckrrh` this way? Suggest: - Replace with: “Hacker” - - Replace with: “Hackish” + - Replace with: “Hickory” - Replace with: “Hackers” @@ -2691,9 +2690,9 @@ Message: | 2206 | his flappers, “—Mystery, ancient and modern, with Seaography: then Drawling—the | ^~~~~~~~~~ Did you mean to spell `Seaography` this way? Suggest: + - Replace with: “Scenography” + - Replace with: “Stenography” - Replace with: “Demography” - - Replace with: “Geography” - - Replace with: “Xerography” @@ -3207,9 +3206,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: “Him” - - Replace with: “Ho” - - Replace with: “Am” + - Replace with: “Ha” + - Replace with: “Ham” + - Replace with: “He” @@ -3237,8 +3236,8 @@ Message: | | ^~~ Did you mean to spell `Soo` this way? Suggest: - Replace with: “So” - - Replace with: “Solo” - - Replace with: “Son” + - Replace with: “Soc” + - Replace with: “Sod” @@ -3279,8 +3278,8 @@ Message: | | ^~~ Did you mean to spell `Soo` this way? Suggest: - Replace with: “So” - - Replace with: “Solo” - - Replace with: “Son” + - Replace with: “Soc” + - Replace with: “Sod” @@ -3311,8 +3310,8 @@ Message: | 2486 | > of the e—e—evening, Beautiful, beautiful Soup! Suggest: - Replace with: “So” - - Replace with: “Solo” - - Replace with: “Son” + - Replace with: “Soc” + - Replace with: “Sod” @@ -3375,8 +3374,8 @@ Message: | | ^~~ Did you mean to spell `Soo` this way? Suggest: - Replace with: “So” - - Replace with: “Solo” - - Replace with: “Son” + - Replace with: “Soc” + - Replace with: “Sod” @@ -3417,8 +3416,8 @@ Message: | | ^~~ Did you mean to spell `Soo` this way? Suggest: - Replace with: “So” - - Replace with: “Solo” - - Replace with: “Son” + - Replace with: “Soc” + - Replace with: “Sod” @@ -3449,8 +3448,8 @@ Message: | 2491 | > e—e—evening, Beautiful, beauti—FUL SOUP!” Suggest: - Replace with: “So” - - Replace with: “Solo” - - Replace with: “Son” + - Replace with: “Soc” + - Replace with: “Sod” @@ -3504,9 +3503,9 @@ Message: | 2491 | > e—e—evening, Beautiful, beauti—FUL SOUP!” | ^~~ Did you mean to spell `FUL` this way? Suggest: - - Replace with: “FUD” - - Replace with: “F's” - - Replace with: “UL” + - Replace with: “Full” + - Replace with: “Flu” + - Replace with: “Fol” @@ -3544,8 +3543,8 @@ Message: | | ^~~ Did you mean to spell `Soo` this way? Suggest: - Replace with: “So” - - Replace with: “Solo” - - Replace with: “Son” + - Replace with: “Soc” + - Replace with: “Sod” diff --git a/harper-core/tests/text/linters/Computer science.snap.yml b/harper-core/tests/text/linters/Computer science.snap.yml index 9e5c6d2d..8633a578 100644 --- a/harper-core/tests/text/linters/Computer science.snap.yml +++ b/harper-core/tests/text/linters/Computer science.snap.yml @@ -11,8 +11,10 @@ Suggest: Lint: Spelling (63 priority) Message: | 45 | Wilhelm Schickard designed and constructed the first working mechanical - | ^~~~~~~~~ Did you mean `Schick`? + | ^~~~~~~~~ Did you mean to spell `Schickard` this way? Suggest: + - Replace with: “Schick's” + - Replace with: “Shipyard” - Replace with: “Schick” @@ -22,9 +24,9 @@ Message: | 46 | calculator in 1623. In 1673, Gottfried Leibniz demonstrated a digital mechanical | ^~~~~~~~~ Did you mean to spell `Gottfried` this way? Suggest: + - Replace with: “Guttered” - Replace with: “Lotteries” - Replace with: “Notified” - - Replace with: “Pottered” @@ -34,9 +36,9 @@ Message: | 47 | calculator, called the Stepped Reckoner. Leibniz may be considered the first | ^~~~~~~~ Did you mean to spell `Reckoner` this way? Suggest: - - Replace with: “Rickover” - Replace with: “Reckoned” - - Replace with: “Beckoned” + - Replace with: “Recover” + - Replace with: “Rickover” @@ -56,9 +58,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” @@ -69,8 +71,8 @@ Message: | | ^~~~~~ Did you mean to spell `Colmar` this way? Suggest: - Replace with: “Collar” - - Replace with: “Colemak” - - Replace with: “Coleman” + - Replace with: “Cellar” + - Replace with: “Clear” @@ -104,17 +106,19 @@ Message: | | ^~~~~~~ Did you mean to spell `Ludgate` this way? 66 | published the 2nd of the only two designs for mechanical analytical engines in Suggest: + - Replace with: “Legate” + - Replace with: “Luggage” - Replace with: “Luddite” - - Replace with: “Vulgate” Lint: Spelling (63 priority) Message: | 67 | history. In 1914, the Spanish engineer Leonardo Torres Quevedo published his - | ^~~~~~~ Did you mean `Acevedo`? + | ^~~~~~~ Did you mean to spell `Quevedo` this way? 68 | Essays on Automatics, and designed, inspired by Babbage, a theoretical Suggest: + - Replace with: “Queued” - Replace with: “Acevedo” @@ -135,6 +139,9 @@ Message: | 72 | Torres presented in Paris the Electromechanical Arithmometer, a prototype that | ^~~~~~~~~~~~ Did you mean to spell `Arithmometer` this way? 73 | demonstrated the feasibility of an electromechanical analytical engine, on which +Suggest: + - Replace with: “Arithmetic” + - Replace with: “Anemometer” @@ -161,9 +168,9 @@ Message: | 78 | ASCC/Harvard Mark I, based on Babbage's Analytical Engine, which itself used | ^~~~ Did you mean to spell `ASCC` this way? Suggest: - - Replace with: “AAC” - - Replace with: “ABC” - - Replace with: “ABCs” + - Replace with: “Ac” + - Replace with: “Ace” + - Replace with: “Act” @@ -184,9 +191,9 @@ Message: | | ^~~~~ Did you mean to spell `ENIAC` this way? 84 | to refer to the machines rather than their human predecessors. As it became Suggest: - - Replace with: “ENE's” - - Replace with: “Anzac” - - Replace with: “Fenian” + - Replace with: “Enact” + - Replace with: “Epic” + - Replace with: “Enc” @@ -231,20 +238,18 @@ Message: | | ^~~~ Did you mean to spell `Fein` this way? 106 | creation of a Graduate School in Computer Sciences analogous to the creation of Suggest: + - Replace with: “Fen” - Replace with: “Fern” - - Replace with: “Vein” - - Replace with: “Rein” + - Replace with: “Fin” Lint: Spelling (63 priority) Message: | 110 | and those of others such as numerical analyst George Forsythe, were rewarded: - | ^~~~~~~~ Did you mean to spell `Forsythe` this way? + | ^~~~~~~~ Did you mean `Forsythia`? Suggest: - Replace with: “Forsythia” - - Replace with: “Forster” - - Replace with: “Porsche” @@ -254,8 +259,8 @@ Message: | 116 | Peter Naur suggested the term datalogy, to reflect the fact that the scientific | ^~~~ Did you mean to spell `Naur` this way? Suggest: - - Replace with: “Nauru” - Replace with: “Nair” + - Replace with: “Nauru” - Replace with: “Na's” @@ -290,8 +295,8 @@ Message: | 120 | Peter Naur being the first professor in datalogy. The term is used mainly in the | ^~~~ Did you mean to spell `Naur` this way? Suggest: - - Replace with: “Nauru” - Replace with: “Nair” + - Replace with: “Nauru” - Replace with: “Na's” @@ -314,8 +319,8 @@ Message: | | ^~~~ Did you mean to spell `Naur` this way? 122 | science; this is now used for a multi-disciplinary field of data analysis, Suggest: - - Replace with: “Nauru” - Replace with: “Nair” + - Replace with: “Nauru” - Replace with: “Na's” @@ -498,8 +503,8 @@ Message: | | ^~~~~~ Did you mean to spell `Edsger` this way? Suggest: - Replace with: “Edger” - - Replace with: “Easter” - - Replace with: “Edgar” + - Replace with: “Eager” + - Replace with: “Edge” @@ -534,11 +539,9 @@ Lint: Spelling (63 priority) Message: | 155 | influenced by the work of mathematicians such as Kurt Gödel, Alan Turing, John 156 | von Neumann, Rózsa Péter and Alonzo Church and there continues to be a useful - | ^~~~~~~ Did you mean to spell `Neumann` this way? + | ^~~~~~~ Did you mean `Newman`? Suggest: - Replace with: “Newman” - - Replace with: “Newman's” - - Replace with: “Normans” @@ -546,11 +549,9 @@ Lint: Spelling (63 priority) Message: | 155 | influenced by the work of mathematicians such as Kurt Gödel, Alan Turing, John 156 | von Neumann, Rózsa Péter and Alonzo Church and there continues to be a useful - | ^~~~~ Did you mean to spell `Rózsa` this way? + | ^~~~~ Did you mean `Rosa`? Suggest: - Replace with: “Rosa” - - Replace with: “R's” - - Replace with: “RV's” @@ -561,8 +562,8 @@ Message: | | ^~~~~ Did you mean to spell `Péter` this way? Suggest: - Replace with: “Peter” - - Replace with: “Patel” - - Replace with: “Pete” + - Replace with: “Pother” + - Replace with: “Paper” @@ -599,9 +600,9 @@ Message: | | ^~~~~~ Did you mean to spell `Parnas` this way? 163 | taking a cue from the relationship between other engineering and science Suggest: + - Replace with: “Paras” + - Replace with: “Parkas” - Replace with: “Parana's” - - Replace with: “Parr's” - - Replace with: “Patna's” @@ -611,9 +612,9 @@ Message: | 182 | Newell and Herbert A. Simon argued in 1975, | ^~~~~~ Did you mean to spell `Newell` this way? Suggest: + - Replace with: “Newel” - Replace with: “Newels” - - Replace with: “Jewell” - - Replace with: “Nell” + - Replace with: “Newel's” @@ -623,9 +624,9 @@ Message: | 182 | Newell and Herbert A. Simon argued in 1975, | ^~ Did you mean to spell `A.` this way? Suggest: - - Replace with: “Ax” - Replace with: “A” - Replace with: “Ab” + - Replace with: “Ac” @@ -665,8 +666,8 @@ Message: | | ^~~~~~ Did you mean to spell `Edsger` this way? Suggest: - Replace with: “Edger” - - Replace with: “Easter” - - Replace with: “Edgar” + - Replace with: “Eager” + - Replace with: “Edge” @@ -701,7 +702,7 @@ Message: | Suggest: - Replace with: “Wagner” - Replace with: “Wigner” - - Replace with: “Warner” + - Replace with: “Wager” @@ -713,7 +714,7 @@ Message: | Suggest: - Replace with: “Deming's” - Replace with: “Dennis's” - - Replace with: “Jennings's” + - Replace with: “Dancing's” @@ -722,9 +723,9 @@ Message: | 216 | that they are theory, abstraction (modeling), and design. Amnon H. Eden | ^~~~~ Did you mean to spell `Amnon` this way? Suggest: - - Replace with: “Amnion” - Replace with: “Anon” - - Replace with: “Aaron” + - Replace with: “Amnion” + - Replace with: “Amazon” @@ -754,9 +755,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: “Ha” - Replace with: “He” - Replace with: “Hi” - - Replace with: “Ht” @@ -777,8 +778,8 @@ Message: | 232 | Computing Sciences Accreditation Board—which is made up of representatives of Suggest: - Replace with: “Cab” - - Replace with: “CPA” - - Replace with: “CSON” + - Replace with: “Crab” + - Replace with: “Cad” @@ -830,8 +831,8 @@ Message: | | ^~~~ Did you mean to spell `CSAB` this way? Suggest: - Replace with: “Cab” - - Replace with: “CPA” - - Replace with: “CSON” + - Replace with: “Crab” + - Replace with: “Cad” @@ -840,9 +841,9 @@ Message: | 252 | According to Peter Denning, the fundamental question underlying computer science | ^~~~~~~ Did you mean to spell `Denning` this way? Suggest: - - Replace with: “Denying” - - Replace with: “Penning” - Replace with: “Dunning” + - Replace with: “Denting” + - Replace with: “Denying” @@ -853,7 +854,7 @@ Message: | Suggest: - Replace with: “Nap” - Replace with: “Nip” - - Replace with: “Nu” + - Replace with: “No” @@ -947,7 +948,7 @@ Message: | Suggest: - Replace with: “Hi” - Replace with: “Chi” - - Replace with: “CI” + - Replace with: “Sci” @@ -959,7 +960,7 @@ Message: | Suggest: - Replace with: “Hi” - Replace with: “Chi” - - Replace with: “CI” + - Replace with: “Sci” @@ -977,9 +978,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: “Re” - Replace with: “RI” - Replace with: “Ra” + - Replace with: “Ru” @@ -989,7 +990,7 @@ Message: | | ^~ Did you mean to spell `P.` this way? Suggest: - Replace with: “Pa” - - Replace with: “Po” + - Replace with: “Pi” - Replace with: “PE” @@ -1002,7 +1003,7 @@ Message: | Suggest: - Replace with: “Petra” - Replace with: “Perth” - - Replace with: “Perry” + - Replace with: “Pear” @@ -1033,6 +1034,7 @@ Message: | Suggest: - Replace with: “Rapport” - Replace with: “Rappaport” + - Replace with: “Rapports” @@ -1041,9 +1043,9 @@ Message: | 439 | - Gottfried Wilhelm Leibniz's, George Boole's, Alan Turing's, Claude Shannon's, | ^~~~~~~~~ Did you mean to spell `Gottfried` this way? Suggest: + - Replace with: “Guttered” - Replace with: “Lotteries” - Replace with: “Notified” - - Replace with: “Pottered” @@ -1063,9 +1065,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” @@ -1083,9 +1085,9 @@ Message: | 458 | - Corrado Böhm and Giuseppe Jacopini's insight: there are only three ways of | ^~~~~~~ Did you mean to spell `Corrado` this way? Suggest: - - Replace with: “Colorado” - - Replace with: “Conrad” - - Replace with: “Coronado” + - Replace with: “Comrade” + - Replace with: “Corral” + - Replace with: “Corridor” @@ -1094,19 +1096,18 @@ Message: | 458 | - Corrado Böhm and Giuseppe Jacopini's insight: there are only three ways of | ^~~~ Did you mean to spell `Böhm` this way? Suggest: - - Replace with: “Baum” - - Replace with: “Bohr” - - Replace with: “Ohm” + - Replace with: “Bah” + - Replace with: “Baht” + - Replace with: “Beam” Lint: Spelling (63 priority) Message: | 458 | - Corrado Böhm and Giuseppe Jacopini's insight: there are only three ways of - | ^~~~~~~~~~ Did you mean to spell `Jacopini's` this way? + | ^~~~~~~~~~ Did you mean `Jacobin's`? Suggest: - Replace with: “Jacobin's” - - Replace with: “Jacobi's” @@ -1136,7 +1137,7 @@ Message: | Suggest: - Replace with: “Boer's” - Replace with: “Bohr's” - - Replace with: “Ohm's” + - Replace with: “Beam's” @@ -1144,10 +1145,9 @@ Lint: Spelling (63 priority) Message: | 467 | - repetition: WHILE such-and-such is the case, DO this. The three rules of 468 | Boehm's and Jacopini's insight can be further simplified with the use of - | ^~~~~~~~~~ Did you mean to spell `Jacopini's` this way? + | ^~~~~~~~~~ Did you mean `Jacobin's`? Suggest: - Replace with: “Jacobin's” - - Replace with: “Jacobi's” @@ -1157,8 +1157,8 @@ Message: | 469 | goto (which means it is more elementary than structured programming). | ^~~~ Did you mean to spell `goto` this way? Suggest: - - Replace with: “got” - Replace with: “goo” + - Replace with: “got” - Replace with: “goths” diff --git a/harper-core/tests/text/linters/Difficult sentences.snap.yml b/harper-core/tests/text/linters/Difficult sentences.snap.yml index e6084fa6..57ea6d6c 100644 --- a/harper-core/tests/text/linters/Difficult sentences.snap.yml +++ b/harper-core/tests/text/linters/Difficult sentences.snap.yml @@ -23,7 +23,7 @@ Message: | Suggest: - Replace with: “armor” - Replace with: “amour” - - Replace with: “armory” + - Replace with: “arbor” @@ -42,8 +42,9 @@ Message: | 88 | By Chabad, it's different. (with, among) | ^~~~~~ Did you mean to spell `Chabad` this way? Suggest: - - Replace with: “Canad” - - Replace with: “Chiba” + - Replace with: “Cabal” + - Replace with: “Chad” + - Replace with: “Charade” @@ -163,8 +164,8 @@ Message: | | ^~~~~~ Did you mean to spell `favour` this way? Suggest: - Replace with: “favor” + - Replace with: “famous” - Replace with: “flavor” - - Replace with: “flour” @@ -213,8 +214,9 @@ Message: | 310 | Ponsonby-Smythe hit a thumping on drive. | ^~~~~~~~ Did you mean to spell `Ponsonby` this way? Suggest: - - Replace with: “Jonson's” - - Replace with: “Jonson” + - Replace with: “Poison” + - Replace with: “Poison's” + - Replace with: “Poisoned” @@ -224,8 +226,8 @@ Message: | | ^~~~~~ Did you mean to spell `Smythe` this way? Suggest: - Replace with: “Scythe” - - Replace with: “Myth” - - Replace with: “Myths” + - Replace with: “Smith” + - Replace with: “Smiths” @@ -306,11 +308,9 @@ Suggest: Lint: Spelling (63 priority) Message: | 358 | Smith scored again on twelve minutes, doubling Mudchester Rovers' lead. - | ^~~~~~~~~~ Did you mean to spell `Mudchester` this way? + | ^~~~~~~~~~ Did you mean `Manchester`? Suggest: - Replace with: “Manchester” - - Replace with: “Rochester” - - Replace with: “Winchester” @@ -343,7 +343,7 @@ Message: | Suggest: - Replace with: “Pa” - Replace with: “Par” - - Replace with: “Pre” + - Replace with: “Pi” 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 86b35010..d9f4987c 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 @@ -27,9 +27,9 @@ Message: | | ^~~ Did you mean to spell `PoS` this way? 9 | POST), also called grammatical tagging is the process of marking up a word in a Suggest: - - Replace with: “PBS” - - Replace with: “PMS” - - Replace with: “POS” + - Replace with: “Pod” + - Replace with: “Poi” + - Replace with: “Pol” @@ -52,7 +52,7 @@ Message: | Suggest: - Replace with: “Brillo's” - Replace with: “Bill's” - - Replace with: “Trill's” + - Replace with: “Drill's” @@ -76,9 +76,9 @@ Message: | 49 | tags. For example, NN for singular common nouns, NNS for plural common nouns, NP | ^~ Did you mean to spell `NN` this way? Suggest: - - Replace with: “Nun” + - Replace with: “No” - Replace with: “Non” - - Replace with: “Na” + - Replace with: “Nu” @@ -89,8 +89,8 @@ Message: | 50 | for singular proper nouns (see the POS tags used in the Brown Corpus). Other Suggest: - Replace with: “NES” - - Replace with: “Ens” - - Replace with: “INS” + - Replace with: “Nos” + - Replace with: “Nuns” @@ -102,7 +102,7 @@ Message: | Suggest: - Replace with: “Nap” - Replace with: “Nip” - - Replace with: “Nu” + - Replace with: “No” @@ -112,9 +112,9 @@ Message: | 56 | Koine Greek (DeRose 1990) has used over 1,000 parts of speech and found that | ^~~~~ Did you mean to spell `Koine` this way? Suggest: - - Replace with: “Kine” - Replace with: “Kline” - - Replace with: “Kane” + - Replace with: “Kine” + - Replace with: “Kin” @@ -125,8 +125,8 @@ Message: | | ^~~~~~ Did you mean to spell `DeRose` this way? Suggest: - Replace with: “Depose” - - Replace with: “Defoe” - - Replace with: “Denise” + - Replace with: “Debase” + - Replace with: “Decode” @@ -157,9 +157,8 @@ Message: | | ^~~~~~ Did you mean to spell `Ncmsan` this way? 60 | Type = common, Gender = masculine, Number = singular, Case = accusative, Animate Suggest: - - Replace with: “Nissan” - Replace with: “Nisan” - - Replace with: “Nampa” + - Replace with: “Nissan” @@ -169,9 +168,9 @@ Message: | 64 | Penn tag set, developed in the Penn Treebank project. It is largely similar to | ^~~~~~~~ Did you mean to spell `Treebank` this way? Suggest: - - Replace with: “Freeman” - - Replace with: “Reembark” - - Replace with: “Debank” + - Replace with: “Tie back” + - Replace with: “Tieback” + - Replace with: “Traceback” @@ -191,8 +190,8 @@ Message: | 76 | al. have proposed a "universal" tag set, with 12 categories (for example, no Suggest: - Replace with: “Petrol” - - Replace with: “Pedro” - - Replace with: “Peron” + - Replace with: “Patrol” + - Replace with: “Patron” @@ -258,6 +257,10 @@ Lint: Spelling (63 priority) Message: | 105 | later part-of-speech tagging systems, such as CLAWS and VOLSUNGA. However, by | ^~~~~~~~ Did you mean to spell `VOLSUNGA` this way? +Suggest: + - Replace with: “Volcanic” + - Replace with: “Volcano” + - Replace with: “Voltage” @@ -280,9 +283,9 @@ Message: | | ^~~~ Did you mean to spell `HMMs` this way? 120 | to disambiguate parts of speech, when working to tag the Lancaster-Oslo-Bergen Suggest: - - Replace with: “Hems” - Replace with: “Hams” - - Replace with: “HMO's” + - Replace with: “Hems” + - Replace with: “Hums” @@ -291,9 +294,9 @@ Message: | 121 | Corpus of British English. HMMs involve counting cases (such as from the Brown | ^~~~ Did you mean to spell `HMMs` this way? Suggest: - - Replace with: “Hems” - Replace with: “Hams” - - Replace with: “HMO's” + - Replace with: “Hems” + - Replace with: “Hums” @@ -302,9 +305,9 @@ Message: | 129 | More advanced ("higher-order") HMMs learn the probabilities not only of pairs | ^~~~ Did you mean to spell `HMMs` this way? Suggest: - - Replace with: “Hems” - Replace with: “Hams” - - Replace with: “HMO's” + - Replace with: “Hems” + - Replace with: “Hums” @@ -328,9 +331,9 @@ Message: | 141 | Eugene Charniak points out in Statistical techniques for natural language | ^~~~~~~~ Did you mean to spell `Charniak` this way? Suggest: - - Replace with: “Carina” - - Replace with: “Carnap” - - Replace with: “Chadian” + - Replace with: “Cardiac” + - Replace with: “Carnal” + - Replace with: “Carnival” @@ -362,9 +365,9 @@ Message: | 153 | HMMs underlie the functioning of stochastic taggers and are used in various | ^~~~ Did you mean to spell `HMMs` this way? Suggest: - - Replace with: “Hems” - Replace with: “Hams” - - Replace with: “HMO's” + - Replace with: “Hems” + - Replace with: “Hums” @@ -374,8 +377,8 @@ Message: | | ^~~~~~ Did you mean to spell `DeRose` this way? Suggest: - Replace with: “Depose” - - Replace with: “Defoe” - - Replace with: “Denise” + - Replace with: “Debase” + - Replace with: “Decode” @@ -397,8 +400,8 @@ Message: | | ^~~~~~~ Did you mean to spell `Viterbi` this way? Suggest: - Replace with: “Vite's” - - Replace with: “Verdi” - - Replace with: “Vite” + - Replace with: “Verb” + - Replace with: “Veteran” @@ -408,8 +411,8 @@ Message: | | ^~~~~~ Did you mean to spell `DeRose` this way? Suggest: - Replace with: “Depose” - - Replace with: “Defoe” - - Replace with: “Denise” + - Replace with: “Debase” + - Replace with: “Decode” @@ -434,7 +437,7 @@ Message: | Suggest: - Replace with: “Defoe's” - Replace with: “Denise's” - - Replace with: “Repose's” + - Replace with: “Demise's” @@ -458,7 +461,7 @@ Message: | Suggest: - Replace with: “Defoe's” - Replace with: “Denise's” - - Replace with: “Repose's” + - Replace with: “Demise's” @@ -484,8 +487,8 @@ Message: | 201 | algorithm, Brill tagger, Constraint Grammar, and the Baum-Welch algorithm (also Suggest: - Replace with: “Vite's” - - Replace with: “Verdi” - - Replace with: “Vite” + - Replace with: “Verb” + - Replace with: “Veteran” @@ -496,8 +499,8 @@ Message: | 202 | known as the forward-backward algorithm). Hidden Markov model and visible Markov Suggest: - Replace with: “Welsh” - - Replace with: “Belch” - - Replace with: “Walsh” + - Replace with: “Wench” + - Replace with: “Watch” @@ -507,8 +510,8 @@ Message: | | ^~~~~~~ Did you mean to spell `Viterbi` this way? Suggest: - Replace with: “Vite's” - - Replace with: “Verdi” - - Replace with: “Vite” + - Replace with: “Verb” + - Replace with: “Veteran” @@ -518,8 +521,8 @@ Message: | | ^~~ Did you mean to spell `SVM` this way? Suggest: - Replace with: “Sim” + - Replace with: “Sum” - Replace with: “SCM” - - Replace with: “SVG” @@ -529,9 +532,9 @@ Message: | | ^~~~~~~~ Did you mean to spell `Treebank` this way? 214 | so the results are directly comparable. However, many significant taggers are Suggest: - - Replace with: “Freeman” - - Replace with: “Reembark” - - Replace with: “Debank” + - Replace with: “Tie back” + - Replace with: “Tieback” + - Replace with: “Traceback” diff --git a/harper-core/tests/text/linters/Spell.US.snap.yml b/harper-core/tests/text/linters/Spell.US.snap.yml index 641c6de0..2f2d3e2e 100644 --- a/harper-core/tests/text/linters/Spell.US.snap.yml +++ b/harper-core/tests/text/linters/Spell.US.snap.yml @@ -5,6 +5,7 @@ Message: | Suggest: - Replace with: “Afterward” - Replace with: “Afterwords” + - Replace with: “Afterword's” @@ -14,8 +15,8 @@ Message: | | ^~~~~~ Did you mean to spell `Centre` this way? Suggest: - Replace with: “Center” - - Replace with: “Entire” - - Replace with: “Entry” + - Replace with: “Censure” + - Replace with: “Cent” @@ -25,8 +26,8 @@ Message: | | ^~~~~~~~ Did you mean to spell `Labelled` this way? Suggest: - Replace with: “Labeled” - - Replace with: “Belled” - - Replace with: “Rebelled” + - Replace with: “Labeler” + - Replace with: “Labelless” @@ -36,7 +37,8 @@ Message: | | ^~~~~~~ Did you mean to spell `Flavour` this way? Suggest: - Replace with: “Flavor” - - Replace with: “Cavour” + - Replace with: “Favor” + - Replace with: “Flour” @@ -46,8 +48,8 @@ Message: | | ^~~~~~~~ Did you mean to spell `Favoured` this way? Suggest: - Replace with: “Favored” - - Replace with: “Devoured” - - Replace with: “Savored” + - Replace with: “Flavored” + - Replace with: “Floured” @@ -57,8 +59,8 @@ Message: | | ^~~~~~ Did you mean to spell `Honour` this way? Suggest: - Replace with: “Honor” - - Replace with: “Concur” - - Replace with: “Contour” + - Replace with: “Hour” + - Replace with: “Honer” @@ -67,18 +69,19 @@ Message: | 15 | - Grey. | ^~~~ Did you mean to spell `Grey` this way? Suggest: + - Replace with: “Gray” + - Replace with: “Grew” - Replace with: “Gorey” - - Replace with: “Greg” - - Replace with: “Trey” Lint: Spelling (63 priority) Message: | 16 | - Quarrelled. - | ^~~~~~~~~~ Did you mean `Quarreled`? + | ^~~~~~~~~~ Did you mean to spell `Quarrelled` this way? Suggest: - Replace with: “Quarreled” + - Replace with: “Quarreler” @@ -94,18 +97,21 @@ Suggest: Lint: Spelling (63 priority) Message: | 18 | - Recognised. - | ^~~~~~~~~~ Did you mean `Recognized`? + | ^~~~~~~~~~ Did you mean to spell `Recognised` this way? Suggest: - Replace with: “Recognized” + - Replace with: “Recognize” + - Replace with: “Recognizer” Lint: Spelling (63 priority) Message: | 19 | - Neighbour. - | ^~~~~~~~~ Did you mean `Neighbor`? + | ^~~~~~~~~ Did you mean to spell `Neighbour` this way? Suggest: - Replace with: “Neighbor” + - Replace with: “Neighbors” @@ -135,17 +141,19 @@ Message: | | ^~~~~~~ Did you mean to spell `Theatre` this way? Suggest: - Replace with: “Theater” - - Replace with: “Sheathe” - - Replace with: “Heater” + - Replace with: “There” + - Replace with: “They're” Lint: Spelling (63 priority) Message: | 23 | - Analyse. - | ^~~~~~~ Did you mean `Analyze`? + | ^~~~~~~ Did you mean to spell `Analyse` this way? Suggest: - Replace with: “Analyze” + - Replace with: “Analyst” + - Replace with: “Analysis” diff --git a/harper-core/tests/text/linters/Spell.snap.yml b/harper-core/tests/text/linters/Spell.snap.yml index 7ab547c5..99454216 100644 --- a/harper-core/tests/text/linters/Spell.snap.yml +++ b/harper-core/tests/text/linters/Spell.snap.yml @@ -89,8 +89,8 @@ Message: | | ^~~~~~ Did you mean to spell `centre` this way? Suggest: - Replace with: “center” - - Replace with: “central” - Replace with: “censure” + - Replace with: “cent” @@ -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: “liter” - Replace with: “lithe” - Replace with: “lire” - - Replace with: “lite” 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 2dcd5fcc..f0a15b5c 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 @@ -20,8 +20,8 @@ Message: | 7 | promote the general Welfare, and secure the Blessings of Liberty to ourselves Suggest: - Replace with: “defense” - - Replace with: “defended” - - Replace with: “defect” + - Replace with: “decency” + - Replace with: “deface” @@ -41,9 +41,9 @@ Message: | 11 | ## Article. I. | ^~ Did you mean to spell `I.` this way? Suggest: - - Replace with: “In” + - Replace with: “I” - Replace with: “Id” - - Replace with: “IC” + - Replace with: “If” @@ -351,8 +351,8 @@ Message: | 125 | Regulations, except as to the Places of chusing Senators. | ^~~~~~~ Did you mean to spell `chusing` this way? Suggest: - - Replace with: “chasing” - Replace with: “causing” + - Replace with: “chasing” - Replace with: “casing” @@ -386,9 +386,10 @@ Lint: Spelling (63 priority) Message: | 139 | Each House may determine the Rules of its Proceedings, punish its Members for 140 | disorderly Behaviour, and, with the Concurrence of two thirds, expel a Member. - | ^~~~~~~~~ Did you mean `Behavior`? + | ^~~~~~~~~ Did you mean to spell `Behaviour` this way? Suggest: - Replace with: “Behavior” + - Replace with: “Behaviors” @@ -583,8 +584,8 @@ Message: | 204 | general Welfare of the United States; but all Duties, Imposts and Excises shall Suggest: - Replace with: “Defense” - - Replace with: “Terence” - - Replace with: “Geofence” + - Replace with: “Decency” + - Replace with: “Deface” @@ -612,8 +613,9 @@ Message: | 229 | 9. To define and punish Piracies and Felonies committed on the high Seas, and | ^~~~~~~~ Did you mean to spell `Piracies` this way? Suggest: - - Replace with: “Curacies” - - Replace with: “Miracles” + - Replace with: “Piracy's” + - Replace with: “Papacies” + - Replace with: “Pirates” @@ -624,7 +626,8 @@ Message: | | ^~~~~~~~ Did you mean to spell `Offences` this way? Suggest: - Replace with: “Offenses” - - Replace with: “Fences” + - Replace with: “Offense's” + - Replace with: “Offender” @@ -826,10 +829,12 @@ Suggest: Lint: Spelling (63 priority) Message: | 315 | United States; and all such Laws shall be subject to the Revision and Controul - | ^~~~~~~~ Did you mean `Control`? + | ^~~~~~~~ Did you mean to spell `Controul` this way? 316 | of the Congress. Suggest: - Replace with: “Control” + - Replace with: “Contour” + - Replace with: “Contrail” @@ -994,8 +999,8 @@ Message: | | ^~~~~~~ Did you mean to spell `chusing` this way? 384 | which they shall give their Votes; which Day shall be the same throughout the Suggest: - - Replace with: “chasing” - Replace with: “causing” + - Replace with: “chasing” - Replace with: “casing” @@ -1350,7 +1355,8 @@ Message: | 482 | States, except in Cases of Impeachment. Suggest: - Replace with: “Offenses” - - Replace with: “Fences” + - Replace with: “Offense's” + - Replace with: “Offender” @@ -1413,10 +1419,11 @@ Lint: Spelling (63 priority) Message: | 525 | time ordain and establish. The Judges, both of the supreme and inferior Courts, 526 | shall hold their Offices during good Behaviour, and shall, at stated Times, - | ^~~~~~~~~ Did you mean `Behavior`? + | ^~~~~~~~~ Did you mean to spell `Behaviour` this way? 527 | receive for their Services, a Compensation, which shall not be diminished Suggest: - Replace with: “Behavior” + - Replace with: “Behaviors” @@ -1615,8 +1622,8 @@ Message: | | ^~~~~~ Did you mean to spell `Labour` this way? Suggest: - Replace with: “Labor” - - Replace with: “About” - - Replace with: “Amour” + - Replace with: “Laborer” + - Replace with: “Layout” @@ -1627,8 +1634,8 @@ Message: | | ^~~~~~ Did you mean to spell `Labour` this way? Suggest: - Replace with: “Labor” - - Replace with: “About” - - Replace with: “Amour” + - Replace with: “Laborer” + - Replace with: “Layout” @@ -1639,8 +1646,8 @@ Message: | | ^~~~~~ Did you mean to spell `Labour` this way? Suggest: - Replace with: “Labor” - - Replace with: “About” - - Replace with: “Amour” + - Replace with: “Laborer” + - Replace with: “Layout” @@ -1726,7 +1733,7 @@ Message: | Suggest: - Replace with: “Vi” - Replace with: “Va” - - Replace with: “Vs.” + - Replace with: “Vb” @@ -1819,6 +1826,7 @@ Message: | 715 | fifteenth Line of the first Page. The Words "is tried" being interlined between Suggest: - Replace with: “Erasure” + - Replace with: “Erasures” - Replace with: “Azure” 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 0ee82d5d..1d94af55 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: “Ff” - - Replace with: “F1” + - Replace with: “F” @@ -14,16 +14,18 @@ Message: | 3 | BY F. SCOTT FITZGERALD | ^~~~~ Did you mean to spell `SCOTT` this way? Suggest: + - Replace with: “Scout” - Replace with: “Scott” - - Replace with: “Scot” - - Replace with: “Scots” + - Replace with: “Scoot” Lint: Spelling (63 priority) Message: | 3 | BY F. SCOTT FITZGERALD - | ^~~~~~~~~~ Did you mean to spell `FITZGERALD` this way? + | ^~~~~~~~~~ Did you mean `Fitzgerald`? +Suggest: + - Replace with: “Fitzgerald” @@ -156,9 +158,11 @@ Message: | Lint: Spelling (63 priority) Message: | 50 | three generations. The Carraways are something of a clan, and we have a - | ^~~~~~~~~ Did you mean `Caraways`? + | ^~~~~~~~~ Did you mean to spell `Carraways` this way? Suggest: - Replace with: “Caraways” + - Replace with: “Caraway's” + - Replace with: “Castaways” @@ -169,8 +173,8 @@ Message: | 52 | founder of my line was my grandfather’s brother, who came here in fifty-one, Suggest: - Replace with: “Buckley's” - - Replace with: “Buckley” - - Replace with: “Nucleic” + - Replace with: “Bleach” + - Replace with: “Buckle” @@ -190,8 +194,8 @@ Message: | 62 | seemed like the ragged edge of the universe—so I decided to go East and learn Suggest: - Replace with: “center” - - Replace with: “central” - Replace with: “censure” + - Replace with: “cent” @@ -258,11 +262,9 @@ Lint: Spelling (63 priority) Message: | 95 | like new money from the mint, promising to unfold the shining secrets that only 96 | Midas and Morgan and Mæcenas knew. And I had the high intention of reading many - | ^~~~~~~ Did you mean to spell `Mæcenas` this way? + | ^~~~~~~ Did you mean `Mycenae`? Suggest: - Replace with: “Mycenae” - - Replace with: “Macon's” - - Replace with: “Mycenae's” @@ -322,8 +324,8 @@ Message: | | ^~~~~ Did you mean to spell `Hôtel` this way? Suggest: - Replace with: “Hotel” - - Replace with: “Havel” - - Replace with: “Hegel” + - Replace with: “Hate” + - Replace with: “Hazel” @@ -343,9 +345,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” @@ -357,7 +359,7 @@ Message: | Suggest: - Replace with: “Vile” - Replace with: “Villa” - - Replace with: “Lille” + - Replace with: “Villi” @@ -475,9 +477,9 @@ Message: | 189 | “It belonged to Demaine, the oil man.” He turned me around again, politely and | ^~~~~~~ Did you mean to spell `Demaine` this way? Suggest: - - Replace with: “Deanne” - - Replace with: “Deming” - - Replace with: “Dewayne” + - Replace with: “Decline” + - Replace with: “Define” + - Replace with: “Demand” @@ -693,7 +695,6 @@ Message: | Suggest: - Replace with: “Westminster” - Replace with: “Winchester” - - Replace with: “Winchesters” @@ -703,7 +704,8 @@ Message: | | ^~~~~~~~ Did you mean to spell `Carraway` this way? Suggest: - Replace with: “Caraway” - - Replace with: “Faraway” + - Replace with: “Caraways” + - Replace with: “Castaway” @@ -819,7 +821,7 @@ Message: | Suggest: - Replace with: “Ta” - Replace with: “Ti” - - Replace with: “Th” + - Replace with: “To” @@ -829,8 +831,8 @@ Message: | | ^~ Did you mean to spell `J.` this way? Suggest: - Replace with: “Jo” - - Replace with: “Jr” - - Replace with: “JD” + - Replace with: “J” + - Replace with: “Jg” @@ -840,8 +842,8 @@ Message: | | ^~~~~~~~~ Did you mean to spell `Eckleburg` this way? Suggest: - Replace with: “Excalibur” + - Replace with: “Heckler” - Replace with: “Vicksburg” - - Replace with: “Iceberg” @@ -853,7 +855,7 @@ Message: | Suggest: - Replace with: “Ta” - Replace with: “Ti” - - Replace with: “Th” + - Replace with: “To” @@ -863,8 +865,8 @@ Message: | | ^~ Did you mean to spell `J.` this way? Suggest: - Replace with: “Jo” - - Replace with: “Jr” - - Replace with: “JD” + - Replace with: “J” + - Replace with: “Jg” @@ -874,8 +876,8 @@ Message: | | ^~~~~~~~~ Did you mean to spell `Eckleburg` this way? Suggest: - Replace with: “Excalibur” + - Replace with: “Heckler” - Replace with: “Vicksburg” - - Replace with: “Iceberg” @@ -909,7 +911,7 @@ Message: | Suggest: - Replace with: “Excalibur's” - Replace with: “Vicksburg's” - - Replace with: “Iceberg's” + - Replace with: “Heckler's” @@ -928,9 +930,9 @@ Message: | 699 | garage—Repairs. George B. Wilson. Cars bought and sold.—and I followed Tom | ^~ Did you mean to spell `B.` this way? Suggest: - - Replace with: “Bu” - Replace with: “Be” - Replace with: “Bi” + - Replace with: “Bu” @@ -980,9 +982,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” @@ -1013,8 +1015,8 @@ Message: | | ^~~~~~~~~ Did you mean to spell `Eckleburg` this way? Suggest: - Replace with: “Excalibur” + - Replace with: “Heckler” - Replace with: “Vicksburg” - - Replace with: “Iceberg” @@ -1034,9 +1036,9 @@ Message: | 769 | to the sensibilities of those East Eggers who might be on the train. | ^~~~~~ Did you mean to spell `Eggers` this way? Suggest: - - Replace with: “Eggo's” - - Replace with: “Engels” - Replace with: “Edgers” + - Replace with: “Eggo's” + - Replace with: “Edge's” @@ -1055,9 +1057,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: “Do” - Replace with: “DA” - Replace with: “DE” - - Replace with: “Di” @@ -1171,9 +1173,11 @@ Suggest: Lint: Spelling (63 priority) Message: | 905 | “Mrs. Eberhardt. She goes around looking at people’s feet in their own homes.” - | ^~~~~~~~~ Did you mean `Bernhardt`? + | ^~~~~~~~~ Did you mean to spell `Eberhardt` this way? Suggest: - - Replace with: “Bernhardt” + - Replace with: “Earhart” + - Replace with: “Earnhardt” + - Replace with: “Erhard” @@ -1213,10 +1217,12 @@ Suggest: Lint: Spelling (63 priority) Message: | 950 | “Two studies. One of them I call ‘Montauk Point—The Gulls,’ and the other I call - | ^~~~~~~ Did you mean `Montague`? + | ^~~~~~~ Did you mean to spell `Montauk` this way? 951 | ‘Montauk Point—The Sea.’” Suggest: + - Replace with: “Montage” - Replace with: “Montague” + - Replace with: “Montana” @@ -1224,9 +1230,11 @@ Lint: Spelling (63 priority) Message: | 950 | “Two studies. One of them I call ‘Montauk Point—The Gulls,’ and the other I call 951 | ‘Montauk Point—The Sea.’” - | ^~~~~~~ Did you mean `Montague`? + | ^~~~~~~ Did you mean to spell `Montauk` this way? Suggest: + - Replace with: “Montage” - Replace with: “Montague” + - Replace with: “Montana” @@ -1245,9 +1253,9 @@ Message: | 990 | B. Wilson at the Gasoline Pump,’ or something like that.” | ^~ Did you mean to spell `B.` this way? Suggest: - - Replace with: “Bu” - Replace with: “Be” - Replace with: “Bi” + - Replace with: “Bu” @@ -1270,8 +1278,8 @@ Message: | | ^~~~ Did you mean to spell `kyke` this way? Suggest: - Replace with: “kike” - - Replace with: “tyke” - Replace with: “dyke” + - Replace with: “tyke” @@ -1344,7 +1352,7 @@ Message: | Suggest: - Replace with: “Dab” - Replace with: “Dad” - - Replace with: “Dais” + - Replace with: “Dag” @@ -1475,8 +1483,8 @@ Message: | 1180 | spiced baked hams crowded against salads of harlequin designs and pastry pigs Suggest: - Replace with: “hours” - - Replace with: “ho's” - Replace with: “hers” + - Replace with: “ho's” @@ -1525,8 +1533,8 @@ Message: | | ^~~~~~~ Did you mean to spell `Castile` this way? Suggest: - Replace with: “Castle” - - Replace with: “Cassie” - - Replace with: “Castillo” + - Replace with: “Captive” + - Replace with: “Caste” @@ -1578,8 +1586,8 @@ Message: | 1204 | triumph, glide on through the sea-change of faces and voices and color under the Suggest: - Replace with: “center” - - Replace with: “central” - Replace with: “censure” + - Replace with: “cent” @@ -1669,9 +1677,9 @@ Message: | | ^~~~~~~~~~ Did you mean to spell `Croirier’s` this way? 1288 | gown in it.” Suggest: - - Replace with: “Currier's” - Replace with: “Courier's” - Replace with: “Crosier's” + - Replace with: “Croupier's” @@ -1734,6 +1742,7 @@ Message: | 1374 | Volume One of the “Stoddard Lectures.” | ^~~~~~~~ Did you mean to spell `Stoddard` this way? Suggest: + - Replace with: “Standard” - Replace with: “Stoppard” - Replace with: “Goddard” @@ -1779,7 +1788,7 @@ Message: | Suggest: - Replace with: “Bela's” - Replace with: “Belau's” - - Replace with: “Basho” + - Replace with: “Balance” @@ -1789,8 +1798,8 @@ Message: | | ^~~~~ Did you mean to spell `Claud` this way? Suggest: - Replace with: “Clad” + - Replace with: “Cloud” - Replace with: “Claude” - - Replace with: “Claus” @@ -2018,9 +2027,9 @@ Message: | 1615 | Sigourney Howard. . . . My aunt. . . .” She was hurrying off as she talked—her | ^~~~~~~~~ Did you mean to spell `Sigourney` this way? Suggest: + - Replace with: “Sojourned” + - Replace with: “Sojourner” - Replace with: “Gurney” - - Replace with: “Journey” - - Replace with: “Tourney” @@ -2075,9 +2084,9 @@ Message: | 1693 | “Wha’s matter?” he inquired calmly. “Did we run outa gas?” | ^~~~~ Did you mean to spell `Wha’s` this way? Suggest: - - Replace with: “W12's” - - Replace with: “WAL's” - - Replace with: “WIP's” + - Replace with: “Wham's” + - Replace with: “What's” + - Replace with: “Who's” @@ -2086,8 +2095,8 @@ Message: | 1693 | “Wha’s matter?” he inquired calmly. “Did we run outa gas?” | ^~~~ Did you mean to spell `outa` this way? Suggest: - - Replace with: “outta” - Replace with: “out” + - Replace with: “outta” - Replace with: “oath” @@ -2113,10 +2122,9 @@ Suggest: Lint: Spelling (63 priority) Message: | 1710 | “Wonder’ff tell me where there’s a gas’line station?” - | ^~~~~~~~~ Did you mean to spell `Wonder’ff` this way? + | ^~~~~~~~~ Did you mean `Wonder's`? Suggest: - Replace with: “Wonder's” - - Replace with: “Wonderbra” @@ -2255,7 +2263,7 @@ Message: | Suggest: - Replace with: “Van” - Replace with: “Vol” - - Replace with: “Eon” + - Replace with: “Vow” @@ -2266,8 +2274,8 @@ Message: | 1849 | Bunsen, whom I knew at Yale, and Doctor Webster Civet, who was drowned last Suggest: - Replace with: “Becker's” - - Replace with: “Bickers” - - Replace with: “Becker” + - Replace with: “Backers” + - Replace with: “Beakers” @@ -2289,7 +2297,7 @@ Message: | Suggest: - Replace with: “Voltaire's” - Replace with: “Voltaire” - - Replace with: “Solitaires” + - Replace with: “Voltages” @@ -2297,8 +2305,9 @@ Lint: Spelling (63 priority) Message: | 1850 | summer up in Maine. And the Hornbeams and the Willie Voltaires, and a whole clan 1851 | named Blackbuck, who always gathered in a corner and flipped up their noses like - | ^~~~~~~~~ Did you mean `Blackburn`? + | ^~~~~~~~~ Did you mean to spell `Blackbuck` this way? Suggest: + - Replace with: “Blackjack” - Replace with: “Blackburn” @@ -2310,8 +2319,8 @@ Message: | 1853 | Auerbach and Mr. Chrystie’s wife), and Edgar Beaver, whose hair, they say, Suggest: - Replace with: “Irma's” + - Replace with: “Ism's” - Replace with: “Dismays” - - Replace with: “Islams” @@ -2333,17 +2342,20 @@ Message: | 1853 | Auerbach and Mr. Chrystie’s wife), and Edgar Beaver, whose hair, they say, | ^~~~~~~~ Did you mean to spell `Auerbach` this way? Suggest: - - Replace with: “Outreach” - - Replace with: “Bernbach” + - Replace with: “Approach” + - Replace with: “Acerbate” + - Replace with: “Acerbic” Lint: Spelling (63 priority) Message: | 1853 | Auerbach and Mr. Chrystie’s wife), and Edgar Beaver, whose hair, they say, - | ^~~~~~~~~~ Did you mean `Christi's`? + | ^~~~~~~~~~ Did you mean to spell `Chrystie’s` this way? Suggest: + - Replace with: “Christie's” - Replace with: “Christi's” + - Replace with: “Christine's” @@ -2352,9 +2364,9 @@ Message: | 1857 | knickerbockers, and had a fight with a bum named Etty in the garden. From | ^~~~ Did you mean to spell `Etty` this way? Suggest: + - Replace with: “Ethyl” - Replace with: “Etta” - - Replace with: “Thy” - - Replace with: “Betty” + - Replace with: “Jetty” @@ -2365,8 +2377,8 @@ Message: | | ^~~~~~~~ Did you mean to spell `Cheadles` this way? Suggest: - Replace with: “Charles” + - Replace with: “Cradles” - Replace with: “Headless” - - Replace with: “Beadles” @@ -2375,9 +2387,9 @@ Message: | 1858 | farther out on the Island came the Cheadles and the O. R. P. Schraeders, and the | ^~ Did you mean to spell `O.` this way? Suggest: - - Replace with: “Os” - - Replace with: “O” - - Replace with: “OD” + - Replace with: “Of” + - Replace with: “Oh” + - Replace with: “Oi” @@ -2386,9 +2398,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: “Re” - Replace with: “RI” - Replace with: “Ra” + - Replace with: “Ru” @@ -2398,7 +2410,7 @@ Message: | | ^~ Did you mean to spell `P.` this way? Suggest: - Replace with: “Pa” - - Replace with: “Po” + - Replace with: “Pi” - Replace with: “PE” @@ -2411,17 +2423,15 @@ Message: | Suggest: - Replace with: “Schroeder's” - Replace with: “Schroeder” - - Replace with: “Threaders” Lint: Spelling (63 priority) Message: | 1859 | Stonewall Jackson Abrams of Georgia, and the Fishguards and the Ripley Snells. - | ^~~~~~~~~~ Did you mean to spell `Fishguards` this way? + | ^~~~~~~~~~ Did you mean `Fireguards`? Suggest: - Replace with: “Fireguards” - - Replace with: “Wireguards” @@ -2431,8 +2441,8 @@ Message: | | ^~~~~~ Did you mean to spell `Snells` this way? Suggest: - Replace with: “Snell's” - - Replace with: “Snell” - - Replace with: “Knells” + - Replace with: “Sells” + - Replace with: “Shells” @@ -2441,9 +2451,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: “Sweat's” + - Replace with: “Sweet's” - Replace with: “Seth's” - - Replace with: “Scott's” - - Replace with: “Sept's” @@ -2452,9 +2462,9 @@ Message: | 1862 | The Dancies came, too, and S. B. Whitebait, who was well over sixty, and Maurice | ^~~~~~~ Did you mean to spell `Dancies` this way? Suggest: - - Replace with: “Dannie's” - - Replace with: “Fancies” - Replace with: “Dances” + - Replace with: “Dandies” + - Replace with: “Dannie's” @@ -2465,7 +2475,7 @@ Message: | Suggest: - Replace with: “So” - Replace with: “SA” - - Replace with: “Si” + - Replace with: “Se” @@ -2474,9 +2484,9 @@ Message: | 1862 | The Dancies came, too, and S. B. Whitebait, who was well over sixty, and Maurice | ^~ Did you mean to spell `B.` this way? Suggest: - - Replace with: “Bu” - Replace with: “Be” - Replace with: “Bi” + - Replace with: “Bu” @@ -2486,9 +2496,9 @@ Message: | 1863 | A. Flink, and the Hammerheads, and Beluga the tobacco importer, and Beluga’s | ^~ Did you mean to spell `A.` this way? Suggest: - - Replace with: “Ax” - Replace with: “A” - Replace with: “Ab” + - Replace with: “Ac” @@ -2497,9 +2507,9 @@ Message: | 1863 | A. Flink, and the Hammerheads, and Beluga the tobacco importer, and Beluga’s | ^~~~~ Did you mean to spell `Flink` this way? Suggest: - - Replace with: “Flunk” - - Replace with: “Blink” - - Replace with: “Link” + - Replace with: “Fink” + - Replace with: “Flank” + - Replace with: “Flick” @@ -2522,6 +2532,7 @@ Message: | | ^~~~~~~~~ Did you mean to spell `Mulreadys` this way? 1867 | Schoen and Gulick the State senator and Newton Orchid, who controlled Films Par Suggest: + - Replace with: “Misreads” - Replace with: “Already” - Replace with: “Unready” @@ -2533,9 +2544,9 @@ Message: | 1867 | Schoen and Gulick the State senator and Newton Orchid, who controlled Films Par | ^~~~~~ Did you mean to spell `Schoen` this way? Suggest: - - Replace with: “Chosen” - - Replace with: “Echoes” - - Replace with: “Echoed” + - Replace with: “School” + - Replace with: “Scion” + - Replace with: “Scone” @@ -2545,9 +2556,9 @@ Message: | 1867 | Schoen and Gulick the State senator and Newton Orchid, who controlled Films Par | ^~~~~~ Did you mean to spell `Gulick` this way? Suggest: + - Replace with: “Gulch” - Replace with: “Click” - Replace with: “Flick” - - Replace with: “Lick” @@ -2568,17 +2579,18 @@ Message: | Suggest: - Replace with: “So” - Replace with: “SA” - - Replace with: “Si” + - Replace with: “Se” Lint: Spelling (63 priority) Message: | 1868 | Excellence, and Eckhaust and Clyde Cohen and Don S. Schwartze (the son) and - | ^~~~~~~~~ Did you mean `Schwartz`? + | ^~~~~~~~~ Did you mean to spell `Schwartze` this way? 1869 | Arthur McCarty, all connected with the movies in one way or another. And the Suggest: - Replace with: “Schwartz” + - Replace with: “Schwartz's” @@ -2586,9 +2598,11 @@ Lint: Spelling (63 priority) Message: | 1869 | Arthur McCarty, all connected with the movies in one way or another. And the 1870 | Catlips and the Bembergs and G. Earl Muldoon, brother to that Muldoon who - | ^~~~~~~ Did you mean `Cali's`? + | ^~~~~~~ Did you mean to spell `Catlips` this way? Suggest: - Replace with: “Cali's” + - Replace with: “Catnip's” + - Replace with: “Caliph” @@ -2599,8 +2613,8 @@ Message: | | ^~~~~~~~ Did you mean to spell `Bembergs` this way? Suggest: - Replace with: “Berber's” + - Replace with: “Bomber's” - Replace with: “Berbers” - - Replace with: “Ember's” @@ -2619,24 +2633,20 @@ Suggest: Lint: Spelling (63 priority) Message: | 1870 | Catlips and the Bembergs and G. Earl Muldoon, brother to that Muldoon who - | ^~~~~~~ Did you mean to spell `Muldoon` this way? + | ^~~~~~~ Did you mean `Mullion`? 1871 | afterward strangled his wife. Da Fontano the promoter came there, and Ed Legros Suggest: - Replace with: “Mullion” - - Replace with: “Moulton” - - Replace with: “Mauldin” Lint: Spelling (63 priority) Message: | 1870 | Catlips and the Bembergs and G. Earl Muldoon, brother to that Muldoon who - | ^~~~~~~ Did you mean to spell `Muldoon` this way? + | ^~~~~~~ Did you mean `Mullion`? 1871 | afterward strangled his wife. Da Fontano the promoter came there, and Ed Legros Suggest: - Replace with: “Mullion” - - Replace with: “Moulton” - - Replace with: “Mauldin” @@ -2654,9 +2664,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” + - Replace with: “Dag” @@ -2676,8 +2686,10 @@ Message: | Lint: Spelling (63 priority) Message: | 1871 | afterward strangled his wife. Da Fontano the promoter came there, and Ed Legros - | ^~~~~~~ Did you mean `Montana`? + | ^~~~~~~ Did you mean to spell `Fontano` this way? Suggest: + - Replace with: “Fondant” + - Replace with: “Fontanel” - Replace with: “Montana” @@ -2689,8 +2701,8 @@ Message: | 1872 | and James B. (“Rot-Gut”) Ferret and the De Jongs and Ernest Lilly—they came to Suggest: - Replace with: “Lear's” - - Replace with: “Negros” - - Replace with: “Lagos” + - Replace with: “Leger's” + - Replace with: “Leer's” @@ -2700,9 +2712,9 @@ Message: | 1872 | and James B. (“Rot-Gut”) Ferret and the De Jongs and Ernest Lilly—they came to | ^~ Did you mean to spell `B.` this way? Suggest: - - Replace with: “Bu” - Replace with: “Be” - Replace with: “Bi” + - Replace with: “Bu” @@ -2743,7 +2755,9 @@ Suggest: Lint: Spelling (63 priority) Message: | 1876 | A man named Klipspringer was there so often and so long that he became known as - | ^~~~~~~~~~~~ Did you mean to spell `Klipspringer` this way? + | ^~~~~~~~~~~~ Did you mean `Kissinger`? +Suggest: + - Replace with: “Kissinger” @@ -2753,9 +2767,9 @@ Message: | 1878 | Gus Waize and Horace O’Donavan and Lester Myer and George Duckweed and Francis | ^~~~~ Did you mean to spell `Waize` this way? Suggest: + - Replace with: “Waive” - Replace with: “Waite” - Replace with: “Maize” - - Replace with: “Waive” @@ -2763,11 +2777,9 @@ Lint: Spelling (63 priority) Message: | 1877 | “the boarder”—I doubt if he had any other home. Of theatrical people there were 1878 | Gus Waize and Horace O’Donavan and Lester Myer and George Duckweed and Francis - | ^~~~~~~~~ Did you mean to spell `O’Donavan` this way? + | ^~~~~~~~~ Did you mean `Donovan`? Suggest: - - Replace with: “O'Donnell” - Replace with: “Donovan” - - Replace with: “Okinawan” @@ -2801,9 +2813,11 @@ Message: | Lint: Spelling (63 priority) Message: | 1879 | Bull. Also from New York were the Chromes and the Backhyssons and the Dennickers - | ^~~~~~~~~~~ Did you mean `Jacksons`? + | ^~~~~~~~~~~ Did you mean to spell `Backhyssons` this way? 1880 | and Russel Betty and the Corrigans and the Kellehers and the Dewars and the Suggest: + - Replace with: “Backhoes” + - Replace with: “Backstops” - Replace with: “Jacksons” @@ -2814,9 +2828,9 @@ Message: | | ^~~~~~~~~~ Did you mean to spell `Dennickers` this way? 1880 | and Russel Betty and the Corrigans and the Kellehers and the Dewars and the Suggest: - - Replace with: “Knickers” - - Replace with: “Nickers” - - Replace with: “Picnickers” + - Replace with: “Deicers” + - Replace with: “Deniers” + - Replace with: “Dickers” @@ -2824,11 +2838,9 @@ Lint: Spelling (63 priority) Message: | 1879 | Bull. Also from New York were the Chromes and the Backhyssons and the Dennickers 1880 | and Russel Betty and the Corrigans and the Kellehers and the Dewars and the - | ^~~~~~~~~ Did you mean to spell `Corrigans` this way? + | ^~~~~~~~~ Did you mean `Cardigans`? Suggest: - Replace with: “Cardigans” - - Replace with: “Corina's” - - Replace with: “Corrine's” @@ -2851,8 +2863,8 @@ Message: | 1881 | Scullys and S. W. Belcher and the Smirkes and the young Quinns, divorced now, Suggest: - Replace with: “Dewar's” - - Replace with: “Dewar” - - Replace with: “Dena's” + - Replace with: “Dears” + - Replace with: “Debars” @@ -2863,8 +2875,8 @@ Message: | | ^~~~~~~ Did you mean to spell `Scullys` this way? Suggest: - Replace with: “Scull's” - - Replace with: “Sculley's” - Replace with: “Sculls” + - Replace with: “Sculley's” @@ -2876,7 +2888,7 @@ Message: | Suggest: - Replace with: “So” - Replace with: “SA” - - Replace with: “Si” + - Replace with: “Se” @@ -2897,8 +2909,8 @@ Message: | | ^~~~~~~ Did you mean to spell `Belcher` this way? Suggest: - Replace with: “Beecher” - - Replace with: “Becker” - - Replace with: “Blucher” + - Replace with: “Belched” + - Replace with: “Belches” @@ -2921,8 +2933,8 @@ Message: | 1882 | and Henry L. Palmetto, who killed himself by jumping in front of a subway train Suggest: - Replace with: “Quinn's” - - Replace with: “Quinn” - - Replace with: “Qin's” + - Replace with: “Quines” + - Replace with: “Quins” @@ -2941,9 +2953,9 @@ Suggest: Lint: Spelling (63 priority) Message: | 1885 | Benny McClenahan arrived always with four girls. They were never quite the same - | ^~~~~~~~~~ Did you mean `McClain`? + | ^~~~~~~~~~ Did you mean `McClellan`? Suggest: - - Replace with: “McClain” + - Replace with: “McClellan” @@ -2977,10 +2989,12 @@ Lint: Spelling (63 priority) Message: | 1887 | inevitably seemed they had been there before. I have forgotten their 1888 | names—Jaqueline, I think, or else Consuela, or Gloria or Judy or June, and their - | ^~~~~~~~ Did you mean `Consuelo`? + | ^~~~~~~~ Did you mean to spell `Consuela` this way? 1889 | last names were either the melodious names of flowers and months or the sterner Suggest: - Replace with: “Consuelo” + - Replace with: “Consul” + - Replace with: “Consular” @@ -2992,6 +3006,7 @@ Message: | Suggest: - Replace with: “Faustian” - Replace with: “Faustino” + - Replace with: “Fasting” @@ -3008,9 +3023,9 @@ Message: | | ^~~~ Did you mean to spell `Haag` this way? 1896 | Fitz-Peters and Mr. P. Jewett, once head of the American Legion, and Miss Suggest: - - Replace with: “Haas” - - Replace with: “Ha's” - Replace with: “Hang” + - Replace with: “Haas” + - Replace with: “Hag” @@ -3032,8 +3047,9 @@ Message: | | ^~~~~~ Did you mean to spell `Ardita` this way? 1896 | Fitz-Peters and Mr. P. Jewett, once head of the American Legion, and Miss Suggest: + - Replace with: “Aria” + - Replace with: “Audit” - Replace with: “Akita” - - Replace with: “Anita” @@ -3043,7 +3059,7 @@ Message: | 1896 | Fitz-Peters and Mr. P. Jewett, once head of the American Legion, and Miss | ^~~~ Did you mean to spell `Fitz` this way? Suggest: - - Replace with: “Fizz” + - Replace with: “Fits” - Replace with: “Fit” - Replace with: “Fritz” @@ -3055,7 +3071,7 @@ Message: | | ^~ Did you mean to spell `P.` this way? Suggest: - Replace with: “Pa” - - Replace with: “Po” + - Replace with: “Pi” - Replace with: “PE” @@ -3065,9 +3081,9 @@ Message: | 1896 | Fitz-Peters and Mr. P. Jewett, once head of the American Legion, and Miss | ^~~~~~ Did you mean to spell `Jewett` this way? Suggest: + - Replace with: “Jewel” - Replace with: “Jewell” - Replace with: “Jewess” - - Replace with: “Dewitt” @@ -3114,7 +3130,7 @@ Message: | | ^~~~ Did you mean to spell `Bois` this way? 1982 | de Boulogne. Suggest: - - Replace with: “Bops” + - Replace with: “Boss” - Replace with: “Boris” - Replace with: “Bios” @@ -3136,9 +3152,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” @@ -3146,8 +3162,9 @@ Lint: Spelling (63 priority) Message: | 1981 | “character” leaking sawdust at every pore as he pursued a tiger through the Bois 1982 | de Boulogne. - | ^~~~~~~~ Did you mean `Cologne`? + | ^~~~~~~~ Did you mean to spell `Boulogne` this way? Suggest: + - Replace with: “Bologna” - Replace with: “Cologne” @@ -3169,8 +3186,8 @@ Message: | 2007 | circular legend, “Montenegro, Nicolas Rex.” Suggest: - Replace with: “Order” - - Replace with: “Oder” - Replace with: “Orders” + - Replace with: “Ordeal” @@ -3190,9 +3207,9 @@ Message: | | ^~ Did you mean to spell `di` this way? 2007 | circular legend, “Montenegro, Nicolas Rex.” Suggest: - - Replace with: “dig” - Replace with: “db” - Replace with: “dc” + - Replace with: “dd” @@ -3202,9 +3219,9 @@ Message: | | ^~~~~~ Did you mean to spell `Danilo` this way? 2007 | circular legend, “Montenegro, Nicolas Rex.” Suggest: + - Replace with: “Daily” + - Replace with: “Danish” - Replace with: “Danial” - - Replace with: “Daniel” - - Replace with: “Daniels” @@ -3214,8 +3231,8 @@ Message: | | ^~~~~~ Did you mean to spell `Valour` this way? Suggest: - Replace with: “Valor” - - Replace with: “Valium” - - Replace with: “Valois” + - Replace with: “Velour” + - Replace with: “Value” @@ -3324,7 +3341,8 @@ Message: | | ^~~~~~~~ Did you mean to spell `Carraway` this way? Suggest: - Replace with: “Caraway” - - Replace with: “Faraway” + - Replace with: “Caraways” + - Replace with: “Castaway” @@ -3350,7 +3368,9 @@ Suggest: Lint: Spelling (63 priority) Message: | 2105 | “I handed the money to Katspaugh and I sid: ‘All right, Katspaugh, don’t pay him - | ^~~~~~~~~ Did you mean to spell `Katspaugh` this way? + | ^~~~~~~~~ Did you mean `Kasparov`? +Suggest: + - Replace with: “Kasparov” @@ -3368,17 +3388,19 @@ Message: | 2105 | “I handed the money to Katspaugh and I sid: ‘All right, Katspaugh, don’t pay him | ^~~ Did you mean to spell `sid` this way? Suggest: + - Replace with: “sad” - Replace with: “said” - Replace with: “sic” - - Replace with: “side” Lint: Spelling (63 priority) Message: | 2105 | “I handed the money to Katspaugh and I sid: ‘All right, Katspaugh, don’t pay him - | ^~~~~~~~~ Did you mean to spell `Katspaugh` this way? + | ^~~~~~~~~ Did you mean `Kasparov`? 2106 | a penny till he shuts his mouth.’ He shut it then and there.” +Suggest: + - Replace with: “Kasparov” @@ -3443,8 +3465,8 @@ Message: | 2124 | “The old Metropole.” | ^~~~~~~~~ Did you mean to spell `Metropole` this way? Suggest: - - Replace with: “Metronome” - Replace with: “Metropolis” + - Replace with: “Metronome” @@ -3453,8 +3475,8 @@ Message: | 2126 | “The old Metropole,” brooded Mr. Wolfshiem gloomily. “Filled with faces dead and | ^~~~~~~~~ Did you mean to spell `Metropole` this way? Suggest: - - Replace with: “Metronome” - Replace with: “Metropolis” + - Replace with: “Metronome” @@ -3543,8 +3565,8 @@ Message: | 2163 | atmosphere of the old Metropole, began to eat with ferocious delicacy. His eyes, | ^~~~~~~~~ Did you mean to spell `Metropole` this way? Suggest: - - Replace with: “Metronome” - Replace with: “Metropolis” + - Replace with: “Metronome” @@ -3569,33 +3591,27 @@ Suggest: Lint: Spelling (63 priority) Message: | 2188 | “He’s an Oggsford man.” - | ^~~~~~~~ Did you mean to spell `Oggsford` this way? + | ^~~~~~~~ Did you mean `Oxford`? Suggest: - Replace with: “Oxford” - - Replace with: “Osborn” - - Replace with: “Osgood” Lint: Spelling (63 priority) Message: | 2192 | “He went to Oggsford College in England. You know Oggsford College?” - | ^~~~~~~~ Did you mean to spell `Oggsford` this way? + | ^~~~~~~~ Did you mean `Oxford`? Suggest: - Replace with: “Oxford” - - Replace with: “Osborn” - - Replace with: “Osgood” Lint: Spelling (63 priority) Message: | 2192 | “He went to Oggsford College in England. You know Oggsford College?” - | ^~~~~~~~ Did you mean to spell `Oggsford` this way? + | ^~~~~~~~ Did you mean `Oxford`? Suggest: - Replace with: “Oxford” - - Replace with: “Osborn” - - Replace with: “Osgood” @@ -3683,7 +3699,8 @@ Message: | | ^~~~~~ Did you mean to spell `How’ve` this way? Suggest: - Replace with: “How're” - - Replace with: “Howe” + - Replace with: “Hove” + - Replace with: “How'd” @@ -3726,8 +3743,8 @@ Message: | 2320 | began to play in tournaments, so I didn’t see Daisy very often. She went with a Suggest: - Replace with: “beau” - - Replace with: “beaut” - Replace with: “beaus” + - Replace with: “beaut” @@ -3772,8 +3789,9 @@ Message: | | ^~~~~~~~ Did you mean to spell `Muhlbach` this way? 2334 | the wedding he gave her a string of pearls valued at three hundred and fifty Suggest: + - Replace with: “Mulch” + - Replace with: “Mullah” - Replace with: “Fullback” - - Replace with: “Pullback” @@ -3785,15 +3803,18 @@ Message: | Suggest: - Replace with: “Sauternes” - Replace with: “Sterne” + - Replace with: “Sauteing” Lint: Spelling (63 priority) Message: | 2342 | “’Gratulate me,” she muttered. “Never had a drink before, but oh how I do enjoy - | ^~~~~~~~~ Did you mean `Granulate`? + | ^~~~~~~~~ Did you mean to spell `Gratulate` this way? Suggest: - Replace with: “Granulate” + - Replace with: “Graduate” + - Replace with: “Granulated” @@ -3803,8 +3824,8 @@ Message: | | ^~~~~~ Did you mean to spell `deares` this way? Suggest: - Replace with: “dear's” - - Replace with: “deaves” - Replace with: “dares” + - Replace with: “dearest” @@ -3864,20 +3885,18 @@ Message: | 2374 | night, and ripped a front wheel off his car. The girl who was with him got into Suggest: - Replace with: “Venture” - - Replace with: “Century” - - Replace with: “Denture” + - Replace with: “Ventral” + - Replace with: “Ventured” Lint: Spelling (63 priority) Message: | 2379 | saw them one spring in Cannes, and later in Deauville, and then they came back - | ^~~~~~~~~ Did you mean to spell `Deauville` this way? + | ^~~~~~~~~ Did you mean `Danville`? 2380 | to Chicago to settle down. Daisy was popular in Chicago, as you know. They moved Suggest: - Replace with: “Danville” - - Replace with: “Melville” - - Replace with: “Neville” @@ -3909,8 +3928,8 @@ Message: | | ^~~~~ Did you mean to spell `Sheik` this way? Suggest: - Replace with: “Sheikh” - - Replace with: “Sabik” - - Replace with: “Seiko” + - Replace with: “Seek” + - Replace with: “Shack” @@ -4002,9 +4021,9 @@ Message: | 2492 | some of the rooms. Let’s go to Coney Island, old sport. In my car.” | ^~~~~ Did you mean to spell `Coney` this way? Suggest: + - Replace with: “Cone” + - Replace with: “Convey” - Replace with: “Conley” - - Replace with: “Corey” - - Replace with: “Coley” @@ -4051,9 +4070,9 @@ Message: | 2562 | Coney Island, or for how many hours he “glanced into rooms” while his house | ^~~~~ Did you mean to spell `Coney` this way? Suggest: + - Replace with: “Cone” + - Replace with: “Convey” - Replace with: “Conley” - - Replace with: “Corey” - - Replace with: “Coley” @@ -4096,7 +4115,7 @@ Message: | Suggest: - Replace with: “blared” - Replace with: “bleated” - - Replace with: “blurred” + - Replace with: “beard” @@ -4113,8 +4132,9 @@ Suggest: Lint: Spelling (63 priority) Message: | 2638 | “That’s the secret of Castle Rackrent. Tell your chauffeur to go far away and - | ^~~~~~~~ Did you mean `Backrest`? + | ^~~~~~~~ Did you mean to spell `Rackrent` this way? Suggest: + - Replace with: “Racket” - Replace with: “Backrest” @@ -4124,9 +4144,9 @@ Message: | 2641 | “Come back in an hour, Ferdie.” Then in a grave murmur: “His name is Ferdie.” | ^~~~~~ Did you mean to spell `Ferdie` this way? Suggest: + - Replace with: “Faerie” + - Replace with: “Fertile” - Replace with: “Fermi” - - Replace with: “Ferris” - - Replace with: “Freddie” @@ -4135,9 +4155,9 @@ Message: | 2641 | “Come back in an hour, Ferdie.” Then in a grave murmur: “His name is Ferdie.” | ^~~~~~ Did you mean to spell `Ferdie` this way? Suggest: + - Replace with: “Faerie” + - Replace with: “Fertile” - Replace with: “Fermi” - - Replace with: “Ferris” - - Replace with: “Freddie” @@ -4236,8 +4256,8 @@ Message: | | ^~~~~~~ Did you mean to spell `postern` this way? Suggest: - Replace with: “poster” - - Replace with: “posters” - Replace with: “pastern” + - Replace with: “posters” @@ -4288,7 +4308,9 @@ Suggest: Lint: Spelling (63 priority) Message: | 2843 | doing liver exercises on the floor. It was Mr. Klipspringer, the ‘‘boarder.” I - | ^~~~~~~~~~~~ Did you mean to spell `Klipspringer` this way? + | ^~~~~~~~~~~~ Did you mean `Kissinger`? +Suggest: + - Replace with: “Kissinger” @@ -4408,14 +4430,18 @@ Suggest: Lint: Spelling (63 priority) Message: | 2940 | “I know what we'll do,” said Gatsby, “we'll have Klipspringer play the piano.” - | ^~~~~~~~~~~~ Did you mean to spell `Klipspringer` this way? + | ^~~~~~~~~~~~ Did you mean `Kissinger`? +Suggest: + - Replace with: “Kissinger” Lint: Spelling (63 priority) Message: | 2949 | “I was asleep,” cried Mr. Klipspringer, in a spasm of embarrassment. “That is, - | ^~~~~~~~~~~~ Did you mean to spell `Klipspringer` this way? + | ^~~~~~~~~~~~ Did you mean `Kissinger`? +Suggest: + - Replace with: “Kissinger” @@ -4431,7 +4457,9 @@ Suggest: Lint: Spelling (63 priority) Message: | 2952 | “Klipspringer plays the piano,” said Gatsby, cutting him off. “Don’t you, Ewing, - | ^~~~~~~~~~~~ Did you mean to spell `Klipspringer` this way? + | ^~~~~~~~~~~~ Did you mean `Kissinger`? +Suggest: + - Replace with: “Kissinger” @@ -4440,8 +4468,8 @@ Message: | 2955 | “I don’t play well. I don’t—I hardly play at all. I’m all out of prac———” | ^~~~ Did you mean to spell `prac` this way? Suggest: - - Replace with: “pray” - Replace with: “prat” + - Replace with: “pray” - Replace with: “pram” @@ -4449,7 +4477,9 @@ Suggest: Lint: Spelling (63 priority) Message: | 2965 | When Klipspringer had played “The Love Nest” he turned around on the bench and - | ^~~~~~~~~~~~ Did you mean to spell `Klipspringer` this way? + | ^~~~~~~~~~~~ Did you mean `Kissinger`? +Suggest: + - Replace with: “Kissinger” @@ -4480,8 +4510,8 @@ Message: | 2969 | prac—” | ^~~~ Did you mean to spell `prac` this way? Suggest: - - Replace with: “pray” - Replace with: “prat” + - Replace with: “pray” - Replace with: “pram” @@ -4515,9 +4545,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: “Gate” - Replace with: “Garth” - Replace with: “Goth” - - Replace with: “Ga's” @@ -4526,9 +4556,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: “Gate” - Replace with: “Garth” - Replace with: “Goth” - - Replace with: “Ga's” @@ -4550,19 +4580,21 @@ 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: “Gate” - Replace with: “Garth” - Replace with: “Goth” - - Replace with: “Ga's” Lint: Spelling (63 priority) Message: | 3031 | Jay Gatsby who borrowed a rowboat, pulled out to the Tuolomee, and informed Cody - | ^~~~~~~~ Did you mean `Toulouse`? + | ^~~~~~~~ Did you mean to spell `Tuolomee` this way? 3032 | that a wind might catch him and break him up in half an hour. Suggest: - Replace with: “Toulouse” + - Replace with: “Twosome” + - Replace with: “Twosomes” @@ -4604,9 +4636,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” @@ -4616,9 +4648,9 @@ Message: | | ^~~~~~~~~ Did you mean to spell `Maintenon` this way? 3075 | sent him to sea in a yacht, were common property of the turgid journalism Suggest: + - Replace with: “Maintain” - Replace with: “Maintenance” - Replace with: “Maintop” - - Replace with: “Matheson” @@ -4639,19 +4671,21 @@ 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: “Gate” - Replace with: “Garth” - Replace with: “Goth” - - Replace with: “Ga's” Lint: Spelling (63 priority) Message: | 3085 | and a yachting cap. And when the Tuolomee left for the West Indies and the - | ^~~~~~~~ Did you mean `Toulouse`? + | ^~~~~~~~ Did you mean to spell `Tuolomee` this way? 3086 | Barbary Coast Gatsby left too. Suggest: - Replace with: “Toulouse” + - Replace with: “Twosome” + - Replace with: “Twosomes” @@ -4875,9 +4909,9 @@ Message: | 3316 | “Wha’?” | ^~~ Did you mean to spell `Wha` this way? Suggest: - - Replace with: “What” - Replace with: “Wham” - - Replace with: “Aha” + - Replace with: “What” + - Replace with: “Who” @@ -4887,8 +4921,8 @@ Message: | | ^~~~~~~ Did you mean to spell `defence` this way? Suggest: - Replace with: “defense” - - Replace with: “defended” - - Replace with: “defect” + - Replace with: “decency” + - Replace with: “deface” @@ -5087,7 +5121,8 @@ Message: | 3508 | over.” Suggest: - Replace with: “Caraway” - - Replace with: “Faraway” + - Replace with: “Caraways” + - Replace with: “Castaway” @@ -5097,7 +5132,8 @@ Message: | | ^~~~~~~~ Did you mean to spell `Carraway` this way? Suggest: - Replace with: “Caraway” - - Replace with: “Faraway” + - Replace with: “Caraways” + - Replace with: “Castaway” @@ -5107,7 +5143,8 @@ Message: | | ^~~~~~~~ Did you mean to spell `Carraway` this way? Suggest: - Replace with: “Caraway” - - Replace with: “Faraway” + - Replace with: “Caraways” + - Replace with: “Castaway” @@ -5212,8 +5249,8 @@ Message: | | ^~~~~~ Did you mean to spell `centre` this way? Suggest: - Replace with: “center” - - Replace with: “central” - Replace with: “censure” + - Replace with: “cent” @@ -5272,9 +5309,9 @@ Message: | 3640 | “Bles-sed pre-cious,” she crooned, holding out her arms. “Come to your own | ^~~~ Did you mean to spell `Bles` this way? Suggest: + - Replace with: “Bless” - Replace with: “Bales” - - Replace with: “Blew” - - Replace with: “Boles” + - Replace with: “Bees” @@ -5327,9 +5364,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” @@ -5338,9 +5375,9 @@ Message: | 3672 | “Come, Pammy.” | ^~~~~ Did you mean to spell `Pammy` this way? Suggest: - - Replace with: “Mammy” - - Replace with: “Sammy” - - Replace with: “Tammy” + - Replace with: “Palmy” + - Replace with: “Pommy” + - Replace with: “Pam's” @@ -5488,7 +5525,7 @@ Message: | Suggest: - Replace with: “Ta” - Replace with: “Ti” - - Replace with: “Th” + - Replace with: “To” @@ -5498,8 +5535,8 @@ Message: | | ^~ Did you mean to spell `J.` this way? Suggest: - Replace with: “Jo” - - Replace with: “Jr” - - Replace with: “JD” + - Replace with: “J” + - Replace with: “Jg” @@ -5511,7 +5548,7 @@ Message: | Suggest: - Replace with: “Excalibur's” - Replace with: “Vicksburg's” - - Replace with: “Iceberg's” + - Replace with: “Heckler's” @@ -5557,7 +5594,7 @@ Message: | Suggest: - Replace with: “Ta” - Replace with: “Ti” - - Replace with: “Th” + - Replace with: “To” @@ -5567,8 +5604,8 @@ Message: | | ^~ Did you mean to spell `J.` this way? Suggest: - Replace with: “Jo” - - Replace with: “Jr” - - Replace with: “JD” + - Replace with: “J” + - Replace with: “Jg” @@ -5579,8 +5616,8 @@ Message: | 3949 | vigil, but I perceived, after a moment, that other eyes were regarding us with Suggest: - Replace with: “Excalibur” + - Replace with: “Heckler” - Replace with: “Vicksburg” - - Replace with: “Iceberg” @@ -5686,9 +5723,9 @@ Message: | 4016 | “Well, we’d better telephone for an axe———” | ^~~ Did you mean to spell `axe` this way? Suggest: - - Replace with: “aye” - Replace with: “ace” - Replace with: “age” + - Replace with: “ale” @@ -5810,9 +5847,9 @@ Message: | | ^~~ Did you mean to spell `Asa` this way? 4084 | brought him around at the last minute and asked if we had room for him.” Suggest: - - Replace with: “Ass” + - Replace with: “Aha” + - Replace with: “Aka” - Replace with: “As” - - Replace with: “Ash” @@ -5917,7 +5954,7 @@ Message: | Suggest: - Replace with: “Kaitlin” - Replace with: “Kaposi” - - Replace with: “Capillary” + - Replace with: “Kaolin” @@ -6040,9 +6077,11 @@ Suggest: Lint: Spelling (63 priority) Message: | 4391 | The young Greek, Michaelis, who ran the coffee joint beside the ashheaps was the - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” @@ -6061,28 +6100,34 @@ Suggest: Lint: Spelling (63 priority) Message: | 4394 | office—really sick, pale as his own pale hair and shaking all over. Michaelis - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? 4395 | advised him to go to bed, but Wilson refused, saying that he’d miss a lot of Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” Lint: Spelling (63 priority) Message: | 4402 | Michaelis was astonished; they had been neighbors for four years, and Wilson had - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” Lint: Spelling (63 priority) Message: | 4409 | So naturally Michaelis tried to find out what had happened, but Wilson wouldn’t - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” @@ -6090,9 +6135,11 @@ Lint: Spelling (63 priority) Message: | 4412 | latter was getting uneasy, some workmen came past the door bound for his 4413 | restaurant, and Michaelis took the opportunity to get away, intending to come - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” @@ -6140,9 +6187,11 @@ Suggest: Lint: Spelling (63 priority) Message: | 4432 | Michaelis and this man reached her first, but when they had torn open her - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” @@ -6162,9 +6211,9 @@ Message: | Lint: Spelling (63 priority) Message: | 4442 | “Wreck!” said Tom. “That’s good. Wilson’ll have a little business at last.” - | ^~~~~~~~~ Did you mean `Wilson`? + | ^~~~~~~~~ Did you mean `Wilson's`? Suggest: - - Replace with: “Wilson” + - Replace with: “Wilson's” @@ -6395,9 +6444,10 @@ Suggest: Lint: Spelling (63 priority) Message: | 4499 | “Auto hit her. Ins’antly killed.” - | ^~~~~~~~~ Did you mean `Instantly`? + | ^~~~~~~~~ Did you mean to spell `Ins’antly` this way? Suggest: - Replace with: “Instantly” + - Replace with: “Insanely” @@ -6415,9 +6465,9 @@ Message: | 4503 | “She ran out ina road. Son-of-a-bitch didn’t even stopus car.” | ^~~ Did you mean to spell `ina` this way? Suggest: - - Replace with: “int” - Replace with: “in” - Replace with: “inc” + - Replace with: “ind” @@ -6427,8 +6477,8 @@ Message: | | ^~~~~~ Did you mean to spell `stopus` this way? Suggest: - Replace with: “stop's” - - Replace with: “stoups” - Replace with: “stops” + - Replace with: “stoups” @@ -6444,9 +6494,11 @@ Suggest: Lint: Spelling (63 priority) Message: | 4505 | “There was two cars,” said Michaelis, “one comin’, one goin’, see?” - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” @@ -6553,12 +6605,10 @@ Suggest: Lint: Spelling (63 priority) Message: | 4510 | half way and fell to his side—“she ran out there an’ the one comin’ from N’York - | ^~~~~~ Did you mean to spell `N’York` this way? + | ^~~~~~ Did you mean `York`? 4511 | knock right into her, goin’ thirty or forty miles an hour.” Suggest: - Replace with: “York” - - Replace with: “Nanook” - - Replace with: “Newark” @@ -6789,9 +6839,9 @@ Message: | | ^~~~~ Did you mean to spell `Beale` this way? 4851 | hundred pairs of golden and silver slippers shuffled the shining dust. At the Suggest: + - Replace with: “Bale” + - Replace with: “Beagle” - Replace with: “Belle” - - Replace with: “Berle” - - Replace with: “Beadle” @@ -6824,9 +6874,9 @@ Message: | | ^~~~~~~~~ Did you mean to spell `Leaves’ll` this way? 4922 | soon, and then there’s always trouble with the pipes.” Suggest: - - Replace with: “Leakey's” - Replace with: “Leave's” - Replace with: “Leaven's” + - Replace with: “Leaver's” @@ -6842,12 +6892,10 @@ Suggest: Lint: Spelling (63 priority) Message: | 4980 | “I’ve left Daisy’s house,” she said. “I’m at Hempstead, and I’m going down to - | ^~~~~~~~~ Did you mean to spell `Hempstead` this way? + | ^~~~~~~~~ Did you mean `Homestead`? 4981 | Southampton this afternoon.” Suggest: - Replace with: “Homestead” - - Replace with: “Bedstead” - - Replace with: “Demisted” @@ -6909,9 +6957,11 @@ Suggest: Lint: Spelling (63 priority) Message: | 5034 | and closed the door. Michaelis and several other men were with him; first, four - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” @@ -6928,10 +6978,12 @@ Suggest: Lint: Spelling (63 priority) Message: | 5035 | or five men, later two or three men. Still later Michaelis had to ask the last - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? 5036 | stranger to wait there fifteen minutes longer, while he went back to his own Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” @@ -6951,9 +7003,11 @@ Message: | Lint: Spelling (63 priority) Message: | 5047 | again in his groaning voice. Michaelis made a clumsy attempt to distract him. - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” @@ -6961,9 +7015,11 @@ Lint: Spelling (63 priority) Message: | 5057 | The hard brown beetles kept thudding against the dull light, and whenever 5058 | Michaelis heard a car go tearing along the road outside it sounded to him like - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” @@ -7014,18 +7070,22 @@ Suggest: Lint: Spelling (63 priority) Message: | 5087 | Michaelis opened the drawer nearest his hand. There was nothing in it but a - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” Lint: Spelling (63 priority) Message: | 5102 | Michaelis didn’t see anything odd in that, and he gave Wilson a dozen reasons - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” @@ -7045,27 +7105,31 @@ Message: | 5122 | ghost of a superior “Hm!” | ^~ Did you mean to spell `Hm` this way? Suggest: - - Replace with: “Him” - - Replace with: “Ho” - - Replace with: “Am” + - Replace with: “Ha” + - Replace with: “Ham” + - Replace with: “He” Lint: Spelling (63 priority) Message: | 5128 | Michaelis had seen this too, but it hadn’t occurred to him that there was any - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” Lint: Spelling (63 priority) Message: | 5136 | He began to rock again, and Michaelis stood twisting the leash in his hand. - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” @@ -7099,9 +7163,11 @@ Message: | Lint: Spelling (63 priority) Message: | 5155 | Standing behind him, Michaelis saw with a shock that he was looking at the eyes - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” @@ -7113,7 +7179,7 @@ Message: | Suggest: - Replace with: “Ta” - Replace with: “Ti” - - Replace with: “Th” + - Replace with: “To” @@ -7123,8 +7189,8 @@ Message: | | ^~ Did you mean to spell `J.` this way? Suggest: - Replace with: “Jo” - - Replace with: “Jr” - - Replace with: “JD” + - Replace with: “J” + - Replace with: “Jg” @@ -7134,17 +7200,19 @@ Message: | | ^~~~~~~~~ Did you mean to spell `Eckleburg` this way? Suggest: - Replace with: “Excalibur” + - Replace with: “Heckler” - Replace with: “Vicksburg” - - Replace with: “Iceberg” Lint: Spelling (63 priority) Message: | 5161 | “That’s an advertisement,” Michaelis assured him. Something made him turn away - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” @@ -7161,18 +7229,22 @@ Suggest: Lint: Spelling (63 priority) Message: | 5165 | By six o’clock Michaelis was worn out, and grateful for the sound of a car - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” Lint: Spelling (63 priority) Message: | 5168 | man ate together. Wilson was quieter now, and Michaelis went home to sleep; when - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” @@ -7182,9 +7254,9 @@ Message: | 5172 | Roosevelt and then to Gad’s Hill, where he bought a sandwich that he didn’t eat, | ^~~~~ Did you mean to spell `Gad’s` this way? Suggest: - - Replace with: “Gab's” - - Replace with: “Gd's” - Replace with: “Ga's” + - Replace with: “Gd's” + - Replace with: “Gab's” @@ -7194,9 +7266,9 @@ Message: | 5174 | reach Gad’s Hill until noon. Thus far there was no difficulty in accounting for | ^~~~~ Did you mean to spell `Gad’s` this way? Suggest: - - Replace with: “Gab's” - - Replace with: “Gd's” - Replace with: “Ga's” + - Replace with: “Gd's” + - Replace with: “Gab's” @@ -7204,9 +7276,11 @@ Lint: Spelling (63 priority) Message: | 5177 | hours he disappeared from view. The police, on the strength of what he said to 5178 | Michaelis, that he “had a way of finding out,” supposed that he spent that time - | ^~~~~~~~~ Did you mean `Michael`? + | ^~~~~~~~~ Did you mean to spell `Michaelis` this way? Suggest: - - Replace with: “Michael” + - Replace with: “Michael's” + - Replace with: “Michaela's” + - Replace with: “Micheal's” @@ -7408,7 +7482,8 @@ Message: | | ^~~~~~~~ Did you mean to spell `Carraway` this way? Suggest: - Replace with: “Caraway” - - Replace with: “Faraway” + - Replace with: “Caraways” + - Replace with: “Castaway” @@ -7459,9 +7534,9 @@ Message: | 5333 | “This is Slagle speaking . . .” | ^~~~~~ Did you mean to spell `Slagle` this way? Suggest: - - Replace with: “Beagle” - - Replace with: “Eagle” - - Replace with: “Plague” + - Replace with: “Sable” + - Replace with: “Sage” + - Replace with: “Sale” @@ -7479,9 +7554,9 @@ Message: | 5341 | “Young Parke’s in trouble,” he said rapidly. “They picked him up when he handed | ^~~~~~~ Did you mean to spell `Parke’s` this way? Suggest: - - Replace with: “Parks's” - Replace with: “Parker's” - - Replace with: “Paige's” + - Replace with: “Parks's” + - Replace with: “Park's” @@ -7519,9 +7594,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: “Co” - Replace with: “Cu” - Replace with: “CI” - - Replace with: “Ce” @@ -7531,9 +7606,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: “Gate” - Replace with: “Garth” - Replace with: “Goth” - - Replace with: “Ga's” @@ -7553,7 +7628,8 @@ Message: | | ^~~~~~~~ Did you mean to spell `Carraway` this way? Suggest: - Replace with: “Caraway” - - Replace with: “Faraway” + - Replace with: “Caraways” + - Replace with: “Castaway” @@ -7562,9 +7638,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: “Gate” - Replace with: “Garth” - Replace with: “Goth” - - Replace with: “Ga's” @@ -7597,9 +7673,9 @@ Message: | 5396 | “Gatz is my name.” | ^~~~ Did you mean to spell `Gatz` this way? Suggest: + - Replace with: “Gate” - Replace with: “Garth” - Replace with: “Goth” - - Replace with: “Ga's” @@ -7608,9 +7684,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: “Gate” - Replace with: “Garth” - Replace with: “Goth” - - Replace with: “Ga's” @@ -7627,8 +7703,8 @@ Message: | | ^~ Did you mean to spell `J.` this way? Suggest: - Replace with: “Jo” - - Replace with: “Jr” - - Replace with: “JD” + - Replace with: “J” + - Replace with: “Jg” @@ -7638,14 +7714,17 @@ Message: | | ^~~~~~~~ Did you mean to spell `Carraway` this way? Suggest: - Replace with: “Caraway” - - Replace with: “Faraway” + - Replace with: “Caraways” + - Replace with: “Castaway” Lint: Spelling (63 priority) Message: | 5425 | “Oh!” He sounded relieved. “This is Klipspringer.” - | ^~~~~~~~~~~~ Did you mean to spell `Klipspringer` this way? + | ^~~~~~~~~~~~ Did you mean `Kissinger`? +Suggest: + - Replace with: “Kissinger” @@ -7654,9 +7733,9 @@ Message: | 5455 | and I’m sort of helpless without them. My address is care of B. F.———” | ^~ Did you mean to spell `B.` this way? Suggest: - - Replace with: “Bu” - Replace with: “Be” - Replace with: “Bi” + - Replace with: “Bu” @@ -7665,9 +7744,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: “Ff” - - Replace with: “F1” + - Replace with: “F” @@ -7707,7 +7786,8 @@ Message: | | ^~~~~~~~ Did you mean to spell `Carraway` this way? Suggest: - Replace with: “Caraway” - - Replace with: “Faraway” + - Replace with: “Caraways” + - Replace with: “Castaway” @@ -7761,9 +7841,10 @@ Message: | Lint: Spelling (63 priority) Message: | 5500 | She vanished. In a moment Meyer Wolfsheim stood solemnly in the doorway, holding - | ^~~~~~~~~ Did you mean `Florsheim`? + | ^~~~~~~~~ Did you mean to spell `Wolfsheim` this way? 5501 | out both hands. He drew me into his office, remarking in a reverent voice that Suggest: + - Replace with: “Waldheim” - Replace with: “Florsheim” @@ -7771,12 +7852,10 @@ Suggest: Lint: Spelling (63 priority) Message: | 5507 | First time I saw him was when he come into Winebrenner’s poolroom at Forty-third - | ^~~~~~~~~~~~~ Did you mean to spell `Winebrenner’s` this way? + | ^~~~~~~~~~~~~ Did you mean `Windbreaker's`? 5508 | Street and asked for a job. He hadn’t eat anything for a couple of days. ‘Come Suggest: - Replace with: “Windbreaker's” - - Replace with: “Icebreaker's” - - Replace with: “Tiebreaker's” @@ -7796,9 +7875,9 @@ Message: | 5509 | on have some lunch with me,’ I sid. He ate more than four dollars’ worth of food | ^~~ Did you mean to spell `sid` this way? Suggest: + - Replace with: “sad” - Replace with: “said” - Replace with: “sic” - - Replace with: “side” @@ -7806,11 +7885,9 @@ Lint: Spelling (63 priority) Message: | 5519 | was a fine-appearing, gentlemanly young man, and when he told me he was an 5520 | Oggsford I knew I could use him good. I got him to join up in the American - | ^~~~~~~~ Did you mean to spell `Oggsford` this way? + | ^~~~~~~~ Did you mean `Oxford`? Suggest: - Replace with: “Oxford” - - Replace with: “Osborn” - - Replace with: “Osgood” @@ -7854,9 +7931,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: “Gate” - Replace with: “Garth” - Replace with: “Goth” - - Replace with: “Ga's” @@ -7895,9 +7972,9 @@ Message: | 5582 | pocket a ragged old copy of a book called “Hopalong Cassidy.” | ^~~~~~~~ Did you mean to spell `Hopalong` this way? Suggest: - - Replace with: “Along” - - Replace with: “Oblong” - - Replace with: “Coaling” + - Replace with: “Hopping” + - Replace with: “Haling” + - Replace with: “Haloing” @@ -7907,9 +7984,9 @@ Message: | 5602 | > No wasting time at Shafters or [a name, indecipherable] No more smokeing or | ^~~~~~~~ Did you mean to spell `Shafters` this way? Suggest: - - Replace with: “Shaffer's” - Replace with: “Shatters” - Replace with: “Shifters” + - Replace with: “Shaffer's” @@ -7969,9 +8046,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: “Gate” - Replace with: “Garth” - Replace with: “Goth” - - Replace with: “Ga's” @@ -8089,6 +8166,7 @@ Message: | 5666 | going to the Ordways’? the Herseys’? the Schultzes’?” and the long green tickets | ^~~~~~~ Did you mean to spell `Ordways` this way? Suggest: + - Replace with: “Ordeals” - Replace with: “Endways” - Replace with: “Midways” @@ -8108,8 +8186,10 @@ Suggest: Lint: Spelling (63 priority) Message: | 5666 | going to the Ordways’? the Herseys’? the Schultzes’?” and the long green tickets - | ^~~~~~~~~ Did you mean `Schultz`? + | ^~~~~~~~~ Did you mean to spell `Schultzes` this way? Suggest: + - Replace with: “Schultz's” + - Replace with: “Schulz's” - Replace with: “Schultz” @@ -8157,7 +8237,8 @@ Message: | 5683 | city where dwellings are still called through decades by a family’s name. I see Suggest: - Replace with: “Caraway” - - Replace with: “Faraway” + - Replace with: “Caraways” + - Replace with: “Castaway” @@ -8213,8 +8294,8 @@ Message: | | ^~ Did you mean to spell `El` this way? Suggest: - Replace with: “Ell” - - Replace with: “Ea” - Replace with: “E” + - Replace with: “Ea” @@ -8223,9 +8304,9 @@ Message: | 5692 | figures in my more fantastic dreams. I see it as a night scene by El Greco: a | ^~~~~ Did you mean to spell `Greco` this way? Suggest: - - Replace with: “Geo” - - Replace with: “Greece” - - Replace with: “Greek” + - Replace with: “Gecko” + - Replace with: “Grace” + - Replace with: “Great” From 1ac108fb2c3691ac5794cd0d640417ebc99d26cd Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Mon, 1 Dec 2025 14:58:25 -0700 Subject: [PATCH 17/84] fix(web): add Edge extension URL as a trusted source --- packages/web/svelte.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/svelte.config.js b/packages/web/svelte.config.js index 38ce2299..a1690a28 100644 --- a/packages/web/svelte.config.js +++ b/packages/web/svelte.config.js @@ -10,6 +10,7 @@ const config = { trustedOrigins: [ 'chrome-extension://lodbfhdipoipcjmlebjbgmmgekckhpfb', 'chrome-extension://hkjdmakdmihopipoiplebkelbhebigea', + 'chrome-extension://ihjkkjfembmnjldmdchmadigpmapkpdh', ], }, prerender: { From 8cfb6847244a3ba6748b456a14363a1a50609809 Mon Sep 17 00:00:00 2001 From: Andrew Dunbar Date: Wed, 3 Dec 2025 15:31:13 +0000 Subject: [PATCH 18/84] =?UTF-8?q?feat:=20wish=20x=20can=20=E2=86=92=20wish?= =?UTF-8?q?=20x=20could=20(#2291)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: wish x can → wish x could * fix: fmt --- harper-core/src/linting/lint_group.rs | 8 +- harper-core/src/linting/mod.rs | 1 + harper-core/src/linting/wish_could.rs | 167 ++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 harper-core/src/linting/wish_could.rs diff --git a/harper-core/src/linting/lint_group.rs b/harper-core/src/linting/lint_group.rs index 506b2bff..d1ee5698 100644 --- a/harper-core/src/linting/lint_group.rs +++ b/harper-core/src/linting/lint_group.rs @@ -184,6 +184,7 @@ use super::well_educated::WellEducated; use super::whereas::Whereas; use super::widely_accepted::WidelyAccepted; use super::win_prize::WinPrize; +use super::wish_could::WishCould; use super::wordpress_dotcom::WordPressDotcom; use super::would_never_have::WouldNeverHave; use super::{ExprLinter, Lint}; @@ -473,6 +474,7 @@ impl LintGroup { // Please maintain alphabetical order. // On *nix you can maintain sort order with `sort -t'(' -k2` insert_expr_rule!(APart, true); + insert_expr_rule!(AWhile, true); insert_expr_rule!(Addicting, true); insert_expr_rule!(AdjectiveDoubleDegree, true); insert_struct_rule!(AdjectiveOfA, true); @@ -498,9 +500,6 @@ impl LintGroup { insert_expr_rule!(CautionaryTale, true); insert_expr_rule!(ChangeTack, true); insert_expr_rule!(ChockFull, true); - insert_expr_rule!(AWhile, true); - insert_struct_rule!(SubjectPronoun, true); - insert_struct_rule!(FindFine, true); insert_struct_rule!(CommaFixes, true); insert_struct_rule!(CompoundNouns, true); insert_expr_rule!(CompoundSubjectI, true); @@ -524,6 +523,7 @@ impl LintGroup { insert_expr_rule!(FeelFell, true); insert_expr_rule!(FewUnitsOfTimeAgo, true); insert_expr_rule!(FillerWords, true); + insert_struct_rule!(FindFine, true); insert_expr_rule!(FirstAidKit, true); insert_expr_rule!(ForNoun, true); insert_expr_rule!(FreePredicate, true); @@ -611,6 +611,7 @@ impl LintGroup { insert_struct_rule!(Spaces, true); insert_struct_rule!(SpelledNumbers, false); insert_expr_rule!(SplitWords, true); + insert_struct_rule!(SubjectPronoun, true); insert_expr_rule!(ThatThan, true); insert_expr_rule!(ThatWhich, true); insert_expr_rule!(TheHowWhy, true); @@ -637,6 +638,7 @@ impl LintGroup { insert_expr_rule!(Whereas, true); insert_expr_rule!(WidelyAccepted, true); insert_expr_rule!(WinPrize, true); + insert_expr_rule!(WishCould, true); insert_struct_rule!(WordPressDotcom, true); insert_expr_rule!(WouldNeverHave, true); diff --git a/harper-core/src/linting/mod.rs b/harper-core/src/linting/mod.rs index 52347c41..47e6fed3 100644 --- a/harper-core/src/linting/mod.rs +++ b/harper-core/src/linting/mod.rs @@ -196,6 +196,7 @@ mod well_educated; mod whereas; mod widely_accepted; mod win_prize; +mod wish_could; mod wordpress_dotcom; mod would_never_have; diff --git a/harper-core/src/linting/wish_could.rs b/harper-core/src/linting/wish_could.rs new file mode 100644 index 00000000..c8165d0a --- /dev/null +++ b/harper-core/src/linting/wish_could.rs @@ -0,0 +1,167 @@ +use super::{Lint, LintKind, Suggestion}; +use crate::Token; +use crate::expr::{Expr, SequenceExpr}; +use crate::linting::{ExprLinter, expr_linter::Chunk}; + +pub struct WishCould { + expr: Box, +} + +impl Default for WishCould { + fn default() -> Self { + Self { + expr: Box::new( + SequenceExpr::word_set(&["wish", "wished", "wishes", "wishing"]) + .t_ws() + .then_any_of(vec![ + Box::new(SequenceExpr::default().then_subject_pronoun()), + Box::new(SequenceExpr::word_set(&[ + // Elective existential indefinite pronouns + "anybody", + "anyone", + // Universal indefinite pronouns + "everybody", + "everyone", + // Negative indefinite pronouns (correct) + "nobody", + // Negative indefinite pronouns (incorrect) + "noone", + // Assertive existential indefinite pronouns + "somebody", + "someone", + // Demonstrative pronouns + "these", + "this", + "those", + ])), + ]) + .t_ws() + .t_aco("can"), + ), + } + } +} + +impl ExprLinter for WishCould { + type Unit = Chunk; + + fn expr(&self) -> &dyn Expr { + &*self.expr + } + + fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option { + let can_tok = toks.last()?; + let can_span = can_tok.span; + + Some(Lint { + span: can_span, + lint_kind: LintKind::Grammar, + suggestions: vec![Suggestion::replace_with_match_case_str( + "could", + can_span.get_content(src), + )], + message: "Use 'could' instead of 'can' after 'wish'.".to_string(), + ..Default::default() + }) + } + + fn description(&self) -> &str { + "Checks for `can` being used after `wish` when it should be `could`." + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::linting::tests::assert_suggestion_result; + + #[test] + fn flag_wish_we_can() { + assert_suggestion_result( + "i wish we can spend more time together", + WishCould::default(), + "i wish we could spend more time together", + ); + } + + #[test] + fn flag_wish_i_can() { + assert_suggestion_result( + "I wish I can finally forgive myself for all the things I am not.", + WishCould::default(), + "I wish I could finally forgive myself for all the things I am not.", + ); + } + + #[test] + fn flag_wish_you_can() { + assert_suggestion_result( + "I wish you can find your true love.", + WishCould::default(), + "I wish you could find your true love.", + ); + } + + #[test] + fn flag_wishes_they_can() { + assert_suggestion_result( + "What your Therapist wishes they can tell you.", + WishCould::default(), + "What your Therapist wishes they could tell you.", + ); + } + + #[test] + fn flag_wishing_someone_can() { + assert_suggestion_result( + "Forever wishing someone can point me in the right direction", + WishCould::default(), + "Forever wishing someone could point me in the right direction", + ); + } + + #[test] + fn flag_wish_they_can() { + assert_suggestion_result( + "I wish they can plant more trees on this road.", + WishCould::default(), + "I wish they could plant more trees on this road.", + ); + } + + #[test] + fn flag_wished_he_can() { + assert_suggestion_result( + "I just wished he can talk and tell me how he feels", + WishCould::default(), + "I just wished he could talk and tell me how he feels", + ); + } + + #[test] + fn wish_this_can() { + assert_suggestion_result( + "but I wish this can be fixed by Electron team", + WishCould::default(), + "but I wish this could be fixed by Electron team", + ) + } + + #[test] + fn wish_it_can() { + assert_suggestion_result( + "Wish it can be supported.", + WishCould::default(), + "Wish it could be supported.", + ) + } + + #[test] + fn wish_somebody_can() { + assert_suggestion_result( + "I wish somebody can fix this issue.", + WishCould::default(), + "I wish somebody could fix this issue.", + ) + } +} From bd319e7130432ae06a9f0c10c3ebba809fd46771 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Wed, 3 Dec 2025 09:35:06 -0700 Subject: [PATCH 19/84] fix(core): ensure `news` isn't corrected to `news` (#2289) * fix(core): ensure `news` isn't corrected to `news` * fix(core): remove unused imports * chore(core): update snapshots --- harper-core/dictionary.dict | 10 +- .../src/linting/orthographic_consistency.rs | 39 +- harper-core/tests/run_tests.rs | 2 +- .../Alice's Adventures in Wonderland.snap.yml | 6 +- .../text/linters/Computer science.snap.yml | 44 -- .../text/linters/Difficult sentences.snap.yml | 9 - harper-core/tests/text/linters/Spell.snap.yml | 2 +- ...Constitution of the United States.snap.yml | 2 +- .../text/linters/The Great Gatsby.snap.yml | 376 ++---------------- 9 files changed, 81 insertions(+), 409 deletions(-) diff --git a/harper-core/dictionary.dict b/harper-core/dictionary.dict index e57ca1e2..2efc39a3 100644 --- a/harper-core/dictionary.dict +++ b/harper-core/dictionary.dict @@ -856,7 +856,7 @@ BRICS/Og BS/ONgV BSA/NO BSD/ONSg -BTU/N # same as Btu? +BTU/N BTW/ BYOB/ Ba/ONg @@ -1548,7 +1548,6 @@ Bryce/Og Brynner/g Bryon/Og Brzezinski/Og -Btu/g # same as BTU? Buber/g Buchanan/Og Bucharest/Og @@ -4803,7 +4802,6 @@ IT/N # removed `5`. nouns can qualify nouns. makes `it` an adject IUD/N IV/JNSg IVF/N -Ia/O Iaccoca/g Iago/Og Ian/Og @@ -7321,8 +7319,6 @@ Nb/g # niobium Nd/g # neodymium Ndjamena/g Ne/g # neon -NeWS -NeWSes Neal/Og Neanderthal/JNSg Neapolitan/JNOg @@ -7352,7 +7348,6 @@ Nelsen/Og Nelson/Og Nembutal/g Nemesis/Og -Neo/Og Neogene/JOg Neolithic/ONJ Nepal/Og @@ -8746,7 +8741,6 @@ Rollerblade/g Rollins/Og Rolodex/NgV Rolvaag/g -Rom/ONJ Roman/JNOSg Romanesque/JSg Romania/Og @@ -10471,7 +10465,6 @@ VRAM/Ng # video memory VT/ONJV # US state (Vermont) VTOL/N # vertical takeoff and landing VW/OgS # car brand; cf. Volkswagen -Va/Og Vacaville/Og Vader/g Vaduz/Og @@ -53555,6 +53548,7 @@ data point/NgS day-to-day/JR de-extinct/VGd de-extinction/NwgS +de de facto/JNg de jure/RJ de minimis/J diff --git a/harper-core/src/linting/orthographic_consistency.rs b/harper-core/src/linting/orthographic_consistency.rs index 04a39d23..a50c5a9f 100644 --- a/harper-core/src/linting/orthographic_consistency.rs +++ b/harper-core/src/linting/orthographic_consistency.rs @@ -39,7 +39,26 @@ impl ExprLinter for OrthographicConsistency { self.expr.as_ref() } - fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option { + fn match_to_lint_with_context( + &self, + matched_tokens: &[Token], + source: &[char], + context: Option<(&[Token], &[Token])>, + ) -> Option { + if let Some((pre, post)) = context { + if let Some(pre_tok) = pre.last() + && pre_tok.kind.is_hyphen() + { + return None; + } + + if let Some(post_tok) = post.first() + && post_tok.kind.is_hyphen() + { + return None; + } + } + let word = &matched_tokens[0]; let Some(Some(metadata)) = word.kind.as_word() else { @@ -52,6 +71,7 @@ impl ExprLinter for OrthographicConsistency { if metadata.is_allcaps() && !metadata.is_lowercase() + && !metadata.is_upper_camel() && !cur_flags.contains(OrthFlags::ALLCAPS) { return Some(Lint { @@ -74,9 +94,12 @@ impl ExprLinter for OrthographicConsistency { ]; if flags_to_check - .iter() - .any(|flag| canonical_flags.contains(*flag) != cur_flags.contains(*flag)) + .into_iter() + .filter(|flag| canonical_flags.contains(*flag) != cur_flags.contains(*flag)) + .count() + == 1 && let Some(canonical) = self.dict.get_correct_capitalization_of(chars) + && canonical != chars { return Some(Lint { span: word.span, @@ -100,7 +123,7 @@ impl ExprLinter for OrthographicConsistency { lint_kind: LintKind::Capitalization, suggestions: vec![Suggestion::ReplaceWith(canonical.to_vec())], message: format!( - "The canonical dictionary spelling is `{}`.", + "The canonical dictionary spelling is title case: `{}`.", canonical.iter().collect::() ), priority: 127, @@ -356,4 +379,12 @@ mod tests { assert_no_lints(sentence, OrthographicConsistency::default()); } } + + #[test] + fn allows_news() { + assert_no_lints( + "This is the best part of the news broadcast.", + OrthographicConsistency::default(), + ); + } } diff --git a/harper-core/tests/run_tests.rs b/harper-core/tests/run_tests.rs index c9ad5efb..56bc279e 100644 --- a/harper-core/tests/run_tests.rs +++ b/harper-core/tests/run_tests.rs @@ -101,4 +101,4 @@ create_test!(issue_2151.md, 4, Dialect::British); create_test!(lukas_homework.md, 3, Dialect::American); // Org mode tests -create_org_test!(index.org, 50, Dialect::American); +create_org_test!(index.org, 49, Dialect::American); 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 e09a0759..b7c0e061 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 @@ -1505,7 +1505,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 964 | “Come, my head’s free at last!” said Alice in a tone of delight, which changed - | ^~~~~~ The canonical dictionary spelling is `head's`. + | ^~~~~~ The canonical dictionary spelling is title case: `head's`. Suggest: - Replace with: “head's” @@ -1670,7 +1670,7 @@ Message: | Lint: Capitalization (127 priority) Message: | 1127 | “Oh, there’s no use in talking to him,” said Alice desperately: “he’s perfectly - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. 1128 | idiotic!” And she opened the door and went in. Suggest: - Replace with: “he's” @@ -2919,7 +2919,7 @@ Lint: Capitalization (127 priority) Message: | 2306 | > “Will you walk a little faster?” said a whiting to a snail. “There’s a 2307 | > porpoise close behind us, and he’s treading on my tail. See how eagerly the - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. Suggest: - Replace with: “he's” diff --git a/harper-core/tests/text/linters/Computer science.snap.yml b/harper-core/tests/text/linters/Computer science.snap.yml index 8633a578..8008c0ae 100644 --- a/harper-core/tests/text/linters/Computer science.snap.yml +++ b/harper-core/tests/text/linters/Computer science.snap.yml @@ -42,28 +42,6 @@ Suggest: -Lint: Capitalization (127 priority) -Message: | - 49 | including the fact that he documented the binary number system. In 1820, Thomas - 50 | de Colmar launched the mechanical calculator industry[note 1] when he invented - | ^~ This word's canonical spelling is all-caps. -Suggest: - - Replace with: “DE” - - - -Lint: Spelling (63 priority) -Message: | - 49 | including the fact that he documented the binary number system. In 1820, Thomas - 50 | de Colmar launched the mechanical calculator industry[note 1] when he invented - | ^~ Did you mean to spell `de` this way? -Suggest: - - Replace with: “db” - - Replace with: “dc” - - Replace with: “dd” - - - Lint: Spelling (63 priority) Message: | 49 | including the fact that he documented the binary number system. In 1820, Thomas @@ -1049,28 +1027,6 @@ Suggest: -Lint: Capitalization (127 priority) -Message: | - 444 | > easily distinguishable states, such as "on/off", "magnetized/de-magnetized", - | ^~ This word's canonical spelling is all-caps. - 445 | > "high-voltage/low-voltage", etc.). -Suggest: - - Replace with: “DE” - - - -Lint: Spelling (63 priority) -Message: | - 444 | > easily distinguishable states, such as "on/off", "magnetized/de-magnetized", - | ^~ Did you mean to spell `de` this way? - 445 | > "high-voltage/low-voltage", etc.). -Suggest: - - Replace with: “db” - - Replace with: “dc” - - Replace with: “dd” - - - Lint: Capitalization (31 priority) Message: | 456 | - print 1 at current location. diff --git a/harper-core/tests/text/linters/Difficult sentences.snap.yml b/harper-core/tests/text/linters/Difficult sentences.snap.yml index 57ea6d6c..4536cf55 100644 --- a/harper-core/tests/text/linters/Difficult sentences.snap.yml +++ b/harper-core/tests/text/linters/Difficult sentences.snap.yml @@ -75,15 +75,6 @@ Suggest: -Lint: Capitalization (31 priority) -Message: | - 166 | You can't get all your news from the Internet. - | ^~~~ The canonical dictionary spelling is `news`. -Suggest: - - Replace with: “news” - - - Lint: Spelling (63 priority) Message: | 180 | I’ve been doing this from pickney. diff --git a/harper-core/tests/text/linters/Spell.snap.yml b/harper-core/tests/text/linters/Spell.snap.yml index 99454216..7b257f50 100644 --- a/harper-core/tests/text/linters/Spell.snap.yml +++ b/harper-core/tests/text/linters/Spell.snap.yml @@ -11,7 +11,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 7 | My favourite color is blu. - | ^~~ The canonical dictionary spelling is `Blu`. + | ^~~ The canonical dictionary spelling is title case: `Blu`. Suggest: - Replace with: “Blu” 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 f0a15b5c..907c097a 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 @@ -1732,7 +1732,7 @@ Message: | | ^~ Did you mean to spell `V.` this way? Suggest: - Replace with: “Vi” - - Replace with: “Va” + - Replace with: “VA” - Replace with: “Vb” 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 1d94af55..2c3ca071 100644 --- a/harper-core/tests/text/linters/The Great Gatsby.snap.yml +++ b/harper-core/tests/text/linters/The Great Gatsby.snap.yml @@ -181,7 +181,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 57 | reference to the rather hard-boiled painting that hangs in father’s office. I - | ^~~~~~~~ The canonical dictionary spelling is `father's`. + | ^~~~~~~~ The canonical dictionary spelling is title case: `father's`. Suggest: - Replace with: “father's” @@ -281,16 +281,6 @@ Message: | -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`. - 99 | bring back all such things into my life and become again that most limited of -Suggest: - - Replace with: “news” - - - Lint: Readability (127 priority) Message: | 106 | natural curiosities, two unusual formations of land. Twenty miles from the city @@ -329,28 +319,6 @@ Suggest: -Lint: Capitalization (127 priority) -Message: | - 119 | thousand a season. The one on my right was a colossal affair by any standard—it - 120 | was a factual imitation of some Hôtel de Ville in Normandy, with a tower on one - | ^~ This word's canonical spelling is all-caps. -Suggest: - - Replace with: “DE” - - - -Lint: Spelling (63 priority) -Message: | - 119 | thousand a season. The one on my right was a colossal affair by any standard—it - 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: “db” - - Replace with: “dc” - - Replace with: “dd” - - - Lint: Spelling (63 priority) Message: | 120 | was a factual imitation of some Hôtel de Ville in Normandy, with a tower on one @@ -566,7 +534,7 @@ Lint: Capitalization (127 priority) Message: | 402 | “I’ll tell you a family secret,” she whispered enthusiastically. “It’s about the 403 | butler’s nose. Do you want to hear about the butler’s nose?” - | ^~~~~~~~ The canonical dictionary spelling is `butler's`. + | ^~~~~~~~ The canonical dictionary spelling is title case: `butler's`. Suggest: - Replace with: “butler's” @@ -575,7 +543,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 403 | butler’s nose. Do you want to hear about the butler’s nose?” - | ^~~~~~~~ The canonical dictionary spelling is `butler's`. + | ^~~~~~~~ The canonical dictionary spelling is title case: `butler's`. Suggest: - Replace with: “butler's” @@ -966,28 +934,6 @@ Message: | -Lint: Capitalization (127 priority) -Message: | - 728 | stout, but she carried her flesh sensuously as some women can. Her face, above a - 729 | spotted dress of dark blue crêpe-de-chine, contained no facet or gleam of - | ^~ This word's canonical spelling is all-caps. -Suggest: - - Replace with: “DE” - - - -Lint: Spelling (63 priority) -Message: | - 728 | stout, but she carried her flesh sensuously as some women can. Her face, above a - 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: “db” - - Replace with: “dc” - - Replace with: “dd” - - - Lint: Spelling (63 priority) Message: | 730 | beauty, but there was an immediately perceptible vitality about her as if the @@ -1000,15 +946,6 @@ Suggest: -Lint: Capitalization (31 priority) -Message: | - 747 | “I’ll meet you by the news-stand on the lower level.” - | ^~~~ The canonical dictionary spelling is `news`. -Suggest: - - Replace with: “news” - - - Lint: Spelling (63 priority) Message: | 756 | “Terrible place, isn’t it,” said Tom, exchanging a frown with Doctor Eckleburg. @@ -1024,7 +961,7 @@ Lint: Capitalization (127 priority) Message: | 764 | “Wilson? He thinks she goes to see her sister in New York. He’s so dumb he 765 | doesn’t know he’s alive.” - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. Suggest: - Replace with: “he's” @@ -1042,16 +979,6 @@ Suggest: -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 - | ^~~~ The canonical dictionary spelling is `news`. -Suggest: - - Replace with: “news” - - - Lint: Spelling (63 priority) Message: | 784 | We backed up to a gray old man who bore an absurd resemblance to John D. @@ -1059,7 +986,7 @@ Message: | Suggest: - Replace with: “Do” - Replace with: “DA” - - Replace with: “DE” + - Replace with: “Di” @@ -1241,7 +1168,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 964 | “Well, they say he’s a nephew or a cousin of Kaiser Wilhelm’s. That’s where all - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. Suggest: - Replace with: “he's” @@ -1286,7 +1213,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 1039 | kept saying to me: ‘Lucille, that man’s ’way below you!’ But if I hadn’t met - | ^~~~~ The canonical dictionary spelling is `man's`. + | ^~~~~ The canonical dictionary spelling is title case: `man's`. Suggest: - Replace with: “man's” @@ -1470,7 +1397,7 @@ Lint: Capitalization (127 priority) Message: | 1174 | juice of two hundred oranges in half an hour if a little button was pressed two 1175 | hundred times by a butler’s thumb. - | ^~~~~~~~ The canonical dictionary spelling is `butler's`. + | ^~~~~~~~ The canonical dictionary spelling is title case: `butler's`. Suggest: - Replace with: “butler's” @@ -1591,16 +1518,6 @@ Suggest: -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`. - 1211 | goes around that she is Gilda Gray’s understudy from the Follies. The party has -Suggest: - - Replace with: “news” - - - Lint: Readability (127 priority) Message: | 1223 | I had been actually invited. A chauffeur in a uniform of robin’s-egg blue @@ -1614,16 +1531,6 @@ Message: | -Lint: Capitalization (127 priority) -Message: | - 1223 | I had been actually invited. A chauffeur in a uniform of robin’s-egg blue - | ^~~~~~~ The canonical dictionary spelling is `robin's`. - 1224 | crossed my lawn early that Saturday morning with a surprisingly formal note from -Suggest: - - Replace with: “robin's” - - - Lint: Readability (127 priority) Message: | 1230 | Dressed up in white flannels I went over to his lawn a little after seven, and @@ -1982,7 +1889,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 1569 | “She had a fight with a man who says he’s her husband,” explained a girl at my - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. 1570 | elbow. Suggest: - Replace with: “he's” @@ -2229,7 +2136,7 @@ Message: | Lint: Capitalization (127 priority) Message: | 1797 | passed so close to some workmen that our fender flicked a button on one man’s - | ^~~~~ The canonical dictionary spelling is `man's`. + | ^~~~~ The canonical dictionary spelling is title case: `man's`. 1798 | coat. Suggest: - Replace with: “man's” @@ -2718,28 +2625,6 @@ Suggest: -Lint: Capitalization (127 priority) -Message: | - 1872 | and James B. (“Rot-Gut”) Ferret and the De Jongs and Ernest Lilly—they came to - | ^~ This word's canonical spelling is all-caps. - 1873 | gamble, and when Ferret wandered into the garden it meant he was cleaned out and -Suggest: - - Replace with: “DE” - - - -Lint: Spelling (63 priority) -Message: | - 1872 | and James B. (“Rot-Gut”) Ferret and the De Jongs and Ernest Lilly—they came to - | ^~ Did you mean to spell `De` this way? - 1873 | gamble, and when Ferret wandered into the garden it meant he was cleaned out and -Suggest: - - Replace with: “Db” - - Replace with: “Dc” - - Replace with: “Dd” - - - Lint: Spelling (63 priority) Message: | 1872 | and James B. (“Rot-Gut”) Ferret and the De Jongs and Ernest Lilly—they came to @@ -3136,28 +3021,6 @@ Suggest: -Lint: Capitalization (127 priority) -Message: | - 1981 | “character” leaking sawdust at every pore as he pursued a tiger through the Bois - 1982 | de Boulogne. - | ^~ This word's canonical spelling is all-caps. -Suggest: - - Replace with: “DE” - - - -Lint: Spelling (63 priority) -Message: | - 1981 | “character” leaking sawdust at every pore as he pursued a tiger through the Bois - 1982 | de Boulogne. - | ^~ Did you mean to spell `de` this way? -Suggest: - - Replace with: “db” - - Replace with: “dc” - - Replace with: “dd” - - - Lint: Spelling (63 priority) Message: | 1981 | “character” leaking sawdust at every pore as he pursued a tiger through the Bois @@ -3274,7 +3137,7 @@ Lint: Capitalization (127 priority) Message: | 2056 | “All right, old sport,” called Gatsby. We slowed down. Taking a white card from 2057 | his wallet, he waved it before the man’s eyes. - | ^~~~~ The canonical dictionary spelling is `man's`. + | ^~~~~ The canonical dictionary spelling is title case: `man's`. Suggest: - Replace with: “man's” @@ -3316,7 +3179,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 2078 | driven by a white chauffeur, in which sat three modish negroes, two bucks and a - | ^~~~~~~ The canonical dictionary spelling is `Negroes`. + | ^~~~~~~ The canonical dictionary spelling is title case: `Negroes`. 2079 | girl. I laughed aloud as the yolks of their eyeballs rolled toward us in haughty Suggest: - Replace with: “Negroes” @@ -3377,7 +3240,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 2105 | “I handed the money to Katspaugh and I sid: ‘All right, Katspaugh, don’t pay him - | ^~~ The canonical dictionary spelling is `Sid`. + | ^~~ The canonical dictionary spelling is title case: `Sid`. Suggest: - Replace with: “Sid” @@ -3618,7 +3481,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 2215 | about women. He would never so much as look at a friend’s wife.” - | ^~~~~~~~ The canonical dictionary spelling is `friend's`. + | ^~~~~~~~ The canonical dictionary spelling is title case: `friend's`. Suggest: - Replace with: “friend's” @@ -3667,7 +3530,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 2243 | “Meyer Wolfshiem? No, he’s a gambler.” Gatsby hesitated, then added coolly: - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. Suggest: - Replace with: “he's” @@ -3904,7 +3767,7 @@ Lint: Capitalization (127 priority) Message: | 2395 | When Jordan Baker had finished telling all this we had left the Plaza for half 2396 | an hour and were driving in a victoria through Central Park. The sun had gone - | ^~~~~~~~ The canonical dictionary spelling is `Victoria`. + | ^~~~~~~~ The canonical dictionary spelling is title case: `Victoria`. Suggest: - Replace with: “Victoria” @@ -3936,7 +3799,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 2425 | “He’s afraid, he’s waited so long. He thought you might be offended. You see, - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. Suggest: - Replace with: “he's” @@ -3946,7 +3809,7 @@ Lint: Capitalization (127 priority) Message: | 2425 | “He’s afraid, he’s waited so long. He thought you might be offended. You see, 2426 | he’s regular tough underneath it all.” - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. Suggest: - Replace with: “he's” @@ -3970,7 +3833,7 @@ Message: | Lint: Capitalization (127 priority) Message: | 2448 | whole idea. He doesn’t know very much about Tom, though he says he’s read a - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. 2449 | Chicago paper for years just on the chance of catching a glimpse of Daisy’s Suggest: - Replace with: “he's” @@ -4224,15 +4087,6 @@ Suggest: -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`. -Suggest: - - Replace with: “news” - - - Lint: Formatting (255 priority) Message: | 2806 | was in he answered: ‘‘That’s my affair,” before he realized that it wasn’t an @@ -4420,7 +4274,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 2926 | said a small town. . . . He must know what a small town is. . . . Well, he’s no - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. 2927 | use to us if Detroit is his idea of a small town. . . .” He rang off. Suggest: - Replace with: “he's” @@ -4516,16 +4370,6 @@ Suggest: -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 - | ^~~~ The canonical dictionary spelling is `news`. -Suggest: - - Replace with: “news” - - - Lint: Readability (127 priority) Message: | 3020 | short of being news. Contemporary legends such as the “underground pipe-line to @@ -4620,28 +4464,6 @@ Suggest: -Lint: Capitalization (127 priority) -Message: | - 3074 | Ella Kaye, the newspaper woman, played Madame de Maintenon to his weakness and - | ^~ This word's canonical spelling is all-caps. - 3075 | sent him to sea in a yacht, were common property of the turgid journalism -Suggest: - - Replace with: “DE” - - - -Lint: Spelling (63 priority) -Message: | - 3074 | Ella Kaye, the newspaper woman, played Madame de Maintenon to his weakness and - | ^~ 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: “db” - - Replace with: “dc” - - Replace with: “dd” - - - Lint: Spelling (63 priority) Message: | 3074 | Ella Kaye, the newspaper woman, played Madame de Maintenon to his weakness and @@ -4836,7 +4658,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 3213 | “My God, I believe the man’s coming,” said Tom. “Doesn’t he know she doesn’t - | ^~~~~ The canonical dictionary spelling is `man's`. + | ^~~~~ The canonical dictionary spelling is title case: `man's`. Suggest: - Replace with: “man's” @@ -4991,7 +4813,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 3396 | hadn’t been invited. They simply force their way in and he’s too polite to - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. 3397 | object.” Suggest: - Replace with: “he's” @@ -5070,7 +4892,7 @@ Lint: Capitalization (127 priority) Message: | 3488 | somewhere a long time ago. For a moment a phrase tried to take shape in my mouth 3489 | and my lips parted like a dumb man’s, as though there was more struggling upon - | ^~~~~ The canonical dictionary spelling is `man's`. + | ^~~~~ The canonical dictionary spelling is title case: `man's`. Suggest: - Replace with: “man's” @@ -5276,7 +5098,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 3612 | “No, he’s not,” I assured her. “It’s a bona-fide deal. I happen to know about - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. Suggest: - Replace with: “he's” @@ -5348,28 +5170,6 @@ Suggest: -Lint: Capitalization (127 priority) -Message: | - 3646 | “The bles-sed pre-cious! Did mother get powder on your old yellowy hair? Stand - 3647 | up now, and say—How-de-do.” - | ^~ This word's canonical spelling is all-caps. -Suggest: - - Replace with: “DE” - - - -Lint: Spelling (63 priority) -Message: | - 3646 | “The bles-sed pre-cious! Did mother get powder on your old yellowy hair? Stand - 3647 | up now, and say—How-de-do.” - | ^~ Did you mean to spell `de` this way? -Suggest: - - Replace with: “db” - - Replace with: “dc” - - Replace with: “dd” - - - Lint: Spelling (63 priority) Message: | 3672 | “Come, Pammy.” @@ -5396,7 +5196,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 3686 | “I read somewhere that the sun’s getting hotter every year,” said Tom genially. - | ^~~~~ The canonical dictionary spelling is `sun's`. + | ^~~~~ The canonical dictionary spelling is title case: `sun's`. Suggest: - Replace with: “sun's” @@ -5413,7 +5213,7 @@ Lint: Capitalization (127 priority) Message: | 3687 | “It seems that pretty soon the earth’s going to fall into the sun—or wait a 3688 | minute—it’s just the opposite—the sun’s getting colder every year. - | ^~~~~ The canonical dictionary spelling is `sun's`. + | ^~~~~ The canonical dictionary spelling is title case: `sun's`. Suggest: - Replace with: “sun's” @@ -5462,7 +5262,7 @@ Message: | Lint: Capitalization (127 priority) Message: | 3795 | song of it. . . . High in a white palace the king’s daughter, the golden girl. . - | ^~~~~~ The canonical dictionary spelling is `king's`. + | ^~~~~~ The canonical dictionary spelling is title case: `king's`. Suggest: - Replace with: “king's” @@ -5512,7 +5312,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 3864 | “Nevertheless he’s an Oxford man.” - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. Suggest: - Replace with: “he's” @@ -6053,15 +5853,6 @@ Suggest: -Lint: Capitalization (127 priority) -Message: | - 4373 | “No . . . I just remembered that to-day’s my birthday.” - | ^~~~~ The canonical dictionary spelling is `day's`. -Suggest: - - Replace with: “day's” - - - Lint: Spelling (63 priority) Message: | 4377 | It was seven o’clock when we got into the coupé with him and started for Long @@ -6265,24 +6056,6 @@ Suggest: -Lint: Capitalization (127 priority) -Message: | - 4479 | “Oh, my Ga-od! Oh, my Ga-od! Oh, Ga-od! Oh, my Ga-od!” - | ^~ This word's canonical spelling is all-caps. -Suggest: - - Replace with: “GA” - - - -Lint: Capitalization (127 priority) -Message: | - 4479 | “Oh, my Ga-od! Oh, my Ga-od! Oh, Ga-od! Oh, my Ga-od!” - | ^~ This word's canonical spelling is all-caps. -Suggest: - - Replace with: “OD” - - - Lint: Spelling (63 priority) Message: | 4479 | “Oh, my Ga-od! Oh, my Ga-od! Oh, Ga-od! Oh, my Ga-od!” @@ -6294,24 +6067,6 @@ Suggest: -Lint: Capitalization (127 priority) -Message: | - 4479 | “Oh, my Ga-od! Oh, my Ga-od! Oh, Ga-od! Oh, my Ga-od!” - | ^~ This word's canonical spelling is all-caps. -Suggest: - - Replace with: “GA” - - - -Lint: Capitalization (127 priority) -Message: | - 4479 | “Oh, my Ga-od! Oh, my Ga-od! Oh, Ga-od! Oh, my Ga-od!” - | ^~ This word's canonical spelling is all-caps. -Suggest: - - Replace with: “OD” - - - Lint: Spelling (63 priority) Message: | 4479 | “Oh, my Ga-od! Oh, my Ga-od! Oh, Ga-od! Oh, my Ga-od!” @@ -6323,24 +6078,6 @@ Suggest: -Lint: Capitalization (127 priority) -Message: | - 4479 | “Oh, my Ga-od! Oh, my Ga-od! Oh, Ga-od! Oh, my Ga-od!” - | ^~ This word's canonical spelling is all-caps. -Suggest: - - Replace with: “GA” - - - -Lint: Capitalization (127 priority) -Message: | - 4479 | “Oh, my Ga-od! Oh, my Ga-od! Oh, Ga-od! Oh, my Ga-od!” - | ^~ This word's canonical spelling is all-caps. -Suggest: - - Replace with: “OD” - - - Lint: Spelling (63 priority) Message: | 4479 | “Oh, my Ga-od! Oh, my Ga-od! Oh, Ga-od! Oh, my Ga-od!” @@ -6352,24 +6089,6 @@ Suggest: -Lint: Capitalization (127 priority) -Message: | - 4479 | “Oh, my Ga-od! Oh, my Ga-od! Oh, Ga-od! Oh, my Ga-od!” - | ^~ This word's canonical spelling is all-caps. -Suggest: - - Replace with: “GA” - - - -Lint: Capitalization (127 priority) -Message: | - 4479 | “Oh, my Ga-od! Oh, my Ga-od! Oh, Ga-od! Oh, my Ga-od!” - | ^~ This word's canonical spelling is all-caps. -Suggest: - - Replace with: “OD” - - - Lint: Spelling (63 priority) Message: | 4479 | “Oh, my Ga-od! Oh, my Ga-od! Oh, Ga-od! Oh, my Ga-od!” @@ -6401,15 +6120,6 @@ Suggest: -Lint: Capitalization (127 priority) -Message: | - 4486 | “No, r—” corrected the man, “M-a-v-r-o———” - | ^ This word's canonical spelling is all-caps. -Suggest: - - Replace with: “O” - - - Lint: Spelling (63 priority) Message: | 4486 | “No, r—” corrected the man, “M-a-v-r-o———” @@ -6454,7 +6164,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 4503 | “She ran out ina road. Son-of-a-bitch didn’t even stopus car.” - | ^~~ The canonical dictionary spelling is `Ina`. + | ^~~ The canonical dictionary spelling is title case: `Ina`. Suggest: - Replace with: “Ina” @@ -6718,7 +6428,7 @@ Lint: Capitalization (127 priority) Message: | 4622 | house. I sat down for a few minutes with my head in my hands, until I heard the 4623 | phone taken up inside and the butler’s voice calling a taxi. Then I walked - | ^~~~~~~~ The canonical dictionary spelling is `butler's`. + | ^~~~~~~~ The canonical dictionary spelling is title case: `butler's`. Suggest: - Replace with: “butler's” @@ -7389,16 +7099,6 @@ Message: | -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`. - 5252 | to West Egg village, every surmise about him, and every practical question, was -Suggest: - - Replace with: “news” - - - Lint: Readability (127 priority) Message: | 5253 | referred to me. At first I was surprised and confused; then, as he lay in his @@ -7522,7 +7222,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 5330 | thought this would be Daisy at last. But the connection came through as a man’s - | ^~~~~ The canonical dictionary spelling is `man's`. + | ^~~~~ The canonical dictionary spelling is title case: `man's`. 5331 | voice, very thin and far away. Suggest: - Replace with: “man's” @@ -7805,7 +7505,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 5487 | “But I know he’s there.” - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. Suggest: - Replace with: “he's” @@ -7814,7 +7514,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 5493 | “We’re getting sick in tired of it. When I say he’s in Chicago, he’s in - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. 5494 | Chicago.” Suggest: - Replace with: “he's” @@ -7824,7 +7524,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 5493 | “We’re getting sick in tired of it. When I say he’s in Chicago, he’s in - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. 5494 | Chicago.” Suggest: - Replace with: “he's” @@ -7863,7 +7563,7 @@ Lint: Capitalization (127 priority) Message: | 5508 | Street and asked for a job. He hadn’t eat anything for a couple of days. ‘Come 5509 | on have some lunch with me,’ I sid. He ate more than four dollars’ worth of food - | ^~~ The canonical dictionary spelling is `Sid`. + | ^~~ The canonical dictionary spelling is title case: `Sid`. Suggest: - Replace with: “Sid” @@ -7909,7 +7609,7 @@ Message: | Lint: Capitalization (127 priority) Message: | 5528 | “Now he’s dead,” I said after a moment. “You were his closest friend, so I know - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. Suggest: - Replace with: “he's” @@ -7940,7 +7640,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 5560 | up and down excitedly in the hall. His pride in his son and in his son’s - | ^~~~~ The canonical dictionary spelling is `son's`. + | ^~~~~ The canonical dictionary spelling is title case: `son's`. 5561 | possessions was continually increasing and now he had something to show me. Suggest: - Replace with: “son's” @@ -8005,7 +7705,7 @@ Suggest: Lint: Capitalization (127 priority) Message: | 5612 | something. Do you notice what he’s got about improving his mind? He was always - | ^~~~ The canonical dictionary spelling is `he's`. + | ^~~~ The canonical dictionary spelling is title case: `he's`. Suggest: - Replace with: “he's” From fc87bd518bb90e837ee778551f1a8fbaa2223905 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Wed, 3 Dec 2025 09:56:00 -0700 Subject: [PATCH 20/84] fix(core): fix problems with the `NeedToNoun` rule (#2287) --- harper-core/dictionary.dict | 2 +- harper-core/src/linting/need_to_noun.rs | 33 +++++++++++++++++-- harper-core/tests/run_tests.rs | 1 + harper-core/tests/test_sources/issue_2240.md | 1 + .../Alice's Adventures in Wonderland.md | 2 +- .../tests/text/tagged/Difficult sentences.md | 2 +- 6 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 harper-core/tests/test_sources/issue_2240.md diff --git a/harper-core/dictionary.dict b/harper-core/dictionary.dict index 2efc39a3..5580d5f4 100644 --- a/harper-core/dictionary.dict +++ b/harper-core/dictionary.dict @@ -36986,7 +36986,7 @@ overcharge/VdGSNg overclock/VGdSN overcloud/VSGd overcoat/NgSV -overcome/~VGSN +overcome/~VGS overcompensate/VdSGn overcompensation/Nmg overconfidence/Nmg diff --git a/harper-core/src/linting/need_to_noun.rs b/harper-core/src/linting/need_to_noun.rs index b7eb9a84..77977765 100644 --- a/harper-core/src/linting/need_to_noun.rs +++ b/harper-core/src/linting/need_to_noun.rs @@ -18,8 +18,13 @@ pub struct NeedToNoun { impl Default for NeedToNoun { fn default() -> Self { let postfix_exceptions = LongestMatchOf::new(vec![ - Box::new(|tok: &Token, _: &[char]| tok.kind.is_adverb() || tok.kind.is_determiner()), - Box::new(WordSet::new(&["about"])), + Box::new(|tok: &Token, _: &[char]| { + tok.kind.is_adverb() + || tok.kind.is_determiner() + || tok.kind.is_unlintable() + || tok.kind.is_pronoun() + }), + Box::new(WordSet::new(&["about", "it"])), ]); let exceptions = SequenceExpr::default() @@ -421,4 +426,28 @@ mod tests { fn allows_issue_2252() { assert_no_lints("Things I need to do today:", NeedToNoun::default()); } + + #[test] + fn allows_install() { + assert_no_lints( + "You need to install it separately, as it's a standalone application.", + NeedToNoun::default(), + ); + } + + #[test] + fn allows_lay() { + assert_no_lints( + "Okay, this is a long one, but I feel like I need to lay everything out.", + NeedToNoun::default(), + ); + } + + #[test] + fn allows_overcome() { + assert_no_lints( + "We believe every family deserves the opportunity to flourish, and we are committed to providing the resources they need to overcome adversity.", + NeedToNoun::default(), + ); + } } diff --git a/harper-core/tests/run_tests.rs b/harper-core/tests/run_tests.rs index 56bc279e..1bf1627e 100644 --- a/harper-core/tests/run_tests.rs +++ b/harper-core/tests/run_tests.rs @@ -94,6 +94,7 @@ create_test!(issue_2054_clean.md, 0, Dialect::British); create_test!(issue_1873.md, 0, Dialect::British); create_test!(issue_2246.md, 0, Dialect::American); create_test!(issue_2233.md, 0, Dialect::American); +create_test!(issue_2240.md, 0, Dialect::American); // It just matters that it is > 1 create_test!(issue_2151.md, 4, Dialect::British); diff --git a/harper-core/tests/test_sources/issue_2240.md b/harper-core/tests/test_sources/issue_2240.md new file mode 100644 index 00000000..599f9575 --- /dev/null +++ b/harper-core/tests/test_sources/issue_2240.md @@ -0,0 +1 @@ +There is no more need to run `git`. 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 0bd253b7..5e48171d 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 @@ -5543,7 +5543,7 @@ > work very diligently to write out a history of the accident , all except the # N🅪Sg/VB J/R R P NSg/VB NSg/VB/J/R/P D/P N🅪Sg P D NSg/J+ . NSg/I/J/C/Dq VB/C/P D > Lizard , who seemed too much overcome to do anything but sit with its mouth open , -# NSg . NPr/I+ VP/J R NSg/I/J/R/Dq NSg/VB P VXB NSg/I/VB+ NSg/C/P NSg/VB P ISg/D$+ NSg/VB+ NSg/VB/J . +# NSg . NPr/I+ VP/J R NSg/I/J/R/Dq VB P VXB NSg/I/VB+ NSg/C/P NSg/VB P ISg/D$+ NSg/VB+ NSg/VB/J . > gazing up into the roof of the court . # Nᴹ/Vg/J NSg/VB/J/P P D NSg/VB P D N🅪Sg/VB/J+ . > diff --git a/harper-core/tests/text/tagged/Difficult sentences.md b/harper-core/tests/text/tagged/Difficult sentences.md index 5784b2e2..b336333a 100644 --- a/harper-core/tests/text/tagged/Difficult sentences.md +++ b/harper-core/tests/text/tagged/Difficult sentences.md @@ -919,7 +919,7 @@ > The sailors were infected with malaria . # D+ NPl+ NSg/VPt NSg/VP/J P Nᴹ+ . > overcome with happiness -# NSg/VB P Nᴹ+ +# VB P Nᴹ+ > green with envy ; flushed with success # NPr🅪Sg/VB/J P NSg/VB+ . VP/J P N🅪Sg+ > She was with Acme for twenty years before retiring last fall . From e40967cf3e8eec97873da249f79cf538b0481da9 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Wed, 3 Dec 2025 12:27:39 -0700 Subject: [PATCH 21/84] fix(core): seal extension traits --- harper-core/src/char_ext.rs | 8 +++++++- harper-core/src/token_string_ext.rs | 12 +++++++++++- harper-core/src/vec_ext.rs | 8 +++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/harper-core/src/char_ext.rs b/harper-core/src/char_ext.rs index 1e67e143..ef218a98 100644 --- a/harper-core/src/char_ext.rs +++ b/harper-core/src/char_ext.rs @@ -3,7 +3,13 @@ use unicode_width::UnicodeWidthChar; use crate::Punctuation; -pub trait CharExt { +mod private { + pub trait Sealed {} + + impl Sealed for char {} +} + +pub trait CharExt: private::Sealed { fn is_cjk(&self) -> bool; /// Whether a character can be a component of an English word. fn is_english_lingual(&self) -> bool; diff --git a/harper-core/src/token_string_ext.rs b/harper-core/src/token_string_ext.rs index 69ede6d1..8b384819 100644 --- a/harper-core/src/token_string_ext.rs +++ b/harper-core/src/token_string_ext.rs @@ -47,8 +47,18 @@ macro_rules! create_fns_for { }; } +mod private { + use crate::{Document, Token}; + + pub trait Sealed {} + + impl Sealed for [Token] {} + + impl Sealed for Document {} +} + /// Extension methods for [`Token`] sequences that make them easier to wrangle and query. -pub trait TokenStringExt { +pub trait TokenStringExt: private::Sealed { fn first_sentence_word(&self) -> Option<&Token>; fn first_non_whitespace(&self) -> Option<&Token>; /// Grab the span that represents the beginning of the first element and the diff --git a/harper-core/src/vec_ext.rs b/harper-core/src/vec_ext.rs index 2c1bcf9d..80d3c7d9 100644 --- a/harper-core/src/vec_ext.rs +++ b/harper-core/src/vec_ext.rs @@ -1,7 +1,13 @@ use std::collections::VecDeque; +mod private { + pub trait Sealed {} + + impl Sealed for Vec {} +} + /// Extensions on top of [`Vec`] that make certain common operations easier. -pub trait VecExt { +pub trait VecExt: private::Sealed { /// Removes a list of indices from a Vector. /// Assumes that the provided indices are already in sorted order. fn remove_indices(&mut self, to_remove: VecDeque); From 72fd089764a8fe99e443498104797d91232e5cd1 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Wed, 3 Dec 2025 12:28:15 -0700 Subject: [PATCH 22/84] Release 1.1.0 harper-brill@1.1.0 harper-comments@1.1.0 harper-core@1.1.0 harper-html@1.1.0 harper-ink@1.1.0 harper-jjdescription@1.1.0 harper-literate-haskell@1.1.0 harper-ls@1.1.0 harper-pos-utils@1.1.0 harper-python@1.1.0 harper-stats@1.1.0 harper-tree-sitter@1.1.0 harper-typst@1.1.0 Generated by cargo-workspaces --- Cargo.lock | 26 +++++++++++++------------- harper-brill/Cargo.toml | 2 +- harper-comments/Cargo.toml | 2 +- harper-core/Cargo.toml | 2 +- harper-html/Cargo.toml | 2 +- harper-ink/Cargo.toml | 2 +- harper-jjdescription/Cargo.toml | 2 +- harper-literate-haskell/Cargo.toml | 2 +- harper-ls/Cargo.toml | 2 +- harper-pos-utils/Cargo.toml | 2 +- harper-python/Cargo.toml | 2 +- harper-stats/Cargo.toml | 2 +- harper-tree-sitter/Cargo.toml | 2 +- harper-typst/Cargo.toml | 2 +- packages/chrome-plugin/package.json | 2 +- packages/harper.js/package.json | 2 +- packages/obsidian-plugin/package.json | 2 +- packages/vscode-plugin/package.json | 20 +++++++++++++++++++- 18 files changed, 48 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 97c62edd..122bb73d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2669,7 +2669,7 @@ dependencies = [ [[package]] name = "harper-brill" -version = "1.0.0" +version = "1.1.0" dependencies = [ "harper-pos-utils", "lazy_static", @@ -2703,7 +2703,7 @@ dependencies = [ [[package]] name = "harper-comments" -version = "1.0.0" +version = "1.1.0" dependencies = [ "harper-core", "harper-html", @@ -2737,7 +2737,7 @@ dependencies = [ [[package]] name = "harper-core" -version = "1.0.0" +version = "1.1.0" dependencies = [ "ammonia", "bitflags 2.10.0", @@ -2775,7 +2775,7 @@ dependencies = [ [[package]] name = "harper-html" -version = "1.0.0" +version = "1.1.0" dependencies = [ "harper-core", "harper-tree-sitter", @@ -2786,7 +2786,7 @@ dependencies = [ [[package]] name = "harper-ink" -version = "1.0.0" +version = "1.1.0" dependencies = [ "harper-core", "harper-tree-sitter", @@ -2797,7 +2797,7 @@ dependencies = [ [[package]] name = "harper-jjdescription" -version = "1.0.0" +version = "1.1.0" dependencies = [ "harper-core", "harper-tree-sitter", @@ -2808,7 +2808,7 @@ dependencies = [ [[package]] name = "harper-literate-haskell" -version = "1.0.0" +version = "1.1.0" dependencies = [ "harper-comments", "harper-core", @@ -2819,7 +2819,7 @@ dependencies = [ [[package]] name = "harper-ls" -version = "1.0.0" +version = "1.1.0" dependencies = [ "anyhow", "clap", @@ -2850,7 +2850,7 @@ dependencies = [ [[package]] name = "harper-pos-utils" -version = "1.0.0" +version = "1.1.0" dependencies = [ "burn", "burn-ndarray", @@ -2868,7 +2868,7 @@ dependencies = [ [[package]] name = "harper-python" -version = "1.0.0" +version = "1.1.0" dependencies = [ "harper-core", "harper-tree-sitter", @@ -2879,7 +2879,7 @@ dependencies = [ [[package]] name = "harper-stats" -version = "1.0.0" +version = "1.1.0" dependencies = [ "chrono", "harper-core", @@ -2890,7 +2890,7 @@ dependencies = [ [[package]] name = "harper-tree-sitter" -version = "1.0.0" +version = "1.1.0" dependencies = [ "harper-core", "tree-sitter", @@ -2909,7 +2909,7 @@ dependencies = [ [[package]] name = "harper-typst" -version = "1.0.0" +version = "1.1.0" dependencies = [ "harper-core", "itertools 0.14.0", diff --git a/harper-brill/Cargo.toml b/harper-brill/Cargo.toml index fb1c3a81..7c03d84d 100644 --- a/harper-brill/Cargo.toml +++ b/harper-brill/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "harper-brill" -version = "1.0.0" +version = "1.1.0" edition = "2024" description = "The language checker for developers." license = "Apache-2.0" diff --git a/harper-comments/Cargo.toml b/harper-comments/Cargo.toml index d9c0e092..c4070335 100644 --- a/harper-comments/Cargo.toml +++ b/harper-comments/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "harper-comments" -version = "1.0.0" +version = "1.1.0" edition = "2024" description = "The language checker for developers." license = "Apache-2.0" diff --git a/harper-core/Cargo.toml b/harper-core/Cargo.toml index 0aa29ba4..7aef6f63 100644 --- a/harper-core/Cargo.toml +++ b/harper-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "harper-core" -version = "1.0.0" +version = "1.1.0" edition = "2024" description = "The language checker for developers." license = "Apache-2.0" diff --git a/harper-html/Cargo.toml b/harper-html/Cargo.toml index 4921ef8a..ca085165 100644 --- a/harper-html/Cargo.toml +++ b/harper-html/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "harper-html" -version = "1.0.0" +version = "1.1.0" edition = "2024" description = "The language checker for developers." license = "Apache-2.0" diff --git a/harper-ink/Cargo.toml b/harper-ink/Cargo.toml index 9b8f1b3d..6bd5d5b1 100644 --- a/harper-ink/Cargo.toml +++ b/harper-ink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "harper-ink" -version = "1.0.0" +version = "1.1.0" edition = "2024" description = "The language checker for developers." license = "Apache-2.0" diff --git a/harper-jjdescription/Cargo.toml b/harper-jjdescription/Cargo.toml index 860493e7..d5c2af11 100644 --- a/harper-jjdescription/Cargo.toml +++ b/harper-jjdescription/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "harper-jjdescription" -version = "1.0.0" +version = "1.1.0" edition = "2024" description = "The language checker for developers." license = "Apache-2.0" diff --git a/harper-literate-haskell/Cargo.toml b/harper-literate-haskell/Cargo.toml index cc8eb4fc..57c92df7 100644 --- a/harper-literate-haskell/Cargo.toml +++ b/harper-literate-haskell/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "harper-literate-haskell" -version = "1.0.0" +version = "1.1.0" edition = "2024" description = "The language checker for developers." license = "Apache-2.0" diff --git a/harper-ls/Cargo.toml b/harper-ls/Cargo.toml index 60b57a4d..70149f17 100644 --- a/harper-ls/Cargo.toml +++ b/harper-ls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "harper-ls" -version = "1.0.0" +version = "1.1.0" edition = "2024" description = "The language checker for developers." license = "Apache-2.0" diff --git a/harper-pos-utils/Cargo.toml b/harper-pos-utils/Cargo.toml index 79184b8a..b1209e2f 100644 --- a/harper-pos-utils/Cargo.toml +++ b/harper-pos-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "harper-pos-utils" -version = "1.0.0" +version = "1.1.0" edition = "2024" description = "The language checker for developers." license = "Apache-2.0" diff --git a/harper-python/Cargo.toml b/harper-python/Cargo.toml index 3ab11dff..42656ae3 100644 --- a/harper-python/Cargo.toml +++ b/harper-python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "harper-python" -version = "1.0.0" +version = "1.1.0" edition = "2024" description = "The language checker for developers." license = "Apache-2.0" diff --git a/harper-stats/Cargo.toml b/harper-stats/Cargo.toml index e0114864..0c07c58d 100644 --- a/harper-stats/Cargo.toml +++ b/harper-stats/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "harper-stats" -version = "1.0.0" +version = "1.1.0" edition = "2021" description = "The language checker for developers." license = "Apache-2.0" diff --git a/harper-tree-sitter/Cargo.toml b/harper-tree-sitter/Cargo.toml index 3062b33b..7c59a59a 100644 --- a/harper-tree-sitter/Cargo.toml +++ b/harper-tree-sitter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "harper-tree-sitter" -version = "1.0.0" +version = "1.1.0" edition = "2024" description = "The language checker for developers." license = "Apache-2.0" diff --git a/harper-typst/Cargo.toml b/harper-typst/Cargo.toml index 68f5daff..1c8a959b 100644 --- a/harper-typst/Cargo.toml +++ b/harper-typst/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "harper-typst" -version = "1.0.0" +version = "1.1.0" edition = "2024" description = "The language checker for developers." license = "Apache-2.0" diff --git a/packages/chrome-plugin/package.json b/packages/chrome-plugin/package.json index 5e17cac8..5d09b92f 100644 --- a/packages/chrome-plugin/package.json +++ b/packages/chrome-plugin/package.json @@ -1,7 +1,7 @@ { "name": "chrome-plugin", "displayName": "chrome-plugin", - "version": "1.0.0", + "version": "1.1.0", "author": "no one", "description": "The private grammar checker for 21st Century English", "type": "module", diff --git a/packages/harper.js/package.json b/packages/harper.js/package.json index 2c7f7eb4..c701371b 100644 --- a/packages/harper.js/package.json +++ b/packages/harper.js/package.json @@ -1,6 +1,6 @@ { "name": "harper.js", - "version": "1.0.0", + "version": "1.1.0", "license": "Apache-2.0", "author": "Elijah Potter", "description": "The grammar checker that respects your privacy.", diff --git a/packages/obsidian-plugin/package.json b/packages/obsidian-plugin/package.json index 8370a825..047a42b5 100644 --- a/packages/obsidian-plugin/package.json +++ b/packages/obsidian-plugin/package.json @@ -1,7 +1,7 @@ { "name": "obsidian-plugin", "private": true, - "version": "1.0.0", + "version": "1.1.0", "main": "main.js", "devDependencies": { "@playwright/test": "^1.52.0", diff --git a/packages/vscode-plugin/package.json b/packages/vscode-plugin/package.json index 9a6853cc..a29e0eca 100644 --- a/packages/vscode-plugin/package.json +++ b/packages/vscode-plugin/package.json @@ -2,7 +2,7 @@ "name": "harper", "displayName": "Harper - Grammar / Spell Checking", "description": "The grammar checker for developers", - "version": "1.0.0", + "version": "1.1.0", "private": true, "author": "Elijah Potter", "publisher": "elijah-potter", @@ -689,6 +689,12 @@ "default": true, "description": "Corrects `conform` typos to `confirm`." }, + "harper.linters.Copyright": { + "scope": "resource", + "type": "boolean", + "default": true, + "description": "Corrects `copywrite` to `copyright`. `Copywrite` refers to writing copy, while `copyright` is the legal right to creative works." + }, "harper.linters.CorrectNumberSuffix": { "scope": "resource", "type": "boolean", @@ -983,6 +989,12 @@ "default": true, "description": "Expands the abbreviation `w/o` to the full word `without` for clarity." }, + "harper.linters.Expat": { + "scope": "resource", + "type": "boolean", + "default": true, + "description": "Corrects the mistake of writing `expat` as two words." + }, "harper.linters.Expatriate": { "scope": "resource", "type": "boolean", @@ -2957,6 +2969,12 @@ "default": true, "description": "Catches the mix-up between `price`/`prise` and `prize` after the verb `win`." }, + "harper.linters.WishCould": { + "scope": "resource", + "type": "boolean", + "default": true, + "description": "Checks for `can` being used after `wish` when it should be `could`." + }, "harper.linters.WordPressDotcom": { "scope": "resource", "type": "boolean", From 94a83d7e78a242f369d8ca1664ae01853981713d Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Wed, 3 Dec 2025 15:25:10 -0700 Subject: [PATCH 23/84] hotfix(harper.js): doc generation for systems without parallel --- packages/harper.js/docs.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/harper.js/docs.sh b/packages/harper.js/docs.sh index 73028247..7d2ab310 100755 --- a/packages/harper.js/docs.sh +++ b/packages/harper.js/docs.sh @@ -20,7 +20,7 @@ fi mkdir -p "$harperjs_docs_dir" || true echo "Rendering HTML..." -if command -v parallel &> /dev/null; then +if false; then parallel ' base=$(basename {} .md) node renderPage.js "${base#"harper.js."} - Harper" "API reference documentation for harper.js" {} "html/${base}.html" @@ -29,7 +29,7 @@ else echo "parallel not found, falling back to sequential processing" for file in ./markdown/*.md; do base=$(basename "$file" .md) - node renderPage.js "${base#"harper.js."} - Harper" "API reference documentation for harper.js" {} "html/${base}.html" + node renderPage.js "${base#"harper.js."} - Harper" "API reference documentation for harper.js" "$file" "html/${base}.html" done fi mv -f "$html_dir" "${harperjs_docs_dir}/ref" From c2466549068604151f7f7ce6a82828d414ef3543 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Wed, 3 Dec 2025 15:14:49 -0700 Subject: [PATCH 24/84] feat(core): many new rules (#2292) * feat(core): `Handful` * feat(core): add several additional rules * refactor(core): combine `ItsContraction` duplicates * fix(core): `ItsContraction` should emit `LintKind::Punctuation` In accordance to part of #2220 * chore(core): update snapshots * feat(core): many more rules * feat(core): many more * feat(core): `CureFor` * fix(core): merge fallout * feat(core): expand according to feedback * fix(core): appease clippy --- harper-core/src/linting/apart_from.rs | 144 +++++++++++ harper-core/src/linting/cure_for.rs | 147 +++++++++++ harper-core/src/linting/handful.rs | 160 ++++++++++++ .../src/linting/its_contraction/general.rs | 88 +++++++ .../mod.rs} | 191 +++++++------- .../linting/its_contraction/proper_noun.rs | 121 +++++++++ harper-core/src/linting/jealous_of.rs | 164 ++++++++++++ harper-core/src/linting/johns_hopkins.rs | 149 +++++++++++ harper-core/src/linting/lint_group.rs | 16 ++ harper-core/src/linting/mod.rs | 9 + .../src/linting/phrase_corrections/mod.rs | 7 + .../src/linting/phrase_corrections/tests.rs | 42 +++ harper-core/src/linting/respond.rs | 180 +++++++++++++ harper-core/src/linting/right_click.rs | 164 ++++++++++++ harper-core/src/linting/soon_to_be.rs | 240 +++++++++++++++++ harper-core/src/linting/take_medicine.rs | 242 ++++++++++++++++++ .../text/linters/The Great Gatsby.snap.yml | 2 +- 17 files changed, 1969 insertions(+), 97 deletions(-) create mode 100644 harper-core/src/linting/apart_from.rs create mode 100644 harper-core/src/linting/cure_for.rs create mode 100644 harper-core/src/linting/handful.rs create mode 100644 harper-core/src/linting/its_contraction/general.rs rename harper-core/src/linting/{its_contraction.rs => its_contraction/mod.rs} (61%) create mode 100644 harper-core/src/linting/its_contraction/proper_noun.rs create mode 100644 harper-core/src/linting/jealous_of.rs create mode 100644 harper-core/src/linting/johns_hopkins.rs create mode 100644 harper-core/src/linting/respond.rs create mode 100644 harper-core/src/linting/right_click.rs create mode 100644 harper-core/src/linting/soon_to_be.rs create mode 100644 harper-core/src/linting/take_medicine.rs diff --git a/harper-core/src/linting/apart_from.rs b/harper-core/src/linting/apart_from.rs new file mode 100644 index 00000000..ca50c6b7 --- /dev/null +++ b/harper-core/src/linting/apart_from.rs @@ -0,0 +1,144 @@ +use crate::Token; +use crate::expr::{Expr, SequenceExpr}; +use crate::linting::expr_linter::Chunk; + +use super::{ExprLinter, Lint, LintKind, Suggestion}; + +pub struct ApartFrom { + expr: Box, +} + +impl Default for ApartFrom { + fn default() -> Self { + let expr = SequenceExpr::any_capitalization_of("apart") + .t_ws() + .then_any_capitalization_of("form"); + + Self { + expr: Box::new(expr), + } + } +} + +impl ExprLinter for ApartFrom { + type Unit = Chunk; + + fn expr(&self) -> &dyn Expr { + self.expr.as_ref() + } + + fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option { + let span = matched_tokens.last()?.span; + + Some(Lint { + span, + lint_kind: LintKind::WordChoice, + suggestions: vec![Suggestion::replace_with_match_case_str( + "from", + span.get_content(source), + )], + message: "Use `from` to spell `apart from`.".to_owned(), + priority: 50, + }) + } + + fn description(&self) -> &'static str { + "Flags the misspelling `apart form` and suggests `apart from`." + } +} + +#[cfg(test)] +mod tests { + use super::ApartFrom; + use crate::linting::tests::{assert_lint_count, assert_suggestion_result}; + + #[test] + fn corrects_basic_typo() { + assert_suggestion_result( + "Christianity was set apart form other religions.", + ApartFrom::default(), + "Christianity was set apart from other religions.", + ); + } + + #[test] + fn corrects_title_case() { + assert_suggestion_result( + "Apart Form these files, everything uploaded fine.", + ApartFrom::default(), + "Apart From these files, everything uploaded fine.", + ); + } + + #[test] + fn corrects_all_caps() { + assert_suggestion_result( + "APART FORM THE REST OF THE FIELD.", + ApartFrom::default(), + "APART FROM THE REST OF THE FIELD.", + ); + } + + #[test] + fn corrects_with_comma() { + assert_suggestion_result( + "It was apart form, not apart from, the original plan.", + ApartFrom::default(), + "It was apart from, not apart from, the original plan.", + ); + } + + #[test] + fn corrects_with_newline() { + assert_suggestion_result( + "They stood apart\nform everyone else at the rally.", + ApartFrom::default(), + "They stood apart\nfrom everyone else at the rally.", + ); + } + + #[test] + fn corrects_extra_spacing() { + assert_suggestion_result( + "We keep the archive apart form public assets.", + ApartFrom::default(), + "We keep the archive apart from public assets.", + ); + } + + #[test] + fn allows_correct_phrase() { + assert_lint_count( + "Lebanon's freedoms set it apart from other Arab states.", + ApartFrom::default(), + 0, + ); + } + + #[test] + fn ignores_hyphenated() { + assert_lint_count( + "Their apart-form design wasn’t what we needed.", + ApartFrom::default(), + 0, + ); + } + + #[test] + fn ignores_split_by_comma() { + assert_lint_count( + "They stood apart, form lines when asked.", + ApartFrom::default(), + 0, + ); + } + + #[test] + fn ignores_unrelated_form_usage() { + assert_lint_count( + "The form was kept apart to dry after printing.", + ApartFrom::default(), + 0, + ); + } +} diff --git a/harper-core/src/linting/cure_for.rs b/harper-core/src/linting/cure_for.rs new file mode 100644 index 00000000..82d3265e --- /dev/null +++ b/harper-core/src/linting/cure_for.rs @@ -0,0 +1,147 @@ +use crate::{ + Span, Token, + expr::{Expr, SequenceExpr}, + linting::expr_linter::Chunk, + linting::{ExprLinter, Lint, LintKind, Suggestion}, + patterns::{DerivedFrom, Word}, +}; + +pub struct CureFor { + expr: Box, +} + +impl Default for CureFor { + fn default() -> Self { + let expr = SequenceExpr::default() + .then(DerivedFrom::new_from_str("cure")) + .t_ws() + .then(Word::new("against")); + + Self { + expr: Box::new(expr), + } + } +} + +impl ExprLinter for CureFor { + type Unit = Chunk; + + fn expr(&self) -> &dyn Expr { + self.expr.as_ref() + } + + fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option { + let against = matched_tokens.last()?; + + let template: Vec = against.span.get_content(source).to_vec(); + let suggestion = Suggestion::replace_with_match_case_str("for", &template); + + Some(Lint { + span: Span::new(against.span.start, against.span.end), + lint_kind: LintKind::Usage, + suggestions: vec![suggestion], + message: "Prefer `cure for` when describing a treatment target.".to_owned(), + priority: 31, + }) + } + + fn description(&self) -> &str { + "Flags `cure against` and prefers the standard `cure for` pairing." + } +} + +#[cfg(test)] +mod tests { + use super::CureFor; + use crate::linting::tests::{assert_lint_count, assert_suggestion_result}; + + #[test] + fn corrects_simple_cure_against() { + assert_suggestion_result( + "Researchers sought a cure against the stubborn illness.", + CureFor::default(), + "Researchers sought a cure for the stubborn illness.", + ); + } + + #[test] + fn corrects_plural_cures_against() { + assert_suggestion_result( + "Doctors insist this serum cures against the new variant.", + CureFor::default(), + "Doctors insist this serum cures for the new variant.", + ); + } + + #[test] + fn corrects_past_participle_cured_against() { + assert_suggestion_result( + "The remedy was cured against the infection last spring.", + CureFor::default(), + "The remedy was cured for the infection last spring.", + ); + } + + #[test] + fn corrects_uppercase_against() { + assert_suggestion_result( + "We still trust the cure AGAINST the dreaded plague.", + CureFor::default(), + "We still trust the cure FOR the dreaded plague.", + ); + } + + #[test] + fn corrects_at_sentence_start() { + assert_suggestion_result( + "Cure against that condition became the rallying cry.", + CureFor::default(), + "Cure for that condition became the rallying cry.", + ); + } + + #[test] + fn does_not_flag_cure_for() { + assert_lint_count( + "They finally found a cure for the fever.", + CureFor::default(), + 0, + ); + } + + #[test] + fn does_not_flag_cure_from() { + assert_lint_count( + "A cure from this rare herb is on the horizon.", + CureFor::default(), + 0, + ); + } + + #[test] + fn does_not_flag_with_comma() { + assert_lint_count( + "A cure, against all odds, appeared in the files.", + CureFor::default(), + 0, + ); + } + + #[test] + fn does_not_flag_unrelated_against() { + assert_lint_count( + "Travelers stand against the roaring wind on the cliffs.", + CureFor::default(), + 0, + ); + } + + #[test] + fn does_not_flag_secure_against() { + assert_lint_count( + "The fortress stayed secure against the invaders.", + CureFor::default(), + 0, + ); + } +} diff --git a/harper-core/src/linting/handful.rs b/harper-core/src/linting/handful.rs new file mode 100644 index 00000000..d13d6549 --- /dev/null +++ b/harper-core/src/linting/handful.rs @@ -0,0 +1,160 @@ +use crate::expr::{Expr, SequenceExpr, SpaceOrHyphen}; +use crate::linting::expr_linter::Chunk; +use crate::{Token, TokenStringExt}; + +use super::{ExprLinter, Lint, LintKind, Suggestion}; + +pub struct Handful { + expr: Box, +} + +impl Default for Handful { + fn default() -> Self { + let expr = SequenceExpr::default() + .then_any_capitalization_of("hand") + .then_one_or_more(SpaceOrHyphen) + .then_any_capitalization_of("full") + .then_one_or_more(SpaceOrHyphen) + .then_any_capitalization_of("of"); + + Self { + expr: Box::new(expr), + } + } +} + +impl ExprLinter for Handful { + type Unit = Chunk; + + fn expr(&self) -> &dyn Expr { + self.expr.as_ref() + } + + fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option { + if matched_tokens.len() < 2 { + return None; + } + + let mut highlight_end = matched_tokens.len() - 1; + while highlight_end > 0 { + let prev = &matched_tokens[highlight_end - 1]; + if prev.kind.is_whitespace() || prev.kind.is_hyphen() { + highlight_end -= 1; + } else { + break; + } + } + + if highlight_end == 0 { + return None; + } + + let replacement = &matched_tokens[..highlight_end]; + let span = replacement.span()?; + let template = matched_tokens.first()?.span.get_content(source); + + Some(Lint { + span, + lint_kind: LintKind::BoundaryError, + suggestions: vec![Suggestion::replace_with_match_case( + "handful".chars().collect(), + template, + )], + message: "Write this quantity as the single word `handful`.".to_owned(), + priority: 31, + }) + } + + fn description(&self) -> &'static str { + "Keeps the palm-sized quantity expressed by `handful` as one word." + } +} + +#[cfg(test)] +mod tests { + use super::Handful; + use crate::linting::tests::{assert_lint_count, assert_no_lints, assert_suggestion_result}; + + #[test] + fn suggests_plain_spacing() { + assert_suggestion_result( + "Her basket held a hand full of berries.", + Handful::default(), + "Her basket held a handful of berries.", + ); + } + + #[test] + fn suggests_capitalized_form() { + assert_suggestion_result( + "Hand full of tales lined the shelf.", + Handful::default(), + "Handful of tales lined the shelf.", + ); + } + + #[test] + fn suggests_hyphenated_form() { + assert_suggestion_result( + "A hand-full of marbles scattered across the floor.", + Handful::default(), + "A handful of marbles scattered across the floor.", + ); + } + + #[test] + fn suggests_space_hyphen_combo() { + assert_suggestion_result( + "A hand - full of seeds spilled on the workbench.", + Handful::default(), + "A handful of seeds spilled on the workbench.", + ); + } + + #[test] + fn suggests_initial_hyphen_variants() { + assert_suggestion_result( + "Hand-Full of furniture, the cart creaked slowly.", + Handful::default(), + "Handful of furniture, the cart creaked slowly.", + ); + } + + #[test] + fn flags_multiple_instances() { + assert_lint_count( + "She carried a hand full of carrots and a hand full of radishes.", + Handful::default(), + 2, + ); + } + + #[test] + fn allows_correct_handful() { + assert_no_lints( + "A handful of volunteers arrived in time.", + Handful::default(), + ); + } + + #[test] + fn allows_parenthetical_hand() { + assert_no_lints( + "His hand, full of ink, kept writing without pause.", + Handful::default(), + ); + } + + #[test] + fn allows_hand_is_full() { + assert_no_lints("The hand is full of water.", Handful::default()); + } + + #[test] + fn allows_handfull_typo() { + assert_no_lints( + "The word handfull is an incorrect spelling.", + Handful::default(), + ); + } +} diff --git a/harper-core/src/linting/its_contraction/general.rs b/harper-core/src/linting/its_contraction/general.rs new file mode 100644 index 00000000..4621df51 --- /dev/null +++ b/harper-core/src/linting/its_contraction/general.rs @@ -0,0 +1,88 @@ +use harper_brill::UPOS; + +use crate::{ + Document, Token, TokenStringExt, + expr::{All, Expr, ExprExt, OwnedExprExt, SequenceExpr}, + linting::{Lint, LintKind, Linter, Suggestion}, + patterns::{NominalPhrase, Pattern, UPOSSet, WordSet}, +}; + +pub struct General { + expr: Box, +} + +impl Default for General { + fn default() -> Self { + let positive = SequenceExpr::default().t_aco("its").then_whitespace().then( + UPOSSet::new(&[UPOS::VERB, UPOS::AUX, UPOS::DET, UPOS::PRON]) + .or(WordSet::new(&["because"])), + ); + + let exceptions = SequenceExpr::default() + .then_anything() + .then_anything() + .then(WordSet::new(&["own", "intended"])); + + let inverted = SequenceExpr::default().then_unless(exceptions); + + let expr = All::new(vec![Box::new(positive), Box::new(inverted)]).or_longest( + SequenceExpr::aco("its") + .t_ws() + .then(UPOSSet::new(&[UPOS::ADJ])) + .t_ws() + .then(UPOSSet::new(&[UPOS::SCONJ, UPOS::PART])), + ); + + Self { + expr: Box::new(expr), + } + } +} + +impl Linter for General { + fn lint(&mut self, document: &Document) -> Vec { + let mut lints = Vec::new(); + let source = document.get_source(); + + for chunk in document.iter_chunks() { + lints.extend( + self.expr + .iter_matches(chunk, source) + .filter_map(|match_span| { + self.match_to_lint(&chunk[match_span.start..], source) + }), + ); + } + + lints + } + + fn description(&self) -> &str { + "Detects the possessive `its` before `had`, `been`, or `got` and offers `it's` or `it has`." + } +} + +impl General { + fn match_to_lint(&self, toks: &[Token], source: &[char]) -> Option { + let offender = toks.first()?; + let offender_chars = offender.span.get_content(source); + + if toks.get(2)?.kind.is_upos(UPOS::VERB) + && NominalPhrase.matches(&toks[2..], source).is_some() + { + return None; + } + + Some(Lint { + span: offender.span, + lint_kind: LintKind::Punctuation, + suggestions: vec![ + Suggestion::replace_with_match_case_str("it's", offender_chars), + Suggestion::replace_with_match_case_str("it has", offender_chars), + ], + message: "Use `it's` (short for `it has` or `it is`) here, not the possessive `its`." + .to_owned(), + priority: 54, + }) + } +} diff --git a/harper-core/src/linting/its_contraction.rs b/harper-core/src/linting/its_contraction/mod.rs similarity index 61% rename from harper-core/src/linting/its_contraction.rs rename to harper-core/src/linting/its_contraction/mod.rs index 8ffb1a65..50b94616 100644 --- a/harper-core/src/linting/its_contraction.rs +++ b/harper-core/src/linting/its_contraction/mod.rs @@ -1,102 +1,15 @@ -use harper_brill::UPOS; +use super::merge_linters::merge_linters; -use crate::Document; -use crate::TokenStringExt; -use crate::expr::All; -use crate::expr::Expr; -use crate::expr::ExprExt; -use crate::expr::OwnedExprExt; -use crate::expr::SequenceExpr; -use crate::patterns::NominalPhrase; -use crate::patterns::Pattern; -use crate::patterns::UPOSSet; -use crate::patterns::WordSet; -use crate::{ - Token, - linting::{Lint, LintKind, Suggestion}, -}; +mod general; +mod proper_noun; -use super::Linter; +use general::General; +use proper_noun::ProperNoun; -pub struct ItsContraction { - expr: Box, -} - -impl Default for ItsContraction { - fn default() -> Self { - let positive = SequenceExpr::default().t_aco("its").then_whitespace().then( - UPOSSet::new(&[UPOS::VERB, UPOS::AUX, UPOS::DET, UPOS::PRON]) - .or(WordSet::new(&["because"])), - ); - - let exceptions = SequenceExpr::default() - .then_anything() - .then_anything() - .then(WordSet::new(&["own", "intended"])); - - let inverted = SequenceExpr::default().then_unless(exceptions); - - let expr = All::new(vec![Box::new(positive), Box::new(inverted)]).or_longest( - SequenceExpr::aco("its") - .t_ws() - .then(UPOSSet::new(&[UPOS::ADJ])) - .t_ws() - .then(UPOSSet::new(&[UPOS::SCONJ, UPOS::PART])), - ); - - Self { - expr: Box::new(expr), - } - } -} - -impl Linter for ItsContraction { - fn lint(&mut self, document: &Document) -> Vec { - let mut lints = Vec::new(); - let source = document.get_source(); - - for chunk in document.iter_chunks() { - lints.extend( - self.expr - .iter_matches(chunk, source) - .filter_map(|match_span| { - self.match_to_lint(&chunk[match_span.start..], source) - }), - ); - } - - lints - } - - fn description(&self) -> &str { - "Detects the possessive `its` before `had`, `been`, or `got` and offers `it's` or `it has`." - } -} - -impl ItsContraction { - fn match_to_lint(&self, toks: &[Token], source: &[char]) -> Option { - let offender = toks.first()?; - let offender_chars = offender.span.get_content(source); - - if toks.get(2)?.kind.is_upos(UPOS::VERB) - && NominalPhrase.matches(&toks[2..], source).is_some() - { - return None; - } - - Some(Lint { - span: offender.span, - lint_kind: LintKind::WordChoice, - suggestions: vec![ - Suggestion::replace_with_match_case_str("it's", offender_chars), - Suggestion::replace_with_match_case_str("it has", offender_chars), - ], - message: "Use `it's` (short for `it has` or `it is`) here, not the possessive `its`." - .to_owned(), - priority: 54, - }) - } -} +merge_linters!( + ItsContraction => General, ProperNoun => + "Detects places where the possessive `its` should be the contraction `it's`, including before verbs/clauses and before proper nouns after opinion verbs." +); #[cfg(test)] mod tests { @@ -284,4 +197,90 @@ mod tests { ItsContraction::default(), ); } + + #[test] + fn corrects_think_google() { + assert_suggestion_result( + "I think its Google, not Microsoft.", + ItsContraction::default(), + "I think it's Google, not Microsoft.", + ); + } + + #[test] + fn corrects_hope_katie() { + assert_suggestion_result( + "I hope its Katie.", + ItsContraction::default(), + "I hope it's Katie.", + ); + } + + #[test] + fn corrects_guess_date() { + assert_suggestion_result( + "I guess its March 6.", + ItsContraction::default(), + "I guess it's March 6.", + ); + } + + #[test] + fn corrects_assume_john() { + assert_suggestion_result( + "We assume its John.", + ItsContraction::default(), + "We assume it's John.", + ); + } + + #[test] + fn corrects_doubt_tesla() { + assert_suggestion_result( + "They doubt its Tesla this year.", + ItsContraction::default(), + "They doubt it's Tesla this year.", + ); + } + + #[test] + fn handles_two_word_name() { + assert_suggestion_result( + "She thinks its New York.", + ItsContraction::default(), + "She thinks it's New York.", + ); + } + + #[test] + fn ignores_existing_contraction() { + assert_lint_count("I think it's Google.", ItsContraction::default(), 0); + } + + #[test] + fn ignores_possessive_noun_after_name() { + assert_lint_count( + "I think its Google product launch.", + ItsContraction::default(), + 0, + ); + } + + #[test] + fn ignores_without_opinion_verb() { + assert_lint_count( + "Its Google Pixel lineup is impressive.", + ItsContraction::default(), + 0, + ); + } + + #[test] + fn ignores_common_noun_target() { + assert_lint_count( + "We hope its accuracy improves.", + ItsContraction::default(), + 0, + ); + } } diff --git a/harper-core/src/linting/its_contraction/proper_noun.rs b/harper-core/src/linting/its_contraction/proper_noun.rs new file mode 100644 index 00000000..2e5ffded --- /dev/null +++ b/harper-core/src/linting/its_contraction/proper_noun.rs @@ -0,0 +1,121 @@ +use std::ops::Range; +use std::sync::Arc; + +use harper_brill::UPOS; + +use crate::{ + Document, Token, TokenStringExt, + expr::{Expr, ExprExt, ExprMap, OwnedExprExt, SequenceExpr}, + linting::{Lint, LintKind, Linter, Suggestion}, + patterns::{DerivedFrom, UPOSSet}, +}; + +pub struct ProperNoun { + expr: Box, + map: Arc>>, +} + +impl Default for ProperNoun { + fn default() -> Self { + let mut map = ExprMap::default(); + + let opinion_verbs = DerivedFrom::new_from_str("think") + .or(DerivedFrom::new_from_str("hope")) + .or(DerivedFrom::new_from_str("assume")) + .or(DerivedFrom::new_from_str("doubt")) + .or(DerivedFrom::new_from_str("guess")); + + let capitalized_word = |tok: &Token, src: &[char]| { + tok.kind.is_word() + && tok + .span + .get_content(src) + .first() + .map(|c| c.is_uppercase()) + .unwrap_or(false) + }; + + let name_head = UPOSSet::new(&[UPOS::PROPN]).or(capitalized_word); + + let lookahead_word = SequenceExpr::default().t_ws().then_any_word(); + + map.insert( + SequenceExpr::default() + .then(opinion_verbs) + .t_ws() + .t_aco("its") + .t_ws() + .then(name_head) + .then_optional(lookahead_word), + 2..3, + ); + + let map = Arc::new(map); + + Self { + expr: Box::new(map.clone()), + map, + } + } +} + +impl Linter for ProperNoun { + fn lint(&mut self, document: &Document) -> Vec { + let mut lints = Vec::new(); + let source = document.get_source(); + + for chunk in document.iter_chunks() { + lints.extend( + self.expr + .iter_matches(chunk, source) + .filter_map(|match_span| { + let matched = &chunk[match_span.start..match_span.end]; + self.match_to_lint(matched, source) + }), + ); + } + + lints + } + + fn description(&self) -> &str { + "Suggests the contraction `it's` after opinion verbs when it introduces a proper noun." + } +} + +impl ProperNoun { + fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option { + if matched_tokens.len() >= 7 + && let Some(next_word) = matched_tokens.get(6) + { + let is_lowercase = next_word + .span + .get_content(source) + .first() + .map(|c| c.is_lowercase()) + .unwrap_or(false); + + if is_lowercase + && (next_word.kind.is_upos(UPOS::NOUN) || next_word.kind.is_upos(UPOS::ADJ)) + { + return None; + } + } + + let range = self.map.lookup(0, matched_tokens, source)?.clone(); + let offending = matched_tokens.get(range.start)?; + let offender_text = offending.span.get_content(source); + + Some(Lint { + span: offending.span, + lint_kind: LintKind::Punctuation, + suggestions: vec![Suggestion::replace_with_match_case_str( + "it's", + offender_text, + )], + message: "Use `it's` (short for \"it is\") before a proper noun in this construction." + .to_owned(), + priority: 31, + }) + } +} diff --git a/harper-core/src/linting/jealous_of.rs b/harper-core/src/linting/jealous_of.rs new file mode 100644 index 00000000..897eb755 --- /dev/null +++ b/harper-core/src/linting/jealous_of.rs @@ -0,0 +1,164 @@ +use crate::{ + Token, + expr::{Expr, SequenceExpr}, + linting::expr_linter::Chunk, + linting::{ExprLinter, Lint, LintKind, Suggestion}, +}; + +pub struct JealousOf { + expr: Box, +} + +impl Default for JealousOf { + fn default() -> Self { + let valid_object = |tok: &Token, _source: &[char]| { + (tok.kind.is_nominal() || !tok.kind.is_verb()) + && (tok.kind.is_oov() || tok.kind.is_nominal()) + && !tok.kind.is_preposition() + }; + + let pattern = SequenceExpr::default() + .t_aco("jealous") + .t_ws() + .t_aco("from") + .t_ws() + .then_optional(SequenceExpr::default().then_determiner().t_ws()) + .then(valid_object); + + Self { + expr: Box::new(pattern), + } + } +} + +impl ExprLinter for JealousOf { + type Unit = Chunk; + + fn expr(&self) -> &dyn Expr { + self.expr.as_ref() + } + + fn match_to_lint(&self, tokens: &[Token], source: &[char]) -> Option { + let from_token = &tokens[2]; + + Some(Lint { + span: from_token.span, + lint_kind: LintKind::WordChoice, + suggestions: vec![Suggestion::replace_with_match_case_str( + "of", + from_token.span.get_content(source), + )], + message: "Use `of` after `jealous`.".to_owned(), + ..Default::default() + }) + } + + fn description(&self) -> &str { + "Encourages the standard preposition after `jealous`." + } +} + +#[cfg(test)] +mod tests { + use super::JealousOf; + use crate::linting::tests::{assert_lint_count, assert_suggestion_result}; + + #[test] + fn replaces_basic_from() { + assert_suggestion_result( + "She was jealous from her sister's success.", + JealousOf::default(), + "She was jealous of her sister's success.", + ); + } + + #[test] + fn handles_optional_determiner() { + assert_suggestion_result( + "He grew jealous from the attention.", + JealousOf::default(), + "He grew jealous of the attention.", + ); + } + + #[test] + fn fixes_pronoun_object() { + assert_suggestion_result( + "They became jealous from him.", + JealousOf::default(), + "They became jealous of him.", + ); + } + + #[test] + fn allows_oov_target() { + assert_suggestion_result( + "I'm jealous from Zybrix.", + JealousOf::default(), + "I'm jealous of Zybrix.", + ); + } + + #[test] + fn corrects_uppercase_preposition() { + assert_suggestion_result( + "Jealous FROM his fame.", + JealousOf::default(), + "Jealous OF his fame.", + ); + } + + #[test] + fn fixes_longer_phrase() { + assert_suggestion_result( + "They felt jealous from the sudden praise she received.", + JealousOf::default(), + "They felt jealous of the sudden praise she received.", + ); + } + + #[test] + fn fixes_minimal_phrase() { + assert_suggestion_result( + "jealous from success", + JealousOf::default(), + "jealous of success", + ); + } + + #[test] + fn does_not_flag_correct_usage() { + assert_lint_count( + "She was jealous of her sister's success.", + JealousOf::default(), + 0, + ); + } + + #[test] + fn does_not_flag_other_preposition_sequence() { + assert_lint_count( + "They stayed jealous from within the fortress.", + JealousOf::default(), + 0, + ); + } + + #[test] + fn fixes_following_gerund() { + assert_suggestion_result( + "He was jealous from being ignored.", + JealousOf::default(), + "He was jealous of being ignored.", + ); + } + + #[test] + fn ignores_numbers_after_from() { + assert_lint_count( + "She remained jealous from 2010 through 2015.", + JealousOf::default(), + 0, + ); + } +} diff --git a/harper-core/src/linting/johns_hopkins.rs b/harper-core/src/linting/johns_hopkins.rs new file mode 100644 index 00000000..0de2cabd --- /dev/null +++ b/harper-core/src/linting/johns_hopkins.rs @@ -0,0 +1,149 @@ +use crate::{ + CharStringExt, Token, + expr::{Expr, SequenceExpr}, + linting::expr_linter::Chunk, + linting::{ExprLinter, Lint, LintKind, Suggestion}, +}; + +pub struct JohnsHopkins { + expr: Box, +} + +impl Default for JohnsHopkins { + fn default() -> Self { + let expr = SequenceExpr::default() + .then(|tok: &Token, src: &[char]| { + tok.kind.is_proper_noun() + && tok.span.get_content(src).eq_ignore_ascii_case_str("john") + }) + .t_ws() + .then(|tok: &Token, src: &[char]| { + tok.kind.is_proper_noun() + && tok + .span + .get_content(src) + .eq_ignore_ascii_case_str("hopkins") + }); + + Self { + expr: Box::new(expr), + } + } +} + +impl ExprLinter for JohnsHopkins { + type Unit = Chunk; + + fn expr(&self) -> &dyn Expr { + self.expr.as_ref() + } + + fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option { + let span = matched_tokens.first()?.span; + let template = span.get_content(source); + + Some(Lint { + span, + lint_kind: LintKind::WordChoice, + suggestions: vec![Suggestion::replace_with_match_case_str("Johns", template)], + message: "Use `Johns Hopkins` for this name.".to_owned(), + priority: 31, + }) + } + + fn description(&self) -> &'static str { + "Recommends the proper spelling `Johns Hopkins`." + } +} + +#[cfg(test)] +mod tests { + use super::JohnsHopkins; + use crate::linting::tests::{assert_lint_count, assert_suggestion_result}; + + #[test] + fn corrects_university_reference() { + assert_suggestion_result( + "I applied to John Hopkins University last fall.", + JohnsHopkins::default(), + "I applied to Johns Hopkins University last fall.", + ); + } + + #[test] + fn corrects_hospital_reference() { + assert_suggestion_result( + "She works at the John Hopkins hospital.", + JohnsHopkins::default(), + "She works at the Johns Hopkins hospital.", + ); + } + + #[test] + fn corrects_standalone_name() { + assert_suggestion_result( + "We toured John Hopkins yesterday.", + JohnsHopkins::default(), + "We toured Johns Hopkins yesterday.", + ); + } + + #[test] + fn corrects_lowercase_usage() { + assert_suggestion_result( + "I studied at john hopkins online.", + JohnsHopkins::default(), + "I studied at johns hopkins online.", + ); + } + + #[test] + fn corrects_across_newline_whitespace() { + assert_suggestion_result( + "We met at John\nHopkins for lunch.", + JohnsHopkins::default(), + "We met at Johns\nHopkins for lunch.", + ); + } + + #[test] + fn corrects_with_trailing_punctuation() { + assert_suggestion_result( + "I toured John Hopkins, and it was great.", + JohnsHopkins::default(), + "I toured Johns Hopkins, and it was great.", + ); + } + + #[test] + fn corrects_before_hyphenated_unit() { + assert_suggestion_result( + "She joined the John Hopkins-affiliated lab.", + JohnsHopkins::default(), + "She joined the Johns Hopkins-affiliated lab.", + ); + } + + #[test] + fn allows_correct_spelling() { + assert_lint_count( + "Johns Hopkins University has a great program.", + JohnsHopkins::default(), + 0, + ); + } + + #[test] + fn allows_apostrophized_form() { + assert_lint_count( + "John Hopkins's novel won awards.", + JohnsHopkins::default(), + 0, + ); + } + + #[test] + fn allows_reversed_name_order() { + assert_lint_count("Hopkins, John is a contact.", JohnsHopkins::default(), 0); + } +} diff --git a/harper-core/src/linting/lint_group.rs b/harper-core/src/linting/lint_group.rs index d1ee5698..eb52cc4e 100644 --- a/harper-core/src/linting/lint_group.rs +++ b/harper-core/src/linting/lint_group.rs @@ -26,6 +26,7 @@ use super::and_in::AndIn; use super::and_the_like::AndTheLike; use super::another_thing_coming::AnotherThingComing; use super::another_think_coming::AnotherThinkComing; +use super::apart_from::ApartFrom; use super::ask_no_preposition::AskNoPreposition; use super::avoid_curses::AvoidCurses; use super::back_in_the_day::BackInTheDay; @@ -44,6 +45,7 @@ use super::compound_subject_i::CompoundSubjectI; use super::confident::Confident; use super::correct_number_suffix::CorrectNumberSuffix; use super::criteria_phenomena::CriteriaPhenomena; +use super::cure_for::CureFor; use super::currency_placement::CurrencyPlacement; use super::despite_of::DespiteOf; use super::didnt::Didnt; @@ -83,6 +85,8 @@ use super::interested_in::InterestedIn; use super::it_looks_like_that::ItLooksLikeThat; use super::its_contraction::ItsContraction; use super::its_possessive::ItsPossessive; +use super::jealous_of::JealousOf; +use super::johns_hopkins::JohnsHopkins; use super::left_right_hand::LeftRightHand; use super::less_worse::LessWorse; use super::let_to_do::LetToDo; @@ -140,6 +144,8 @@ use super::quote_spacing::QuoteSpacing; use super::redundant_additive_adverbs::RedundantAdditiveAdverbs; use super::regionalisms::Regionalisms; use super::repeated_words::RepeatedWords; +use super::respond::Respond; +use super::right_click::RightClick; use super::roller_skated::RollerSkated; use super::safe_to_save::SafeToSave; use super::save_to_safe::SaveToSafe; @@ -152,12 +158,14 @@ use super::single_be::SingleBe; use super::some_without_article::SomeWithoutArticle; use super::something_is::SomethingIs; use super::somewhat_something::SomewhatSomething; +use super::soon_to_be::SoonToBe; use super::sought_after::SoughtAfter; use super::spaces::Spaces; use super::spell_check::SpellCheck; use super::spelled_numbers::SpelledNumbers; use super::split_words::SplitWords; use super::subject_pronoun::SubjectPronoun; +use super::take_medicine::TakeMedicine; use super::that_than::ThatThan; use super::that_which::ThatWhich; use super::the_how_why::TheHowWhy; @@ -474,6 +482,7 @@ impl LintGroup { // Please maintain alphabetical order. // On *nix you can maintain sort order with `sort -t'(' -k2` insert_expr_rule!(APart, true); + insert_expr_rule!(ApartFrom, true); insert_expr_rule!(AWhile, true); insert_expr_rule!(Addicting, true); insert_expr_rule!(AdjectiveDoubleDegree, true); @@ -506,6 +515,7 @@ impl LintGroup { insert_expr_rule!(Confident, true); insert_struct_rule!(CorrectNumberSuffix, true); insert_expr_rule!(CriteriaPhenomena, true); + insert_expr_rule!(CureFor, true); insert_struct_rule!(CurrencyPlacement, true); insert_expr_rule!(Dashes, true); insert_expr_rule!(DespiteOf, true); @@ -539,6 +549,8 @@ impl LintGroup { insert_expr_rule!(IAmAgreement, true); insert_expr_rule!(IfWouldve, true); insert_expr_rule!(InterestedIn, true); + insert_expr_rule!(JealousOf, true); + insert_expr_rule!(JohnsHopkins, true); insert_expr_rule!(ItLooksLikeThat, true); insert_struct_rule!(ItsContraction, true); insert_expr_rule!(ItsPossessive, true); @@ -596,6 +608,8 @@ impl LintGroup { insert_struct_rule!(QuoteSpacing, true); insert_expr_rule!(RedundantAdditiveAdverbs, true); insert_struct_rule!(RepeatedWords, true); + insert_expr_rule!(Respond, true); + insert_expr_rule!(RightClick, true); insert_expr_rule!(RollerSkated, true); insert_expr_rule!(SafeToSave, true); insert_expr_rule!(SaveToSafe, true); @@ -603,6 +617,7 @@ impl LintGroup { insert_expr_rule!(ShootOneselfInTheFoot, true); insert_expr_rule!(SimplePastToPastParticiple, true); insert_expr_rule!(SinceDuration, true); + insert_expr_rule!(SoonToBe, true); insert_expr_rule!(SingleBe, true); insert_expr_rule!(SomeWithoutArticle, true); insert_expr_rule!(SomethingIs, true); @@ -611,6 +626,7 @@ impl LintGroup { insert_struct_rule!(Spaces, true); insert_struct_rule!(SpelledNumbers, false); insert_expr_rule!(SplitWords, true); + insert_expr_rule!(TakeMedicine, true); insert_struct_rule!(SubjectPronoun, true); insert_expr_rule!(ThatThan, true); insert_expr_rule!(ThatWhich, true); diff --git a/harper-core/src/linting/mod.rs b/harper-core/src/linting/mod.rs index 47e6fed3..8cdb5438 100644 --- a/harper-core/src/linting/mod.rs +++ b/harper-core/src/linting/mod.rs @@ -17,6 +17,7 @@ mod and_in; mod and_the_like; mod another_thing_coming; mod another_think_coming; +mod apart_from; mod ask_no_preposition; mod avoid_curses; mod back_in_the_day; @@ -37,6 +38,7 @@ mod compound_subject_i; mod confident; mod correct_number_suffix; mod criteria_phenomena; +mod cure_for; mod currency_placement; mod dashes; mod despite_of; @@ -62,6 +64,7 @@ mod for_noun; mod free_predicate; mod friend_of_me; mod go_so_far_as_to; +mod handful; mod have_pronoun; mod have_take_a_look; mod hedging; @@ -83,6 +86,8 @@ mod it_looks_like_that; mod it_would_be; mod its_contraction; mod its_possessive; +mod jealous_of; +mod johns_hopkins; mod left_right_hand; mod less_worse; mod let_to_do; @@ -150,6 +155,8 @@ mod quote_spacing; mod redundant_additive_adverbs; mod regionalisms; mod repeated_words; +mod respond; +mod right_click; mod roller_skated; mod safe_to_save; mod save_to_safe; @@ -162,6 +169,7 @@ mod single_be; mod some_without_article; mod something_is; mod somewhat_something; +mod soon_to_be; mod sought_after; mod spaces; mod spell_check; @@ -169,6 +177,7 @@ mod spelled_numbers; mod split_words; mod subject_pronoun; mod suggestion; +mod take_medicine; mod take_serious; mod that_than; mod that_which; diff --git a/harper-core/src/linting/phrase_corrections/mod.rs b/harper-core/src/linting/phrase_corrections/mod.rs index 6469f698..1cc14031 100644 --- a/harper-core/src/linting/phrase_corrections/mod.rs +++ b/harper-core/src/linting/phrase_corrections/mod.rs @@ -725,6 +725,13 @@ pub fn lint_group() -> LintGroup { "Corrects wrong variations of the idiomatic adjective `last-ditch`.", LintKind::Usage ), + "LastNight" => ( + ["yesterday night"], + ["last night"], + "The idiomatic phrase is `last night`.", + "Flags `yesterday night` and suggests the standard phrasing `last night`.", + LintKind::WordChoice + ), "LetAlone" => ( ["let along"], ["let alone"], diff --git a/harper-core/src/linting/phrase_corrections/tests.rs b/harper-core/src/linting/phrase_corrections/tests.rs index 00901ac7..ce715d8e 100644 --- a/harper-core/src/linting/phrase_corrections/tests.rs +++ b/harper-core/src/linting/phrase_corrections/tests.rs @@ -1102,6 +1102,48 @@ fn correct_last_ditch_space() { ); } +// LastNight +#[test] +fn corrects_yesterday_night_basic() { + assert_suggestion_result( + "I was there yesterday night.", + lint_group(), + "I was there last night.", + ); +} + +#[test] +fn corrects_yesterday_night_capitalized() { + assert_suggestion_result( + "Yesterday night was fun.", + lint_group(), + "Last night was fun.", + ); +} + +#[test] +fn corrects_yesterday_night_with_comma() { + assert_suggestion_result( + "Yesterday night, we watched a movie.", + lint_group(), + "Last night, we watched a movie.", + ); +} + +#[test] +fn corrects_yesterday_night_across_newline() { + assert_suggestion_result( + "They left yesterday\nnight after the show.", + lint_group(), + "They left last night after the show.", + ); +} + +#[test] +fn no_lint_for_last_night_phrase() { + assert_lint_count("I remember last night clearly.", lint_group(), 0); +} + // LetAlone #[test] fn let_along() { diff --git a/harper-core/src/linting/respond.rs b/harper-core/src/linting/respond.rs new file mode 100644 index 00000000..75354342 --- /dev/null +++ b/harper-core/src/linting/respond.rs @@ -0,0 +1,180 @@ +use std::sync::Arc; + +use crate::Token; +use crate::expr::{Expr, ExprMap, SequenceExpr}; +use crate::linting::expr_linter::Chunk; +use crate::linting::{ExprLinter, Lint, LintKind, Suggestion}; +use crate::patterns::Word; + +pub struct Respond { + expr: Box, + map: Arc>, +} + +impl Default for Respond { + fn default() -> Self { + let mut map = ExprMap::default(); + + let helper_verb = |tok: &Token, src: &[char]| { + if tok.kind.is_auxiliary_verb() { + return true; + } + + if !tok.kind.is_verb() { + return false; + } + + let lower = tok.span.get_content_string(src).to_lowercase(); + matches!( + lower.as_str(), + "do" | "did" | "does" | "won't" | "don't" | "didn't" | "doesn't" + ) + }; + + map.insert( + SequenceExpr::default() + .then_nominal() + .t_ws() + .then(helper_verb) + .t_ws() + .then(Word::new("response")), + 4, + ); + + map.insert( + SequenceExpr::default() + .then_nominal() + .t_ws() + .then(helper_verb) + .t_ws() + .then_adverb() + .t_ws() + .then(Word::new("response")), + 6, + ); + + let map = Arc::new(map); + + Self { + expr: Box::new(map.clone()), + map, + } + } +} + +impl ExprLinter for Respond { + type Unit = Chunk; + + fn expr(&self) -> &dyn Expr { + self.expr.as_ref() + } + + fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option { + let response_index = *self.map.lookup(0, matched_tokens, source)?; + let response_token = matched_tokens.get(response_index)?; + + Some(Lint { + span: response_token.span, + lint_kind: LintKind::WordChoice, + suggestions: vec![Suggestion::replace_with_match_case_str( + "respond", + response_token.span.get_content(source), + )], + message: "Use the verb `respond` here.".to_owned(), + priority: 40, + }) + } + + fn description(&self) -> &'static str { + "Flags uses of the noun `response` where the verb `respond` is needed after an auxiliary." + } +} + +#[cfg(test)] +mod tests { + use super::Respond; + use crate::linting::tests::{assert_lint_count, assert_no_lints, assert_suggestion_result}; + + #[test] + fn fixes_will_response() { + assert_suggestion_result( + "He will response soon.", + Respond::default(), + "He will respond soon.", + ); + } + + #[test] + fn fixes_can_response() { + assert_suggestion_result( + "They can response to the survey.", + Respond::default(), + "They can respond to the survey.", + ); + } + + #[test] + fn fixes_did_not_response() { + assert_suggestion_result( + "I did not response yesterday.", + Respond::default(), + "I did not respond yesterday.", + ); + } + + #[test] + fn fixes_might_quickly_response() { + assert_suggestion_result( + "She might quickly response to feedback.", + Respond::default(), + "She might quickly respond to feedback.", + ); + } + + #[test] + fn fixes_wont_response() { + assert_suggestion_result( + "They won't response in time.", + Respond::default(), + "They won't respond in time.", + ); + } + + #[test] + fn fixes_would_response() { + assert_suggestion_result( + "We would response if we could.", + Respond::default(), + "We would respond if we could.", + ); + } + + #[test] + fn fixes_should_response() { + assert_suggestion_result( + "You should response politely.", + Respond::default(), + "You should respond politely.", + ); + } + + #[test] + fn does_not_flag_correct_respond() { + assert_no_lints("Please respond when you can.", Respond::default()); + } + + #[test] + fn does_not_flag_noun_use() { + assert_no_lints("The response time was great.", Respond::default()); + } + + #[test] + fn does_not_flag_question_subject() { + assert_lint_count("Should response times be logged?", Respond::default(), 0); + } + + #[test] + fn does_not_flag_response_as_object() { + assert_no_lints("I have no response for that.", Respond::default()); + } +} diff --git a/harper-core/src/linting/right_click.rs b/harper-core/src/linting/right_click.rs new file mode 100644 index 00000000..07c899e8 --- /dev/null +++ b/harper-core/src/linting/right_click.rs @@ -0,0 +1,164 @@ +use std::sync::Arc; + +use crate::{ + Token, TokenStringExt, + expr::{Expr, ExprMap, SequenceExpr}, + linting::expr_linter::Chunk, + linting::{ExprLinter, Lint, LintKind, Suggestion}, + patterns::DerivedFrom, +}; + +pub struct RightClick { + expr: Box, + map: Arc>, +} + +impl Default for RightClick { + fn default() -> Self { + let mut map = ExprMap::default(); + + map.insert( + SequenceExpr::default() + .then_word_set(&["right", "left", "middle"]) + .t_ws() + .then(DerivedFrom::new_from_str("click")), + 0, + ); + + let map = Arc::new(map); + + Self { + expr: Box::new(map.clone()), + map, + } + } +} + +impl ExprLinter for RightClick { + type Unit = Chunk; + + fn expr(&self) -> &dyn Expr { + self.expr.as_ref() + } + + fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option { + let start_idx = *self.map.lookup(0, matched_tokens, source)?; + let click_idx = matched_tokens.len().checked_sub(1)?; + let span = matched_tokens.get(start_idx..=click_idx)?.span()?; + let template = span.get_content(source); + + let direction = matched_tokens.get(start_idx)?.span.get_content(source); + let click = matched_tokens.get(click_idx)?.span.get_content(source); + + let replacement: Vec = direction + .iter() + .copied() + .chain(['-']) + .chain(click.iter().copied()) + .collect(); + + Some(Lint { + span, + lint_kind: LintKind::Punctuation, + suggestions: vec![Suggestion::replace_with_match_case(replacement, template)], + message: "Hyphenate this mouse command.".to_owned(), + priority: 40, + }) + } + + fn description(&self) -> &'static str { + "Hyphenates right-click style mouse commands." + } +} + +#[cfg(test)] +mod tests { + use super::RightClick; + use crate::linting::tests::{assert_lint_count, assert_suggestion_result}; + + #[test] + fn hyphenates_basic_command() { + assert_suggestion_result( + "Right click the icon.", + RightClick::default(), + "Right-click the icon.", + ); + } + + #[test] + fn hyphenates_with_preposition() { + assert_suggestion_result( + "Please right click on the link.", + RightClick::default(), + "Please right-click on the link.", + ); + } + + #[test] + fn hyphenates_past_tense() { + assert_suggestion_result( + "They right clicked the submit button.", + RightClick::default(), + "They right-clicked the submit button.", + ); + } + + #[test] + fn hyphenates_gerund() { + assert_suggestion_result( + "Right clicking the item highlights it.", + RightClick::default(), + "Right-clicking the item highlights it.", + ); + } + + #[test] + fn hyphenates_plural_noun() { + assert_suggestion_result( + "Right clicks are tracked in the log.", + RightClick::default(), + "Right-clicks are tracked in the log.", + ); + } + + #[test] + fn hyphenates_all_caps() { + assert_suggestion_result( + "He RIGHT CLICKED the file.", + RightClick::default(), + "He RIGHT-CLICKED the file.", + ); + } + + #[test] + fn hyphenates_left_click() { + assert_suggestion_result( + "Left click the checkbox.", + RightClick::default(), + "Left-click the checkbox.", + ); + } + + #[test] + fn hyphenates_middle_click() { + assert_suggestion_result( + "Middle click to open in a new tab.", + RightClick::default(), + "Middle-click to open in a new tab.", + ); + } + + #[test] + fn allows_hyphenated_form() { + assert_lint_count("Right-click the icon.", RightClick::default(), 0); + } + + #[test] + fn ignores_unrelated_right_and_click() { + assert_lint_count( + "Click the right button to continue.", + RightClick::default(), + 0, + ); + } +} diff --git a/harper-core/src/linting/soon_to_be.rs b/harper-core/src/linting/soon_to_be.rs new file mode 100644 index 00000000..48eb2c1e --- /dev/null +++ b/harper-core/src/linting/soon_to_be.rs @@ -0,0 +1,240 @@ +use std::{ops::Range, sync::Arc}; + +use crate::{ + Token, TokenKind, TokenStringExt, + expr::{Expr, ExprMap, SequenceExpr}, + linting::expr_linter::Chunk, + linting::{ExprLinter, Lint, LintKind, Suggestion}, + patterns::NominalPhrase, +}; + +pub struct SoonToBe { + expr: Box, + map: Arc>>, +} + +impl Default for SoonToBe { + fn default() -> Self { + let mut map = ExprMap::default(); + + let soon_to_be = || { + SequenceExpr::default() + .t_aco("soon") + .t_ws() + .t_aco("to") + .t_ws() + .t_aco("be") + }; + + let nominal_tail = || { + SequenceExpr::default() + .then_optional(SequenceExpr::default().then_one_or_more_adverbs().t_ws()) + .then(NominalPhrase) + }; + + let hyphenated_number_modifier = || { + SequenceExpr::default() + .then_number() + .then_hyphen() + .then_nominal() + .then_optional(SequenceExpr::default().then_hyphen().then_adjective()) + .t_ws() + .then_nominal() + }; + + let hyphenated_compound = || { + SequenceExpr::default() + .then_kind_any(&[TokenKind::is_word_like as fn(&TokenKind) -> bool]) + .then_hyphen() + .then_nominal() + }; + + let trailing_phrase = || { + SequenceExpr::default().then_any_of(vec![ + Box::new(hyphenated_number_modifier()), + Box::new(hyphenated_compound()), + Box::new(nominal_tail()), + ]) + }; + + map.insert( + SequenceExpr::default() + .then_determiner() + .t_ws() + .then_seq(soon_to_be()) + .t_ws() + .then_seq(trailing_phrase()), + 2..7, + ); + + map.insert( + SequenceExpr::default() + .then_seq(soon_to_be()) + .t_ws() + .then_seq(trailing_phrase()), + 0..5, + ); + + let map = Arc::new(map); + + Self { + expr: Box::new(map.clone()), + map, + } + } +} + +impl ExprLinter for SoonToBe { + type Unit = Chunk; + + fn expr(&self) -> &dyn Expr { + self.expr.as_ref() + } + + fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option { + let range = self.map.lookup(0, matched_tokens, source)?; + let span = matched_tokens.get(range.start..range.end)?.span()?; + let template = span.get_content(source); + + let mut nominal_found = false; + for tok in matched_tokens.iter().skip(range.end) { + if tok.kind.is_whitespace() || tok.kind.is_hyphen() { + continue; + } + + if tok.kind.is_punctuation() { + break; + } + + if tok.kind.is_nominal() { + if tok.kind.is_preposition() { + continue; + } else { + nominal_found = true; + break; + } + } + } + + if !nominal_found { + return None; + } + + Some(Lint { + span, + lint_kind: LintKind::Miscellaneous, + suggestions: vec![Suggestion::replace_with_match_case_str( + "soon-to-be", + template, + )], + message: "Use hyphens when `soon to be` modifies a noun.".to_owned(), + priority: 31, + }) + } + + fn description(&self) -> &'static str { + "Hyphenates `soon-to-be` when it appears before a noun." + } +} + +#[cfg(test)] +mod tests { + use super::SoonToBe; + use crate::linting::tests::{assert_lint_count, assert_no_lints, assert_suggestion_result}; + + #[test] + fn hyphenates_possessive_phrase() { + assert_suggestion_result( + "We met his soon to be boss at lunch.", + SoonToBe::default(), + "We met his soon-to-be boss at lunch.", + ); + } + + #[test] + fn hyphenates_article_phrase() { + assert_suggestion_result( + "They toasted the soon to be couple.", + SoonToBe::default(), + "They toasted the soon-to-be couple.", + ); + } + + #[test] + fn hyphenates_sentence_start() { + assert_suggestion_result( + "Soon to be parents filled the classroom.", + SoonToBe::default(), + "Soon-to-be parents filled the classroom.", + ); + } + + #[test] + fn allows_existing_hyphens() { + assert_no_lints("We met his soon-to-be boss yesterday.", SoonToBe::default()); + } + + #[test] + fn keeps_non_adjectival_use() { + assert_no_lints("The concert is soon to be over.", SoonToBe::default()); + } + + #[test] + fn hyphenates_with_adverb() { + assert_suggestion_result( + "Our soon to be newly married friends visited.", + SoonToBe::default(), + "Our soon-to-be newly married friends visited.", + ); + } + + #[test] + fn hyphenates_hyphenated_number_phrase() { + assert_suggestion_result( + "Our soon to be 5-year-old son starts school.", + SoonToBe::default(), + "Our soon-to-be 5-year-old son starts school.", + ); + } + + #[test] + fn hyphenates_in_law_phrase() { + assert_suggestion_result( + "She thanked her soon to be in-laws for hosting.", + SoonToBe::default(), + "She thanked her soon-to-be in-laws for hosting.", + ); + } + + #[test] + fn hyphenates_future_event() { + assert_suggestion_result( + "We reserved space for our soon to be celebration.", + SoonToBe::default(), + "We reserved space for our soon-to-be celebration.", + ); + } + + #[test] + fn ignores_misaligned_verb_chain() { + assert_lint_count( + "They will soon to be moving overseas.", + SoonToBe::default(), + 0, + ); + } + + #[test] + fn hyphenates_guest_example() { + assert_suggestion_result( + "I cooked for my soon to be guests.", + SoonToBe::default(), + "I cooked for my soon-to-be guests.", + ); + } + + #[test] + fn ignores_rearranged_phrase() { + assert_no_lints("We hope to soon be home.", SoonToBe::default()); + } +} diff --git a/harper-core/src/linting/take_medicine.rs b/harper-core/src/linting/take_medicine.rs new file mode 100644 index 00000000..aef01c99 --- /dev/null +++ b/harper-core/src/linting/take_medicine.rs @@ -0,0 +1,242 @@ +use crate::{ + Token, + expr::{Expr, OwnedExprExt, SequenceExpr}, + linting::expr_linter::Chunk, + linting::{ExprLinter, Lint, LintKind, Suggestion}, + patterns::DerivedFrom, +}; + +pub struct TakeMedicine { + expr: Box, +} + +impl Default for TakeMedicine { + fn default() -> Self { + let eat_verb = DerivedFrom::new_from_str("eat") + .or(DerivedFrom::new_from_str("eats")) + .or(DerivedFrom::new_from_str("ate")) + .or(DerivedFrom::new_from_str("eating")) + .or(DerivedFrom::new_from_str("eaten")); + + let medication = DerivedFrom::new_from_str("antibiotic") + .or(DerivedFrom::new_from_str("medicine")) + .or(DerivedFrom::new_from_str("medication")) + .or(DerivedFrom::new_from_str("pill")) + .or(DerivedFrom::new_from_str("tablet")) + .or(DerivedFrom::new_from_str("aspirin")) + .or(DerivedFrom::new_from_str("paracetamol")); + + let modifiers = SequenceExpr::default() + .then_any_of(vec![ + Box::new(SequenceExpr::default().then_determiner()), + Box::new(SequenceExpr::default().then_possessive_determiner()), + Box::new(SequenceExpr::default().then_quantifier()), + ]) + .t_ws(); + + let adjectives = SequenceExpr::default().then_one_or_more_adjectives().t_ws(); + + let pattern = SequenceExpr::default() + .then(eat_verb) + .t_ws() + .then_optional(modifiers) + .then_optional(adjectives) + .then(medication); + + Self { + expr: Box::new(pattern), + } + } +} + +fn replacement_for( + verb: &Token, + source: &[char], + base: &str, + third_person: &str, + past: &str, + past_participle: &str, + progressive: &str, +) -> Suggestion { + let replacement = if verb.kind.is_verb_progressive_form() { + progressive + } else if verb.kind.is_verb_third_person_singular_present_form() { + third_person + } else if verb.kind.is_verb_past_participle_form() && !verb.kind.is_verb_simple_past_form() { + past_participle + } else if verb.kind.is_verb_simple_past_form() { + past + } else { + base + }; + + Suggestion::replace_with_match_case( + replacement.chars().collect(), + verb.span.get_content(source), + ) +} + +impl ExprLinter for TakeMedicine { + type Unit = Chunk; + + fn expr(&self) -> &dyn Expr { + self.expr.as_ref() + } + + fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option { + let verb = matched_tokens.first()?; + let span = verb.span; + + let suggestions = vec![ + replacement_for(verb, source, "take", "takes", "took", "taken", "taking"), + replacement_for( + verb, + source, + "swallow", + "swallows", + "swallowed", + "swallowed", + "swallowing", + ), + ]; + + Some(Lint { + span, + lint_kind: LintKind::Usage, + suggestions, + message: "Use a verb like `take` or `swallow` with medicine instead of `eat`." + .to_string(), + priority: 63, + }) + } + + fn description(&self) -> &'static str { + "Encourages pairing medicine-related nouns with verbs like `take` or `swallow` instead of `eat`." + } +} + +#[cfg(test)] +mod tests { + use super::TakeMedicine; + use crate::linting::tests::{ + assert_lint_count, assert_nth_suggestion_result, assert_suggestion_result, + }; + + #[test] + fn swaps_ate_antibiotics() { + assert_suggestion_result( + "I ate antibiotics for a week.", + TakeMedicine::default(), + "I took antibiotics for a week.", + ); + } + + #[test] + fn swaps_eat_medicine() { + assert_suggestion_result( + "You should eat the medicine now.", + TakeMedicine::default(), + "You should take the medicine now.", + ); + } + + #[test] + fn swaps_eats_medication() { + assert_suggestion_result( + "She eats medication daily.", + TakeMedicine::default(), + "She takes medication daily.", + ); + } + + #[test] + fn swaps_eating_medicines() { + assert_suggestion_result( + "Are you eating medicines for that illness?", + TakeMedicine::default(), + "Are you taking medicines for that illness?", + ); + } + + #[test] + fn swaps_eaten_medication() { + assert_suggestion_result( + "He has eaten medication already.", + TakeMedicine::default(), + "He has taken medication already.", + ); + } + + #[test] + fn swaps_eat_pills() { + assert_suggestion_result( + "He ate the pills without water.", + TakeMedicine::default(), + "He took the pills without water.", + ); + } + + #[test] + fn swaps_eating_paracetamol() { + assert_suggestion_result( + "She is eating paracetamol for her headache.", + TakeMedicine::default(), + "She is taking paracetamol for her headache.", + ); + } + + #[test] + fn handles_possessive_modifier() { + assert_suggestion_result( + "Please eat my antibiotics.", + TakeMedicine::default(), + "Please take my antibiotics.", + ); + } + + #[test] + fn handles_adjectives() { + assert_suggestion_result( + "They ate the prescribed antibiotics.", + TakeMedicine::default(), + "They took the prescribed antibiotics.", + ); + } + + #[test] + fn supports_uppercase() { + assert_suggestion_result( + "Eat antibiotics with water.", + TakeMedicine::default(), + "Take antibiotics with water.", + ); + } + + #[test] + fn offers_swallow_alternative() { + assert_nth_suggestion_result( + "He ate the medication without water.", + TakeMedicine::default(), + "He swallowed the medication without water.", + 1, + ); + } + + #[test] + fn ignores_correct_usage() { + assert_lint_count( + "She took antibiotics last winter.", + TakeMedicine::default(), + 0, + ); + } + + #[test] + fn ignores_unrelated_eat() { + assert_lint_count( + "They ate dinner after taking medicine.", + TakeMedicine::default(), + 0, + ); + } +} 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 2c3ca071..5b45782a 100644 --- a/harper-core/tests/text/linters/The Great Gatsby.snap.yml +++ b/harper-core/tests/text/linters/The Great Gatsby.snap.yml @@ -4910,7 +4910,7 @@ Suggest: -Lint: WordChoice (54 priority) +Lint: Punctuation (54 priority) Message: | 3495 | It was when curiosity about Gatsby was at its highest that the lights in his | ^~~ Use `it's` (short for `it has` or `it is`) here, not the possessive `its`. From 483c30611094e4d916e93303a9d9c592c1d1df3e Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Wed, 3 Dec 2025 15:15:01 -0700 Subject: [PATCH 25/84] fix(web): problems with the title casing page (#2293) --- .../web/src/routes/titlecase/+page.svelte | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/packages/web/src/routes/titlecase/+page.svelte b/packages/web/src/routes/titlecase/+page.svelte index 008063ea..e3d1d433 100644 --- a/packages/web/src/routes/titlecase/+page.svelte +++ b/packages/web/src/routes/titlecase/+page.svelte @@ -1,21 +1,24 @@ -

Title Case Converter

@@ -56,7 +71,7 @@ onMount(() => {