Skip to content

Commit

Permalink
Add JSDoc based types
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Oct 20, 2021
1 parent 646929e commit d4833f8
Show file tree
Hide file tree
Showing 8 changed files with 275 additions and 179 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,5 +1,6 @@
coverage/
node_modules/
.DS_Store
*.d.ts
*.log
yarn.lock
28 changes: 20 additions & 8 deletions cli.js
@@ -1,6 +1,7 @@
#!/usr/bin/env node
import fs from 'node:fs'
import process from 'node:process'
import {URL} from 'node:url'
import notifier from 'update-notifier'
import supportsColor from 'supports-color'
import meow from 'meow'
Expand All @@ -20,8 +21,9 @@ import retextProfanities from 'retext-profanities'
import unifiedDiff from 'unified-diff'
import {filter} from './filter.js'

/** @type {import('type-fest').PackageJson} */
const pack = JSON.parse(
fs.readFileSync(new URL('./package.json', import.meta.url))
String(fs.readFileSync(new URL('./package.json', import.meta.url)))
)

const textExtensions = [
Expand All @@ -38,6 +40,7 @@ const htmlExtensions = ['htm', 'html']
const mdxExtensions = ['mdx']

// Update messages.
/** @ts-expect-error: `package.json` is fine. */
notifier({pkg: pack}).notify()

// Set-up meow.
Expand Down Expand Up @@ -88,7 +91,9 @@ const extensions = cli.flags.html
? mdxExtensions
: textExtensions
const defaultGlobs = ['{docs/**/,doc/**/,}*.{' + extensions.join(',') + '}']
/** @type {boolean|undefined} */
let silentlyIgnore
/** @type {string[]|undefined} */
let globs

if (cli.flags.stdin) {
Expand All @@ -112,7 +117,7 @@ engine(
output: false,
rcName: '.alexrc',
packageField: 'alex',
color: supportsColor.stderr,
color: Boolean(supportsColor.stderr),
reporter: cli.flags.reporter || vfileReporter,
reporterOptions: {
verbose: cli.flags.why
Expand All @@ -121,36 +126,43 @@ engine(
ignoreName: '.alexignore',
silentlyIgnore,
frail: true,
defaultConfig: transform()
defaultConfig: transform({})
},
function (error, code) {
if (error) console.error(error.message)
process.exit(code)
}
)

function transform(options) {
const settings = options || {}
/**
* @type {import('unified-engine').ConfigTransform}
* @param {import('./index.js').OptionsObject} [options]
*/
function transform(options = {}) {
/** @type {import('unified').PluggableList} */
let plugins = [
retextEnglish,
[retextProfanities, {sureness: settings.profanitySureness}],
[retextEquality, {noBinary: settings.noBinary}]
[retextProfanities, {sureness: options.profanitySureness}],
[retextEquality, {noBinary: options.noBinary}]
]

if (cli.flags.html) {
// @ts-expect-error: types are having a hard time for bridges.
plugins = [rehypeParse, [rehypeRetext, unified().use({plugins})]]
} else if (cli.flags.mdx) {
// @ts-expect-error: types are having a hard time for bridges.
plugins = [remarkParse, remarkMdx, [remarkRetext, unified().use({plugins})]]
} else if (!cli.flags.text) {
plugins = [
remarkParse,
remarkGfm,
[remarkFrontmatter, ['yaml', 'toml']],
// @ts-expect-error: types are having a hard time for bridges.
[remarkRetext, unified().use({plugins})]
]
}

plugins.push([filter, {allow: settings.allow, deny: settings.deny}])
plugins.push([filter, {allow: options.allow, deny: options.deny}])

// Hard to check.
/* c8 ignore next 3 */
Expand Down
31 changes: 23 additions & 8 deletions filter.js
@@ -1,20 +1,35 @@
import remarkMessageControl from 'remark-message-control'
/**
* @typedef {import('mdast').Root} Root
*
* @typedef Options
* Configuration.
* @property {string[]} [deny]
* The `deny` field should be an array of rules or `undefined` (the default is
* `undefined`).
* When provided, *only* the rules specified are reported.
* You cannot use both `allow` and `deny` at the same time.
* @property {string[]} [allow]
* The `allow` field should be an array of rules or `undefined` (the default
* is `undefined`).
* When provided, the rules specified are skipped and not reported.
* You cannot use both `allow` and `deny` at the same time.
*/

export function filter(options) {
/* c8 ignore next */
const settings = options || {}
import remarkMessageControl from 'remark-message-control'

if (settings.allow && settings.deny) {
/** @type {import('unified').Plugin<[Options?]|[], Root>} */
export function filter(options = {}) {
if (options.allow && options.deny) {
throw new Error(
'Do not provide both allow and deny configuration parameters'
)
}

return remarkMessageControl({
name: 'alex',
reset: Boolean(settings.deny),
enable: settings.deny,
disable: settings.allow,
reset: Boolean(options.deny),
enable: options.deny,
disable: options.allow,
source: ['retext-equality', 'retext-profanities']
})
}
118 changes: 87 additions & 31 deletions index.js
@@ -1,3 +1,17 @@
/**
* @typedef {import('nlcst').Root} Root
* @typedef {import('./filter.js').Options} FilterOptions
*
* @typedef {boolean|undefined} NoBinaryOption
* @typedef {0|1|2|undefined} SurenessOption
*
* @typedef {{noBinary: NoBinaryOption, sureness: SurenessOption}} TextOptions
*
* @typedef {{noBinary?: NoBinaryOption, profanitySureness?: SurenessOption} & FilterOptions} OptionsObject
* @typedef {import('vfile').VFileCompatible} Input
* @typedef {OptionsObject|string[]|undefined} Options
*/

import {VFile} from 'vfile'
import {unified} from 'unified'
import remarkParse from 'remark-parse'
Expand All @@ -13,31 +27,24 @@ import rehypeRetext from 'rehype-retext'
import {sort} from 'vfile-sort'
import {filter} from './filter.js'

function makeText(config) {
/** @param {TextOptions} options */
function makeText(options) {
return unified()
.use(retextEnglish)
.use(retextEquality, {
noBinary: config && config.noBinary
})
.use(retextProfanities, {
sureness: config && config.profanitySureness
})
.use(retextEquality, options)
.use(retextProfanities, options)
}

// Alex’s core.
function core(value, config, processor) {
let allow
let deny

if (Array.isArray(config)) {
allow = config
} else if (config) {
allow = config.allow
deny = config.deny
}

/**
* Alex’s core.
*
* @param {Input} value
* @param {FilterOptions} options
* @param {import('unified').Processor<void, Root>} processor
*/
function core(value, options, processor) {
const file = new VFile(value)
const tree = processor.use(filter, {allow, deny}).parse(file)
const tree = processor.use(filter, options).parse(file)

processor.runSync(tree, file)

Expand All @@ -48,41 +55,90 @@ function core(value, config, processor) {

export default markdown

// Alex.
/**
* Alex (markdown).
*
* @param {Input} value
* @param {Options} [config]
*/
export function markdown(value, config) {
const options = splitOptions(config)
return core(
value,
config,
options.filter,
unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkFrontmatter, ['yaml', 'toml'])
.use(remarkRetext, makeText(config))
.use(remarkRetext, makeText(options.text))
)
}

// Alex, for MDX.
/**
* Alex (MDX).
*
* @param {Input} value
* @param {Options} [config]
*/
export function mdx(value, config) {
const options = splitOptions(config)
return core(
value,
config,
options.filter,
unified()
.use(remarkParse)
.use(remarkMdx)
.use(remarkRetext, makeText(config))
.use(remarkRetext, makeText(options.text))
)
}

// Alex, for HTML.
/**
* Alex (HTML).
*
* @param {Input} value
* @param {Options} [config]
*/
export function html(value, config) {
const options = splitOptions(config)
return core(
value,
config,
unified().use(rehypeParse).use(rehypeRetext, makeText(config))
options.filter,
unified().use(rehypeParse).use(rehypeRetext, makeText(options.text))
)
}

// Alex, without the markdown.
/**
* Alex (plain text).
*
* @param {Input} value
* @param {Options} [config]
*/
export function text(value, config) {
return core(value, config, makeText(config))
const options = splitOptions(config)
return core(value, options.filter, makeText(options.text))
}

/**
* @param {Options} options
*/
function splitOptions(options) {
/** @type {string[]|undefined} */
let allow
/** @type {string[]|undefined} */
let deny
/** @type {boolean|undefined} */
let noBinary
/** @type {SurenessOption} */
let sureness

if (Array.isArray(options)) {
allow = options
} else if (options) {
allow = options.allow
deny = options.deny
noBinary = options.noBinary
sureness = options.profanitySureness
}

return {filter: {allow, deny}, text: {noBinary, sureness}}
}
20 changes: 19 additions & 1 deletion package.json
Expand Up @@ -58,12 +58,17 @@
"type": "module",
"sideEffects": false,
"bin": "cli.js",
"types": "index.d.ts",
"files": [
"index.d.ts",
"index.js",
"filter.d.ts",
"filter.js",
"cli.js"
],
"dependencies": {
"@types/mdast": "^3.0.0",
"@types/nlcst": "^1.0.0",
"meow": "^10.0.0",
"rehype-parse": "^8.0.0",
"rehype-retext": "^3.0.0",
Expand All @@ -85,19 +90,26 @@
"vfile-sort": "^3.0.0"
},
"devDependencies": {
"@types/tape": "^4.0.0",
"@types/update-notifier": "^5.0.0",
"c8": "^7.10.0",
"prettier": "^2.0.0",
"remark-cli": "^10.0.0",
"remark-preset-wooorm": "^9.0.0",
"rimraf": "^3.0.0",
"tape": "^5.0.0",
"type-coverage": "^2.0.0",
"type-fest": "^2.0.0",
"typescript": "^4.0.0",
"vfile-reporter-json": "^3.0.0",
"xo": "^0.45.0"
},
"scripts": {
"build": "rimraf \"test/**/*.d.ts\" \"*.d.ts\" && tsc && type-coverage",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
"test-api": "node test/index.js",
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
"test": "npm run format && npm run test-coverage"
"test": "npm run build && npm run format && npm run test-coverage"
},
"alex": {
"allow": [
Expand Down Expand Up @@ -129,5 +141,11 @@
}
]
]
},
"typeCoverage": {
"atLeast": 100,
"detail": true,
"strict": true,
"ignoreCatch": true
}
}
1 change: 1 addition & 0 deletions test/api.js
@@ -1,4 +1,5 @@
import fs from 'node:fs'
import {URL} from 'node:url'
import test from 'tape'
import alex, {markdown, mdx, text, html} from '../index.js'

Expand Down

0 comments on commit d4833f8

Please sign in to comment.