What's Changed
Hey all! We're finally releasing Harper 2.0. Why?
It isn't because we have any new groundbreaking features in this release, because we don't let major versioning stop us from pushing those out as soon as they're ready. It's because we have some breaking changes.
The biggest one only applies to you if you consume harper.js. From now on, instead of importing your binary from the main Harper module, you'll import it from one of four specialized modules.
Previously, you'd import the Harper WebAssembly binary with:
import { LocalLinter, binary } from "harper.js";As of Harper 2.0, you'll import it like this:
import { LocalLinter } from "harper.js";
import { binary } from "harper.js/binary"; It's that simple! Thanks to this change, you'll find that your applications are more conducive to tree-shaking and therefore might even be a bit smaller!
In addition to that large breaking change, we also have a bunch of smaller improvements rolling out. I won't go through them one by one, but you're free to read through any of the linked pull requests below.
- refactor(core): use
Option::get_or_insert_withby @86xsk in #2977 - test(linting): add comprehensive test cases for were/where lint rule by @estefafdez in #2984
- feat: means a lot for→means a lot to by @hippietrail in #2982
- feat: add subcommand to generate shell completions by @deckstose in #2978
- refactor(core): extract indefinite article logic into its own module by @mvanhorn in #2980
- feat(web): auto-detect dark theme from OS preference in #2963
- build(deps): bump tar from 0.4.44 to 0.4.45 by @dependabot[bot] in #2986
- feat: as evident by -> as evidenced by by @mvanhorn in #2985
- fix(core):
SplitWordsallowing uncommon merges by @elijah-potter in #2874 - feat: add lint rule for flaunt/flout mix-up in #2962
- test: add regression tests for need_to_noun false positives (#2320) by @majiayu000 in #3014
- fix: case-insensitive same-form check in SimplePastToPastParticiple by @saschabuehrle in #3012
- build(deps): bump pulldown-cmark from 0.13.1 to 0.13.3 by @dependabot[bot] in #3002
- build(deps): bump tree-sitter-rust from 0.24.0 to 0.24.1 by @dependabot[bot] in #3003
- build(deps): bump zip from 8.1.0 to 8.3.1 by @dependabot[bot] in #3000
- build(deps): bump cached from 0.56.0 to 0.59.0 by @dependabot[bot] in #3001
- chore(harper.js): update
raw-webexample to1.12.0by @elijah-potter in #3015 - feat: "a" & "an" both OK for "URL" by @hippietrail in #2981
- feat: except of→except for / exception of by @hippietrail in #2997
- build(deps): bump rustls-webpki from 0.103.8 to 0.103.10 by @dependabot[bot] in #2991
- feat: aspire for → aspire to by @hippietrail in #3004
- docs: added link to agent policy by @elijah-potter in #3016
- fix(lint-framework): reconnect popup host after DOM replacement by @k4sperski in #2998
- fix(harper-cli): return exit code 0 when no lints are found by @Ryujiyasu in #2836
- chore: dictionary curation 2026-03-19 by @hippietrail in #2968
- Added native Neovim LSP config to docs by @RobertCrummett in #2987
- fix: don't flag "more orange than red" etc. by @hippietrail in #2928
- perf(core): cache
WordSetinModalVerbby @86xsk in #3017 - feat: flag "use to" when "used to" is intended in #2975
- feat: flag nonstandard "verse" used as a verb (from "versus") in #2973
- feat: flag "date back from" as blend of "date from" and "date back to" in #2976
- feat(core): allow users to include dictionaries in Weirpacks by @elijah-potter in #2922
- feat: try out one's luck→try one's luck by @hippietrail in #2916
- fix(chrome-ext): reverse the toggle button to it appears "on" by @elijah-potter in #3026
- chore: dictionary curation by @hippietrail in #3023
- Improve embedded form support in iframes by @k4sperski in #3010
- refactor: compact token/lint content & char comparison by @hippietrail in #2957
- feat: expression to match pronoun-be, word pair or contraction by @hippietrail in #2944
- fix(chrome-ext): persist lint rule changes from settings page by @queelius in #3028
- feat: in 1 day from now→in one day/one day from now by @hippietrail in #3049
- build(deps): bump tree-sitter-ink-lbz from 0.0.1 to 0.0.5 by @dependabot[bot] in #3064
- build(deps): bump ordered-float from 5.1.0 to 5.3.0 by @dependabot[bot] in #3063
- build(deps): bump zip from 8.3.1 to 8.4.0 by @dependabot[bot] in #3065
- feat: support Quarto qmd files as markdown by @itamarst in #3053
- feat: thrive off (of)→thrive on by @hippietrail in #3055
- perf(core): optimizations in
LintGroupby @86xsk in #3052 - feat: worth while/worth-while → worthwhile by @hippietrail in #3056
- refactor(core)!:
Lrc<[char]>instead ofLrc<Vec<char>>by @86xsk in #3060 - chore: dictionary curation by @hippietrail in #3041
- build(deps): bump tree-sitter-rust from 0.24.1 to 0.24.2 by @dependabot[bot] in #3061
- fix(obsidian): OOM error by reducing WASM bundle size by @elijah-potter in #3050
- build(deps): bump tree-sitter-scala from 0.24.0 to 0.25.0 by @dependabot[bot] in #3062
- fix(lint): add 'side' to ignore list for effect/affect rule (fix issue #2958) by @estefafdez in #2983
- perf(spell): skip redundant lowercase DFA search in FstDictionary by @k4sperski in #3025
- refactor!: take argument by value instead of mut ref by @86xsk in #3051
- feat: Sneaked vs snuck by @hippietrail in #2209
- feat: skeleton for new
ExprLintermodules by @hippietrail in #3024 - feat: web scrapping → web scraping by @hippietrail in #3059
- fix: add key stroke→keystroke to key stoke→keystroke by @hippietrail in #3067
- perf(core): avoid conversion between string and char array by @86xsk in #3074
- fix: don't flag "each of whom" by @hippietrail in #3078
- docs(web): add link to @KraXen72's work by @elijah-potter in #3079
- feat: at all cost → at all costs by @hippietrail in #3082
- fix(core): address recent false positives and negatives by @elijah-potter in #3090
- feat: add styling for user-defined monospace fonts by @stanley-910 in #3105
- feat: I am bias/you are prejudice→biased/prejudiced by @hippietrail in #3058
- fix: don't flag "to" before "only" in ToTwoToo by @joaquinhuigomez in #3093
- feat: ppl→people by @hippietrail in #3116
- feat/obsidian web lint styling by @stanley-910 in #3104
- fix(lint-framework): preserve cursor position when dismissing suggestion popup by @draphy in #3112
- build(deps): bump uuid from 1.22.0 to 1.23.0 by @dependabot[bot] in #3117
- fix: skip PronounVerbAgreement for hyphenated compounds by @mvanhorn in #3095
- fix(harper-tex): mask additional LaTeX math environments by @jurajkl in #3103
- feat: look forward for→look forward to by @hippietrail in #3109
- build(deps): bump tempfile from 3.20.0 to 3.27.0 by @dependabot[bot] in #3118
- build(deps): bump indexmap from 2.13.0 to 2.13.1 by @dependabot[bot] in #3121
- build(deps): bump tracing-subscriber from 0.3.22 to 0.3.23 by @dependabot[bot] in #3120
- build(deps): bump zip from 8.4.0 to 8.5.0 by @dependabot[bot] in #3119
- fix(core): exclude prepositions and conjunctions from QuiteQuiet pattern by @draphy in #3127
- feat: arrive to → arrive at/in by @hippietrail in #3125
- feat(core): create
UseEllipsisCharacterrule by @elijah-potter in #3099 - Fix indentation handling in harper-tex by @jurajkl in #3106
- feat: will ran → will run / ran by @hippietrail in #2268
- feat: in the best of times→at the best of times by @hippietrail in #3133
- feat: render configuration structurally by @elijah-potter in #3123
- fix(chrome-ext): issues with Google Docs by @elijah-potter in #3141
New Contributors
- @estefafdez made their first contribution in #2984
- @deckstose made their first contribution in #2978
- @mvanhorn made their first contribution in #2980
- @majiayu000 made their first contribution in #3014
- @saschabuehrle made their first contribution in #3012
- @k4sperski made their first contribution in #2998
- @Ryujiyasu made their first contribution in #2836
- @RobertCrummett made their first contribution in #2987
- @queelius made their first contribution in #3028
- @itamarst made their first contribution in #3053
- @joaquinhuigomez made their first contribution in #3093
- @jurajkl made their first contribution in #3103
Full Changelog: v1.12.0...v2.0.0