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.
@@ -0,0 +1,11 @@
1
+ # `commonjs-simple`
2
+
3
+ An example of using `harper.js` in a Node.js application.
4
+ Read the source code in `index.cjs` to see what's going on.
5
+
6
+ You can run it using Yarn:
7
+
8
+ ```
9
+ yarn install
10
+ yarn start
11
+ ```
@@ -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,11 @@
1
+ {
2
+ "name": "commonjs-simple",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "scripts": {
6
+ "start": "node index.cjs"
7
+ },
8
+ "dependencies": {
9
+ "harper.js": "^0.13.0"
10
+ }
11
+ }
@@ -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,4 @@
1
+ # `raw-web`
2
+
3
+ An example of using `harper.js` in a raw HTML webpage.
4
+ You can open it using [`microserver`](https://crates.io/crates/microserver):
@@ -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.13.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",
@@ -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
- /** A interface for an object that can perform linting actions. */
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
  }
@@ -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) => {
package/vite.config.js CHANGED
@@ -46,7 +46,7 @@ export default defineConfig({
46
46
  browser: {
47
47
  provider: 'playwright',
48
48
  enabled: true,
49
- name: 'chromium'
49
+ headless: true
50
50
  }
51
51
  },
52
52
  assetsInclude: ['**/*.wasm']