diff --git a/.gitignore b/.gitignore index 735f4af..53a29e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -.DS_Store -*.log coverage/ node_modules/ +.DS_Store +*.d.ts +*.log yarn.lock diff --git a/index.d.ts b/index.d.ts deleted file mode 100644 index 953eba0..0000000 --- a/index.d.ts +++ /dev/null @@ -1,116 +0,0 @@ -// TypeScript Version: 3.4 - -import {Node} from 'unist' -import {Transformer} from 'unified' -import {Test} from 'unist-util-is' - -declare namespace messageControl { - /** - * A comment marker. - */ - interface Marker { - /** - * Name of marker - */ - name: string - - /** - * Value after name - */ - attributes: string - - /** - * Parsed attributes - */ - parameters: Record - - /** - * Reference to given node - */ - node: N - } - - /** - * Parse a possible comment marker node to a Marker - */ - type MarkerParser = (node: N) => Marker | null - - interface MessageControlOptionsWithReset - extends BaseMessageControlOptions { - /** - * Whether to treat all messages as turned off initially - */ - reset: true - - /** - * List of `ruleId`s to initially turn on. - */ - enable?: string[] - } - - interface MessageControlOptionsWithoutReset - extends BaseMessageControlOptions { - /** - * Whether to treat all messages as turned off initially - */ - reset?: false - - /** - * List of `ruleId`s to turn off - */ - disable?: string[] - } - - interface BaseMessageControlOptions { - /** - * Name of markers that can control the message sources. - * - * For example. `{name: 'alpha'}` controls `alpha` markers: - * - * `` - */ - name: string - - /** - * Test for possible markers - */ - test: Test - - /** - * Parse a possible marker to a comment marker object (Marker) - * if possible the marker isn't a marker, should return `null`. - */ - marker: MarkerParser - - /** - * List of allowed `ruleId`s. When given a warning is shown - * when someone tries to control an unknown rule. - * - * For example, `{name: 'alpha', known: ['bravo']}` results - * in a warning if `charlie is configured: - * - * `` - */ - known?: string[] - - /** - * Sources that can be controlled with `name` markers. - * - * @defaultValue `MessageControlOptions.name` - */ - sources?: string | string[] - } - - type MessageControlOptions = - | MessageControlOptionsWithoutReset - | MessageControlOptionsWithReset -} - -/** - * Enable, disable, and ignore messages with unified. - */ -declare function messageControl( - options?: messageControl.MessageControlOptions -): Transformer - -export = messageControl diff --git a/index.js b/index.js index 5bcbab2..0719e92 100644 --- a/index.js +++ b/index.js @@ -1,60 +1,149 @@ +/** + * @typedef {import('unist').Node} Node + * @typedef {import('unist').Parent} Parent + * @typedef {import('unist').Point} Point + * @typedef {import('unist-util-is').Test} Test + * @typedef {import('vfile').VFile} VFile + * @typedef {import('vfile-message').VFileMessage} VFileMessage + * + * @typedef {OptionsWithoutReset|OptionsWithReset} Options + * @typedef {OptionsBaseFields & OptionsWithoutResetFields} OptionsWithoutReset + * @typedef {OptionsBaseFields & OptionsWithResetFields} OptionsWithReset + * + * @typedef OptionsWithoutResetFields + * @property {false} [reset] + * Whether to treat all messages as turned off initially. + * @property {string[]} [disable] + * List of `ruleId`s to turn off. + * + * @typedef OptionsWithResetFields + * @property {true} reset + * Whether to treat all messages as turned off initially. + * @property {string[]} [enable] + * List of `ruleId`s to initially turn on. + * + * @typedef OptionsBaseFields + * @property {string} name + * Name of markers that can control the message sources. + * + * For example, `{name: 'alpha'}` controls `alpha` markers: + * + * ```html + * + * ``` + * @property {MarkerParser} marker + * Parse a possible marker to a comment marker object (Marker). + * If the marker isn't a marker, should return `null`. + * @property {Test} [test] + * Test for possible markers + * @property {string[]} [known] + * List of allowed `ruleId`s. When given a warning is shown + * when someone tries to control an unknown rule. + * + * For example, `{name: 'alpha', known: ['bravo']}` results in a warning if + * `charlie` is configured: + * + * ```html + * + * ``` + * @property {string|string[]} [source] + * Sources that can be controlled with `name` markers. + * Defaults to `name`. + * + * @callback MarkerParser + * Parse a possible comment marker node to a Marker. + * @param {Node} node + * Node to parse + * + * @typedef Marker + * A comment marker. + * @property {string} name + * Name of marker. + * @property {string} attributes + * Value after name. + * @property {Record} parameters + * Parsed attributes. + * @property {Node} node + * Reference to given node. + * + * @typedef Mark + * @property {Point|undefined} point + * @property {boolean} state + */ + import {location} from 'vfile-location' import {visit} from 'unist-util-visit' const own = {}.hasOwnProperty +/** + * @param {Options} options + */ export function messageControl(options) { - const settings = options || {} - const enable = settings.enable || [] - const disable = settings.disable || [] - let sources = settings.source - let reset = settings.reset - - if (!settings.name) { - throw new Error('Expected `name` in `options`, got `' + settings.name + '`') + if (!options || typeof options !== 'object' || !options.name) { + throw new Error( + 'Expected `name` in `options`, got `' + (options || {}).name + '`' + ) } - if (!settings.marker) { + if (!options.marker) { throw new Error( - 'Expected `marker` in `options`, got `' + settings.marker + '`' + 'Expected `marker` in `options`, got `' + options.marker + '`' ) } - if (!sources) { - sources = [settings.name] - } else if (typeof sources === 'string') { - sources = [sources] - } + const enable = 'enable' in options && options.enable ? options.enable : [] + const disable = 'disable' in options && options.disable ? options.disable : [] + let reset = options.reset + const sources = + typeof options.source === 'string' + ? [options.source] + : options.source || [options.name] return transformer + /** + * @param {Node} tree + * @param {VFile} file + */ function transformer(tree, file) { const toOffset = location(file).toOffset const initial = !reset const gaps = detectGaps(tree, file) + /** @type {Record} */ const scope = {} + /** @type {Mark[]} */ const globals = [] - visit(tree, settings.test, visitor) + visit(tree, options.test, visitor) file.messages = file.messages.filter((m) => filter(m)) + /** + * @param {Node} node + * @param {number|null} position + * @param {Parent|null} parent + */ function visitor(node, position, parent) { - const mark = settings.marker(node) + /** @type {Marker|null} */ + const mark = options.marker(node) - if (!mark || mark.name !== settings.name) { + if (!mark || mark.name !== options.name) { return } const ruleIds = mark.attributes.split(/\s/g) - const verb = ruleIds.shift() - const pos = mark.node.position && mark.node.position.start - const tail = - parent.children[position + 1] && - parent.children[position + 1].position && - parent.children[position + 1].position.end + const point = mark.node.position && mark.node.position.start + const next = + (parent && position !== null && parent.children[position + 1]) || + undefined + const tail = (next && next.position && next.position.end) || undefined let index = -1 + /** @type {string} */ + // @ts-expect-error: we’ll check for unknown values next. + const verb = ruleIds.shift() + if (verb !== 'enable' && verb !== 'disable' && verb !== 'ignore') { file.fail( 'Unknown keyword `' + @@ -71,7 +160,7 @@ export function messageControl(options) { const ruleId = ruleIds[index] if (isKnown(ruleId, verb, mark.node)) { - toggle(pos, verb === 'enable', ruleId) + toggle(point, verb === 'enable', ruleId) if (verb === 'ignore') { toggle(tail, true, ruleId) @@ -79,14 +168,18 @@ export function messageControl(options) { } } } else if (verb === 'ignore') { - toggle(pos, false) + toggle(point, false) toggle(tail, true) } else { - toggle(pos, verb === 'enable') + toggle(point, verb === 'enable') reset = verb !== 'enable' } } + /** + * @param {VFileMessage} message + * @returns {boolean} + */ function filter(message) { let gapIndex = gaps.length @@ -106,37 +199,51 @@ export function messageControl(options) { } // Check whether the warning is inside a gap. - const pos = toOffset(message) + // @ts-expect-error: we just normalized `null` to `number`s. + const offset = toOffset(message) while (gapIndex--) { - if (gaps[gapIndex].start <= pos && gaps[gapIndex].end > pos) { + if (gaps[gapIndex][0] <= offset && gaps[gapIndex][1] > offset) { return false } } // Check whether allowed by specific and global states. return ( - check(message, scope[message.ruleId], message.ruleId) && + (!message.ruleId || + check(message, scope[message.ruleId], message.ruleId)) && check(message, globals) ) } - // Helper to check (and possibly warn) if a `ruleId` is unknown. - function isKnown(ruleId, verb, pos) { - const result = settings.known ? settings.known.includes(ruleId) : true + /** + * Helper to check (and possibly warn) if a `ruleId` is unknown. + * + * @param {string} ruleId + * @param {string} verb + * @param {Node} node + * @returns {boolean} + */ + function isKnown(ruleId, verb, node) { + const result = options.known ? options.known.includes(ruleId) : true if (!result) { file.message( 'Unknown rule: cannot ' + verb + " `'" + ruleId + "'`", - pos + node ) } return result } - // Get the latest state of a rule. - // When without `ruleId`, gets global state. + /** + * Get the latest state of a rule. + * When without `ruleId`, gets global state. + * + * @param {string|undefined} ruleId + * @returns {boolean} + */ function getState(ruleId) { const ranges = ruleId ? scope[ruleId] : globals @@ -151,53 +258,73 @@ export function messageControl(options) { return reset ? enable.includes(ruleId) : !disable.includes(ruleId) } - // Handle a rule. - function toggle(pos, state, ruleId) { + /** + * Handle a rule. + * + * @param {Point|undefined} point + * @param {boolean} state + * @param {string|undefined} [ruleId] + * @returns {void} + */ + function toggle(point, state, ruleId) { let markers = ruleId ? scope[ruleId] : globals if (!markers) { markers = [] - scope[ruleId] = markers + scope[String(ruleId)] = markers } const previousState = getState(ruleId) if (state !== previousState) { - markers.push({state, position: pos}) + markers.push({state, point}) } // Toggle all known rules. if (!ruleId) { for (ruleId in scope) { if (own.call(scope, ruleId)) { - toggle(pos, state, ruleId) + toggle(point, state, ruleId) } } } } - // Check all `ranges` for `message`. + /** + * Check all `ranges` for `message`. + * + * @param {VFileMessage} message + * @param {Mark[]|undefined} ranges + * @param {string|undefined} [ruleId] + * @returns {boolean} + */ function check(message, ranges, ruleId) { - // Check the state at the message’s position. - let index = ranges ? ranges.length : 0 - - while (index--) { - if ( - ranges[index].position && - ranges[index].position.line && - ranges[index].position.column && - (ranges[index].position.line < message.line || - (ranges[index].position.line === message.line && - ranges[index].position.column <= message.column)) - ) { - return ranges[index].state === true + if (ranges && ranges.length > 0) { + // Check the state at the message’s position. + let index = ranges.length + + while (index--) { + const range = ranges[index] + + if ( + message.line && + message.column && + range.point && + range.point.line && + range.point.column && + (range.point.line < message.line || + (range.point.line === message.line && + range.point.column <= message.column)) + ) { + return range.state === true + } } } // The first marker ocurred after the first message, so we check the // initial state. if (!ruleId) { - return initial || reset + return Boolean(initial || reset) } return reset ? enable.includes(ruleId) : !disable.includes(ruleId) @@ -205,12 +332,21 @@ export function messageControl(options) { } } -// Detect gaps in `tree`. +/** + * Detect gaps in `tree`. + * + * @param {Node} tree + * @param {VFile} file + */ function detectGaps(tree, file) { + /** @type {Node[]} */ + // @ts-expect-error: fine. const children = tree.children || [] const lastNode = children[children.length - 1] + /** @type {[number, number][]} */ const gaps = [] let offset = 0 + /** @type {boolean|undefined} */ let gap // Find all gaps. @@ -230,28 +366,40 @@ function detectGaps(tree, file) { update() update( - tree && tree.position && tree.position.end && tree.position.end.offset - 1 + tree && + tree.position && + tree.position.end && + tree.position.end.offset && + tree.position.end.offset - 1 ) } return gaps + /** + * @param {Node} node + */ function one(node) { update(node.position && node.position.start && node.position.start.offset) - if (!node.children) { + if (!('children' in node)) { update(node.position && node.position.end && node.position.end.offset) } } - // Detect a new position. + /** + * Detect a new position. + * + * @param {number|undefined} [latest] + * @returns {void} + */ function update(latest) { if (latest === null || latest === undefined) { gap = true } else if (offset < latest) { if (gap) { - gaps.push({start: offset, end: latest}) - gap = null + gaps.push([offset, latest]) + gap = undefined } offset = latest diff --git a/package.json b/package.json index 33730fb..d93e29f 100644 --- a/package.json +++ b/package.json @@ -32,13 +32,15 @@ "index.js" ], "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", "unist-util-visit": "^3.0.0", - "vfile-location": "^4.0.0" + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "vfile-message": "^3.0.0" }, "devDependencies": { - "@types/hast": "^2.0.0", - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", + "@types/tape": "^4.0.0", "c8": "^7.0.0", "mdast-comment-marker": "^2.0.0", "prettier": "^2.0.0", @@ -46,17 +48,19 @@ "remark-cli": "^9.0.0", "remark-preset-wooorm": "^8.0.0", "remark-toc": "^7.0.0", + "rimraf": "^3.0.0", "tape": "^5.0.0", + "type-coverage": "^2.0.0", + "typescript": "^4.0.0", "unified": "^10.0.0-beta.1", - "unist-util-is": "^5.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", - "xxx-test-types": "dtslint", - "test": "npm run format && npm run test-coverage" + "test": "npm run build && npm run format && npm run test-coverage" }, "prettier": { "tabWidth": 2, @@ -67,14 +71,17 @@ "trailingComma": "none" }, "xo": { - "prettier": true, - "ignores": [ - "*.ts" - ] + "prettier": true }, "remarkConfig": { "plugins": [ "preset-wooorm" ] + }, + "typeCoverage": { + "atLeast": 100, + "detail": true, + "strict": true, + "ignoreCatch": true } } diff --git a/test.js b/test.js index d7bcb13..0f37025 100644 --- a/test.js +++ b/test.js @@ -1,12 +1,21 @@ +/** + * @typedef {import('unist').Node} Node + */ + import test from 'tape' -import remark from 'remark' +import {unified} from 'unified' +import remarkParse from 'remark-parse' +import remarkStringify from 'remark-stringify' import remarkToc from 'remark-toc' import {commentMarker} from 'mdast-comment-marker' import {messageControl} from './index.js' +const remark = unified().use(remarkParse).use(remarkStringify).freeze() + test('messageControl()', (t) => { t.throws( () => { + // @ts-expect-error: runtime. remark().use(messageControl).freeze() }, /Expected `name` in `options`, got `undefined`/, @@ -15,6 +24,7 @@ test('messageControl()', (t) => { t.throws( () => { + // @ts-expect-error: runtime. remark().use(messageControl, {name: 'foo'}).freeze() }, /Expected `marker` in `options`, got `undefined`/, @@ -36,6 +46,7 @@ test('messageControl()', (t) => { }) return function (tree, file) { + // @ts-expect-error: fine. file.message('Error', tree.children[1], 'foo:bar') transformer(tree, file) } @@ -44,7 +55,7 @@ test('messageControl()', (t) => { '\n\nThis is a paragraph.', (error, file) => { t.deepEqual( - [error].concat(file.messages), + [error, ...file.messages], [null], 'should “disable” a message' ) @@ -60,6 +71,7 @@ test('messageControl()', (t) => { }) return function (tree, file) { + // @ts-expect-error: fine. file.message('Error', tree.children[1], 'foo:bar') transformer(tree, file) @@ -67,7 +79,7 @@ test('messageControl()', (t) => { }) .process('\n\nThis is a paragraph.', (error, file) => { t.deepEqual( - [error].concat(file.messages), + [error, ...file.messages], [null], 'should “disable” all message without `ruleId`s' ) @@ -83,7 +95,9 @@ test('messageControl()', (t) => { }) return function (tree, file) { + // @ts-expect-error: fine. file.message('Error', tree.children[0], 'foo:bar') + // @ts-expect-error: fine. file.message('Error', tree.children[2], 'foo:bar') transformer(tree, file) @@ -99,7 +113,7 @@ test('messageControl()', (t) => { ].join('\n'), (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null, '5:1-5:21: Error'], 'should support `reset`' ) @@ -116,6 +130,7 @@ test('messageControl()', (t) => { }) return function (tree, file) { + // @ts-expect-error: fine. file.message('Error', tree.children[1], 'foo:bar') transformer(tree, file) @@ -123,7 +138,7 @@ test('messageControl()', (t) => { }) .process('\n\nThis is a paragraph.', (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null, '3:1-3:21: Error'], 'should enable with a marker, when `reset`' ) @@ -138,7 +153,9 @@ test('messageControl()', (t) => { }) return function (tree, file) { + // @ts-expect-error: fine. file.message('Error', tree.children[1], 'foo:bar') + // @ts-expect-error: fine. file.message('Error', tree.children[3], 'foo:bar') transformer(tree, file) @@ -156,7 +173,7 @@ test('messageControl()', (t) => { ].join('\n'), (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null, '7:1-7:21: Error'], 'should enable a message' ) @@ -172,7 +189,9 @@ test('messageControl()', (t) => { }) return function (tree, file) { + // @ts-expect-error: fine. file.message('Error', tree.children[1], 'foo:bar') + // @ts-expect-error: fine. file.message('Error', tree.children[3], 'foo:bar') transformer(tree, file) @@ -190,7 +209,7 @@ test('messageControl()', (t) => { ].join('\n'), (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null, '7:1-7:21: Error'], 'should enable all message without `ruleId`s' ) @@ -206,7 +225,9 @@ test('messageControl()', (t) => { }) return function (tree, file) { + // @ts-expect-error: fine. file.message('Error', tree.children[1], 'foo:bar') + // @ts-expect-error: fine. file.message('Error', tree.children[2], 'foo:bar') transformer(tree, file) @@ -222,7 +243,7 @@ test('messageControl()', (t) => { ].join('\n'), (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null, '5:1-5:21: Error'], 'should ignore a message' ) @@ -238,7 +259,9 @@ test('messageControl()', (t) => { }) return function (tree, file) { + // @ts-expect-error: fine. file.message('Error', tree.children[1], 'foo:bar') + // @ts-expect-error: fine. file.message('Error', tree.children[2], 'foo:bar') transformer(tree, file) @@ -254,7 +277,7 @@ test('messageControl()', (t) => { ].join('\n'), (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null, '5:1-5:21: Error'], 'should ignore all message without `ruleId`s' ) @@ -270,7 +293,9 @@ test('messageControl()', (t) => { }) return function (tree, file) { + // @ts-expect-error: fine. file.message('Error', tree.children[1], 'foo:bar') + // @ts-expect-error: fine. file.message('Error', tree.children[1], 'foo:baz') transformer(tree, file) @@ -280,7 +305,7 @@ test('messageControl()', (t) => { '\n\nThis is a paragraph.', (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null], 'should ignore multiple rules' ) @@ -332,7 +357,7 @@ test('messageControl()', (t) => { ].join('\n'), (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null, '7:1: Error'], 'should ignore gaps' ) @@ -352,6 +377,7 @@ test('messageControl()', (t) => { file.message('Error', {line: 5, column: 1}, 'foo:bar') // Remove list. + // @ts-expect-error: fine. tree.children.pop() transformer(tree, file) @@ -367,7 +393,7 @@ test('messageControl()', (t) => { ].join('\n'), (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null], 'should ignore final gaps' ) @@ -383,14 +409,14 @@ test('messageControl()', (t) => { }) return function (tree, file) { - file.message('Error', 'foo:bar') + file.message('Error', undefined, 'foo:bar') transformer(tree, file) } }) .process('', (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null, '1:1: Error'], 'should support empty documents' ) @@ -405,6 +431,7 @@ test('messageControl()', (t) => { }) return function (tree, file) { + // @ts-expect-error: fine. file.message('Error', tree.position.end, 'foo:bar') transformer(tree, file) @@ -412,7 +439,7 @@ test('messageControl()', (t) => { }) .process('# README\n', (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null, '2:1: Error'], 'should message at the end of the document' ) @@ -427,6 +454,7 @@ test('messageControl()', (t) => { }) return function (tree, file) { + // @ts-expect-error: fine. file.message('Error', tree.children[1].position.end, 'foo:bar') transformer(tree, file) @@ -434,7 +462,7 @@ test('messageControl()', (t) => { }) .process('# README\n\n* List', (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null, '3:9: Error'], 'should message at the end of the document' ) @@ -449,7 +477,9 @@ test('messageControl()', (t) => { }) return function (tree, file) { + // @ts-expect-error: fine. file.message('Error', tree.children[1], 'foo:bar') + // @ts-expect-error: fine. file.message('Error', tree.children[3], 'foo:bar') transformer(tree, file) @@ -467,7 +497,7 @@ test('messageControl()', (t) => { ].join('\n'), (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null], 'should ignore double disables' ) @@ -483,14 +513,14 @@ test('messageControl()', (t) => { }) return function (tree, file) { - file.message('Error', 'foo:bar') + file.message('Error', undefined, 'foo:bar') transformer(tree, file) } }) .process('Foo', (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null, '1:1: Error'], 'should not ignore messages without location information' ) @@ -506,7 +536,7 @@ test('messageControl()', (t) => { }) .process('\n\n\n', (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null], 'should ignore non-markers' ) @@ -525,7 +555,7 @@ test('messageControl()', (t) => { '\n\n', (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null, "3:1-3:26: Unknown rule: cannot ignore `'unknown'`"], 'should support a list of `known` values, and warn on unknowns' ) @@ -542,6 +572,7 @@ test('messageControl()', (t) => { }) return function (tree, file) { + // @ts-expect-error: fine. file.message('Error', tree.children[1], 'baz:bar') transformer(tree, file) @@ -549,7 +580,7 @@ test('messageControl()', (t) => { }) .process('\n\nFoo', (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null], 'should ignore by `source`, when given as a string' ) @@ -565,7 +596,9 @@ test('messageControl()', (t) => { }) return function (tree, file) { + // @ts-expect-error: fine. file.message('Error', tree.children[1], 'bravo:delta') + // @ts-expect-error: fine. file.message('Error', tree.children[3], 'charlie:echo') transformer(tree, file) @@ -583,7 +616,7 @@ test('messageControl()', (t) => { ].join('\n'), (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null], 'should ignore by `source`, when given as an array' ) @@ -600,6 +633,7 @@ test('messageControl()', (t) => { }) return function (tree, file) { + // @ts-expect-error: fine. file.message('Error', tree.children[0], 'foo:bar') transformer(tree, file) @@ -607,7 +641,7 @@ test('messageControl()', (t) => { }) .process('This is a paragraph.', (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null], 'should support initial `disable`s' ) @@ -624,6 +658,7 @@ test('messageControl()', (t) => { }) return function (tree, file) { + // @ts-expect-error: fine. file.message('Error', tree.children[0], 'foo:bar') transformer(tree, file) @@ -631,7 +666,7 @@ test('messageControl()', (t) => { }) .process('This is a paragraph.', (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null, '1:1-1:21: Error'], 'should support initial `enable`s' ) @@ -653,7 +688,7 @@ test('messageControl()', (t) => { }) .process('\n', (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null], 'should disable messages at the start of a marker' ) @@ -676,7 +711,7 @@ test('messageControl()', (t) => { }) .process('\n', (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null, '1:1: Error'], 'should enable messages at the start of a marker' ) @@ -691,14 +726,14 @@ test('messageControl()', (t) => { }) return function (tree, file) { - file.message('Error', 'foo:bar') + file.message('Error', undefined, 'foo:bar') transformer(tree, file) } }) .process('\n', (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null], 'should disable messages without positional info (at the start of a document)' ) @@ -714,14 +749,14 @@ test('messageControl()', (t) => { }) return function (tree, file) { - file.message('Error', 'foo:bar') + file.message('Error', undefined, 'foo:bar') transformer(tree, file) } }) .process('\n', (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null, '1:1: Error'], 'should enable messages without positional info (at the start of a document)' ) @@ -731,13 +766,14 @@ test('messageControl()', (t) => { .use(() => { return function (tree, file) { file.message('Error') + // @ts-expect-error: fine. delete tree.children messageControl({name: 'foo', marker: commentMarker})(tree, file) } }) .process('', (error, file) => { t.deepEqual( - [error].concat(file.messages.map((m) => String(m))), + [error, ...file.messages.map((m) => String(m))], [null, '1:1: Error'], 'should not fail when there are messages but no `children` on `tree`' ) diff --git a/tsconfig.json b/tsconfig.json index 3d21d61..e31adf8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,16 @@ { + "include": ["*.js"], "compilerOptions": { - "lib": ["es2015"], - "strict": true, - "baseUrl": ".", - "paths": { - "unified-message-control": ["."] - } + "target": "ES2020", + "lib": ["ES2020"], + "module": "ES2020", + "moduleResolution": "node", + "allowJs": true, + "checkJs": true, + "declaration": true, + "emitDeclarationOnly": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "strict": true } } diff --git a/tslint.json b/tslint.json deleted file mode 100644 index e339e79..0000000 --- a/tslint.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "dtslint/dtslint.json", - "rules": { - "semicolon": false, - "whitespace": false, - "no-unnecessary-generics": false - } -} diff --git a/type-test.ts b/type-test.ts deleted file mode 100644 index ce69923..0000000 --- a/type-test.ts +++ /dev/null @@ -1,190 +0,0 @@ -/** - * This file is purely for typechecking and does not produce code - */ - -/* eslint-disable-next-line import/no-extraneous-dependencies */ -import * as control from 'unified-message-control' -import * as unified from 'unified' -import {Node} from 'unist' -import {HTML} from 'mdast' -import {Element} from 'hast' - -// $ExpectError -control({}) - -control({ - name: 'foo', - marker: (n: Node) => null, - test: 'html' -}) - -control({ - name: 'foo', - marker: (n: Node) => ({ - name: 'foo', - attributes: 'bar=false', - parameters: { - bar: false - }, - node: { - type: 'foo', - value: 'bar=false' - } - }), - test: 'foo' -}) - -control({ - name: 'foo', - marker: (n: Element) => null, - test: 'element' -}) - -control({ - name: 'foo', - marker: (n: HTML) => null, - test: 'html' -}) - -control({ - name: 'foo', - marker: (n: Node) => null, - test: 'html', - known: ['rule-1', 'rule-2'] -}) - -control({ - name: 'foo', - marker: (n: Node) => null, - test: 'html', - sources: 'example' -}) - -control({ - name: 'foo', - marker: (n: Node) => null, - test: 'html', - sources: ['one', 'two'] -}) - -control({ - name: 'foo', - marker: (n: Node) => null, - test: 'html', - reset: false, - disable: ['rule-id'] -}) - -// prettier-ignore -control({ - name: 'foo', - marker: (n: Node) => null, - test: 'html', - reset: false, enable: ['rule-id'] // $ExpectError -}) - -control({ - name: 'foo', - marker: (n: Node) => null, - test: 'html', - reset: true, - enable: ['rule-id'] -}) - -// prettier-ignore -control({ - name: 'foo', - marker: (n: Node) => null, - test: 'html', - reset: true, disable: ['rule-id'] // $ExpectError -}) - -// $ExpectError -unified().use(control, {}) - -unified().use(control, { - name: 'foo', - marker: (n: Node) => null, - test: 'html' -}) - -unified().use(control, { - name: 'foo', - marker: (n: Node) => ({ - name: 'foo', - attributes: 'bar=false', - parameters: { - bar: false - }, - node: { - type: 'foo', - value: 'bar=false' - } - }), - test: 'foo' -}) - -unified().use(control, { - name: 'foo', - marker: (n: Element) => null, - test: 'element' -}) - -unified().use(control, { - name: 'foo', - marker: (n: HTML) => null, - test: 'html' -}) - -unified().use(control, { - name: 'foo', - marker: (n: Node) => null, - test: 'html', - known: ['rule-1', 'rule-2'] -}) - -unified().use(control, { - name: 'foo', - marker: (n: Node) => null, - test: 'html', - sources: 'example' -}) - -unified().use(control, { - name: 'foo', - marker: (n: Node) => null, - test: 'html', - sources: ['one', 'two'] -}) - -unified().use(control, { - name: 'foo', - marker: (n: Node) => null, - test: 'html', - reset: false, - disable: ['rule-id'] -}) - -// prettier-ignore -unified().use(control, { - name: 'foo', - marker: (n: Node) => null, - test: 'html', - reset: false, enable: ['rule-id'] // $ExpectError -}) - -unified().use(control, { - name: 'foo', - marker: (n: Node) => null, - test: 'html', - reset: true, - enable: ['rule-id'] -}) - -// prettier-ignore -unified().use(control, { - name: 'foo', - marker: (n: Node) => null, - test: 'html', - reset: true, disable: ['rule-id'] // $ExpectError -})