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..ae4037f 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,11 @@ "test": "jasmine-focused --captureExceptions --coffee 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 index ff25c8b..c5dc50f 100644 --- a/spec/spellchecker-spec.coffee +++ b/spec/spellchecker-spec.coffee @@ -33,6 +33,16 @@ invalidLength4BytePair = [invalidLength4Byte, invalidLength4Byte].join " " spellType = null 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. +isDictionaryAvailable = (fixture, locale) -> + return true if spellType is 'hunspell' + tag = if spellType is 'win' then locale.replace('_', '-') else locale + tag in fixture.getAvailableDictionaries() + for testAlwaysUseHunspell in [true, false] describe 'SpellChecker', -> describe '.setDictionary', -> @@ -79,6 +89,8 @@ for testAlwaysUseHunspell in [true, false] expect(@fixture.isMisspelled('Kine')).toBe true it 'returns true if Latin German word is misspelled with UTF-8 file', -> + return unless isDictionaryAvailable(@fixture, 'de_DE') + expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true expect(@fixture.isMisspelled('Kine')).toBe true @@ -90,6 +102,8 @@ for testAlwaysUseHunspell in [true, false] expect(@fixture.isMisspelled('Nacht')).toBe false it 'returns false if Latin German word is not misspelled with UTF-8 file', -> + return unless isDictionaryAvailable(@fixture, 'de_DE') + expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true expect(@fixture.isMisspelled('Nacht')).toBe false @@ -101,6 +115,8 @@ for testAlwaysUseHunspell in [true, false] expect(@fixture.isMisspelled('möchtzn')).toBe true it 'returns true if Unicode German word is misspelled with UTF-8 file', -> + return unless isDictionaryAvailable(@fixture, 'de_DE') + expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true expect(@fixture.isMisspelled('möchtzn')).toBe true @@ -112,6 +128,8 @@ for testAlwaysUseHunspell in [true, false] expect(@fixture.isMisspelled('vermöchten')).toBe false it 'returns false if Unicode German word is not misspelled with UTF-8 file', -> + return unless isDictionaryAvailable(@fixture, 'de_DE') + expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true expect(@fixture.isMisspelled('vermöchten')).toBe false @@ -208,6 +226,8 @@ for testAlwaysUseHunspell in [true, false] ] it 'returns an array of character ranges of misspelled German words with UTF-8 file', -> + return unless isDictionaryAvailable(@fixture, 'de_DE') + expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true string = 'Kein Kine vermöchten möchtzn' @@ -218,6 +238,8 @@ for testAlwaysUseHunspell in [true, false] ] it 'returns an array of character ranges of misspelled French words', -> + return unless isDictionaryAvailable(@fixture, 'fr') + expect(@fixture.setDictionary('fr', dictionaryDirectory)).toBe true string = 'Française Françoize' @@ -427,10 +449,13 @@ for testAlwaysUseHunspell in [true, false] 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) + if spellType is 'win' + expect(corrections[0] is 'cheesy' or corrections[0] is 'cheeses').toEqual(true) + else + 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. @@ -443,11 +468,18 @@ for testAlwaysUseHunspell in [true, false] expect(corrections[0]).toEqual(correction) it 'returns an array of possible corrections for a correct Latin German word with UTF-8 file', -> + return unless isDictionaryAvailable(@fixture, 'de_DE') + expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true - correction = ['Acht', 'Macht', 'Acht'][spellIndex] + correction = ['Acht', 'Nicht', 'Acht'][spellIndex] corrections = @fixture.getCorrectionsForMisspelling('Nacht') expect(corrections.length).toBeGreaterThan 0 - expect(corrections[0]).toEqual(correction) + 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 a incorrect Latin German word with ISO8859-1 file', -> # de_DE_frami is invalid outside of Hunspell dictionaries. @@ -457,9 +489,16 @@ for testAlwaysUseHunspell in [true, false] correction = ['Acht', 'Nicht', 'Acht'][spellIndex] corrections = @fixture.getCorrectionsForMisspelling('Nacht') expect(corrections.length).toBeGreaterThan 0 - expect(corrections[0]).toEqual(correction) + 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 a incorrect Latin German word with UTF-8 file', -> + return unless isDictionaryAvailable(@fixture, 'de_DE') + expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true correction = ['Acht', 'SEE BELOW', 'Acht'][spellIndex] corrections = @fixture.getCorrectionsForMisspelling('Nacht') @@ -483,6 +522,8 @@ for testAlwaysUseHunspell in [true, false] expect(corrections[0]).toEqual(correction) it 'returns an array of possible corrections for correct Unicode German word with UTF-8 file', -> + return unless isDictionaryAvailable(@fixture, 'de_DE') + expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true correction = ['vermöchten', 'vermochten', 'vermochte'][spellIndex] corrections = @fixture.getCorrectionsForMisspelling('vermöchten') @@ -500,6 +541,8 @@ for testAlwaysUseHunspell in [true, false] expect(corrections[0]).toEqual(correction) it 'returns an array of possible corrections for incorrect Unicode German word with UTF-8 file', -> + return unless isDictionaryAvailable(@fixture, 'de_DE') + expect(@fixture.setDictionary('de_DE', dictionaryDirectory)).toBe true correction = ['möchten', 'möchten', 'möchten'][spellIndex] corrections = @fixture.getCorrectionsForMisspelling('möchtzn') @@ -507,6 +550,8 @@ for testAlwaysUseHunspell in [true, false] expect(corrections[0]).toEqual(correction) it 'returns an array of possible corrections for correct Unicode French word', -> + return unless isDictionaryAvailable(@fixture, 'fr') + expect(@fixture.setDictionary('fr', dictionaryDirectory)).toBe true correction = ['Françoise', 'Françoise', 'française'][spellIndex] corrections = @fixture.getCorrectionsForMisspelling('Française') @@ -514,6 +559,8 @@ for testAlwaysUseHunspell in [true, false] expect(corrections[0]).toEqual(correction) it 'returns an array of possible corrections for incorrect Unicode French word', -> + return unless isDictionaryAvailable(@fixture, 'fr') + expect(@fixture.setDictionary('fr', dictionaryDirectory)).toBe true correction = ['Françoise', 'Françoise', 'Françoise'][spellIndex] corrections = @fixture.getCorrectionsForMisspelling('Françoize') @@ -561,8 +608,8 @@ for testAlwaysUseHunspell in [true, false] @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 + # 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() @@ -587,8 +634,8 @@ for testAlwaysUseHunspell in [true, false] 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. + # '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 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