mirror of
https://github.com/Automattic/harper.git
synced 2025-12-23 08:48:15 +00:00
Merge branch 'master' into pronoun-verb-agreement-233
This commit is contained in:
commit
9711f9089a
12 changed files with 165 additions and 34 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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`.",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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-
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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` | ✅ |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue