diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24ab011..d9c5486 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,12 +7,15 @@ env: jobs: Test: - runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest, windows-2022, macos-latest] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v1 - uses: actions/setup-node@v2 with: - node-version: '14' + node-version: 20 - name: Install dependencies run: npm i - name: Run tests diff --git a/.gitignore b/.gitignore index c24784c..ba71211 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ .DS_Store build/ node_modules/ +.cache +.tool-versions npm-debug.log +compile_commands.json package-lock.json diff --git a/binding.gyp b/binding.gyp index bfb0fa4..334f27f 100644 --- a/binding.gyp +++ b/binding.gyp @@ -18,7 +18,8 @@ ['OS=="win"', { 'msvs_disabled_warnings': [ 4267, # conversion from 'size_t' to 'int', possible loss of data - 4530, # C++ exception handler used, but unwind semantics are not enabled + 4530, # C++ exception handler used, but unwind semantics are not + # enabled 4506, # no definition for inline function ], }], @@ -33,7 +34,19 @@ 'targets': [ { 'target_name': 'spellchecker', - 'include_dirs': [ '=0.4.2" + } }, - "any-promise": { + "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" }, - "async": { + "node_modules/async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true }, - "balanced-match": { + "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "brace-expansion": { + "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "requires": { + "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "coffee-script": { + "node_modules/coffee-script": { "version": "1.12.7", "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", - "dev": true + "deprecated": "CoffeeScript on NPM has moved to \"coffeescript\" (no hyphen)", + "dev": true, + "bin": { + "cake": "bin/cake", + "coffee": "bin/coffee" + }, + "engines": { + "node": ">=0.8.0" + } }, - "coffeestack": { + "node_modules/coffeestack": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/coffeestack/-/coffeestack-1.1.2.tgz", "integrity": "sha1-NSePO+uc5vXQraH7bgh4UrZXzpg=", "dev": true, - "requires": { + "dependencies": { "coffee-script": "~1.8.0", "fs-plus": "^2.5.0", "source-map": "~0.1.43" - }, + } + }, + "node_modules/coffeestack/node_modules/coffee-script": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.8.0.tgz", + "integrity": "sha1-nJ8dK0pSoADe0Vtll5FwNkgmPB0=", + "deprecated": "CoffeeScript on NPM has moved to \"coffeescript\" (no hyphen)", + "dev": true, "dependencies": { - "coffee-script": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.8.0.tgz", - "integrity": "sha1-nJ8dK0pSoADe0Vtll5FwNkgmPB0=", - "dev": true, - "requires": { - "mkdirp": "~0.3.5" - } - } + "mkdirp": "~0.3.5" + }, + "bin": { + "cake": "bin/cake", + "coffee": "bin/coffee" + }, + "engines": { + "node": ">=0.8.0" } }, - "concat-map": { + "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "fileset": { + "node_modules/fileset": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.1.8.tgz", "integrity": "sha1-UGuRqTluqn4y+0KoQHfHoMc2t0E=", "dev": true, - "requires": { + "dependencies": { "glob": "3.x", "minimatch": "0.x" + } + }, + "node_modules/fileset/node_modules/glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "dependencies": { + "inherits": "2", + "minimatch": "0.3" }, + "engines": { + "node": "*" + } + }, + "node_modules/fileset/node_modules/glob/node_modules/minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", + "dev": true, "dependencies": { - "glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", - "dev": true, - "requires": { - "inherits": "2", - "minimatch": "0.3" - }, - "dependencies": { - "minimatch": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", - "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", - "dev": true, - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - } - } - }, - "minimatch": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.4.0.tgz", - "integrity": "sha1-vSx9Bg0sjI/Xzefx8u0tWycP2xs=", - "dev": true, - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - } + "lru-cache": "2", + "sigmund": "~1.0.0" + }, + "engines": { + "node": "*" } }, - "fs-plus": { + "node_modules/fileset/node_modules/minimatch": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.4.0.tgz", + "integrity": "sha1-vSx9Bg0sjI/Xzefx8u0tWycP2xs=", + "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", + "dev": true, + "dependencies": { + "lru-cache": "2", + "sigmund": "~1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fs-plus": { "version": "2.10.1", "resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-2.10.1.tgz", "integrity": "sha1-MgR4HXhAYR5jZOe2+wWMljJ8WqU=", "dev": true, - "requires": { + "dependencies": { "async": "^1.5.2", "mkdirp": "^0.5.1", "rimraf": "^2.5.2", "underscore-plus": "1.x" - }, + } + }, + "node_modules/fs-plus/node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, "dependencies": { - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - } + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" } }, - "fs.realpath": { + "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "gaze": { + "node_modules/gaze": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.3.4.tgz", "integrity": "sha1-X5S92gr+U7xxCWm81vKCVI1gwnk=", "dev": true, - "requires": { + "dependencies": { "fileset": "~0.1.5", "minimatch": "~0.2.9" }, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/gaze/node_modules/minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", + "dev": true, "dependencies": { - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "dev": true, - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - } + "lru-cache": "2", + "sigmund": "~1.0.0" + }, + "engines": { + "node": "*" } }, - "glob": { + "node_modules/glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "requires": { + "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" } }, - "inflight": { + "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "requires": { + "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, - "inherits": { + "node_modules/inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, - "jasmine-focused": { + "node_modules/jasmine-focused": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/jasmine-focused/-/jasmine-focused-1.0.7.tgz", "integrity": "sha1-uDx1fIAOaOHW78GjoaE/85/23NI=", "dev": true, - "requires": { + "dependencies": { "jasmine-node": "git+https://github.com/kevinsawicki/jasmine-node.git#81af4f953a2b7dfb5bde8331c05362a4b464c5ef", "underscore-plus": "1.x", "walkdir": "0.0.7" + }, + "bin": { + "jasmine-focused": "bin/jasmine-focused", + "nof": "bin/nof" } }, - "jasmine-node": { - "version": "git+https://github.com/kevinsawicki/jasmine-node.git#81af4f953a2b7dfb5bde8331c05362a4b464c5ef", - "from": "jasmine-node@git+https://github.com/kevinsawicki/jasmine-node.git#81af4f953a2b7dfb5bde8331c05362a4b464c5ef", + "node_modules/jasmine-node": { + "version": "1.10.2", + "resolved": "git+ssh://git@github.com/kevinsawicki/jasmine-node.git#81af4f953a2b7dfb5bde8331c05362a4b464c5ef", + "integrity": "sha512-V/IgVMSqSgd8oYJBLIqErFhioPT4NuHQqkU0s2MLWHpUoarw90qFHJ1WZ7cQXhtEnqLSAPoFgzD9pVCH/j2aPg==", "dev": true, - "requires": { + "dependencies": { "coffee-script": ">=1.0.1", "coffeestack": ">=1 <2", "gaze": "~0.3.2", @@ -220,146 +275,182 @@ "requirejs": ">=0.27.1", "underscore": ">= 1.3.1", "walkdir": ">= 0.0.1" + }, + "bin": { + "jasmine-node": "bin/jasmine-node" } }, - "jasmine-reporters": { + "node_modules/jasmine-reporters": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-2.3.2.tgz", "integrity": "sha512-u/7AT9SkuZsUfFBLLzbErohTGNsEUCKaQbsVYnLFW1gEuL2DzmBL4n8v90uZsqIqlWvWUgian8J6yOt5Fyk/+A==", "dev": true, - "requires": { + "dependencies": { "mkdirp": "^0.5.1", "xmldom": "^0.1.22" - }, + } + }, + "node_modules/jasmine-reporters/node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, "dependencies": { - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - } + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" } }, - "lru-cache": { + "node_modules/lru-cache": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", "dev": true }, - "minimatch": { + "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, - "requires": { + "dependencies": { "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "minimist": { + "node_modules/minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, - "mkdirp": { + "node_modules/mkdirp": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", "dev": true }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + "node_modules/node-addon-api": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.8.0.tgz", + "integrity": "sha512-c5Ko1fZJIJmzhFIkhRN76WTq+fC6tWnGy9CXA0fA+XygsWZmEwG8vmbkNqxMyoaa0Tin4djul49NzdVcJJcjeA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } }, - "once": { + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "requires": { + "dependencies": { "wrappy": "1" } }, - "path-is-absolute": { + "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "requirejs": { + "node_modules/requirejs": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", - "dev": true + "dev": true, + "bin": { + "r_js": "bin/r.js", + "r.js": "bin/r.js" + }, + "engines": { + "node": ">=0.4.0" + } }, - "rimraf": { + "node_modules/rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, - "requires": { + "dependencies": { "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" } }, - "sigmund": { + "node_modules/sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", "dev": true }, - "source-map": { + "node_modules/source-map": { "version": "0.1.43", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", "dev": true, - "requires": { + "dependencies": { "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" } }, - "underscore": { + "node_modules/underscore": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", "dev": true }, - "underscore-plus": { + "node_modules/underscore-plus": { "version": "1.6.8", "resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.6.8.tgz", "integrity": "sha512-88PrCeMKeAAC1L4xjSiiZ3Fg6kZOYrLpLGVPPeqKq/662DfQe/KTSKdSR/Q/tucKNnfW2MNAUGSCkDf8HmXC5Q==", "dev": true, - "requires": { - "underscore": "~1.8.3" - }, "dependencies": { - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", - "dev": true - } + "underscore": "~1.8.3" } }, - "walkdir": { + "node_modules/underscore-plus/node_modules/underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true + }, + "node_modules/walkdir": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.7.tgz", "integrity": "sha1-BNoCcKh6d4VAFzzb8KLbSZqNnik=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.6.0" + } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "xmldom": { + "node_modules/xmldom": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", - "dev": true + "deprecated": "Deprecated due to CVE-2021-21366 resolved in 0.5.0", + "dev": true, + "engines": { + "node": ">=0.1" + } } } } diff --git a/package.json b/package.json index c04e8c0..fbc1f0b 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,14 @@ }, "homepage": "http://atom.github.io/node-spellchecker", "scripts": { - "test": "jasmine-focused --captureExceptions --coffee spec/" + "test": "jasmine-focused --captureExceptions spec/" }, "devDependencies": { - "jasmine-focused": "^1.0.7" + "jasmine-focused": "^1.0.7", + "node-addon-api": "^8.8.0" }, "dependencies": { - "any-promise": "^1.3.0", - "nan": "^2.14.0" + "node-addon-api": "8.8.0", + "any-promise": "^1.3.0" } } diff --git a/spec/spellchecker-spec.coffee b/spec/spellchecker-spec.coffee deleted file mode 100644 index ff25c8b..0000000 --- a/spec/spellchecker-spec.coffee +++ /dev/null @@ -1,604 +0,0 @@ -{Spellchecker, ALWAYS_USE_HUNSPELL} = require '../lib/spellchecker' -path = require 'path' - -enUS = 'A robot is a mechanical or virtual artificial agent, usually an electronic machine' -deDE = 'Ein Roboter ist eine technische Apparatur, die üblicherweise dazu dient, dem Menschen mechanische Arbeit abzunehmen.' -frFR = 'Les robots les plus évolués sont capables de se déplacer et de se recharger par eux-mêmes' - -defaultLanguage = if process.platform is 'darwin' then '' else 'en_US' -dictionaryDirectory = path.join(__dirname, 'dictionaries') - -# Because we are dealing with C++ and buffers, we want -# to make sure the user doesn't pass in a string that -# causes a buffer overrun. We limit our buffers to -# 256 characters (64-character word in UTF-8). -maximumLength1Byte = 'a'.repeat(256) -maximumLength2Byte = 'ö'.repeat(128) -maximumLength3Byte = 'ऐ'.repeat(85) -maximumLength4Byte = '𐅐'.repeat(64) -invalidLength1Byte = maximumLength1Byte + 'a' -invalidLength2Byte = maximumLength2Byte + 'ö' -invalidLength3Byte = maximumLength3Byte + 'ऐ' -invalidLength4Byte = maximumLength4Byte + '𐄇' - -maximumLength1BytePair = [maximumLength1Byte, maximumLength1Byte].join " " -maximumLength2BytePair = [maximumLength2Byte, maximumLength2Byte].join " " -maximumLength3BytePair = [maximumLength3Byte, maximumLength3Byte].join " " -maximumLength4BytePair = [maximumLength4Byte, maximumLength4Byte].join " " -invalidLength1BytePair = [invalidLength1Byte, invalidLength1Byte].join " " -invalidLength2BytePair = [invalidLength2Byte, invalidLength2Byte].join " " -invalidLength3BytePair = [invalidLength3Byte, invalidLength3Byte].join " " -invalidLength4BytePair = [invalidLength4Byte, invalidLength4Byte].join " " - -spellType = null -spellIndex = null - -for testAlwaysUseHunspell in [true, false] - describe 'SpellChecker', -> - describe '.setDictionary', -> - beforeEach -> - @fixture = buildSpellChecker() - - it 'returns true for en_US', -> - @fixture.setDictionary('en_US', dictionaryDirectory) - - it 'returns true for de_DE_frami', -> - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - @fixture.setDictionary('de_DE_frami', dictionaryDirectory) - - it 'returns true for de_DE', -> - @fixture.setDictionary('en_US', dictionaryDirectory) - - it 'returns true for fr', -> - @fixture.setDictionary('fr', dictionaryDirectory) - - describe '.isMisspelled(word)', -> - beforeEach -> - @fixture = buildSpellChecker() - @fixture.setDictionary defaultLanguage, dictionaryDirectory - - it 'returns true if the word is mispelled', -> - @fixture.setDictionary('en_US', dictionaryDirectory) - expect(@fixture.isMisspelled('wwoorrddd')).toBe true - - it 'returns false if the word is not mispelled: word', -> - @fixture.setDictionary('en_US', dictionaryDirectory) - expect(@fixture.isMisspelled('word')).toBe false - - it 'returns false if the word is not mispelled: cheese', -> - @fixture.setDictionary('en_US', dictionaryDirectory) - expect(@fixture.isMisspelled('cheese')).toBe false - - it 'returns true if Latin German word is misspelled with ISO8859-1 file', -> - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - expect(@fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe true - expect(@fixture.isMisspelled('Kine')).toBe true - - it 'returns true if Latin German word is misspelled with UTF-8 file', -> - expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true - expect(@fixture.isMisspelled('Kine')).toBe true - - it 'returns false if Latin German word is not misspelled with ISO8859-1 file', -> - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - expect(@fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe true - expect(@fixture.isMisspelled('Nacht')).toBe false - - it 'returns false if Latin German word is not misspelled with UTF-8 file', -> - expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true - expect(@fixture.isMisspelled('Nacht')).toBe false - - it 'returns true if Unicode German word is misspelled with ISO8859-1 file', -> - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - expect(@fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe true - expect(@fixture.isMisspelled('möchtzn')).toBe true - - it 'returns true if Unicode German word is misspelled with UTF-8 file', -> - expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true - expect(@fixture.isMisspelled('möchtzn')).toBe true - - it 'returns false if Unicode German word is not misspelled with ISO8859-1 file', -> - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - expect(@fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe true - expect(@fixture.isMisspelled('vermöchten')).toBe false - - it 'returns false if Unicode German word is not misspelled with UTF-8 file', -> - expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true - expect(@fixture.isMisspelled('vermöchten')).toBe false - - it 'throws an exception when no word specified', -> - expect(-> @fixture.isMisspelled()).toThrow() - - it 'returns true for a string of 256 1-byte characters', -> - if process.platform is 'linux' - expect(@fixture.isMisspelled(maximumLength1Byte)).toBe true - - it 'returns true for a string of 128 2-byte characters', -> - if process.platform is 'linux' - expect(@fixture.isMisspelled(maximumLength2Byte)).toBe true - - it 'returns true for a string of 85 3-byte characters', -> - if process.platform is 'linux' - expect(@fixture.isMisspelled(maximumLength3Byte)).toBe true - - it 'returns true for a string of 64 4-byte characters', -> - if process.platform is 'linux' - expect(@fixture.isMisspelled(maximumLength4Byte)).toBe true - - it 'returns false for a string of 257 1-byte characters', -> - if process.platform is 'linux' - expect(@fixture.isMisspelled(invalidLength1Byte)).toBe false - - it 'returns false for a string of 65 2-byte characters', -> - if process.platform is 'linux' - expect(@fixture.isMisspelled(invalidLength2Byte)).toBe false - - it 'returns false for a string of 86 3-byte characters', -> - if process.platform is 'linux' - expect(@fixture.isMisspelled(invalidLength3Byte)).toBe false - - it 'returns false for a string of 65 4-byte characters', -> - if process.platform is 'linux' - expect(@fixture.isMisspelled(invalidLength4Byte)).toBe false - - describe '.checkSpelling(string)', -> - beforeEach -> - @fixture = buildSpellChecker() - @fixture.setDictionary defaultLanguage, dictionaryDirectory - - it 'automatically detects languages on OS X', -> - return unless process.platform is 'darwin' - - expect(@fixture.checkSpelling(enUS)).toEqual [] - expect(@fixture.checkSpelling(deDE)).toEqual [] - expect(@fixture.checkSpelling(frFR)).toEqual [] - - it 'correctly switches languages', -> - expect(@fixture.setDictionary('en_US', dictionaryDirectory)).toBe true - expect(@fixture.checkSpelling(enUS)).toEqual [] - expect(@fixture.checkSpelling(deDE)).not.toEqual [] - expect(@fixture.checkSpelling(frFR)).not.toEqual [] - - # de_DE_frami is invalid outside of Hunspell dictionaries. - if spellType is 'hunspell' - if @fixture.setDictionary('de_DE_frami', dictionaryDirectory) - expect(@fixture.checkSpelling(enUS)).not.toEqual [] - expect(@fixture.checkSpelling(deDE)).toEqual [] - expect(@fixture.checkSpelling(frFR)).not.toEqual [] - - if @fixture.setDictionary('de_DE', dictionaryDirectory) - expect(@fixture.checkSpelling(enUS)).not.toEqual [] - expect(@fixture.checkSpelling(deDE)).toEqual [] - expect(@fixture.checkSpelling(frFR)).not.toEqual [] - - @fixture = buildSpellChecker() - if @fixture.setDictionary('fr_FR', dictionaryDirectory) - expect(@fixture.checkSpelling(enUS)).not.toEqual [] - expect(@fixture.checkSpelling(deDE)).not.toEqual [] - expect(@fixture.checkSpelling(frFR)).toEqual [] - - it 'returns an array of character ranges of misspelled words', -> - string = 'cat caat dog dooog' - - expect(@fixture.checkSpelling(string)).toEqual [ - {start: 4, end: 8}, - {start: 13, end: 18}, - ] - - it 'returns an array of character ranges of misspelled German words with ISO8859-1 file', -> - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - expect(@fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe true - - string = 'Kein Kine vermöchten möchtzn' - - expect(@fixture.checkSpelling(string)).toEqual [ - {start: 5, end: 9}, - {start: 21, end: 28}, - ] - - it 'returns an array of character ranges of misspelled German words with UTF-8 file', -> - expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true - - string = 'Kein Kine vermöchten möchtzn' - - expect(@fixture.checkSpelling(string)).toEqual [ - {start: 5, end: 9}, - {start: 21, end: 28}, - ] - - it 'returns an array of character ranges of misspelled French words', -> - expect(@fixture.setDictionary('fr', dictionaryDirectory)).toBe true - - string = 'Française Françoize' - - expect(@fixture.checkSpelling(string)).toEqual [ - {start: 10, end: 19}, - ] - - it 'accounts for UTF16 pairs', -> - string = '😎 cat caat dog dooog' - - expect(@fixture.checkSpelling(string)).toEqual [ - {start: 7, end: 11}, - {start: 16, end: 21}, - ] - - it "accounts for other non-word characters", -> - string = "'cat' (caat. :dooog)" - expect(@fixture.checkSpelling(string)).toEqual [ - {start: 7, end: 11}, - {start: 20, end: 25}, - ] - - it 'does not treat non-english letters as word boundaries', -> - @fixture.add('cliché') - expect(@fixture.checkSpelling('what cliché nonsense')).toEqual [] - @fixture.remove('cliché') - - it 'handles words with apostrophes', -> - string = "doesn't isn't aint hasn't" - expect(@fixture.checkSpelling(string)).toEqual [ - {start: string.indexOf("aint"), end: string.indexOf("aint") + 4} - ] - - string = "you say you're 'certain', but are you really?" - expect(@fixture.checkSpelling(string)).toEqual [] - - string = "you say you're 'sertan', but are you really?" - expect(@fixture.checkSpelling(string)).toEqual [ - {start: string.indexOf("sertan"), end: string.indexOf("',")} - ] - - it 'handles invalid inputs', -> - fixture = @fixture - expect(fixture.checkSpelling('')).toEqual [] - expect(-> fixture.checkSpelling()).toThrow('Bad argument') - expect(-> fixture.checkSpelling(null)).toThrow('Bad argument') - expect(-> fixture.checkSpelling({})).toThrow('Bad argument') - - it 'returns values for a pair of 256 1-byte character strings', -> - if process.platform is 'linux' - expect(@fixture.checkSpelling(maximumLength1BytePair)).toEqual [ - {start: 0, end: 256}, - {start: 257, end: 513}, - ] - - it 'returns values for a string of 128 2-byte character strings', -> - if process.platform is 'linux' - expect(@fixture.checkSpelling(maximumLength2BytePair)).toEqual [ - {start: 0, end: 128}, - {start: 129, end: 257}, - ] - - it 'returns values for a string of 85 3-byte character strings', -> - if process.platform is 'linux' - expect(@fixture.checkSpelling(maximumLength3BytePair)).toEqual [ - {start: 0, end: 85}, - {start: 86, end: 171}, - ] - - # # Linux doesn't seem to handle 4-byte encodings, so this test is just to - # # comment that fact. - # xit 'returns values for a string of 64 4-byte character strings', -> - # expect(@fixture.checkSpelling(maximumLength4BytePair)).toEqual [ - # {start: 0, end: 128}, - # {start: 129, end: 257}, - # ] - - it 'returns nothing for a pair of 257 1-byte character strings', -> - if process.platform is 'linux' - expect(@fixture.checkSpelling(invalidLength1BytePair)).toEqual [] - - it 'returns nothing for a pair of 129 2-byte character strings', -> - if process.platform is 'linux' - expect(@fixture.checkSpelling(invalidLength2BytePair)).toEqual [] - - it 'returns nothing for a pair of 86 3-byte character strings', -> - if process.platform is 'linux' - expect(@fixture.checkSpelling(invalidLength3BytePair)).toEqual [] - - it 'returns nothing for a pair of 65 4-byte character strings', -> - if process.platform is 'linux' - expect(@fixture.checkSpelling(invalidLength4BytePair)).toEqual [] - - it 'returns values for a pair of 256 1-byte character strings with encoding', -> - if process.platform is 'linux' - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - @fixture.setDictionary('de_DE_frami', dictionaryDirectory) - expect(@fixture.checkSpelling(maximumLength1BytePair)).toEqual [ - {start: 0, end: 256}, - {start: 257, end: 513}, - ] - - it 'returns values for a string of 128 2-byte character strings with encoding', -> - if process.platform is 'linux' - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - @fixture.setDictionary('de_DE_frami', dictionaryDirectory) - expect(@fixture.checkSpelling(maximumLength2BytePair)).toEqual [ - {start: 0, end: 128}, - {start: 129, end: 257}, - ] - - it 'returns values for a string of 85 3-byte character strings with encoding', -> - if process.platform is 'linux' - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - @fixture.setDictionary('de_DE_frami', dictionaryDirectory) - @fixture.checkSpelling(invalidLength3BytePair) - - # # Linux doesn't seem to handle 4-byte encodings - #it 'returns values for a string of 64 4-byte character strings with encoding', -> - # # de_DE_frami is invalid outside of Hunspell dictionaries. - # return unless spellType is 'hunspell' - - # @fixture.setDictionary('de_DE_frami', dictionaryDirectory) - # expect(@fixture.checkSpelling(maximumLength4BytePair)).toEqual [ - # {start: 0, end: 128}, - # {start: 129, end: 257}, - # ] - - it 'returns nothing for a pair of 257 1-byte character strings with encoding', -> - if process.platform is not 'linux' - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - @fixture.setDictionary('de_DE_frami', dictionaryDirectory) - expect(@fixture.checkSpelling(maximumLength2BytePair)).toEqual [] - - it 'returns nothing for a pair of 129 2-byte character strings with encoding', -> - return if process.platform is not 'linux' - # We are only testing for allocation errors. - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - @fixture.setDictionary('de_DE_frami', dictionaryDirectory) - @fixture.checkSpelling(invalidLength2BytePair) - - it 'returns nothing for a pair of 86 3-byte character strings with encoding', -> - return if process.platform is not 'linux' - # We are only testing for allocation errors. - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - @fixture.setDictionary('de_DE_frami', dictionaryDirectory) - @fixture.checkSpelling(invalidLength3BytePair) - - it 'returns nothing for a pair of 65 4-byte character strings with encoding', -> - return if process.platform is not 'linux' - # We are only testing for allocation errors. - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - @fixture.setDictionary('de_DE_frami', dictionaryDirectory) - @fixture.checkSpelling(invalidLength4BytePair) - - describe '.checkSpellingAsync(string)', -> - beforeEach -> - @fixture = buildSpellChecker() - @fixture.setDictionary defaultLanguage, dictionaryDirectory - - it 'returns an array of character ranges of misspelled words', -> - string = 'cat caat dog dooog' - ranges = null - - @fixture.checkSpellingAsync(string).then (r) -> ranges = r - - waitsFor -> ranges isnt null - - runs -> - expect(ranges).toEqual [ - {start: 4, end: 8} - {start: 13, end: 18} - ] - - it 'handles invalid inputs', -> - expect(=> @fixture.checkSpelling()).toThrow('Bad argument') - expect(=> @fixture.checkSpelling(null)).toThrow('Bad argument') - expect(=> @fixture.checkSpelling(47)).toThrow('Bad argument') - - describe '.getCorrectionsForMisspelling(word)', -> - beforeEach -> - @fixture = buildSpellChecker() - @fixture.setDictionary defaultLanguage, dictionaryDirectory - - it 'returns an array of possible corrections', -> - correction = ['word', 'world', 'word'][spellIndex] - corrections = @fixture.getCorrectionsForMisspelling('worrd') - expect(corrections.length).toBeGreaterThan 0 - expect(corrections[0]).toEqual(correction) - - it 'throws an exception when no word specified', -> - expect(-> @fixture.getCorrectionsForMisspelling()).toThrow() - - it 'returns an array of possible corrections for a correct English word', -> - correction = ['cheese', 'chaise', 'cheesy'][spellIndex] - corrections = @fixture.getCorrectionsForMisspelling('cheese') - expect(corrections.length).toBeGreaterThan 0 - expect(corrections[0]).toEqual(correction) - - it 'returns an array of possible corrections for a correct Latin German word with ISO8859-1 file', -> - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - expect(@fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe true - correction = ['Acht', 'Macht', 'Acht'][spellIndex] - corrections = @fixture.getCorrectionsForMisspelling('Nacht') - expect(corrections.length).toBeGreaterThan 0 - expect(corrections[0]).toEqual(correction) - - it 'returns an array of possible corrections for a correct Latin German word with UTF-8 file', -> - expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true - correction = ['Acht', 'Macht', 'Acht'][spellIndex] - corrections = @fixture.getCorrectionsForMisspelling('Nacht') - expect(corrections.length).toBeGreaterThan 0 - expect(corrections[0]).toEqual(correction) - - it 'returns an array of possible corrections for a incorrect Latin German word with ISO8859-1 file', -> - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - expect(@fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe true - correction = ['Acht', 'Nicht', 'Acht'][spellIndex] - corrections = @fixture.getCorrectionsForMisspelling('Nacht') - expect(corrections.length).toBeGreaterThan 0 - expect(corrections[0]).toEqual(correction) - - it 'returns an array of possible corrections for a incorrect Latin German word with UTF-8 file', -> - expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true - correction = ['Acht', 'SEE BELOW', 'Acht'][spellIndex] - corrections = @fixture.getCorrectionsForMisspelling('Nacht') - expect(corrections.length).toBeGreaterThan 0 - - if spellType == "mac" - # For some reason, the CI build will produce inconsistent results on - # the Mac based on some external factor. - expect(corrections[0] is 'Nicht' or corrections[0] is 'Macht').toEqual(true) - else - expect(corrections[0]).toEqual(correction) - - it 'returns an array of possible corrections for correct Unicode German word with ISO8859-1 file', -> - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - expect(@fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe true - correction = ['vermöchten', 'vermochten', 'vermochte'][spellIndex] - corrections = @fixture.getCorrectionsForMisspelling('vermöchten') - expect(corrections.length).toBeGreaterThan 0 - expect(corrections[0]).toEqual(correction) - - it 'returns an array of possible corrections for correct Unicode German word with UTF-8 file', -> - expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true - correction = ['vermöchten', 'vermochten', 'vermochte'][spellIndex] - corrections = @fixture.getCorrectionsForMisspelling('vermöchten') - expect(corrections.length).toBeGreaterThan 0 - expect(corrections[0]).toEqual(correction) - - it 'returns an array of possible corrections for incorrect Unicode German word with ISO8859-1 file', -> - # de_DE_frami is invalid outside of Hunspell dictionaries. - return unless spellType is 'hunspell' - - expect(@fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe true - correction = ['möchten', 'möchten', 'möchten'][spellIndex] - corrections = @fixture.getCorrectionsForMisspelling('möchtzn') - expect(corrections.length).toBeGreaterThan 0 - expect(corrections[0]).toEqual(correction) - - it 'returns an array of possible corrections for incorrect Unicode German word with UTF-8 file', -> - expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true - correction = ['möchten', 'möchten', 'möchten'][spellIndex] - corrections = @fixture.getCorrectionsForMisspelling('möchtzn') - expect(corrections.length).toBeGreaterThan 0 - expect(corrections[0]).toEqual(correction) - - it 'returns an array of possible corrections for correct Unicode French word', -> - expect(@fixture.setDictionary('fr', dictionaryDirectory)).toBe true - correction = ['Françoise', 'Françoise', 'française'][spellIndex] - corrections = @fixture.getCorrectionsForMisspelling('Française') - expect(corrections.length).toBeGreaterThan 0 - expect(corrections[0]).toEqual(correction) - - it 'returns an array of possible corrections for incorrect Unicode French word', -> - expect(@fixture.setDictionary('fr', dictionaryDirectory)).toBe true - correction = ['Françoise', 'Françoise', 'Françoise'][spellIndex] - corrections = @fixture.getCorrectionsForMisspelling('Françoize') - expect(corrections.length).toBeGreaterThan 0 - expect(corrections[0]).toEqual(correction) - - describe '.add(word) and .remove(word)', -> - beforeEach -> - @fixture = buildSpellChecker() - @fixture.setDictionary defaultLanguage, dictionaryDirectory - - it 'allows words to be added and removed to the dictionary', -> - # NB: Windows spellchecker cannot remove words, and since it holds onto - # words, rerunning this test >1 time causes it to incorrectly fail - return if process.platform is 'win32' - - expect(@fixture.isMisspelled('wwoorrdd')).toBe true - - @fixture.add('wwoorrdd') - expect(@fixture.isMisspelled('wwoorrdd')).toBe false - - @fixture.remove('wwoorrdd') - expect(@fixture.isMisspelled('wwoorrdd')).toBe true - - it 'add throws an error if no word is specified', -> - errorOccurred = false - try - @fixture.add() - catch - errorOccurred = true - expect(errorOccurred).toBe true - - it 'remove throws an error if no word is specified', -> - errorOccurred = false - try - @fixture.remove() - catch - errorOccurred = true - expect(errorOccurred).toBe true - - - describe '.getAvailableDictionaries()', -> - beforeEach -> - @fixture = buildSpellChecker() - @fixture.setDictionary defaultLanguage, dictionaryDirectory - - it 'returns an array of string dictionary names', -> - # NB: getAvailableDictionaries is nop'ped in hunspell and it also doesn't - # work inside Appveyor's CI environment - return if spellType is 'hunspell' or process.env.CI - - dictionaries = @fixture.getAvailableDictionaries() - expect(Array.isArray(dictionaries)).toBe true - - expect(dictionaries.length).toBeGreaterThan 0 - for dictionary in dictionaries.length - expect(typeof dictionary).toBe 'string' - expect(diction.length).toBeGreaterThan 0 - - describe '.setDictionary(lang, dictDirectory)', -> - it 'sets the spell checkers language, and dictionary directory', -> - awesome = true - expect(awesome).toBe true - - buildSpellChecker = -> - checker = new Spellchecker() - if testAlwaysUseHunspell - checker.setSpellcheckerType(ALWAYS_USE_HUNSPELL) - spellType = 'hunspell' - spellIndex = 0 - else - # We can get different results based on using Hunspell, Mac, or Windows - # checkers. To simplify the rules, we create a variable that contains - # 'hunspell', 'mac', or 'win' for filtering. We also create an index variable - # to go into arrays. - if process.env.SPELLCHECKER_PREFER_HUNSPELL - spellType = 'hunspell' - spellIndex = 0 - else if process.platform is 'darwin' - spellType = 'mac' - spellIndex = 1 - else if process.platform is 'win32' - spellType = 'win' - spellIndex = 2 - else - spellType = 'hunspell' - spellIndex = 0 - checker diff --git a/spec/spellchecker-spec.js b/spec/spellchecker-spec.js new file mode 100644 index 0000000..a83abdb --- /dev/null +++ b/spec/spellchecker-spec.js @@ -0,0 +1,785 @@ +const { Spellchecker, ALWAYS_USE_HUNSPELL } = require('../lib/spellchecker'); +const path = require('path'); + +const enUS = 'A robot is a mechanical or virtual artificial agent, usually an electronic machine'; +const deDE = 'Ein Roboter ist eine technische Apparatur, die üblicherweise dazu dient, dem Menschen mechanische Arbeit abzunehmen.'; +const frFR = 'Les robots les plus évolués sont capables de se déplacer et de se recharger par eux-mêmes'; + +const defaultLanguage = process.platform === 'darwin' ? '' : 'en_US'; +const dictionaryDirectory = path.join(__dirname, 'dictionaries'); + +// Because we are dealing with C++ and buffers, we want +// to make sure the user doesn't pass in a string that +// causes a buffer overrun. We limit our buffers to +// 256 characters (64-character word in UTF-8). +const maximumLength1Byte = 'a'.repeat(256); +const maximumLength2Byte = 'ö'.repeat(128); +const maximumLength3Byte = 'ऐ'.repeat(85); +const maximumLength4Byte = '𐅐'.repeat(64); +const invalidLength1Byte = maximumLength1Byte + 'a'; +const invalidLength2Byte = maximumLength2Byte + 'ö'; +const invalidLength3Byte = maximumLength3Byte + 'ऐ'; +const invalidLength4Byte = maximumLength4Byte + '𐄇'; + +const maximumLength1BytePair = [maximumLength1Byte, maximumLength1Byte].join(" "); +const maximumLength2BytePair = [maximumLength2Byte, maximumLength2Byte].join(" "); +const maximumLength3BytePair = [maximumLength3Byte, maximumLength3Byte].join(" "); +const maximumLength4BytePair = [maximumLength4Byte, maximumLength4Byte].join(" "); +const invalidLength1BytePair = [invalidLength1Byte, invalidLength1Byte].join(" "); +const invalidLength2BytePair = [invalidLength2Byte, invalidLength2Byte].join(" "); +const invalidLength3BytePair = [invalidLength3Byte, invalidLength3Byte].join(" "); +const invalidLength4BytePair = [invalidLength4Byte, invalidLength4Byte].join(" "); + +let spellType = null; +let spellIndex = null; + +// Hunspell dictionaries are loaded from spec/dictionaries by file name, so +// they're always "available." The native mac/win backends only support +// whatever language data is actually installed on the machine (e.g. Windows +// CI runners only ship en-* language packs), so we skip locale-specific +// assertions there rather than fail on missing OS data. +function isDictionaryAvailable(fixture, locale) { + if (spellType === 'hunspell') return true; + const tag = spellType === 'win' ? locale.replace('_', '-') : locale; + return fixture.getAvailableDictionaries().includes(tag); +} + +const IS_WINDOWS_CI = process.env.CI && process.platform === 'win32'; + +for (let testAlwaysUseHunspell of [true, false]) { + describe('SpellChecker', () => { + let fixture; + describe('.setDictionary', () => { + beforeEach(() => { + fixture = buildSpellChecker(); + }); + + it('returns true for en_US', () => { + expect( + fixture.setDictionary('en_US', dictionaryDirectory) + ).toBe(true); + }); + + it('returns true for de_DE_frami', () => { + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + expect( + fixture.setDictionary('de_DE_frami', dictionaryDirectory) + ).toBe(true); + }); + + it('returns true for de_DE', () => { + if (spellType !== 'hunspell' && IS_WINDOWS_CI) return; + expect( + fixture.setDictionary('en_US', dictionaryDirectory) + ).toBe(true); + }); + + it('returns true for fr', () => { + if (spellType !== 'hunspell' && IS_WINDOWS_CI) return; + expect( + fixture.setDictionary('fr', dictionaryDirectory) + ).toBe(true); + }); + }); + + describe('.isMisspelled(word)', () => { + beforeEach(() => { + fixture = buildSpellChecker(); + fixture.setDictionary(defaultLanguage, dictionaryDirectory); + }); + + it('returns true if the word is mispelled', () => { + fixture.setDictionary('en_US', dictionaryDirectory); + expect(fixture.isMisspelled('wwoorrddd')).toBe(true); + }); + + it('returns false if the word is not mispelled: word', () => { + fixture.setDictionary('en_US', dictionaryDirectory); + expect(fixture.isMisspelled('word')).toBe(false); + }); + + it('returns false if the word is not mispelled: cheese', () => { + fixture.setDictionary('en_US', dictionaryDirectory); + expect(fixture.isMisspelled('cheese')).toBe(false); + }); + + it('returns true if Latin German word is misspelled with ISO8859-1 file', () => { + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + expect(fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe(true); + expect(fixture.isMisspelled('Kine')).toBe(true); + }); + + it('returns true if Latin German word is misspelled with UTF-8 file', () => { + if (!isDictionaryAvailable(fixture, 'de_DE')) return; + + expect(fixture.setDictionary('de_DE', dictionaryDirectory)).toBe(true); + expect(fixture.isMisspelled('Kine')).toBe(true); + }); + + it('returns false if Latin German word is not misspelled with ISO8859-1 file', () => { + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + expect(fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe(true); + expect(fixture.isMisspelled('Nacht')).toBe(false); + }); + + it('returns false if Latin German word is not misspelled with UTF-8 file', () => { + if (!isDictionaryAvailable(fixture, 'de_DE')) return; + + expect(fixture.setDictionary('de_DE', dictionaryDirectory)).toBe(true); + expect(fixture.isMisspelled('Nacht')).toBe(false); + }); + + it('returns true if Unicode German word is misspelled with ISO8859-1 file', () => { + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + expect(fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe(true); + expect(fixture.isMisspelled('möchtzn')).toBe(true); + }); + + it('returns true if Unicode German word is misspelled with UTF-8 file', () => { + if (!isDictionaryAvailable(fixture, 'de_DE')) return; + + expect(fixture.setDictionary('de_DE', dictionaryDirectory)).toBe(true); + expect(fixture.isMisspelled('möchtzn')).toBe(true); + }); + + it('returns false if Unicode German word is not misspelled with ISO8859-1 file', () => { + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + expect(fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe(true); + expect(fixture.isMisspelled('vermöchten')).toBe(false); + }); + + it('returns false if Unicode German word is not misspelled with UTF-8 file', () => { + if (!isDictionaryAvailable(fixture, 'de_DE')) return; + + expect(fixture.setDictionary('de_DE', dictionaryDirectory)).toBe(true); + expect(fixture.isMisspelled('vermöchten')).toBe(false); + }); + + it('throws an exception when no word specified', () => { + expect(() => fixture.isMisspelled()).toThrow(); + }); + + it('returns true for a string of 256 1-byte characters', () => { + if (process.platform === 'linux') { + expect(fixture.isMisspelled(maximumLength1Byte)).toBe(true); + } + }); + + it('returns true for a string of 128 2-byte characters', () => { + if (process.platform === 'linux') { + expect(fixture.isMisspelled(maximumLength2Byte)).toBe(true); + } + }); + + it('returns true for a string of 85 3-byte characters', () => { + if (process.platform === 'linux') { + expect(fixture.isMisspelled(maximumLength3Byte)).toBe(true); + } + }); + + it('returns true for a string of 64 4-byte characters', () => { + if (process.platform === 'linux') { + expect(fixture.isMisspelled(maximumLength4Byte)).toBe(true); + } + }); + + it('returns false for a string of 257 1-byte characters', () => { + if (process.platform === 'linux') { + expect(fixture.isMisspelled(invalidLength1Byte)).toBe(false); + } + }); + + it('returns false for a string of 65 2-byte characters', () => { + if (process.platform === 'linux') { + expect(fixture.isMisspelled(invalidLength2Byte)).toBe(false); + } + }); + + it('returns false for a string of 86 3-byte characters', () => { + if (process.platform === 'linux') { + expect(fixture.isMisspelled(invalidLength3Byte)).toBe(false); + } + }); + + it('returns false for a string of 65 4-byte characters', () => { + if (process.platform === 'linux') { + expect(fixture.isMisspelled(invalidLength4Byte)).toBe(false); + } + }); + }); + + describe('.checkSpelling(string)', () => { + beforeEach(() => { + fixture = buildSpellChecker(); + fixture.setDictionary(defaultLanguage, dictionaryDirectory); + }); + + it('automatically detects languages on OS X', () => { + if (process.platform !== 'darwin') return; + + expect(fixture.checkSpelling(enUS)).toEqual([]); + expect(fixture.checkSpelling(deDE)).toEqual([]); + expect(fixture.checkSpelling(frFR)).toEqual([]); + }); + + it('correctly switches languages', () => { + expect(fixture.setDictionary('en_US', dictionaryDirectory)).toBe(true); + expect(fixture.checkSpelling(enUS)).toEqual([]); + expect(fixture.checkSpelling(deDE)).not.toEqual([]); + expect(fixture.checkSpelling(frFR)).not.toEqual([]); + + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType === 'hunspell') { + if (fixture.setDictionary('de_DE_frami', dictionaryDirectory)) { + expect(fixture.checkSpelling(enUS)).not.toEqual([]); + expect(fixture.checkSpelling(deDE)).toEqual([]); + expect(fixture.checkSpelling(frFR)).not.toEqual([]); + } + } + + if (fixture.setDictionary('de_DE', dictionaryDirectory)) { + expect(fixture.checkSpelling(enUS)).not.toEqual([]); + expect(fixture.checkSpelling(deDE)).toEqual([]); + expect(fixture.checkSpelling(frFR)).not.toEqual([]); + } + + fixture = buildSpellChecker(); + if (fixture.setDictionary('fr_FR', dictionaryDirectory)) { + expect(fixture.checkSpelling(enUS)).not.toEqual([]); + expect(fixture.checkSpelling(deDE)).not.toEqual([]); + expect(fixture.checkSpelling(frFR)).toEqual([]); + } + }); + + it('returns an array of character ranges of misspelled words', () => { + const string = 'cat caat dog dooog'; + + expect(fixture.checkSpelling(string)).toEqual([ + { start: 4, end: 8 }, + { start: 13, end: 18 } + ]); + }); + + it('returns an array of character ranges of misspelled German words with ISO8859-1 file', () => { + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + expect(fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe(true); + + const string = 'Kein Kine vermöchten möchtzn'; + + expect(fixture.checkSpelling(string)).toEqual([ + { start: 5, end: 9 }, + { start: 21, end: 28 } + ]); + }); + + it('returns an array of character ranges of misspelled German words with UTF-8 file', () => { + if (!isDictionaryAvailable(fixture, 'de_DE')) return; + + expect(fixture.setDictionary('de_DE', dictionaryDirectory)).toBe(true); + + const string = 'Kein Kine vermöchten möchtzn'; + + expect(fixture.checkSpelling(string)).toEqual([ + { start: 5, end: 9 }, + { start: 21, end: 28 } + ]); + }); + + it('returns an array of character ranges of misspelled French words', () => { + if (!isDictionaryAvailable(fixture, 'fr')) return; + + expect(fixture.setDictionary('fr', dictionaryDirectory)).toBe(true); + + const string = 'Française Françoize'; + + expect(fixture.checkSpelling(string)).toEqual([ + { start: 10, end: 19 } + ]); + }); + + it('accounts for UTF16 pairs', () => { + const string = '😎 cat caat dog dooog'; + + expect(fixture.checkSpelling(string)).toEqual([ + { start: 7, end: 11 }, + { start: 16, end: 21 } + ]); + }); + + it("accounts for other non-word characters", () => { + const string = "'cat' (caat. :dooog)"; + expect(fixture.checkSpelling(string)).toEqual([ + { start: 7, end: 11 }, + { start: 20, end: 25 } + ]); + }); + + it('does not treat non-english letters as word boundaries', () => { + fixture.add('cliché'); + expect(fixture.checkSpelling('what cliché nonsense')).toEqual([]); + fixture.remove('cliché'); + }); + + it('handles words with apostrophes', () => { + let string = "doesn't isn't aint hasn't"; + expect(fixture.checkSpelling(string)).toEqual([ + { start: string.indexOf("aint"), end: string.indexOf("aint") + 4 } + ]); + + string = "you say you're 'certain', but are you really?"; + expect(fixture.checkSpelling(string)).toEqual([]); + + string = "you say you're 'sertan', but are you really?"; + expect(fixture.checkSpelling(string)).toEqual([ + { start: string.indexOf("sertan"), end: string.indexOf("',") } + ]); + }); + + it('handles invalid inputs', () => { + expect(fixture.checkSpelling('')).toEqual([]); + expect(() => fixture.checkSpelling()).toThrow('Bad argument'); + expect(() => fixture.checkSpelling(null)).toThrow('Bad argument'); + expect(() => fixture.checkSpelling({})).toThrow('Bad argument'); + }); + + it('returns values for a pair of 256 1-byte character strings', () => { + if (process.platform === 'linux') { + expect(fixture.checkSpelling(maximumLength1BytePair)).toEqual([ + { start: 0, end: 256 }, + { start: 257, end: 513 }, + ]); + } + }); + + it('returns values for a string of 128 2-byte character strings', () => { + if (process.platform === 'linux') { + expect(fixture.checkSpelling(maximumLength2BytePair)).toEqual([ + { start: 0, end: 128 }, + { start: 129, end: 257 }, + ]); + } + }); + + it('returns values for a string of 85 3-byte character strings', () => { + if (process.platform === 'linux') { + expect(fixture.checkSpelling(maximumLength3BytePair)).toEqual([ + { start: 0, end: 85 }, + { start: 86, end: 171 }, + ]); + } + }); + + // # Linux doesn't seem to handle 4-byte encodings, so this test is just to + // # comment that fact. + // xit 'returns values for a string of 64 4-byte character strings', -> + // expect(@fixture.checkSpelling(maximumLength4BytePair)).toEqual [ + // {start: 0, end: 128}, + // {start: 129, end: 257}, + // ] + + it('returns nothing for a pair of 257 1-byte character strings', () => { + if (process.platform === 'linux') { + expect(fixture.checkSpelling(invalidLength1BytePair)).toEqual([]); + } + }); + + it('returns nothing for a pair of 129 2-byte character strings', () => { + if (process.platform === 'linux') { + expect(fixture.checkSpelling(invalidLength2BytePair)).toEqual([]); + } + }); + + it('returns nothing for a pair of 86 3-byte character strings', () => { + if (process.platform === 'linux') { + expect(fixture.checkSpelling(invalidLength3BytePair)).toEqual([]); + } + }); + + it('returns nothing for a pair of 65 4-byte character strings', () => { + if (process.platform === 'linux') { + expect(fixture.checkSpelling(invalidLength4BytePair)).toEqual([]); + } + }); + + it('returns values for a pair of 256 1-byte character strings with encoding', () => { + if (process.platform === 'linux') { + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + fixture.setDictionary('de_DE_frami', dictionaryDirectory); + expect(fixture.checkSpelling(maximumLength1BytePair)).toEqual([ + { start: 0, end: 256 }, + { start: 257, end: 513 }, + ]); + } + }); + + it('returns values for a string of 128 2-byte character strings with encoding', () => { + if (process.platform === 'linux') { + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + fixture.setDictionary('de_DE_frami', dictionaryDirectory); + expect(fixture.checkSpelling(maximumLength2BytePair)).toEqual([ + { start: 0, end: 128 }, + { start: 129, end: 257 }, + ]); + } + }); + + it('returns values for a string of 85 3-byte character strings with encoding', () => { + if (process.platform === 'linux') { + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + fixture.setDictionary('de_DE_frami', dictionaryDirectory); + fixture.checkSpelling(invalidLength3BytePair).length + } + }); + + // # Linux doesn't seem to handle 4-byte encodings + //it 'returns values for a string of 64 4-byte character strings with encoding', -> + // # de_DE_frami is invalid outside of Hunspell dictionaries. + // return unless spellType is 'hunspell' + + // @fixture.setDictionary('de_DE_frami', dictionaryDirectory) + // expect(@fixture.checkSpelling(maximumLength4BytePair)).toEqual [ + // {start: 0, end: 128}, + // {start: 129, end: 257}, + // ] + + it('returns nothing for a pair of 257 1-byte character strings with encoding', () => { + if (process.platform !== 'linux') { + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + fixture.setDictionary('de_DE_frami', dictionaryDirectory); + expect(fixture.checkSpelling(maximumLength2BytePair)).toEqual([]); + } + }); + + it('returns nothing for a pair of 129 2-byte character strings with encoding', () => { + if (process.platform !== 'linux') return; + // We are only testing for allocation errors. + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + fixture.setDictionary('de_DE_frami', dictionaryDirectory); + expect(fixture.checkSpelling(invalidLength2BytePair)).toEqual([]); + }); + + it('returns nothing for a pair of 86 3-byte character strings with encoding', () => { + if (process.platform !== 'linux') return; + // We are only testing for allocation errors. + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + fixture.setDictionary('de_DE_frami', dictionaryDirectory); + expect(fixture.checkSpelling(invalidLength3BytePair)).toEqual([]); + }); + + it('returns nothing for a pair of 65 4-byte character strings with encoding', () => { + if (process.platform !== 'linux') return; + // We are only testing for allocation errors. + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + fixture.setDictionary('de_DE_frami', dictionaryDirectory); + expect(fixture.checkSpelling(invalidLength4BytePair)).toEqual([]); + }); + }); + + describe('.checkSpellingAsync(string)', () => { + beforeEach(() => { + fixture = buildSpellChecker(); + fixture.setDictionary(defaultLanguage, dictionaryDirectory); + }); + + it('returns an array of character ranges of misspelled words', () => { + const string = 'cat caat dog dooog'; + let ranges = null; + + fixture.checkSpellingAsync(string).then(r => ranges = r); + + waitsFor(() => ranges !== null); + + runs(() => expect(ranges).toEqual([ + { start: 4, end: 8 }, + { start: 13, end: 18 } + ])); + }); + + it('handles invalid inputs', () => { + expect(() => fixture.checkSpelling()).toThrow('Bad argument'); + expect(() => fixture.checkSpelling(null)).toThrow('Bad argument'); + expect(() => fixture.checkSpelling(47)).toThrow('Bad argument'); + }); + }); + + describe('.getCorrectionsForMisspelling(word)', () => { + beforeEach(() => { + fixture = buildSpellChecker(); + fixture.setDictionary(defaultLanguage, dictionaryDirectory); + }); + + it('returns an array of possible corrections', () => { + const correction = ['word', 'world', 'word'][spellIndex]; + const corrections = fixture.getCorrectionsForMisspelling('worrd'); + expect(corrections.length).toBeGreaterThan(0); + expect(corrections[0]).toEqual(correction); + }); + + it('throws an exception when no word specified', () => { + expect( + () => fixture.getCorrectionsForMisspelling() + ).toThrow(); + }); + + it('returns an array of possible corrections for a correct English word', () => { + const corrections = fixture.getCorrectionsForMisspelling('cheese'); + expect(corrections.length).toBeGreaterThan(0); + if (spellType === 'win') { + expect((corrections[0] === 'cheesy') || (corrections[0] === 'cheeses')).toEqual(true); + } else { + const correction = ['cheese', 'chaise', 'cheesy'][spellIndex]; + expect(corrections[0]).toEqual(correction); + } + }); + + it('returns an array of possible corrections for a correct Latin German word with ISO8859-1 file', () => { + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + expect(fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe(true); + const correction = ['Acht', 'Macht', 'Acht'][spellIndex]; + const corrections = fixture.getCorrectionsForMisspelling('Nacht'); + expect(corrections.length).toBeGreaterThan(0); + expect(corrections[0]).toEqual(correction); + }); + + it('returns an array of possible corrections for a correct Latin German word with UTF-8 file', () => { + if (!isDictionaryAvailable(fixture, 'de_DE')) return; + + expect(fixture.setDictionary('de_DE', dictionaryDirectory)).toBe(true); + const correction = ['Acht', 'Nicht', 'Acht'][spellIndex]; + const corrections = fixture.getCorrectionsForMisspelling('Nacht'); + expect(corrections.length).toBeGreaterThan(0); + if (spellType === "mac") { + // For some reason, the CI build will produce inconsistent results on + // the Mac based on some external factor. + expect((corrections[0] === 'Nicht') || (corrections[0] === 'Macht')).toEqual(true); + } else { + expect(corrections[0]).toEqual(correction); + } + }); + + it('returns an array of possible corrections for a incorrect Latin German word with ISO8859-1 file', () => { + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + expect(fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe(true); + const correction = ['Acht', 'Nicht', 'Acht'][spellIndex]; + const corrections = fixture.getCorrectionsForMisspelling('Nacht'); + expect(corrections.length).toBeGreaterThan(0); + if (spellType === "mac") { + // For some reason, the CI build will produce inconsistent results on + // the Mac based on some external factor. + expect((corrections[0] === 'Nicht') || (corrections[0] === 'Macht')).toEqual(true); + } else { + expect(corrections[0]).toEqual(correction); + } + }); + + it('returns an array of possible corrections for a incorrect Latin German word with UTF-8 file', () => { + if (!isDictionaryAvailable(fixture, 'de_DE')) return; + + expect(fixture.setDictionary('de_DE', dictionaryDirectory)).toBe(true); + const correction = ['Acht', 'SEE BELOW', 'Acht'][spellIndex]; + const corrections = fixture.getCorrectionsForMisspelling('Nacht'); + expect(corrections.length).toBeGreaterThan(0); + + if (spellType === "mac") { + // For some reason, the CI build will produce inconsistent results on + // the Mac based on some external factor. + expect((corrections[0] === 'Nicht') || (corrections[0] === 'Macht')).toEqual(true); + } else { + expect(corrections[0]).toEqual(correction); + } + }); + + it('returns an array of possible corrections for correct Unicode German word with ISO8859-1 file', () => { + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + expect(fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe(true); + const correction = ['vermöchten', 'vermochten', 'vermochte'][spellIndex]; + const corrections = fixture.getCorrectionsForMisspelling('vermöchten'); + expect(corrections.length).toBeGreaterThan(0); + expect(corrections[0]).toEqual(correction); + }); + + it('returns an array of possible corrections for correct Unicode German word with UTF-8 file', () => { + if (!isDictionaryAvailable(fixture, 'de_DE')) return; + + expect(fixture.setDictionary('de_DE', dictionaryDirectory)).toBe(true); + const correction = ['vermöchten', 'vermochten', 'vermochte'][spellIndex]; + const corrections = fixture.getCorrectionsForMisspelling('vermöchten'); + expect(corrections.length).toBeGreaterThan(0); + expect(corrections[0]).toEqual(correction); + }); + + it('returns an array of possible corrections for incorrect Unicode German word with ISO8859-1 file', () => { + // de_DE_frami is invalid outside of Hunspell dictionaries. + if (spellType !== 'hunspell') return; + + expect(fixture.setDictionary('de_DE_frami', dictionaryDirectory)).toBe(true); + const correction = ['möchten', 'möchten', 'möchten'][spellIndex]; + const corrections = fixture.getCorrectionsForMisspelling('möchtzn'); + expect(corrections.length).toBeGreaterThan(0); + expect(corrections[0]).toEqual(correction); + }); + + it('returns an array of possible corrections for incorrect Unicode German word with UTF-8 file', () => { + if (!isDictionaryAvailable(fixture, 'de_DE')) return; + + expect(fixture.setDictionary('de_DE', dictionaryDirectory)).toBe(true); + const correction = ['möchten', 'möchten', 'möchten'][spellIndex]; + const corrections = fixture.getCorrectionsForMisspelling('möchtzn'); + expect(corrections.length).toBeGreaterThan(0); + expect(corrections[0]).toEqual(correction); + }); + + it('returns an array of possible corrections for correct Unicode French word', () => { + if (!isDictionaryAvailable(fixture, 'fr')) return; + + expect(fixture.setDictionary('fr', dictionaryDirectory)).toBe(true); + const correction = ['Françoise', 'Françoise', 'française'][spellIndex]; + const corrections = fixture.getCorrectionsForMisspelling('Française'); + expect(corrections.length).toBeGreaterThan(0); + expect(corrections[0]).toEqual(correction); + }); + + it('returns an array of possible corrections for incorrect Unicode French word', () => { + if (!isDictionaryAvailable(fixture, 'fr')) return; + + expect(fixture.setDictionary('fr', dictionaryDirectory)).toBe(true); + const correction = ['Françoise', 'Françoise', 'Françoise'][spellIndex]; + const corrections = fixture.getCorrectionsForMisspelling('Françoize'); + expect(corrections.length).toBeGreaterThan(0); + expect(corrections[0]).toEqual(correction); + }); + }); + + describe('.add(word) and .remove(word)', () => { + beforeEach(() => { + fixture = buildSpellChecker(); + fixture.setDictionary(defaultLanguage, dictionaryDirectory); + }); + + it('allows words to be added and removed to the dictionary', () => { + // NB: Windows spellchecker cannot remove words, and since it holds onto + // words, rerunning this test >1 time causes it to incorrectly fail + if (process.platform === 'win32') return; + + expect(fixture.isMisspelled('wwoorrdd')).toBe(true); + + fixture.add('wwoorrdd'); + expect(fixture.isMisspelled('wwoorrdd')).toBe(false); + + fixture.remove('wwoorrdd'); + expect(fixture.isMisspelled('wwoorrdd')).toBe(true); + }); + + it('add throws an error if no word is specified', () => { + let errorOccurred = false; + try { + fixture.add(); + } catch (error) { + errorOccurred = true; + } + expect(errorOccurred).toBe(true); + }); + + it('remove throws an error if no word is specified', () => { + let errorOccurred = false; + try { + fixture.remove(); + } catch (error) { + errorOccurred = true; + } + expect(errorOccurred).toBe(true); + }); + }); + + + describe('.getAvailableDictionaries()', () => { + beforeEach(() => { + fixture = buildSpellChecker(); + fixture.setDictionary(defaultLanguage, dictionaryDirectory); + }); + + it('returns an array of string dictionary names', () => { + // NB: getAvailableDictionaries is nop'ped in hunspell and it also + // doesn't work inside Appveyor's CI environment + if ((spellType === 'hunspell') || process.env.CI) return; + + const dictionaries = fixture.getAvailableDictionaries(); + expect(Array.isArray(dictionaries)).toBe(true); + + expect(dictionaries.length).toBeGreaterThan(0); + for (var dictionary of dictionaries) { + expect(typeof dictionary).toBe('string'); + expect(dictionary.length).toBeGreaterThan(0); + } + }); + }); + + describe('.setDictionary(lang, dictDirectory)', () => { + it('sets the spell checkers language, and dictionary directory', () => { + // TODO: Well, this sure doesn't do what it claims. + const awesome = true; + expect(awesome).toBe(true); + }); + }); + }); + + var buildSpellChecker = function () { + const checker = new Spellchecker(); + if (testAlwaysUseHunspell) { + checker.setSpellcheckerType(ALWAYS_USE_HUNSPELL); + spellType = 'hunspell'; + spellIndex = 0; + } else { + // We can get different results based on using Hunspell, Mac, or Windows + // checkers. To simplify the rules, we create a variable that contains + // 'hunspell', 'mac', or 'win' for filtering. We also create an index + // variable to go into arrays. + if (process.env.SPELLCHECKER_PREFER_HUNSPELL) { + spellType = 'hunspell'; + spellIndex = 0; + } else if (process.platform === 'darwin') { + spellType = 'mac'; + spellIndex = 1; + } else if (process.platform === 'win32') { + spellType = 'win'; + spellIndex = 2; + } else { + spellType = 'hunspell'; + spellIndex = 0; + } + } + return checker; + }; +} diff --git a/src/main.cc b/src/main.cc index af0867b..54f9a4f 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,302 +1,242 @@ #include -#include -#include "nan.h" +#include "napi.h" +#include "main.h" #include "spellchecker.h" #include "worker.h" -using Nan::ObjectWrap; using namespace spellchecker; -using namespace v8; -namespace { - -class Spellchecker : public Nan::ObjectWrap { - SpellcheckerImplementation* impl; +Spellchecker::Spellchecker(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info), impl(NULL) { +} - static NAN_METHOD(New) { - Nan::HandleScope scope; - Spellchecker* that = new Spellchecker(); - that->Wrap(info.This()); +Spellchecker::~Spellchecker() { + delete impl; +} - info.GetReturnValue().Set(info.This()); +void Spellchecker::EnsureLoadedImplementation() { + if (!impl) { + impl = SpellcheckerFactory::CreateSpellchecker(USE_SYSTEM_DEFAULTS); } +} - static NAN_METHOD(SetSpellcheckerType) { - // Pull out the handle to the spellchecker instance. - Nan::HandleScope scope; - - if (info.Length() < 1) { - return Nan::ThrowError("Bad argument: missing mode"); - } - - Spellchecker* that = Nan::ObjectWrap::Unwrap(info.Holder()); - - // If we already have an implementation, then we want to complain because - // we can't handle reinitializing the dictionary paths. - if (that->impl) { - return Nan::ThrowError("Cannot call SetSpellcheckerType after the dictionary has been configured or used"); - } - - // Make sure we have a sane value for our enumeration. - int modeNumber = info[0]->Int32Value(Nan::GetCurrentContext()).ToChecked(); - int spellcheckerType = USE_SYSTEM_DEFAULTS; - - switch (modeNumber) - { - case 0: - break; - case 1: - spellcheckerType = ALWAYS_USE_SYSTEM; - break; - case 2: - spellcheckerType = ALWAYS_USE_HUNSPELL; - break; - default: - return Nan::ThrowError("Bad argument: SetSpellcheckerType must be given 0, 1, or 2 as a parameter"); - } - - // Create a new one with the appropriate checker type. - that->impl = SpellcheckerFactory::CreateSpellchecker(spellcheckerType); - } +Napi::Value Spellchecker::SetSpellcheckerType(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - static NAN_METHOD(SetDictionary) { - Nan::HandleScope scope; + if (info.Length() < 1) { + Napi::Error::New(env, "Bad argument: missing mode").ThrowAsJavaScriptException(); + return env.Undefined(); + } - if (info.Length() < 2) { - return Nan::ThrowError("Bad arguments"); - } + // If we already have an implementation, then we want to complain because + // we can't handle reinitializing the dictionary paths. + if (impl) { + Napi::Error::New(env, "Cannot call SetSpellcheckerType after the dictionary has been configured or used").ThrowAsJavaScriptException(); + return env.Undefined(); + } - Spellchecker* that = Nan::ObjectWrap::Unwrap(info.Holder()); + // Make sure we have a sane value for our enumeration. + int modeNumber = info[0].As().Int32Value(); + int spellcheckerType = USE_SYSTEM_DEFAULTS; + + switch (modeNumber) { + case 0: + break; + case 1: + spellcheckerType = ALWAYS_USE_SYSTEM; + break; + case 2: + spellcheckerType = ALWAYS_USE_HUNSPELL; + break; + default: + Napi::Error::New(env, "Bad argument: SetSpellcheckerType must be given 0, 1, or 2 as a parameter").ThrowAsJavaScriptException(); + return env.Undefined(); + } - std::string language = *Nan::Utf8String(info[0]); - std::string directory = "."; - if (info.Length() > 1) { - directory = *Nan::Utf8String(info[1]); - } + // Create a new one with the appropriate checker type. + impl = SpellcheckerFactory::CreateSpellchecker(spellcheckerType); + return env.Undefined(); +} - // Make sure we have the implementation loaded. - Spellchecker::EnsureLoadedImplementation(that); +Napi::Value Spellchecker::SetDictionary(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - bool result = that->impl->SetDictionary(language, directory); - info.GetReturnValue().Set(Nan::New(result)); + if (info.Length() < 1) { + Napi::Error::New(env, "Bad arguments").ThrowAsJavaScriptException(); + return env.Undefined(); } - static NAN_METHOD(IsMisspelled) { - Nan::HandleScope scope; - if (info.Length() < 1) { - return Nan::ThrowError("Bad argument"); - } + std::string language = info[0].As().Utf8Value(); + std::string directory = "."; + if (info.Length() > 1) { + directory = info[1].As().Utf8Value(); + } - Spellchecker* that = Nan::ObjectWrap::Unwrap(info.Holder()); - std::string word = *Nan::Utf8String(info[0]); + EnsureLoadedImplementation(); - // Make sure we have the implementation loaded. - Spellchecker::EnsureLoadedImplementation(that); + bool result = impl->SetDictionary(language, directory); + return Napi::Boolean::New(env, result); +} - info.GetReturnValue().Set(Nan::New(that->impl->IsMisspelled(word))); - } +Napi::Value Spellchecker::IsMisspelled(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - static NAN_METHOD(CheckSpelling) { - Nan::HandleScope scope; - if (info.Length() < 1) { - return Nan::ThrowError("Bad argument"); - } - - Local string = Local::Cast(info[0]); - if (!string->IsString()) { - return Nan::ThrowError("Bad argument"); - } - - Local result = Nan::New(); - info.GetReturnValue().Set(result); - - if (string->Length() == 0) { - return; - } - - std::vector text(string->Length() + 1); - string->Write( -#if V8_MAJOR_VERSION > 6 - info.GetIsolate(), -#endif - reinterpret_cast(text.data())); - - Spellchecker* that = Nan::ObjectWrap::Unwrap(info.Holder()); - - // Make sure we have the implementation loaded. - Spellchecker::EnsureLoadedImplementation(that); - - std::vector misspelled_ranges = that->impl->CheckSpelling(text.data(), text.size()); - - std::vector::const_iterator iter = misspelled_ranges.begin(); - v8::Local context = Nan::GetCurrentContext(); - for (; iter != misspelled_ranges.end(); ++iter) { - size_t index = iter - misspelled_ranges.begin(); - uint32_t start = iter->start, end = iter->end; - - Local misspelled_range = Nan::New(); - misspelled_range->Set(context, Nan::New("start").ToLocalChecked(), Nan::New(start)); - misspelled_range->Set(context, Nan::New("end").ToLocalChecked(), Nan::New(end)); - result->Set(context, index, misspelled_range); - } + if (info.Length() < 1) { + Napi::Error::New(env, "Bad argument").ThrowAsJavaScriptException(); + return env.Undefined(); } - static NAN_METHOD(CheckSpellingAsync) { - Nan::HandleScope scope; - if (info.Length() < 2) { - return Nan::ThrowError("Bad argument"); - } + std::string word = info[0].As().Utf8Value(); - Local string = Local::Cast(info[0]); - if (!string->IsString()) { - return Nan::ThrowError("Bad argument"); - } + EnsureLoadedImplementation(); - Nan::Callback *callback = new Nan::Callback(info[1].As()); + return Napi::Boolean::New(env, impl->IsMisspelled(word)); +} - std::vector corpus(string->Length() + 1); - string->Write( -#if V8_MAJOR_VERSION > 6 - info.GetIsolate(), -#endif - reinterpret_cast(corpus.data())); +Napi::Value Spellchecker::CheckSpelling(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - Spellchecker* that = Nan::ObjectWrap::Unwrap(info.Holder()); + if (info.Length() < 1 || !info[0].IsString()) { + Napi::Error::New(env, "Bad argument").ThrowAsJavaScriptException(); + return env.Undefined(); + } - // Make sure we have the implementation loaded. - Spellchecker::EnsureLoadedImplementation(that); + Napi::Array result = Napi::Array::New(env); - CheckSpellingWorker* worker = new CheckSpellingWorker(std::move(corpus), that->impl, callback); - Nan::AsyncQueueWorker(worker); + std::u16string text = info[0].As().Utf16Value(); + if (text.empty()) { + return result; } - static NAN_METHOD(Add) { - Nan::HandleScope scope; - if (info.Length() < 1) { - return Nan::ThrowError("Bad argument"); - } + EnsureLoadedImplementation(); - Spellchecker* that = Nan::ObjectWrap::Unwrap(info.Holder()); + // Include the implicit trailing null terminator that std::u16string + // guarantees at data()[size()]; the state machine in CheckSpelling uses + // it as a sentinel to flush the final word in the string. + std::vector misspelled_ranges = + impl->CheckSpelling(reinterpret_cast(text.data()), text.size() + 1); - // Make sure we have the implementation loaded. - Spellchecker::EnsureLoadedImplementation(that); + for (size_t index = 0; index < misspelled_ranges.size(); ++index) { + const MisspelledRange& range = misspelled_ranges[index]; - std::string word = *Nan::Utf8String(info[0]); - that->impl->Add(word); - return; + Napi::Object misspelled_range = Napi::Object::New(env); + misspelled_range.Set("start", Napi::Number::New(env, range.start)); + misspelled_range.Set("end", Napi::Number::New(env, range.end)); + result.Set(index, misspelled_range); } - static NAN_METHOD(Remove) { - Nan::HandleScope scope; - if (info.Length() < 1) { - return Nan::ThrowError("Bad argument"); - } - - Spellchecker* that = Nan::ObjectWrap::Unwrap(info.Holder()); + return result; +} - // Make sure we have the implementation loaded. - Spellchecker::EnsureLoadedImplementation(that); +void Spellchecker::CheckSpellingAsync(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - std::string word = *Nan::Utf8String(info[0]); - that->impl->Remove(word); + if (info.Length() < 2 || !info[0].IsString()) { + Napi::Error::New(env, "Bad argument").ThrowAsJavaScriptException(); return; } - static NAN_METHOD(GetAvailableDictionaries) { - Nan::HandleScope scope; + std::u16string corpus = info[0].As().Utf16Value(); + Napi::Function callback = info[1].As(); - Spellchecker* that = Nan::ObjectWrap::Unwrap(info.Holder()); + EnsureLoadedImplementation(); - // Make sure we have the implementation loaded. - Spellchecker::EnsureLoadedImplementation(that); + // Include the trailing null terminator (see CheckSpelling above) so the + // worker's final word gets flushed too. + CheckSpellingWorker* worker = new CheckSpellingWorker( + std::vector(corpus.data(), corpus.data() + corpus.size() + 1), impl, callback); + worker->Queue(); +} - std::string path = "."; - if (info.Length() > 0) { - std::string path = *Nan::Utf8String(info[0]); - } +void Spellchecker::Add(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - std::vector dictionaries = - that->impl->GetAvailableDictionaries(path); + if (info.Length() < 1) { + Napi::Error::New(env, "Bad argument").ThrowAsJavaScriptException(); + return; + } - v8::Local context = Nan::GetCurrentContext(); - Local result = Nan::New(dictionaries.size()); - for (size_t i = 0; i < dictionaries.size(); ++i) { - const std::string& dict = dictionaries[i]; - result->Set(context, i, Nan::New(dict.data(), dict.size()).ToLocalChecked()); - } + EnsureLoadedImplementation(); - info.GetReturnValue().Set(result); - } + std::string word = info[0].As().Utf8Value(); + impl->Add(word); +} - static NAN_METHOD(GetCorrectionsForMisspelling) { - Nan::HandleScope scope; - if (info.Length() < 1) { - return Nan::ThrowError("Bad argument"); - } +void Spellchecker::Remove(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - Spellchecker* that = Nan::ObjectWrap::Unwrap(info.Holder()); + if (info.Length() < 1) { + Napi::Error::New(env, "Bad argument").ThrowAsJavaScriptException(); + return; + } - // Make sure we have the implementation loaded. - Spellchecker::EnsureLoadedImplementation(that); + EnsureLoadedImplementation(); - std::string word = *Nan::Utf8String(info[0]); - std::vector corrections = - that->impl->GetCorrectionsForMisspelling(word); + std::string word = info[0].As().Utf8Value(); + impl->Remove(word); +} - Local result = Nan::New(corrections.size()); - v8::Local context = Nan::GetCurrentContext(); - for (size_t i = 0; i < corrections.size(); ++i) { - const std::string& word = corrections[i]; +Napi::Value Spellchecker::GetAvailableDictionaries(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - Nan::MaybeLocal val = Nan::New(word.data(), word.size()); - result->Set(context, i, val.ToLocalChecked()); - } + EnsureLoadedImplementation(); - info.GetReturnValue().Set(result); + std::string path = "."; + if (info.Length() > 0) { + path = info[0].As().Utf8Value(); } - Spellchecker() { - impl = NULL; - } + std::vector dictionaries = impl->GetAvailableDictionaries(path); - // actual destructor - virtual ~Spellchecker() { - delete impl; + Napi::Array result = Napi::Array::New(env, dictionaries.size()); + for (size_t i = 0; i < dictionaries.size(); ++i) { + result.Set(i, Napi::String::New(env, dictionaries[i])); } - static void EnsureLoadedImplementation(Spellchecker *that) { - if (!that->impl) { - that->impl = SpellcheckerFactory::CreateSpellchecker(USE_SYSTEM_DEFAULTS); - } + return result; +} + +Napi::Value Spellchecker::GetCorrectionsForMisspelling(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + if (info.Length() < 1) { + Napi::Error::New(env, "Bad argument").ThrowAsJavaScriptException(); + return env.Undefined(); } - public: - static void Init(Local exports) { - Local tpl = Nan::New(Spellchecker::New); - - tpl->SetClassName(Nan::New("Spellchecker").ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - - Nan::SetPrototypeMethod(tpl, "setSpellcheckerType", Spellchecker::SetSpellcheckerType); - Nan::SetPrototypeMethod(tpl, "setDictionary", Spellchecker::SetDictionary); - Nan::SetPrototypeMethod(tpl, "getAvailableDictionaries", Spellchecker::GetAvailableDictionaries); - Nan::SetPrototypeMethod(tpl, "getCorrectionsForMisspelling", Spellchecker::GetCorrectionsForMisspelling); - Nan::SetPrototypeMethod(tpl, "isMisspelled", Spellchecker::IsMisspelled); - Nan::SetPrototypeMethod(tpl, "checkSpelling", Spellchecker::CheckSpelling); - Nan::SetPrototypeMethod(tpl, "checkSpellingAsync", Spellchecker::CheckSpellingAsync); - Nan::SetPrototypeMethod(tpl, "add", Spellchecker::Add); - Nan::SetPrototypeMethod(tpl, "remove", Spellchecker::Remove); - - Isolate* isolate = exports->GetIsolate(); - Local context = isolate->GetCurrentContext(); - Nan::Set(exports, Nan::New("Spellchecker").ToLocalChecked(), tpl->GetFunction(context).ToLocalChecked()); + EnsureLoadedImplementation(); + + std::string word = info[0].As().Utf8Value(); + std::vector corrections = impl->GetCorrectionsForMisspelling(word); + + Napi::Array result = Napi::Array::New(env, corrections.size()); + for (size_t i = 0; i < corrections.size(); ++i) { + result.Set(i, Napi::String::New(env, corrections[i])); } -}; -void Init(Local exports, Local module) { - Spellchecker::Init(exports); + return result; +} + +Napi::Object Spellchecker::Init(Napi::Env env, Napi::Object exports) { + Napi::Function func = DefineClass(env, "Spellchecker", { + InstanceMethod("setSpellcheckerType", &Spellchecker::SetSpellcheckerType), + InstanceMethod("setDictionary", &Spellchecker::SetDictionary), + InstanceMethod("getAvailableDictionaries", &Spellchecker::GetAvailableDictionaries), + InstanceMethod("getCorrectionsForMisspelling", &Spellchecker::GetCorrectionsForMisspelling), + InstanceMethod("isMisspelled", &Spellchecker::IsMisspelled), + InstanceMethod("checkSpelling", &Spellchecker::CheckSpelling), + InstanceMethod("checkSpellingAsync", &Spellchecker::CheckSpellingAsync, + static_cast(napi_writable | napi_configurable)), + InstanceMethod("add", &Spellchecker::Add), + InstanceMethod("remove", &Spellchecker::Remove), + }); + + exports.Set("Spellchecker", func); + return exports; } -} // namespace +Napi::Object Init(Napi::Env env, Napi::Object exports) { + return Spellchecker::Init(env, exports); +} -NODE_MODULE(spellchecker, Init) +NODE_API_MODULE(spellchecker, Init) diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..a86c186 --- /dev/null +++ b/src/main.h @@ -0,0 +1,28 @@ +#ifndef SRC_MAIN_H_ +#define SRC_MAIN_H_ + +#include "napi.h" +#include "spellchecker.h" + +class Spellchecker : public Napi::ObjectWrap { + public: + static Napi::Object Init(Napi::Env env, Napi::Object exports); + explicit Spellchecker(const Napi::CallbackInfo& info); + ~Spellchecker(); + + Napi::Value SetSpellcheckerType(const Napi::CallbackInfo& info); + Napi::Value SetDictionary(const Napi::CallbackInfo& info); + Napi::Value IsMisspelled(const Napi::CallbackInfo& info); + Napi::Value CheckSpelling(const Napi::CallbackInfo& info); + void CheckSpellingAsync(const Napi::CallbackInfo& info); + void Add(const Napi::CallbackInfo& info); + void Remove(const Napi::CallbackInfo& info); + Napi::Value GetAvailableDictionaries(const Napi::CallbackInfo& info); + Napi::Value GetCorrectionsForMisspelling(const Napi::CallbackInfo& info); + + private: + spellchecker::SpellcheckerImplementation* impl; + void EnsureLoadedImplementation(); +}; + +#endif // SRC_MAIN_H_ diff --git a/src/spellchecker_hunspell.cc b/src/spellchecker_hunspell.cc index dbeb218..81532d8 100644 --- a/src/spellchecker_hunspell.cc +++ b/src/spellchecker_hunspell.cc @@ -89,7 +89,7 @@ bool HunspellSpellchecker::IsMisspelled(const std::string& word) { } // If the word is too long, then don't do anything. - if (word.length() > MAX_UTF8_BUFFER) { + if (word.size() > MAX_UTF8_BUFFER) { return false; } diff --git a/src/spellchecker_win.cc b/src/spellchecker_win.cc index ec250dc..a9b1a58 100644 --- a/src/spellchecker_win.cc +++ b/src/spellchecker_win.cc @@ -360,7 +360,7 @@ std::vector WindowsSpellchecker::GetCorrectionsForMisspelling(const return std::vector(); } - std::wstring& wword = ToWString(word); + std::wstring wword = ToWString(word); IEnumString* words = NULL; HRESULT hr = this->currentSpellchecker->Suggest(wword.c_str(), &words); diff --git a/src/worker.cc b/src/worker.cc index c523bcf..c04f96c 100644 --- a/src/worker.cc +++ b/src/worker.cc @@ -1,16 +1,18 @@ #include "worker.h" -#include "nan.h" +#include "napi.h" #include "spellchecker.h" #include #include #include +using namespace spellchecker; + CheckSpellingWorker::CheckSpellingWorker( std::vector&& corpus, SpellcheckerImplementation* impl, - Nan::Callback* callback + Napi::Function& callback ) : AsyncWorker(callback), corpus(std::move(corpus)), impl(impl) { // No-op @@ -26,21 +28,19 @@ void CheckSpellingWorker::Execute() { misspelled_ranges = view->CheckSpelling(corpus.data(), corpus.size()); } -void CheckSpellingWorker::HandleOKCallback() { - Nan::HandleScope scope; +void CheckSpellingWorker::OnOK() { + Napi::Env env = Env(); + Napi::HandleScope scope(env); - v8::Local context = Nan::GetCurrentContext(); - Local result = Nan::New(); - for (auto iter = misspelled_ranges.begin(); iter != misspelled_ranges.end(); ++iter) { - size_t index = iter - misspelled_ranges.begin(); - uint32_t start = iter->start, end = iter->end; + Napi::Array result = Napi::Array::New(env); + for (size_t index = 0; index < misspelled_ranges.size(); ++index) { + const MisspelledRange& range = misspelled_ranges[index]; - Local misspelled_range = Nan::New(); - misspelled_range->Set(context, Nan::New("start").ToLocalChecked(), Nan::New(start)); - misspelled_range->Set(context, Nan::New("end").ToLocalChecked(), Nan::New(end)); - result->Set(context, index, misspelled_range); + Napi::Object misspelled_range = Napi::Object::New(env); + misspelled_range.Set("start", Napi::Number::New(env, range.start)); + misspelled_range.Set("end", Napi::Number::New(env, range.end)); + result.Set(index, misspelled_range); } - Local argv[] = { Nan::Null(), result }; - callback->Call(2, argv); + Callback().Call({ env.Null(), result }); } diff --git a/src/worker.h b/src/worker.h index 5003f9f..cb62c8b 100644 --- a/src/worker.h +++ b/src/worker.h @@ -1,25 +1,22 @@ #ifndef WORKER_H #define WORKER_H -#include "nan.h" +#include "napi.h" #include "spellchecker.h" #include -using namespace spellchecker; -using namespace v8; - -class CheckSpellingWorker : public Nan::AsyncWorker { +class CheckSpellingWorker : public Napi::AsyncWorker { public: - CheckSpellingWorker(std::vector &&corpus, SpellcheckerImplementation* impl, Nan::Callback* callback); + CheckSpellingWorker(std::vector&& corpus, spellchecker::SpellcheckerImplementation* impl, Napi::Function& callback); ~CheckSpellingWorker(); void Execute(); - void HandleOKCallback(); + void OnOK(); private: const std::vector corpus; - SpellcheckerImplementation* impl; - std::vector misspelled_ranges; + spellchecker::SpellcheckerImplementation* impl; + std::vector misspelled_ranges; }; #endif