feat(harper.js): now logs lint application statistics

This commit is contained in:
Elijah Potter 2025-03-27 08:39:46 -06:00
parent 88b0aea00a
commit 4832a328ee
11 changed files with 44 additions and 40 deletions

4
Cargo.lock generated
View file

@ -662,8 +662,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
@ -849,6 +851,7 @@ version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"harper-core",
"harper-stats",
"once_cell",
"serde",
"serde-wasm-bindgen",
@ -2405,6 +2408,7 @@ checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4"
dependencies = [
"getrandom",
"serde",
"wasm-bindgen",
]
[[package]]

View file

@ -12,3 +12,7 @@ serde_json = "1.0.140"
[dev-dependencies]
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
[features]
default = []
js = ["uuid/js"]

View file

@ -61,8 +61,6 @@ impl Stats {
for line_res in br.lines() {
let line = line_res?;
dbg!(&line);
let record: Record = serde_json::from_str(&line)?;
records.push(record);
}

View file

@ -19,3 +19,4 @@ once_cell = "1.20.3"
serde-wasm-bindgen = "0.6.5"
serde_json = "1.0.140"
serde = { version = "1.0.219", features = ["derive"] }
harper-stats = { path = "../harper-stats", version = "0.25.0", features = ["js"] }

View file

@ -10,6 +10,7 @@ use harper_core::{
CharString, Dictionary, Document, FstDictionary, IgnoredLints, Lrc, MergedDictionary,
MutableDictionary, WordMetadata, remove_overlaps,
};
use harper_stats::{Record, RecordKind, Stats};
use serde::{Deserialize, Serialize};
use wasm_bindgen::JsValue;
use wasm_bindgen::prelude::wasm_bindgen;
@ -92,6 +93,7 @@ pub struct Linter {
dictionary: Arc<MergedDictionary>,
ignored_lints: IgnoredLints,
dialect: Dialect,
stats: Stats,
}
#[wasm_bindgen]
@ -109,6 +111,7 @@ impl Linter {
dictionary,
ignored_lints: IgnoredLints::default(),
dialect,
stats: Stats::default(),
}
}
@ -274,6 +277,24 @@ impl Linter {
pub fn get_dialect(&self) -> Dialect {
self.dialect
}
/// Apply a suggestion from a given lint.
/// This action will be logged to the Linter's statistics.
pub fn apply_suggestion(
&mut self,
lint: &Lint,
suggestion: &Suggestion,
) -> Result<String, String> {
let mut source = lint.source.clone();
self.stats.records.push(
Record::now(RecordKind::Lint(lint.inner.lint_kind)).map_err(|err| err.to_string())?,
);
suggestion.inner.apply(lint.inner.span, &mut source);
Ok(source.iter().collect())
}
}
#[wasm_bindgen]
@ -281,20 +302,6 @@ pub fn to_title_case(text: String) -> String {
harper_core::make_title_case_str(&text, &PlainEnglish, &FstDictionary::curated())
}
#[wasm_bindgen]
pub fn apply_suggestion(
text: String,
span: Span,
suggestion: &Suggestion,
) -> Result<String, String> {
let mut source: Vec<_> = text.chars().collect();
let span: harper_core::Span = span.into();
suggestion.inner.apply(span, &mut source);
Ok(source.iter().collect())
}
/// A suggestion to fix a Lint.
#[derive(Debug, Serialize, Deserialize)]
#[wasm_bindgen]

View file

