harper.js 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/harper.d.ts +9 -1
- package/dist/harper.js +70 -35
- package/examples/commonjs-simple/README.md +11 -0
- package/examples/commonjs-simple/index.cjs +28 -0
- package/examples/commonjs-simple/package.json +11 -0
- package/examples/commonjs-simple/yarn.lock +14 -0
- package/examples/raw-web/README.md +4 -0
- package/examples/raw-web/index.html +55 -0
- package/package.json +15 -5
- package/src/Linter.test.ts +19 -0
- package/src/Linter.ts +11 -1
- package/src/LocalLinter.ts +10 -0
- package/src/WorkerLinter/index.ts +8 -0
- package/vite.config.js +1 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
let harper = require('harper.js');
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
// We cannot use `WorkerLinter` on Node.js since it relies on web-specific APIs.
|
|
5
|
+
let linter = new harper.LocalLinter();
|
|
6
|
+
|
|
7
|
+
let lints = await linter.lint('This is a example of how to use `harper.js`.');
|
|
8
|
+
|
|
9
|
+
console.log('Here are the results of linting the above text:');
|
|
10
|
+
|
|
11
|
+
for (let lint of lints) {
|
|
12
|
+
console.log(' - ', lint.span().start, ':', lint.span().end, lint.message());
|
|
13
|
+
|
|
14
|
+
if (lint.suggestion_count() != 0) {
|
|
15
|
+
console.log('Suggestions:');
|
|
16
|
+
|
|
17
|
+
for (let sug of lint.suggestions()) {
|
|
18
|
+
console.log(
|
|
19
|
+
'\t - ',
|
|
20
|
+
sug.kind() == 1 ? 'Remove' : 'Replace with',
|
|
21
|
+
sug.get_replacement_text()
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
main();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
2
|
+
# yarn lockfile v1
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
harper.js@^0.13.0:
|
|
6
|
+
version "0.13.0"
|
|
7
|
+
resolved "https://registry.yarnpkg.com/harper.js/-/harper.js-0.13.0.tgz#f9e945c842f4e07e1b7042e3eef00a8040b2e0dd"
|
|
8
|
+
integrity sha512-XjiQG7kpooKCU2xp46mvNra9PUq5n3z5kXOaWvKjOBRyWIllvuEwvDv40cEUODhWD2ABVTEMea8odXRORfyruw==
|
|
9
|
+
dependencies:
|
|
10
|
+
wasm "link:../../../../../../.cache/yarn/v6/npm-harper-js-0.13.0-f9e945c842f4e07e1b7042e3eef00a8040b2e0dd-integrity/harper-wasm/pkg"
|
|
11
|
+
|
|
12
|
+
"wasm@link:../../../../../../.cache/yarn/v6/npm-harper-js-0.13.0-f9e945c842f4e07e1b7042e3eef00a8040b2e0dd-integrity/harper-wasm/pkg":
|
|
13
|
+
version "0.0.0"
|
|
14
|
+
uid ""
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<script type="module">
|
|
6
|
+
// We can import `harper.js` using native ECMAScript syntax.
|
|
7
|
+
import { WorkerLinter } from 'https://unpkg.com/harper.js@0.13.0/dist/harper.js';
|
|
8
|
+
|
|
9
|
+
// Since we are working in the browser, we can use either `WorkerLinter`, which doesn't block the event loop, or `LocalLinter`, which does.
|
|
10
|
+
let linter = new WorkerLinter();
|
|
11
|
+
|
|
12
|
+
// Every time the `<textarea/>` received an input, we process it and update our list.
|
|
13
|
+
async function onInput(e) {
|
|
14
|
+
let lints = await linter.lint(e.target.value);
|
|
15
|
+
|
|
16
|
+
let list = document.getElementById('errorlist');
|
|
17
|
+
// Clear previous results
|
|
18
|
+
list.innerHTML = '';
|
|
19
|
+
|
|
20
|
+
for (let lint of lints) {
|
|
21
|
+
let item = document.createElement('LI');
|
|
22
|
+
var text = document.createTextNode(lint.message());
|
|
23
|
+
item.appendChild(text);
|
|
24
|
+
list.appendChild(item);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let inputField = document.getElementById('maininput');
|
|
29
|
+
inputField.addEventListener('input', onInput);
|
|
30
|
+
onInput({ target: inputField });
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<!--Make the page look good using SimpleCSS-->
|
|
34
|
+
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css" />
|
|
35
|
+
</head>
|
|
36
|
+
|
|
37
|
+
<body>
|
|
38
|
+
<h1>Demo</h1>
|
|
39
|
+
|
|
40
|
+
<p>
|
|
41
|
+
This page is a simple example of using <code>harper.js</code> on a plain HTML page with a CDN.
|
|
42
|
+
It isn't pretty, but it demonstrates the fundamentals of using Harper. Start typing in the
|
|
43
|
+
text box below to start getting suggestions right in your browser.
|
|
44
|
+
</p>
|
|
45
|
+
|
|
46
|
+
<!--This is an intentional mistake to highlight the technology.-->
|
|
47
|
+
<textarea id="maininput">This is an test</textarea>
|
|
48
|
+
|
|
49
|
+
<h2>Errors</h2>
|
|
50
|
+
|
|
51
|
+
<ul id="errorlist">
|
|
52
|
+
Loading...
|
|
53
|
+
</ul>
|
|
54
|
+
</body>
|
|
55
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harper.js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"author": "Elijah Potter",
|
|
6
|
+
"description": "The grammar checker for developers.",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/automattic/harper.git",
|
|
10
|
+
"directory": "packages/harper.js"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/automattic/harper/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://writewithharper.com",
|
|
4
16
|
"type": "module",
|
|
5
17
|
"scripts": {
|
|
6
18
|
"dev": "vite",
|
|
7
19
|
"build": "tsc && vite build",
|
|
8
|
-
"test": "vitest run"
|
|
9
|
-
},
|
|
10
|
-
"dependencies": {
|
|
11
|
-
"wasm": "link:../../harper-wasm/pkg"
|
|
20
|
+
"test": "vitest run --browser firefox && vitest run --browser chromium"
|
|
12
21
|
},
|
|
13
22
|
"devDependencies": {
|
|
23
|
+
"wasm": "link:../../harper-wasm/pkg",
|
|
14
24
|
"@vitest/browser": "^2.1.8",
|
|
15
25
|
"playwright": "^1.49.1",
|
|
16
26
|
"typescript": "~5.6.2",
|
package/src/Linter.test.ts
CHANGED
|
@@ -102,6 +102,25 @@ for (const [linterName, Linter] of Object.entries(linters)) {
|
|
|
102
102
|
|
|
103
103
|
expect(titleCase).toBe('This Is a Test for Making Titles');
|
|
104
104
|
});
|
|
105
|
+
|
|
106
|
+
test(`${linterName} can get rule descriptions`, async () => {
|
|
107
|
+
const linter = new Linter();
|
|
108
|
+
|
|
109
|
+
const descriptions = await linter.getLintDescriptions();
|
|
110
|
+
|
|
111
|
+
expect(descriptions).toBeTypeOf('object');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test(`${linterName} rule descriptions are not empty`, async () => {
|
|
115
|
+
const linter = new Linter();
|
|
116
|
+
|
|
117
|
+
const descriptions = await linter.getLintDescriptions();
|
|
118
|
+
|
|
119
|
+
for (const value of Object.values(descriptions)) {
|
|
120
|
+
expect(value).toBeTypeOf('string');
|
|
121
|
+
expect(value).not.toHaveLength(0);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
105
124
|
}
|
|
106
125
|
|
|
107
126
|
test('Linters have the same config format', async () => {
|
package/src/Linter.ts
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import type { Lint, Span, Suggestion } from 'wasm';
|
|
2
2
|
import { LintConfig } from './main';
|
|
3
3
|
|
|
4
|
-
/**
|
|
4
|
+
/** An interface for an object that can perform linting actions. */
|
|
5
5
|
export default interface Linter {
|
|
6
6
|
/** Complete any setup that is necessary before linting. This may include downloading and compiling the WebAssembly binary.
|
|
7
7
|
* This setup will complete when needed regardless of whether you call this function.
|
|
8
8
|
* This function exists to allow you to do this work when it is of least impact to the user experiences (i.e. while you're loading something else). */
|
|
9
9
|
setup(): Promise<void>;
|
|
10
|
+
|
|
10
11
|
/** Lint the provided text. */
|
|
11
12
|
lint(text: string): Promise<Lint[]>;
|
|
13
|
+
|
|
12
14
|
/** Apply a suggestion to the given text, returning the transformed result. */
|
|
13
15
|
applySuggestion(text: string, suggestion: Suggestion, span: Span): Promise<string>;
|
|
16
|
+
|
|
14
17
|
/** Determine if the provided text is likely to be intended to be English.
|
|
15
18
|
* The algorithm can be described as "proof of concept" and as such does not work terribly well.*/
|
|
16
19
|
isLikelyEnglish(text: string): Promise<boolean>;
|
|
20
|
+
|
|
17
21
|
/** Determine which parts of a given string are intended to be English, returning those bits.
|
|
18
22
|
* The algorithm can be described as "proof of concept" and as such does not work terribly well.*/
|
|
19
23
|
isolateEnglish(text: string): Promise<string>;
|
|
@@ -30,6 +34,12 @@ export default interface Linter {
|
|
|
30
34
|
/** Set the linter's current configuration from JSON. */
|
|
31
35
|
setLintConfigWithJSON(config: string): Promise<void>;
|
|
32
36
|
|
|
37
|
+
/** Get the linting rule descriptions as a JSON map. */
|
|
38
|
+
getLintDescriptionsAsJSON(): Promise<string>;
|
|
39
|
+
|
|
40
|
+
/** Get the linting rule descriptions as an object */
|
|
41
|
+
getLintDescriptions(): Promise<Record<string, string>>;
|
|
42
|
+
|
|
33
43
|
/** Convert a string to Chicago-style title case. */
|
|
34
44
|
toTitleCase(text: string): Promise<string>;
|
|
35
45
|
}
|
package/src/LocalLinter.ts
CHANGED
|
@@ -74,4 +74,14 @@ export default class LocalLinter implements Linter {
|
|
|
74
74
|
const wasm = await loadWasm();
|
|
75
75
|
return wasm.to_title_case(text);
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
async getLintDescriptions(): Promise<Record<string, string>> {
|
|
79
|
+
await this.initialize();
|
|
80
|
+
return this.inner!.get_lint_descriptions_as_object();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async getLintDescriptionsAsJSON(): Promise<string> {
|
|
84
|
+
await this.initialize();
|
|
85
|
+
return this.inner!.get_lint_descriptions_as_json();
|
|
86
|
+
}
|
|
77
87
|
}
|
|
@@ -98,6 +98,14 @@ export default class WorkerLinter implements Linter {
|
|
|
98
98
|
return this.rpc('toTitleCase', [text]);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
getLintDescriptionsAsJSON(): Promise<string> {
|
|
102
|
+
return this.rpc('getLintDescriptionsAsJSON', []);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async getLintDescriptions(): Promise<Record<string, string>> {
|
|
106
|
+
return JSON.parse(await this.getLintDescriptionsAsJSON()) as Record<string, string>;
|
|
107
|
+
}
|
|
108
|
+
|
|
101
109
|
/** Run a procedure on the remote worker. */
|
|
102
110
|
private async rpc(procName: string, args: any[]): Promise<any> {
|
|
103
111
|
const promise = new Promise((resolve, reject) => {
|