Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
db73857
feat(harper.js): export both `binary` and `inlinedBinary` for differe…
Asuka109 Feb 6, 2025
e866b4a
feat: modify all the use cases
Asuka109 Feb 6, 2025
8a0297f
feat: remove useless imports
Asuka109 Feb 6, 2025
efb38ed
Merge branch 'master' into feat/harper-binary-formats
Asuka109 Feb 10, 2025
06533db
build(harper.js): update typescript version
Asuka109 Feb 10, 2025
60d13be
Merge branch 'master' into feat/harper-binary-formats
Asuka109 Feb 12, 2025
c7ce236
feat(harper.js): use `BinaryModule` to load and manage wasm APIs
Asuka109 Feb 13, 2025
fde9f2e
feat(harper.js): optimize bundler configuration and fix worker shims
Asuka109 Feb 13, 2025
0043887
chore(harper.js): remove logs
Asuka109 Feb 13, 2025
4d9904e
feat(harper.js): export `SuggestionKind` as a enum
Asuka109 Feb 13, 2025
a0412bf
style: eslint issues
Asuka109 Feb 13, 2025
e23c4e8
Merge branch 'master' into feat/harper-binary-formats
Asuka109 Feb 15, 2025
48aca8c
feat(harper.js): rename `invariant` to `assert`
Asuka109 Feb 15, 2025
6e9fdc1
test: debug firefox timeout cases
Asuka109 Feb 15, 2025
ab97727
Revert "test: debug firefox timeout cases"
Asuka109 Feb 15, 2025
83de3e4
build: remove --browser flag
Asuka109 Feb 15, 2025
41d5aea
test: retry on ci
Asuka109 Feb 15, 2025
faf87d7
chore: format code
Asuka109 Feb 15, 2025
0228688
Merge branch 'master' into feat/harper-binary-formats
Asuka109 Mar 8, 2025
a074007
feat(harper.js): use fs.readFile at Node.js runtime to avoid fetch is…
Asuka109 Mar 9, 2025
c99168d
feat(harper.js): add lazy binary loading
Asuka109 Mar 10, 2025
8e9f3ad
Merge branch 'master' into feat/harper-binary-formats
Asuka109 Mar 10, 2025
b3ec558
fix: ran `just format`
elijah-potter Mar 10, 2025
31f8bcc
Merge branch 'master' into feat/harper-binary-formats
Asuka109 Mar 11, 2025
f545f62
Merge branch 'master' into feat/harper-binary-formats
Asuka109 Mar 12, 2025
c794d39
fix(wp): construct linters properly
elijah-potter Mar 13, 2025
ccc7145
docs(harper.js): updated grammar
elijah-potter Mar 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "packages/harper.js/node_modules/typescript/lib"
}
5 changes: 4 additions & 1 deletion packages/harper.js/api-extractor.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
"dtsRollup": {
"enabled": false
},
"bundledPackages": ["wasm"],
"bundledPackages": ["harper-wasm"],
"messages": {
"extractorMessageReporting": {
"ae-missing-release-tag": {
"logLevel": "none"
},
"ae-forgotten-export": {
"logLevel": "none"
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/harper.js/examples/commonjs-simple/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
async function main() {
const harper = await import('harper.js');
// We cannot use `WorkerLinter` on Node.js since it relies on web-specific APIs.
let linter = new harper.LocalLinter();
const linter = new harper.LocalLinter({ binary: harper.binary });

let lints = await linter.lint('This is a example of how to use `harper.js`.');
const lints = await linter.lint('This is a example of how to use `harper.js`.');

console.log('Here are the results of linting the above text:');

Expand Down
2 changes: 1 addition & 1 deletion packages/harper.js/examples/commonjs-simple/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"start": "node index.js"
},
"dependencies": {
"harper.js": "^0.14.0"
"harper.js": "*"
}
}
8 changes: 0 additions & 8 deletions packages/harper.js/examples/commonjs-simple/yarn.lock

This file was deleted.

1 change: 1 addition & 0 deletions packages/harper.js/examples/raw-web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="utf-8" />
<script type="module">
// We can import `harper.js` using native ECMAScript syntax.
// TODO: Update to the latest version.
import { WorkerLinter } from 'https://unpkg.com/harper.js@0.13.0/dist/harper.js';

// Since we are working in the browser, we can use either `WorkerLinter`, which doesn't block the event loop, or `LocalLinter`, which does.
Expand Down
16 changes: 13 additions & 3 deletions packages/harper.js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"test": "vitest run --browser chromium"
"test": "vitest run",
"test:debug": "vitest run --browser.headless false --testTimeout 0",
"api:extractor": "api-extractor run",
"api:documenter": "api-documenter markdown -i temp"
},
"devDependencies": {
"@microsoft/api-documenter": "^7.26.10",
Expand All @@ -29,8 +32,15 @@
"vite-plugin-dts": "^4.5.0",
"vite-plugin-virtual": "^0.3.0",
"vitest": "^3.0.5",
"wasm": "link:../../harper-wasm/pkg"
"harper-wasm": "link:../../harper-wasm/pkg",
"type-fest": "^4.37.0",
"p-memoize": "^7.1.1",
"p-lazy": "^5.0.0"
},
"main": "dist/harper.js",
"types": "dist/harper.d.ts"
"types": "dist/harper.d.ts",
"sideEffects": false,
"files": [
"dist"
]
}
3 changes: 2 additions & 1 deletion packages/harper.js/src/Linter.bench.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { bench } from 'vitest';
import LocalLinter from './LocalLinter';
import WorkerLinter from './WorkerLinter';
import { binary } from './binary';

