├── .gitignore ├── LICENSE ├── README.md ├── default-config.json ├── en_US-tech-industry.dic ├── index.js ├── package.json └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 sparkart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `quality-docs` 2 | 3 | ![Screenshot](./screenshot.png) 4 | 5 | > A CLI tool to check the quality of writing in your project's markdown or plain text documentation. 6 | 7 | This tool uses [`retext`](https://github.com/wooorm/retext) to check the quality of writing in your project's documentation using these plugins; 8 | 9 | - [`retext-equality`](https://github.com/wooorm/retext-equality) warns on insensitive, inconsiderate language. 10 | - [`retext-intensify`](https://github.com/wooorm/retext-intensify) warns on filler, weasel and hedge words. 11 | - [`remark-lint`](https://github.com/wooorm/remark-lint) checks for proper markdown formatting. 12 | - [`retext-readability`](https://github.com/wooorm/retext-readability) checks the reading level of the whole document. 13 | - [`retext-simplify`](https://github.com/wooorm/retext-simplify) warns on complicated phrases. 14 | - [`retext-spell`](https://github.com/wooorm/retext-spell) checks spelling against a US English dictionary and [custom dictionary](#custom-dictionary). 15 | 16 | ## Table of Contents 17 | 18 | - [Installation](#installation) 19 | - [Usage](#usage) 20 | - [Options](#options) 21 | - [Reports](#reports) 22 | - [Custom Dictionary](#custom-dictionary) 23 | - [Changing Default Config](#changing-default-config) 24 | - [Troubleshooting](#troubleshooting) 25 | - [Contribute](#contribute) 26 | - [License](#license) 27 | 28 | ## Installation 29 | 30 | Install the tool globally with [npm](https://npmjs.com/) to use the CLI. 31 | 32 | ```bash 33 | npm install -g quality-docs 34 | ``` 35 | 36 | ## Usage 37 | 38 | ### CLI 39 | 40 | The CLI accepts one argument which is a glob of the documentation files you want to check. For example, to recursively check every `.md` file in your project; 41 | 42 | ```bash 43 | quality-docs {,**/}*.md 44 | ``` 45 | 46 | #### Silent Mode 47 | 48 | The `-s`, `--silent` flag enables silent mode which mutes warnings. Fatal errors are displayed in silent mode. 49 | 50 | #### Config 51 | 52 | The `-c`, `--config` flag passes in a JSON file with a custom config. The config is combined with `default-config.json` and the custom config overrides the defaults. 53 | 54 | ```bash 55 | quality-docs {,**/}*.md --config custom-config.json 56 | ``` 57 | 58 | The override uses this format (without comments): 59 | 60 | ```js 61 | 62 | { 63 | "dictionaries": "sparkart.dic", // Path to custom dictionary file, relative to config file 64 | "rules": { // Turn rules on/off or change their severity 65 | "filler": {"severity": "warn"}, 66 | "hedge": {"severity": "suggest"}, 67 | "list-item-content-indent": false, 68 | "no-auto-link-without-protocol": false, 69 | "retext-readability": { 70 | "age": 18, 71 | "minWords": 7, 72 | "severity": "fatal" 73 | }, 74 | "weasel": {"severity": "suggest"} 75 | }, 76 | "ignore": [ // Words or phrases to ignore 77 | "can", 78 | "forward", 79 | "found", 80 | "start", 81 | "started", 82 | "up" 83 | ], 84 | "noIgnore": [ // Words or phrases ignored by default to not ignore 85 | "require", 86 | "transmit" 87 | ] 88 | } 89 | ``` 90 | 91 | #### Ignore 92 | 93 | When used along with the config flag, the `-i`, `--ignore` flag adds a word to the config file's ignore list. Example; 94 | 95 | ```bash 96 | $ quality-docs {,**/}*.md --config custom-config.json --ignore irregardless 97 | Added 'irregardless' to ignore list. Don't forget to commit the changes to custom-config.json. 98 | ``` 99 | 100 | ### Reports 101 | 102 | The tool uses [`vfile-reporter`](https://github.com/wooorm/vfile-reporter) to print a report of writing quality issues with their location and description. For example, a spelling error in `README.md` from line 76 column 119 to line 76 column 124 prints; 103 | 104 | ```bash 105 | README.md 106 | 76:119-76:124 error thier is misspelled spelling 107 | ``` 108 | 109 | ### Custom Dictionary 110 | 111 | By default, `quality-docs` spell checks documents against [a US English dictionary](https://github.com/wooorm/dictionaries/dictionaries/en_US). To extend the built in dictionary with custom English terms related to your project(s), add a [hunspell format](http://linux.die.net/man/4/hunspell) `.dic` file to your project, and reference it with the `customDictionary` key in a custom config JSON file. See [`en_US-tech-industry.dic`](./en_US-tech-industry.dic) for an example. (Note: `quality-docs` uses the [US English affix file](https://github.com/wooorm/dictionaries/blob/master/dictionaries/en_US/index.aff) to check for valid variants of dictionary words. Non-English characters or prefix/suffix rules are not supported.) 112 | 113 | ### Changing Default Config 114 | 115 | The `quality-docs` CLI ships with an opinionated configuration to improve your writing. If you want to override the defaults of [the `retext` plugins used by this tool](#quality-docs), we recommend one of these three options; 116 | 117 | 1. [Exclude documentation files from the glob argument](http://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm#STANDARD-WILDCARDS). 118 | 2. Use [the `--config` flag](#config) to pass in a JSON file with default configuration overrides. 119 | 3. Use [`remark-message-control` marks](https://github.com/wooorm/remark-message-control) to turn on/off specific rules for individual documents or text nodes. 120 | 121 | ## Troubleshooting 122 | 123 | Here is a list of common confusing issues `quality-docs` flags, and how to resolve them; 124 | 125 | ### Names are flagged as misspelled 126 | 127 | If it's a common name, add it to a [custom dictionary](#custom-dictionary). If it isn't common or you don't want to add it to the dictionary, [add it to the `ignore` array in a config override file](#config). 128 | 129 | ### Other non-dictionary terms are flagged as misspelled 130 | 131 | Any non-dictionary terms in quotes are ignored. If the word is a literal value such as a variable name or package name, put it in backticks; `` `quality-docs` ``. If it's a made-up word or other literal representation of a non-dictionary term, use `"buttonator"` or `'buttonator'`. 132 | 133 | ### Table cells are flagged as being incorrectly padded 134 | 135 | First, check [the rules for formatting table cells in markdown](https://github.com/wooorm/remark-lint/blob/master/doc/rules.md#table-cell-padding). If everything is correct, check for empty table cells, which throw off the linter. Try adding content to blank cells; `n/a` or `(none)`. 136 | 137 | ### Another issue is being incorrectly flagged 138 | 139 | Try using [the methods provided to change the default config](#changing-default-config) to suit your preferences. If custom config doesn't resolve the problem, [file an issue](https://github.com/SparkartGroupInc/quality-docs/issues) including the text of the markdown file you're checking and the error you're seeing in the output. 140 | 141 | ## Contribute 142 | 143 | [Open an issue](https://github.com/sparkartgroupinc/quality-docs/issues/new) to report bugs or ask questions. [Open a PR](https://github.com/sparkartgroupinc/quality-docs/pulls) to contribute. Run `npm run test` to make sure contributions pass the included tests. 144 | 145 | ## License 146 | 147 | [MIT © 2016 Sparkart Group Inc.](./LICENSE) 148 | -------------------------------------------------------------------------------- /default-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "dictionaries": ["en_US-tech-industry.dic"], 3 | "rules": { 4 | "blockquote-indentation": {"severity": "fatal"}, 5 | "checkbox-character-style": {"severity": "fatal"}, 6 | "checkbox-content-indent": {"severity": "fatal"}, 7 | "code-block-style": {"severity": "fatal"}, 8 | "definition-spacing": {"severity": "fatal"}, 9 | "fenced-code-flag": {"severity": "fatal"}, 10 | "filler": {"severity": "suggest"}, 11 | "final-definition": {"severity": "fatal"}, 12 | "final-newline": {"severity": "fatal"}, 13 | "first-heading-level": {"severity": "fatal"}, 14 | "hard-break-spaces": {"severity": "fatal"}, 15 | "heading-increment": {"severity": "fatal"}, 16 | "heading-style": {"severity": "fatal"}, 17 | "hedge": {"severity": "suggest"}, 18 | "link-title-style": {"severity": "fatal"}, 19 | "list-item-indent": false, 20 | "list-item-spacing": false, 21 | "maximum-line-length": false, 22 | "no-auto-link-without-protocol": {"severity": "fatal"}, 23 | "no-duplicate-definitions": {"severity": "fatal"}, 24 | "no-duplicate-headings": {"severity": "fatal"}, 25 | "no-emphasis-as-heading": {"severity": "fatal"}, 26 | "no-file-name-articles": false, 27 | "no-file-name-consecutive-dashes": false, 28 | "no-file-name-irregular-characters": {"severity": "fatal"}, 29 | "no-file-name-mixed-case": false, 30 | "no-file-name-outer-dashes": false, 31 | "no-heading-content-indent": {"severity": "fatal"}, 32 | "no-heading-indent": {"severity": "fatal"}, 33 | "no-html": {"severity": "fatal"}, 34 | "no-literal-urls": {"severity": "fatal"}, 35 | "no-missing-blank-lines": { "exceptTightLists": true, "severity": "fatal" }, 36 | "no-multiple-toplevel-headings": {"severity": "fatal"}, 37 | "no-table-indentation": {"severity": "fatal"}, 38 | "no-tabs": {"severity": "fatal"}, 39 | "no-undefined-references": {"severity": "fatal"}, 40 | "no-unused-definitions": {"severity": "fatal"}, 41 | "ordered-list-marker-style": {"severity": "fatal"}, 42 | "ordered-list-marker-value": {"severity": "fatal"}, 43 | "retext-equality": {"severity": "fatal"}, 44 | "retext-readability": { 45 | "age": 18, 46 | "minWords": 7, 47 | "severity": "fatal" 48 | }, 49 | "retext-simplify": {"severity": "fatal"}, 50 | "rule-style": {"severity": "fatal"}, 51 | "spelling": {"severity": "fatal"}, 52 | "table-pipe-alignment": {"severity": "fatal"}, 53 | "table-pipes": {"severity": "fatal"}, 54 | "unordered-list-marker-style": {"severity": "fatal"}, 55 | "weasel": {"severity": "suggest"} 56 | }, 57 | "ignore": [ 58 | "address", 59 | "attempt", 60 | "capability", 61 | "combined", 62 | "contains", 63 | "effect", 64 | "expiration", 65 | "function", 66 | "host", 67 | "hosts", 68 | "initial", 69 | "minimize", 70 | "minimum", 71 | "multiple", 72 | "option", 73 | "previous", 74 | "request", 75 | "require", 76 | "requires", 77 | "submit", 78 | "transmit", 79 | "try", 80 | "type" 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /en_US-tech-industry.dic: -------------------------------------------------------------------------------- 1 | 120 2 | 1Password/M 3 | Alibaba/M 4 | Apifier/M 5 | AWS/M 6 | Browserify/M 7 | BrowserSync/M 8 | CDN/S 9 | CSS 10 | CircleCI/M 11 | CLI/SM 12 | Clojure/M 13 | Cloudant/M 14 | CloudFlare/M 15 | CloudFront/M 16 | CloudWatch/M 17 | CouchDB/M 18 | CSV/S 19 | Cyberduck/M 20 | DDoS/S 21 | DNS 22 | EC2/M 23 | ECMA/M 24 | ES6/M 25 | ESLint/M 26 | Firefox/M 27 | GB/Sc 28 | Heroku/M 29 | Homebrew/M 30 | HTML 31 | HTTP 32 | HTTPS 33 | IAM/M 34 | IP/S 35 | Jetpack/M 36 | JSON 37 | K/Sc 38 | KB/Sc 39 | LiveReload/M 40 | MailChimp/M 41 | MB/Sc 42 | MongoDB/M 43 | MySQL/M 44 | Namecheap/M 45 | Node.js/M 46 | PR/S 47 | RDS/M 48 | RSS 49 | S3/M 50 | SAML 51 | SEO 52 | SES 53 | SFTP 54 | SLA/S 55 | SMS/S 56 | SSH 57 | SSL 58 | SSO 59 | Subuser/S 60 | TLS/S 61 | Trello/M 62 | UI/S 63 | UTF 64 | VPS/S 65 | WP-CLI 66 | WordPress/M 67 | ad-hoc 68 | am/c 69 | analytic/S 70 | async 71 | backtick/SD 72 | balancer/S 73 | boolean/S 74 | buildpack/S 75 | capability/S 76 | changelog/S 77 | config/S 78 | deliverability 79 | dev/S 80 | dyno/S 81 | expiration/S 82 | filename/S 83 | fulfillment/S 84 | git/M 85 | group/JSZGMDRB 86 | hostname/S 87 | http/S 88 | hunspell 89 | initial 90 | js 91 | keychain/S 92 | lint/R 93 | localhost 94 | lookup/S 95 | metadata 96 | nameserver/S 97 | nginx/M 98 | npm/M 99 | pm/c 100 | pre 101 | readme/S 102 | receive/UDRSZGB 103 | redemption/S 104 | repo/S 105 | reseller 106 | subdomain/S 107 | timesheet/S 108 | transactional 109 | uptime 110 | versioning 111 | webhook/S 112 | webpack/M 113 | wildcard/S 114 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const _ = require('lodash'); 3 | const argv = require('minimist')(process.argv.slice(2)); 4 | const chalk = require('chalk'); 5 | const concise = require('retext-intensify'); 6 | const control = require('remark-message-control'); 7 | const en_US = require('dictionary-en-us'); 8 | const equality = require('retext-equality'); 9 | const fs = require('fs'); 10 | const lint = require('remark-lint'); 11 | const map = require("async/map"); 12 | const meow = require('meow'); 13 | const path = require('path'); 14 | const readability = require('retext-readability'); 15 | const remark = require('remark'); 16 | const remark2retext = require('remark-retext'); 17 | const report = require('vfile-reporter'); 18 | const retext = require('retext'); 19 | const simplify = require('retext-simplify'); 20 | const spell = require('retext-spell'); 21 | const toString = require('nlcst-to-string'); 22 | const toVFile = require('to-vfile'); 23 | const visit = require('unist-util-visit'); 24 | 25 | const cli = meow(` 26 | Usage 27 | $ quality-docs 28 | 29 | Options 30 | -c, --config A JSON config file to override default linting rules. 31 | -i, --ignore A word or phrase to ignore and add to the config file's list. 32 | -s, --silent Silent mode. Mutes warnings and only shows fatal errors. 33 | -v, --verbose Prints which config is used. 34 | 35 | Examples 36 | $ quality-docs --config custom-config.json 37 | `, { 38 | alias: { 39 | c: 'config', 40 | i: 'ignore', 41 | s: 'silent', 42 | v: 'verbose' 43 | } 44 | }); 45 | 46 | var silent = cli.flags.silent || false; 47 | 48 | // Build array of files that match input glob 49 | var docFiles = []; 50 | cli.input.forEach((file) => { if (!file.includes('*')) docFiles.push(file); }); 51 | if (docFiles.length <= 0) { 52 | console.warn('No files found to lint.'); 53 | process.exit(1); 54 | } 55 | 56 | // Use --config file if provided, otherwise defaults 57 | var config = {}; 58 | var customConfig = {}; 59 | var defaultConfig = require('./default-config.json'); 60 | 61 | defaultConfig.dictionaries.forEach((dictPath, index, arr) => { 62 | arr[index] = path.join(__dirname, dictPath); 63 | }); 64 | 65 | 66 | if (!cli.flags.config) { 67 | config = defaultConfig; 68 | } else { 69 | customConfig = JSON.parse(fs.readFileSync(cli.flags.config, 'utf8')); 70 | 71 | // If --config and --ignore are specified, update the config with new ignore 72 | if (customConfig.ignore && cli.flags.ignore) { 73 | var isValidString = /^[ A-Za-z0-9_@./#&+-]*$/.test(cli.flags.ignore); 74 | var isUnique = !_.includes(customConfig.ignore, cli.flags.ignore); 75 | if (isValidString && isUnique) { 76 | customConfig.ignore.push(cli.flags.ignore); 77 | customConfig.ignore.sort(); 78 | fs.writeFile(cli.flags.rules, JSON.stringify(rules, null, 2), function(err) { 79 | if(err) { 80 | return console.log(err); 81 | } 82 | console.log('Added \'' + cli.flags.ignore + '\' to ignore list. Don\'t forget to commit the changes to ' + cli.flags.config + '.'); 83 | }); 84 | } else { 85 | console.log('Could not add \'' + cli.flags.ignore + '\' to ignore list. Please add it manually.'); 86 | } 87 | } 88 | 89 | // If custom dictionaries are provided, prepare their paths 90 | if (customConfig.dictionaries) { 91 | // Convert dictionaries string to an array 92 | var customDict = customConfig.dictionaries; 93 | if (typeof customDict === 'string' || customDict instanceof String) { 94 | customConfig.dictionaries = [customDict]; 95 | } 96 | 97 | // Add cwd to custom dictionary paths 98 | customConfig.dictionaries.forEach((dictionaryPath) => { 99 | dictionaryPath = process.cwd() + dictionaryPath; 100 | }); 101 | } else { 102 | // Remove empty dictonaries key so it doesn't override default config 103 | delete customConfig.dictionaries; 104 | } 105 | 106 | // Merge default and custom rules, preferring customRules and concating arrays 107 | config = _.mergeWith(defaultConfig, customConfig, (objValue, srcValue)=>{ 108 | if (_.isArray(objValue)) { 109 | return _.uniq(objValue.concat(srcValue)); 110 | } 111 | }); 112 | 113 | } 114 | 115 | var dictionary = en_US; 116 | 117 | var myReadFile = function (dictPath, cb) { 118 | fs.readFile(dictPath, function (err, buffer) { 119 | cb(err, !err && buffer); 120 | }); 121 | } 122 | 123 | if (config.dictionaries && config.dictionaries.length >= 1) { 124 | dictionary = function (cb) { 125 | en_US(function(err, primary) { 126 | map(config.dictionaries, myReadFile, function(err, results){ 127 | results.unshift(primary.dic); 128 | var combinedDictionaries = Buffer.concat(results); 129 | cb(err, !err && {aff: primary.aff, dic: combinedDictionaries}); 130 | }); 131 | }); 132 | } 133 | } 134 | 135 | var lintRules = _.mapValues(config.rules, (value)=>{ 136 | var keys = Object.keys(value); 137 | if (_.isBoolean(value)) return value; 138 | if (value.hasOwnProperty('severity')) { 139 | if (Object.keys(value).length == 1) return true; 140 | var newValue = {}; 141 | for (var prop in value) { 142 | if (prop !== 'severity') newValue[prop] = value[prop]; 143 | } 144 | return newValue; 145 | } 146 | return value; 147 | }); 148 | var fatalRules = _.keys(_.pickBy(config.rules, function(value) { 149 | return value.severity == 'fatal'; 150 | })); 151 | var warnRules = _.keys(_.pickBy(config.rules, function(value) { 152 | return (value && (value.severity == 'warn' || !value.severity)); 153 | })); 154 | var suggestRules = _.keys(_.pickBy(config.rules, function(value) { 155 | return value.severity == 'suggest'; 156 | })); 157 | var readabilityConfig = config.rules['retext-readability']; 158 | var ignoreWords = _.difference(config.ignore, config.noIgnore); 159 | 160 | if (cli.flags.verbose) { 161 | console.log(chalk.red.underline('Fatal rules:\n'), chalk.red(fatalRules)); 162 | console.log(chalk.yellow.underline('Warnings:\n'), chalk.yellow(warnRules)); 163 | console.log(chalk.gray.underline('Suggestions:\n'), chalk.gray(suggestRules)); 164 | console.log(chalk.green.underline('Ignoring:\n'), chalk.green(ignoreWords)); 165 | } 166 | 167 | map(docFiles, toVFile.read, function(err, files){ 168 | var hasErrors = false; 169 | 170 | map(files, checkFile, function(err, results) { 171 | console.log(report(err || results, {silent: silent})); 172 | 173 | // Check for errors and exit with error code if found 174 | results.forEach((result) => { 175 | result.messages.forEach((message) => { 176 | if (message.fatal) hasErrors = true; 177 | }); 178 | }); 179 | if (hasErrors) process.exit(1); 180 | 181 | }) 182 | 183 | function checkFile(file, cb) { 184 | remark() 185 | .use(lint, lintRules || {}) 186 | .use(remark2retext, retext() // Convert markdown to plain text 187 | .use(readability, readabilityConfig || {}) 188 | .use(simplify, {ignore: ignoreWords || []}) 189 | .use(equality, {ignore: ignoreWords || []}) 190 | .use(concise, {ignore: ignoreWords || []}) 191 | .use(function () { 192 | return function (tree) { 193 | visit(tree, 'WordNode', function (node, index, parent) { 194 | var word = toString(node); 195 | 196 | var unitArr = config.units || ['GB', 'MB', 'KB', 'K', 'am', 'pm', 'in', 'ft']; 197 | unitArr = unitArr.concat(['-', 'x']); // Add ranges and dimensions to RegExp 198 | var units = unitArr.join('|'); 199 | 200 | // Ignore email addresses and the following types of non-words: 201 | // 500GB, 8am-6pm, 10-11am, 1024x768, 3x5in, etc 202 | var unitFilter = new RegExp('^\\d+(' + units + ')+\\d*(' + units + ')*$','i'); 203 | var emailFilter = new RegExp('^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$', 'i'); 204 | if (emailFilter.test(word) || unitFilter.test(word)) { 205 | parent.children[index] = { 206 | type: 'SourceNode', 207 | value: word, 208 | position: node.position 209 | }; 210 | } 211 | 212 | }); 213 | }; 214 | }) 215 | .use(spell, { 216 | dictionary: dictionary, 217 | ignore: ignoreWords || [], 218 | ignoreLiteral: true 219 | }) 220 | ) 221 | .use(control, {name: 'quality-docs', source: [ 222 | 'remark-lint', 223 | 'retext-readability', 224 | 'retext-simplify', 225 | 'retext-equality', 226 | 'retext-intensify' 227 | ]}) 228 | .process(file, function (err, results) { 229 | var filteredMessages = []; 230 | results.messages.forEach((message) => { 231 | var hasFatalRuleId = _.includes(fatalRules, message.ruleId); 232 | var hasFatalSource = _.includes(fatalRules, message.source); 233 | var hasSuggestedRuleId = _.includes(suggestRules, message.ruleId); 234 | var hasSuggestedSource = _.includes(suggestRules, message.source); 235 | 236 | if (suggestRules && (hasSuggestedRuleId || hasSuggestedSource)) { 237 | message.message = message.message.replace( /don\’t use “(.*)”/ig, (match, word) => { 238 | return 'Use “' + word + '” sparingly'; 239 | }); 240 | delete message.fatal; 241 | } 242 | 243 | if (fatalRules && (hasFatalRuleId || hasFatalSource)) { 244 | message.fatal = true; 245 | } 246 | 247 | filteredMessages.push(message); 248 | }); 249 | results.messages = filteredMessages; 250 | cb(null, results); 251 | }); 252 | } 253 | }); 254 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quality-docs", 3 | "version": "3.2.0", 4 | "description": "A CLI tool to evaluate the quality of writing in your project's markdown files.", 5 | "main": "index.js", 6 | "bin": { 7 | "quality-docs": "./index.js" 8 | }, 9 | "scripts": { 10 | "test": "node index.js {,**/}*.md --config default-config.json" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/SparkartGroupInc/quality-docs.git" 15 | }, 16 | "keywords": [ 17 | "readability", 18 | "documentation", 19 | "quality", 20 | "CI" 21 | ], 22 | "author": "Josiah Sprague (josiah.sprague@gmail.com)", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/SparkartGroupInc/quality-docs/issues" 26 | }, 27 | "homepage": "https://github.com/SparkartGroupInc/quality-docs#readme", 28 | "dependencies": { 29 | "async": "^2.0.1", 30 | "chalk": "^1.1.3", 31 | "dictionary-en-us": "^1.0.2", 32 | "lodash": "^4.14.0", 33 | "meow": "^3.7.0", 34 | "minimist": "^1.2.0", 35 | "nlcst-to-string": "^2.0.0", 36 | "remark": "^7.0.0", 37 | "remark-lint": "^6.0.0", 38 | "remark-message-control": "^4.0.0", 39 | "remark-retext": "^3.0.0", 40 | "retext": "^5.0.0", 41 | "retext-equality": "^3.0.0", 42 | "retext-intensify": "^3.0.0", 43 | "retext-readability": "^4.0.0", 44 | "retext-simplify": "^4.0.0", 45 | "retext-spell": "^2.0.0", 46 | "to-vfile": "^2.1.1", 47 | "unist-util-visit": "^1.1.0", 48 | "vfile-reporter": "^3.0.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkartgroup/quality-docs/14b1560f06d232d36163c52af9c8e7da507de309/screenshot.png --------------------------------------------------------------------------------