@ -1,4 +1,4 @@
import type { Dialect, Lint, Span, Suggestion } from 'harper-wasm';
import type { Dialect, Lint, Suggestion } from 'harper-wasm';
import type { BinaryModule } from './binary';
import type { LintConfig, LintOptions } from './main';
@ -12,8 +12,8 @@ export default interface Linter {
/** Lint the provided text. */
lint(text: string, options?: LintOptions): Promise<Lint[]>;
/** Apply a suggestion to the given text, returning the transformed result. */
applySuggestion(text: string, suggestion: Suggestion, span: Span): Promise<string>;
/** Apply a suggestion from a lint, returning the changed text. */
applySuggestion(lint: Lint, suggestion: Suggestion): Promise<string>;
/** Determine if the provided text is likely to be intended to be English.
* The algorithm can be described as "proof of concept" and as such does not work terribly well.*/

View file

@ -1,4 +1,4 @@
import type { Dialect, Lint, Span, Suggestion, Linter as WasmLinter } from 'harper-wasm';
import type { Dialect, Lint, Suggestion, Linter as WasmLinter } from 'harper-wasm';
import { Language } from 'harper-wasm';
import LazyPromise from 'p-lazy';
import type Linter from './Linter';
@ -38,8 +38,9 @@ export default class LocalLinter implements Linter {
return lints;
}
async applySuggestion(text: string, suggestion: Suggestion, span: Span): Promise<string> {
return await this.binary.applySuggestion(text, suggestion, span);
async applySuggestion(lint: Lint, suggestion: Suggestion): Promise<string> {
const inner = await this.inner;
return inner.apply_suggestion(lint, suggestion);
}
async isLikelyEnglish(text: string): Promise<boolean> {

View file

@ -1,4 +1,4 @@
import type { Dialect, Lint, Span, Suggestion } from 'harper-wasm';
import type { Dialect, Lint, Suggestion } from 'harper-wasm';
import type Linter from '../Linter';
import type { LinterInit } from '../Linter';
import type { BinaryModule, DeserializedRequest } from '../binary';
@ -69,8 +69,8 @@ export default class WorkerLinter implements Linter {
return this.rpc('lint', [text, options]);
}
applySuggestion(text: string, suggestion: Suggestion, span: Span): Promise<string> {
return this.rpc('applySuggestion', [text, suggestion, span]);
applySuggestion(lint: Lint, suggestion: Suggestion): Promise<string> {
return this.rpc('applySuggestion', [lint, suggestion]);
}
isLikelyEnglish(text: string): Promise<boolean> {

View file

@ -1,10 +1,4 @@
import {
Dialect,
type InitInput,
type Span,
type Suggestion,
type Linter as WasmLinter,
} from 'harper-wasm';
import { Dialect, type InitInput, type Linter as WasmLinter } from 'harper-wasm';
import { default as binaryInlinedUrl } from 'harper-wasm/harper_wasm_bg.wasm?inline';
import { default as binaryUrl } from 'harper-wasm/harper_wasm_bg.wasm?no-inline';
import LazyPromise from 'p-lazy';
@ -82,11 +76,6 @@ export class BinaryModule {
);
}
async applySuggestion(text: string, suggestion: Suggestion, span: Span): Promise<string> {
const exported = await this.inner;
return exported.apply_suggestion(text, span, suggestion);
}
async getDefaultLintConfigAsJSON(): Promise<string> {
const exported = await this.inner;
return exported.get_default_lint_config_as_json();

View file

@ -106,7 +106,7 @@ $: superSmall = (w ?? 1024) < 550;
style="height: 40px; margin: 5px 0px;"
on:click={() =>
linter
.applySuggestion(content, suggestion, lint.span())
.applySuggestion(lint, suggestion)
.then((edited) => (content = edited))}
>
{#if suggestion.kind() == SuggestionKind.Remove}
@ -142,7 +142,7 @@ $: superSmall = (w ?? 1024) < 550;
on:click={() =>
focused != null &&
linter
.applySuggestion(content, suggestion, lints[focused].span())
.applySuggestion(lints[focused], suggestion)
.then((edited) => (content = edited))}
>
{#if suggestion.kind() == SuggestionKind.Remove}

View file

@ -54,7 +54,7 @@ export default class RichText {
height: targetRect.height,
lint,
applySuggestion: async (sug: Suggestion) => {
const fixed = await linter.applySuggestion(text, sug, span);
const fixed = await linter.applySuggestion(lint, sug);
this.editContent(fixed);
},