const linters = {
WorkerLinter: WorkerLinter,
LocalLinter: LocalLinter
};

for (const [linterName, Linter] of Object.entries(linters)) {
const linter = new Linter();
const linter = new Linter({ binary });

// Prime caches
linter.setup();
Expand Down
57 changes: 35 additions & 22 deletions packages/harper.js/src/Linter.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect, test } from 'vitest';
import WorkerLinter from './WorkerLinter';
import LocalLinter from './LocalLinter';
import { binary } from './binary';

const linters = {
WorkerLinter: WorkerLinter,
Expand All @@ -9,27 +10,23 @@ const linters = {

for (const [linterName, Linter] of Object.entries(linters)) {
test(`${linterName} detects repeated words`, async () => {
const linter = new Linter();
const linter = new Linter({ binary });

const lints = await linter.lint('The the problem is...');

expect(lints.length).toBe(1);
});

test(`${linterName} detects repeated words with multiple synchronous requests`, async () => {
const linter = new Linter();
const linter = new Linter({ binary });

const promises = [
linter.lint('The problem is that that...'),
linter.lint('The problem is...'),
linter.lint('The the problem is...')
];

const results = [];

for (const promise of promises) {
results.push(await promise);
}
const results = await Promise.all(promises);

expect(results[0].length).toBe(1);
expect(results[0][0].suggestions().length).toBe(1);
Expand All @@ -38,7 +35,7 @@ for (const [linterName, Linter] of Object.entries(linters)) {
});

test(`${linterName} detects repeated words with concurrent requests`, async () => {
const linter = new Linter();
const linter = new Linter({ binary });

const promises = [
linter.lint('The problem is that that...'),
Expand All @@ -55,7 +52,7 @@ for (const [linterName, Linter] of Object.entries(linters)) {
});

test(`${linterName} detects lorem ipsum paragraph as not english`, async () => {
const linter = new Linter();
const linter = new Linter({ binary });

const result = await linter.isLikelyEnglish(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
Expand All @@ -66,20 +63,20 @@ for (const [linterName, Linter] of Object.entries(linters)) {
});

test(`${linterName} can run setup without issues`, async () => {
const linter = new Linter();
const linter = new Linter({ binary });

await linter.setup();
});

test(`${linterName} contains configuration option for repetition`, async () => {
const linter = new Linter();
const linter = new Linter({ binary });

const lintConfig = await linter.getLintConfig();
expect(lintConfig).toHaveProperty('RepeatedWords');
});

test(`${linterName} can both get and set its configuration`, async () => {
const linter = new Linter();
const linter = new Linter({ binary });

let lintConfig = await linter.getLintConfig();

Expand All @@ -96,23 +93,23 @@ for (const [linterName, Linter] of Object.entries(linters)) {
});

test(`${linterName} can make things title case`, async () => {
const linter = new Linter();
const linter = new Linter({ binary });

const titleCase = await linter.toTitleCase('this is a test for making titles');

expect(titleCase).toBe('This Is a Test for Making Titles');
});

test(`${linterName} can get rule descriptions`, async () => {
const linter = new Linter();
const linter = new Linter({ binary });

const descriptions = await linter.getLintDescriptions();

expect(descriptions).toBeTypeOf('object');
});

test(`${linterName} rule descriptions are not empty`, async () => {
const linter = new Linter();
const linter = new Linter({ binary });

const descriptions = await linter.getLintDescriptions();

Expand All @@ -123,7 +120,7 @@ for (const [linterName, Linter] of Object.entries(linters)) {
});

test(`${linterName} default lint config has no null values`, async () => {
const linter = new Linter();
const linter = new Linter({ binary });

const lintConfig = await linter.getDefaultLintConfig();

Expand All @@ -133,7 +130,7 @@ for (const [linterName, Linter] of Object.entries(linters)) {
});

test(`${linterName} can ignore lints`, async () => {
const linter = new Linter();
const linter = new Linter({ binary });
const source = 'This is an test.';

const firstRound = await linter.lint(source);
Expand All @@ -150,7 +147,7 @@ for (const [linterName, Linter] of Object.entries(linters)) {
test(`${linterName} can reimport ignored lints.`, async () => {
const source = 'This is an test of xporting lints.';

const firstLinter = new Linter();
const firstLinter = new Linter({ binary });

const firstLints = await firstLinter.lint(source);

Expand All @@ -161,7 +158,7 @@ for (const [linterName, Linter] of Object.entries(linters)) {
const exported = await firstLinter.exportIgnoredLints();

/// Create a new instance and reimport the lints.
const secondLinter = new Linter();
const secondLinter = new Linter({ binary });
await secondLinter.importIgnoredLints(exported);

const secondLints = await secondLinter.lint(source);
Expand All @@ -173,7 +170,7 @@ for (const [linterName, Linter] of Object.entries(linters)) {
test(`${linterName} can add words to the dictionary`, async () => {
const source = 'asdf is not a word';

const linter = new Linter();
const linter = new Linter({ binary });
let lints = await linter.lint(source);

expect(lints).toHaveLength(1);
Expand All @@ -185,7 +182,7 @@ for (const [linterName, Linter] of Object.entries(linters)) {
});

test(`${linterName} allows correct capitalization of "United States"`, async () => {
const linter = new Linter();
const linter = new Linter({ binary });
const lints = await linter.lint('The United States is a big country.');

expect(lints).toHaveLength(0);
Expand All @@ -196,12 +193,28 @@ test('Linters have the same config format', async () => {
const configs = [];

for (const Linter of Object.values(linters)) {
const linter = new Linter();
const linter = new Linter({ binary });

configs.push(await linter.getLintConfig());
}

for (const config of configs) {
expect(config).toEqual(configs[0]);
expect(config).toBeTypeOf('object');
}
});

test('Linters have the same JSON config format', async () => {
const configs = [];

for (const Linter of Object.values(linters)) {
const linter = new Linter({ binary });

configs.push(await linter.getLintConfig());
}

for (const config of configs) {
// The keys of stringified configs would be unstable, so we'll just check the object.
expect(config).toEqual(configs[0]);
expect(config).toBeTypeOf('object');
}
Expand Down
8 changes: 7 additions & 1 deletion packages/harper.js/src/Linter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Lint, Span, Suggestion } from 'wasm';
import type { Lint, Span, Suggestion } from 'harper-wasm';
import { LintConfig, LintOptions } from './main';
import { BinaryModule } from './binary';

/** An interface for an object that can perform linting actions. */
export default interface Linter {
Expand Down Expand Up @@ -71,3 +72,8 @@ export default interface Linter {
* only words from previous calls to `this.importWords`. */
exportWords(): Promise<string[]>;
}

export interface LinterInit {
/** The module or path to the WebAssembly binary. */
binary: BinaryModule;
}
Loading