Merge branch 'master' into pronoun-verb-agreement-233

This commit is contained in:
Andrew Dunbar 2025-12-19 16:08:55 +08:00 committed by GitHub
commit 9711f9089a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 165 additions and 34 deletions

6
Cargo.lock generated
View file

@ -6230,13 +6230,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.18.1"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
dependencies = [
"getrandom 0.3.4",
"js-sys",
"serde",
"serde_core",
"wasm-bindgen",
]

View file

@ -31,6 +31,7 @@ impl CommentParser {
"dart" => harper_tree_sitter_dart::LANGUAGE,
"go" => tree_sitter_go::LANGUAGE,
"haskell" => tree_sitter_haskell::LANGUAGE,
"daml" => tree_sitter_haskell::LANGUAGE,
"java" => tree_sitter_java::LANGUAGE,
"javascript" => tree_sitter_javascript::LANGUAGE,
"javascriptreact" => tree_sitter_typescript::LANGUAGE_TSX,
@ -89,6 +90,7 @@ impl CommentParser {
"dart" => "dart",
"go" => "go",
"hs" => "haskell",
"daml" => "daml",
"java" => "java",
"js" => "javascript",
"jsx" => "javascriptreact",

View file

@ -859,6 +859,13 @@ pub fn lint_group() -> LintGroup {
"Corrects `ontop of` to `on top of`.",
LintKind::BoundaryError
),
"PartsOfSpeech" => (
["part of speeches", "parts of speeches"],
["parts of speech"],
"The correct plural is `parts of speech`.",
"Corrects pluralizing the wrong noun in `part of speech`.",
LintKind::Grammar
),
"PeaceOfMind" => (
["piece of mind"],
["peace of mind"],
@ -874,7 +881,7 @@ pub fn lint_group() -> LintGroup {
LintKind::Spelling
),
"PointsOfView" => (
["point of views"],
["point of views", "points of views"],
["points of view"],
"The correct plural is `points of view`.",
"Corrects pluralizing the wrong noun in `point of view`.",
@ -953,7 +960,7 @@ pub fn lint_group() -> LintGroup {
LintKind::WordChoice
),
"RulesOfThumb" => (
["rule of thumbs", "rule-of-thumbs"],
["rule of thumbs", "rule-of-thumbs", "rules of thumbs"],
["rules of thumb"],
"The correct plural is `rules of thumb`.",
"Corrects pluralizing the wrong noun in `rule of thumb`.",

View file

@ -1473,6 +1473,26 @@ fn correct_on_top_of() {
);
}
// PartOfSpeech
#[test]
fn corrects_part_of_speeches() {
assert_suggestion_result(
"The part of speeches (POS) or as follows:",
lint_group(),
"The parts of speech (POS) or as follows:",
)
}
// It can connect different parts of speeches e.g noun to adjective, adjective to adverb, noun to verb etc.
#[test]
fn corrects_parts_of_speeches() {
assert_suggestion_result(
"It can connect different parts of speeches e.g noun to adjective, adjective to adverb, noun to verb etc.",
lint_group(),
"It can connect different parts of speech e.g noun to adjective, adjective to adverb, noun to verb etc.",
)
}
// PeaceOfMind
#[test]
fn corrects_piece_of_mind() {
@ -1513,7 +1533,7 @@ fn corrects_per_say_hyphenated() {
// PointsOfView
#[test]
fn corrects_points_of_view() {
fn corrects_point_of_views() {
assert_suggestion_result(
"This will produce a huge amount of raw data, representing the region in multiple point of views.",
lint_group(),
@ -1521,6 +1541,16 @@ fn corrects_points_of_view() {
)
}
// log events, places, moods and self-reflect from various points of views
#[test]
fn corrects_points_of_views() {
assert_suggestion_result(
"log events, places, moods and self-reflect from various points of views",
lint_group(),
"log events, places, moods and self-reflect from various points of view",
)
}
// PrayingMantis
// -none-
@ -1578,7 +1608,7 @@ fn correct_iirc_correctly() {
// RulesOfThumb
#[test]
fn correct_rules_of_thumbs() {
fn correct_rule_of_thumbs() {
assert_suggestion_result(
"Thanks. 0.2 is just from my rule of thumbs.",
lint_group(),
@ -1586,6 +1616,15 @@ fn correct_rules_of_thumbs() {
);
}
#[test]
fn correct_rules_of_thumbs() {
assert_suggestion_result(
"But as rules of thumbs, what is said in config file should be respected whatever parameter (field or directory) is passed to php-cs-fixer.phar.",
lint_group(),
"But as rules of thumb, what is said in config file should be respected whatever parameter (field or directory) is passed to php-cs-fixer.phar.",
);
}
#[test]
fn correct_rules_of_thumbs_hyphenated() {
assert_suggestion_result(

View file

@ -289,6 +289,17 @@ pub fn lint_group() -> LintGroup {
"Use `do` instead of `due` when referring to a resource scarcity.",
"Corrects `make due` to `make do` when followed by `with`."
),
"MakeSense" => (
&[
("make senses", "make sense"),
("made senses", "made sense"),
("makes senses", "makes sense"),
("making senses", "making sense")
],
"Use `sense` instead of `senses`.",
"Corrects `make senses` to `make sense`.",
LintKind::Usage
),
"MootPoint" => (
&[
("mute point", "moot point"),

View file

@ -782,6 +782,44 @@ fn corrects_making_due_with() {
);
}
// MakeSense
#[test]
fn fix_make_senses() {
assert_suggestion_result(
"some symbols make senses only if you have a certain keyboard",
lint_group(),
"some symbols make sense only if you have a certain keyboard",
);
}
#[test]
fn fix_made_senses() {
assert_suggestion_result(
"Usually on the examples of matlab central I have found all with positive magnitude and made senses to me.",
lint_group(),
"Usually on the examples of matlab central I have found all with positive magnitude and made sense to me.",
);
}
#[test]
fn fix_makes_senses() {
assert_suggestion_result(
"If it makes senses I can open a PR.",
lint_group(),
"If it makes sense I can open a PR.",
);
}
#[test]
fn fix_making_senses() {
assert_suggestion_result(
"I appreciate you mentioned the two use cases, which are making senses for both.",
lint_group(),
"I appreciate you mentioned the two use cases, which are making sense for both.",
);
}
// MootPoint
// -point is mute-

View file

@ -1,5 +1,5 @@
use crate::expr::{DurationExpr, Expr, LongestMatchOf, SequenceExpr};
use crate::{Lrc, Token, TokenStringExt};
use crate::expr::{DurationExpr, Expr, SequenceExpr};
use crate::{CharStringExt, Token, TokenStringExt};
use super::{ExprLinter, Lint, LintKind, Suggestion};
use crate::linting::expr_linter::Chunk;
@ -25,23 +25,18 @@ pub struct SinceDuration {
impl Default for SinceDuration {
fn default() -> Self {
let pattern_without_ago = Lrc::new(
SequenceExpr::default()
.then_any_capitalization_of("since")
.then_whitespace()
.then(DurationExpr),
);
let pattern_with_ago = SequenceExpr::default()
.then(pattern_without_ago.clone())
.then_whitespace()
.then_any_capitalization_of("ago");
Self {
expr: Box::new(LongestMatchOf::new(vec![
Box::new(pattern_without_ago),
Box::new(pattern_with_ago),
])),
expr: Box::new(
SequenceExpr::default()
.then_any_capitalization_of("since")
.then_whitespace()
.then(DurationExpr)
.then_optional(
SequenceExpr::default()
.t_ws()
.then_word_set(&["ago", "old"]),
),
),
}
}
}
@ -55,7 +50,11 @@ impl ExprLinter for SinceDuration {
fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
let last = toks.last()?;
if last.span.get_content_string(src).to_lowercase() == "ago" {
if last
.span
.get_content(src)
.eq_any_ignore_ascii_case_chars(&[&['a', 'g', 'o'], &['o', 'l', 'd']])
{
return None;
}
@ -94,7 +93,9 @@ impl ExprLinter for SinceDuration {
#[cfg(test)]
mod tests {
use super::SinceDuration;
use crate::linting::tests::{assert_lint_count, assert_top3_suggestion_result};
use crate::linting::tests::{
assert_lint_count, assert_no_lints, assert_top3_suggestion_result,
};
#[test]
fn catches_spelled() {
@ -107,10 +108,9 @@ mod tests {
#[test]
fn permits_spelled_with_ago() {
assert_lint_count(
assert_no_lints(
"I have been waiting since two hours ago.",
SinceDuration::default(),
0,
);
}
@ -125,10 +125,9 @@ mod tests {
#[test]
fn permits_numerals_with_ago() {
assert_lint_count(
assert_no_lints(
"I have been waiting since 2 hours ago.",
SinceDuration::default(),
0,
);
}
@ -287,4 +286,12 @@ mod tests {
"I use a Wacom Cintiq 27QHDT for several years on Linux",
);
}
#[test]
fn ignore_since_years_old() {
assert_no_lints(
"I've been coding since 11 years old and I'm now 57",
SinceDuration::default(),
);
}
}

View file

@ -10,7 +10,7 @@ repository = "https://github.com/automattic/harper"
[dependencies]
serde = { version = "1.0.228", features = ["derive"] }
harper-core = { path = "../harper-core", version = "1.0.0", features = ["concurrent"] }
uuid = { version = "1.18.1", features = ["serde", "v4"] }
uuid = { version = "1.19.0", features = ["serde", "v4"] }
serde_json = "1.0.145"
chrono = "0.4.42"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before After
Before After

View file

@ -100,7 +100,8 @@ function scan() {
if (
element.matches('[role="combobox"]') ||
element.getAttribute('data-enable-grammarly') === 'false' ||
element.getAttribute('spellcheck') === 'false'
(element.getAttribute('spellcheck') === 'false' &&
element.getAttribute('data-language') !== 'markdown')
) {
return;
}

View file

@ -1,6 +1,7 @@
<script lang="ts">
import { faArrowLeft } from '@fortawesome/free-solid-svg-icons';
import { Button, Link } from 'components';
import { onMount } from 'svelte';
import Fa from 'svelte-fa';
import logo from '/logo.png';
import { main, type PopupState } from '../PopupState';
@ -10,6 +11,23 @@ import ReportProblematicLint from './ReportProblematicLint.svelte';
let popupState: PopupState = $state({ page: 'main' });
let version = `v${chrome.runtime.getManifest().version}`;
let latestVersion: string | null = $state(null);
let versionMismatch = $state(false);
onMount(async () => {
try {
const response = await fetch('https://writewithharper.com/latestversion');
if (!response.ok) return;
const fetchedVersion = (await response.text()).trim();
latestVersion = fetchedVersion;
versionMismatch = !!fetchedVersion && fetchedVersion !== version;
} catch (err) {
console.error('Failed to fetch latest version', err);
}
});
$effect(() => {
chrome.storage.local.get({ popupState: { page: 'onboarding' } }).then((result) => {
popupState = result.popupState;
@ -27,7 +45,7 @@ function openSettings() {
<div class="w-[340px] border border-gray-200 font-sans flex flex-col rounded-lg shadow-sm select-none dark:border-slate-800 dark:text-slate-100">
<header class="flex flex-row justify-between items-center gap-2 px-3 py-2 rounded-t-lg">
<div class="flex flex-row justify-start items-center">
<div class="flex flex-row justify-start items-center gap-1">
<img src={logo} alt="Harper logo" class="h-6 w-auto rounded-lg mx-2" />
<span class="font-semibold text-sm">Harper</span>
</div>
@ -36,6 +54,13 @@ function openSettings() {
<Button on:click={() => {
popupState = main();
}}><Fa icon={faArrowLeft}/></Button>
{:else}
<div>
{#if versionMismatch}
<span class="ml-1" title={`Newer version available: ${latestVersion ?? ''}`}>⚠️</span>
{/if}
<span class="text-sm font-mono">{version}</span>
</div>
{/if}
</header>

View file

@ -274,6 +274,7 @@ These configs are under the `markdown` key:
| CMake | `cmake` | ✅ |
| C++ | `cpp` | ✅ |
| C# | `csharp` | ✅ |
| DAML | `daml` | ✅ |
| Dart | `dart` | ✅ |
| Git Commit | `git-commit`/`gitcommit` | |
| Go | `go` | ✅ |