From 030a21009b7eb71f790e0a4f17f868b94db6cf3e Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sun, 18 Jul 2021 13:11:37 +0200 Subject: [PATCH] Add JSDoc based types --- .gitignore | 1 + index.js | 67 +++++++++++++++++++++++---------------------------- package.json | 17 ++++++++++++- patterns.js | 9 +++++++ test.js | 51 ++++++++++++++++++++------------------- tsconfig.json | 16 ++++++++++++ 6 files changed, 98 insertions(+), 63 deletions(-) create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 4fb0e32..45a3cfc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ coverage/ .DS_Store +*.d.ts *.log yarn.lock diff --git a/index.js b/index.js index ed7e72a..8656802 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,10 @@ +/** + * @typedef Options + * Configuration. + * @property {string[]} [ignore] + * Phrases *not* to warn about (rule IDs). + */ + import {toString} from 'nlcst-to-string' import {quotation} from 'quotation' import {search} from 'nlcst-search' @@ -6,52 +13,38 @@ import {patterns} from './patterns.js' const source = 'retext-simplify' -const list = Object.keys(patterns) - -export default function retextSimplify(options) { - const ignore = (options || {}).ignore || [] +const keys = Object.keys(patterns) - return transformer +/** + * Plugin to check phrases for simpler alternatives. + * + * @type {import('unified').Plugin<[Options?]>} + */ +export default function retextSimplify(options = {}) { + const ignore = options.ignore || [] + const searches = + ignore.length > 0 ? keys.filter((d) => !ignore.includes(d)) : keys - function transformer(tree, file) { - search(tree, list, finder) - - function finder(match, index, parent, phrase) { - const id = phrase.replace(/\s+/g, '-').toLowerCase() + return (tree, file) => { + search(tree, searches, (match, _, _1, phrase) => { const pattern = patterns[phrase] - const expected = pattern.replace const actual = toString(match) - let reason - - if (ignore.includes(id)) { - return - } - - if (pattern.omit && expected.length === 0) { - reason = 'Remove ' + quotation(actual, '`') - } else { - reason = - 'Replace ' + - quotation(actual, '`') + - ' with ' + - quotation(expected, '`').join(', ') - - if (pattern.omit) { - reason += ', or remove it' - } - } + const expected = pattern.replace Object.assign( file.message( - reason, - { - start: pointStart(match[0]), - end: pointEnd(match[match.length - 1]) - }, - [source, id].join(':') + pattern.omit && expected.length === 0 + ? 'Remove ' + quotation(actual, '`') + : 'Replace ' + + quotation(actual, '`') + + ' with ' + + quotation(expected, '`').join(', ') + + (pattern.omit ? ', or remove it' : ''), + {start: pointStart(match[0]), end: pointEnd(match[match.length - 1])}, + [source, phrase.replace(/\s+/g, '-').toLowerCase()].join(':') ), {actual, expected} ) - } + }) } } diff --git a/package.json b/package.json index 9679f4a..10beec0 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,11 @@ "sideEffects": false, "type": "module", "main": "index.js", + "types": "index.d.ts", "files": [ + "index.d.ts", "index.js", + "patterns.d.ts", "patterns.js" ], "dependencies": { @@ -39,22 +42,28 @@ "nlcst-to-string": "^3.0.0", "object-keys": "^1.0.9", "quotation": "^2.0.0", + "unified": "^10.0.0", "unist-util-position": "^4.0.0" }, "devDependencies": { + "@types/tape": "^4.0.0", "c8": "^7.0.0", "prettier": "^2.0.0", "remark-cli": "^9.0.0", "remark-preset-wooorm": "^8.0.0", "retext": "^8.0.0", + "rimraf": "^3.0.0", "tape": "^5.0.0", + "type-coverage": "^2.0.0", + "typescript": "^4.0.0", "xo": "^0.39.0" }, "scripts": { + "build": "rimraf \"*.d.ts\" && tsc && type-coverage", "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", "test-api": "node --conditions development test.js", "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node --conditions development test.js", - "test": "npm run format && npm run test-coverage" + "test": "npm run build && npm run format && npm run test-coverage" }, "prettier": { "tabWidth": 2, @@ -71,5 +80,11 @@ "plugins": [ "preset-wooorm" ] + }, + "typeCoverage": { + "atLeast": 100, + "detail": true, + "strict": true, + "ignoreCatch": true } } diff --git a/patterns.js b/patterns.js index 053c44e..0b65eb5 100644 --- a/patterns.js +++ b/patterns.js @@ -1,3 +1,12 @@ +/** + * @typedef Pattern + * @property {string[]} replace + * @property {boolean} [omit=false] + */ + +/** + * @type {Object.} + */ export const patterns = { 'a number of': { replace: ['many', 'some'] diff --git a/test.js b/test.js index d9e7780..66d18fc 100644 --- a/test.js +++ b/test.js @@ -7,11 +7,11 @@ test('retext-simplify', (t) => { retext() .use(retextSimplify) - .process('You can utilize a shorter word.', (error, file) => { + .process('You can utilize a shorter word.') + .then((file) => { t.deepEqual( - [error].concat(JSON.parse(JSON.stringify(file.messages))), + JSON.parse(JSON.stringify(file.messages)), [ - null, { name: '1:9-1:16', message: 'Replace `utilize` with `use`', @@ -31,43 +31,44 @@ test('retext-simplify', (t) => { ], 'should emit messages' ) - }) + }, t.ifErr) retext() .use(retextSimplify) - .process('In order for this to work, clap your hands.', (error, file) => { + .process('In order for this to work, clap your hands.') + .then((file) => { t.deepEqual( - [error].concat(file.messages.map((d) => String(d))), - [null, '1:1-1:13: Replace `In order for` with `for`'], + file.messages.map((d) => String(d)), + ['1:1-1:13: Replace `In order for` with `for`'], 'should warn about wordiness' ) - }) + }, t.ifErr) retext() .use(retextSimplify) .process( - 'You can utilize a shorter word.\nBe advised, don’t do this.\nThat’s the appropriate thing to do.', - (error, file) => { - t.deepEqual( - [error].concat(file.messages.map((d) => String(d))), - [ - null, - '1:9-1:16: Replace `utilize` with `use`', - '2:1-2:11: Remove `Be advised`', - '3:12-3:23: Replace `appropriate` with `proper`, `right`, or remove it' - ], - 'should warn about simpler synonyms' - ) - } + 'You can utilize a shorter word.\nBe advised, don’t do this.\nThat’s the appropriate thing to do.' ) + .then((file) => { + t.deepEqual( + file.messages.map((d) => String(d)), + [ + '1:9-1:16: Replace `utilize` with `use`', + '2:1-2:11: Remove `Be advised`', + '3:12-3:23: Replace `appropriate` with `proper`, `right`, or remove it' + ], + 'should warn about simpler synonyms' + ) + }, t.ifErr) retext() .use(retextSimplify, {ignore: ['utilize']}) - .process('You can utilize a shorter word.', (error, file) => { + .process('You can utilize a shorter word.') + .then((file) => { t.deepEqual( - [error].concat(file.messages.map((d) => String(d))), - [null], + file.messages.map((d) => String(d)), + [], 'should not warn for ignored phrases' ) - }) + }, t.ifErr) }) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e31adf8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "include": ["*.js"], + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020"], + "module": "ES2020", + "moduleResolution": "node", + "allowJs": true, + "checkJs": true, + "declaration": true, + "emitDeclarationOnly": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "strict": true + } +}