diff --git a/.gitignore b/.gitignore index 33d4929..53a29e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ coverage/ node_modules/ .DS_Store +*.d.ts *.log yarn.lock diff --git a/index.js b/index.js index 4014423..af6373a 100644 --- a/index.js +++ b/index.js @@ -1,17 +1,37 @@ +/** + * @typedef {import('mdast').Root} Root + * @typedef {import('micromark-extension-frontmatter').Options} Options + */ + import {frontmatter} from 'micromark-extension-frontmatter' import { frontmatterFromMarkdown, frontmatterToMarkdown } from 'mdast-util-frontmatter' -export default function remarkFrontmatter(options) { +/** + * Plugin to add support for frontmatter. + * + * @type {import('unified').Plugin<[Options?]|void[], Root>} + */ +export default function remarkFrontmatter(options = 'yaml') { const data = this.data() + add('micromarkExtensions', frontmatter(options)) add('fromMarkdownExtensions', frontmatterFromMarkdown(options)) add('toMarkdownExtensions', frontmatterToMarkdown(options)) + + /** + * @param {string} field + * @param {unknown} value + */ function add(field, value) { - /* istanbul ignore if - other extensions. */ - if (data[field]) data[field].push(value) - else data[field] = [value] + const list = /** @type {unknown[]} */ ( + // Other extensions + /* c8 ignore next 2 */ + data[field] ? data[field] : (data[field] = []) + ) + + list.push(value) } } diff --git a/package.json b/package.json index f64fa9d..8badbc5 100644 --- a/package.json +++ b/package.json @@ -29,30 +29,38 @@ "sideEffects": false, "type": "module", "main": "index.js", + "types": "index.d.ts", "files": [ + "index.d.ts", "index.js" ], "dependencies": { + "@types/mdast": "^3.0.0", "mdast-util-frontmatter": "^1.0.0", - "micromark-extension-frontmatter": "^1.0.0" + "micromark-extension-frontmatter": "^1.0.0", + "unified": "^10.0.0" }, "devDependencies": { + "@types/tape": "^4.0.0", "c8": "^7.0.0", "is-hidden": "^2.0.0", "prettier": "^2.0.0", "remark": "^14.0.0", "remark-cli": "^10.0.0", "remark-preset-wooorm": "^8.0.0", + "rimraf": "^3.0.0", "tape": "^5.0.0", "to-vfile": "^7.0.0", - "unified": "^10.0.0", + "type-coverage": "^2.0.0", + "typescript": "^4.0.0", "xo": "^0.39.0" }, "scripts": { + "build": "rimraf \"test/**/*.d.ts\" \"*.d.ts\" && tsc && type-coverage", "format": "remark . -qfo --ignore-pattern test/ && prettier . -w --loglevel warn && xo --fix", "test-api": "node --conditions development 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" }, "prettier": { "tabWidth": 2, @@ -79,5 +87,11 @@ ], "preset-wooorm" ] + }, + "typeCoverage": { + "atLeast": 100, + "detail": true, + "strict": true, + "ignoreCatch": true } } diff --git a/test/index.js b/test/index.js index b39e481..d95974f 100644 --- a/test/index.js +++ b/test/index.js @@ -1,17 +1,18 @@ +/** + * @typedef {import('vfile').VFile} VFile + * @typedef {import('mdast').Root} Root + * @typedef {import('../index.js').Options} Options + */ + import fs from 'fs' import path from 'path' import test from 'tape' -import {readSync} from 'to-vfile' +import {readSync, writeSync} from 'to-vfile' import {unified} from 'unified' import {remark} from 'remark' import {isHidden} from 'is-hidden' import remarkFrontmatter from '../index.js' -const join = path.join -const read = fs.readFileSync -const write = fs.writeFileSync -const dir = fs.readdirSync - test('remarkFrontmatter', (t) => { t.doesNotThrow(() => { remark().use(remarkFrontmatter).freeze() @@ -23,6 +24,7 @@ test('remarkFrontmatter', (t) => { t.throws( () => { + // @ts-expect-error: invalid input. unified().use(remarkFrontmatter, [1]).freeze() }, /^Error: Expected matter to be an object, not `1`/, @@ -31,6 +33,7 @@ test('remarkFrontmatter', (t) => { t.throws( () => { + // @ts-expect-error: invalid input. unified().use(remarkFrontmatter, ['jsonml']).freeze() }, /^Error: Missing matter definition for `jsonml`/, @@ -40,6 +43,7 @@ test('remarkFrontmatter', (t) => { t.throws( () => { unified() + // @ts-expect-error: invalid input. .use(remarkFrontmatter, [{marker: '*'}]) .freeze() }, @@ -50,6 +54,7 @@ test('remarkFrontmatter', (t) => { t.throws( () => { unified() + // @ts-expect-error: invalid input. .use(remarkFrontmatter, [{type: 'jsonml'}]) .freeze() }, @@ -61,8 +66,8 @@ test('remarkFrontmatter', (t) => { }) test('fixtures', (t) => { - const base = join('test', 'fixtures') - const entries = dir(base).filter((d) => !isHidden(d)) + const base = path.join('test', 'fixtures') + const entries = fs.readdirSync(base).filter((d) => !isHidden(d)) let index = -1 t.plan(entries.length) @@ -70,36 +75,44 @@ test('fixtures', (t) => { while (++index < entries.length) { const fixture = entries[index] t.test(fixture, (st) => { - const input = readSync(join(base, fixture, 'input.md')) - const treePath = join(base, fixture, 'tree.json') - const outputPath = join(base, fixture, 'output.md') + const input = readSync(path.join(base, fixture, 'input.md')) + const treePath = path.join(base, fixture, 'tree.json') + const outputPath = path.join(base, fixture, 'output.md') + /** @type {VFile} */ let output + /** @type {Root} */ let expected + /** @type {Options|undefined} */ let config try { - config = JSON.parse(read(join(base, fixture, 'config.json'))) + config = JSON.parse( + String(readSync(path.join(base, fixture, 'config.json'))) + ) } catch {} const proc = remark().use(remarkFrontmatter, config) - const actual = JSON.parse(JSON.stringify(proc.parse(input))) + const actual = proc.parse(input) try { - output = read(outputPath, 'utf8') + output = readSync(outputPath) } catch { - output = String(input) + output = input } try { - expected = JSON.parse(read(treePath)) + expected = JSON.parse(String(readSync(treePath))) } catch { // New fixture. - write(treePath, JSON.stringify(actual, 0, 2) + '\n') + writeSync({ + path: treePath, + value: JSON.stringify(actual, null, 2) + '\n' + }) expected = actual } st.deepEqual(actual, expected, 'tree') - st.equal(String(proc.processSync(input)), output, 'process') + st.equal(String(proc.processSync(input)), String(output), 'process') st.end() }) } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a93b9f9 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "include": ["test/**/*.js", "*.js"], + "compilerOptions": { + "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/types/index.d.ts b/types/index.d.ts deleted file mode 100644 index a256daf..0000000 --- a/types/index.d.ts +++ /dev/null @@ -1,63 +0,0 @@ -// Minimum TypeScript Version: 3.2 -import {Plugin} from 'unified' -import {Node} from 'unist' - -declare namespace remarkFrontmatter { - type Frontmatter = Plugin<[RemarkFrontmatterOptions?]> - - type Preset = 'yaml' | 'toml' - - interface Fence { - open: string - close: string - } - - interface Matter { - /** - * Node type to parse to in [mdast](https://github.com/syntax-tree/mdast) and compile from. - */ - type: string - - /** - * Character used to construct fences. - * By providing an object with `open` and `close`. - * different characters can be used for opening and closing fences. - * For example the character `'-'` will result in `'---'` being used as the - * fence. - */ - marker?: string | Fence - - /** - * String used as the complete fence. - * By providing an object with `open` and `close` different values can be used - * for opening and closing fences. - * This can be used too if fences contain different characters or lengths other - * than 3 - */ - fence?: string | Fence - - /** - * If `true`, matter can be found anywhere in the document. - * If `false` (default), only matter at the start of the document is recognized - * - * @default false - */ - anywhere?: boolean - } - - type RemarkFrontmatterOptions = Array - - interface YamlNode extends Node { - type: 'yaml' - value: string - } - - interface TomlNode extends Node { - type: 'toml' - value: string - } -} - -declare const remarkFrontmatter: remarkFrontmatter.Frontmatter - -export = remarkFrontmatter diff --git a/types/remark-frontmatter-tests.ts b/types/remark-frontmatter-tests.ts deleted file mode 100644 index c95cde8..0000000 --- a/types/remark-frontmatter-tests.ts +++ /dev/null @@ -1,23 +0,0 @@ -import remark = require('remark') - -import frontmatter = require('remark-frontmatter') - -remark().use(frontmatter) - -remark().use(frontmatter, []) - -remark().use(frontmatter, ['yaml']) - -remark().use(frontmatter, ['toml']) - -remark().use(frontmatter, [{type: 'yaml', marker: '---'}]) - -remark().use(frontmatter, [{type: 'toml', marker: '+'}]) - -remark().use(frontmatter, [{type: 'custom', marker: {open: '<', close: '>'}}]) - -remark().use(frontmatter, [{type: 'custom', fence: '+=+=+=+'}]) - -remark().use(frontmatter, [{type: 'json', fence: {open: '{', close: '}'}}]) - -remark().use(frontmatter, [{type: 'anywhere', marker: '```', anywhere: true}]) diff --git a/types/tsconfig.json b/types/tsconfig.json deleted file mode 100644 index eec4612..0000000 --- a/types/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "lib": ["es2015"], - "strict": true, - "baseUrl": ".", - "paths": { - "remark-frontmatter": ["index.d.ts"] - } - } -} diff --git a/types/tslint.json b/types/tslint.json deleted file mode 100644 index 759caa0..0000000 --- a/types/tslint.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "dtslint/dtslint.json", - "rules": { - "no-redundant-jsdoc": false, - "semicolon": false, - "whitespace": false - } -}