diff --git a/.gitignore b/.gitignore index fcb26070..69d75b1a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ node_modules/ *.log yarn.lock !/index.d.ts +!/test/types.d.ts diff --git a/index.d.ts b/index.d.ts index ffb08843..e2d124a9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -7,6 +7,7 @@ export type { Compiler, CompilerClass, CompilerFunction, + // `Data` is typed and exposed below. Pluggable, PluggableList, Plugin, @@ -18,6 +19,7 @@ export type { ProcessCallback, Processor, RunCallback, + // `Settings` is typed and exposed below. TransformCallback, Transformer } from './lib/index.js' @@ -40,6 +42,8 @@ export {unified} from './lib/index.js' * ReactNode: ReactNode * } * } + * + * export {} // You may not need this, but it makes sure the file is a module. * ``` * * Use {@link CompileResults `CompileResults`} to access the values. @@ -49,3 +53,54 @@ export interface CompileResultMap { Uint8Array: Uint8Array string: string } + +/** + * Interface of known data that can be supported by all plugins. + * + * Typically, options can be given to a specific plugin, but sometimes it makes + * sense to have information shared with several plugins. + * For example, a list of HTML elements that are self-closing, which is needed + * during all phases. + * + * To type this, do something like: + * + * ```ts + * declare module 'unified' { + * interface Data { + * htmlVoidElements?: Array | undefined + * } + * } + * + * export {} // You may not need this, but it makes sure the file is a module. + * ``` + */ +export interface Data { + settings?: Settings | undefined +} + +/** + * Interface of known extra options, that can be supported by parser and + * compilers. + * + * This exists so that users can use packages such as `remark`, which configure + * both parsers and compilers (in this case `remark-parse` and + * `remark-stringify`), and still provide options for them. + * + * When you make parsers or compilers, that could be packaged up together, + * you should support `this.data('settings')` as input and merge it with + * explicitly passed `options`. + * Then, to type it, using `remark-stringify` as an example, do something like: + * + * ```ts + * declare module 'unified' { + * interface Settings { + * bullet: '*' | '+' | '-' + * // … + * } + * } + * + * export {} // You may not need this, but it makes sure the file is a module. + * ``` + */ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface Settings {} diff --git a/lib/index.js b/lib/index.js index 0fcc16a5..9dbeec78 100644 --- a/lib/index.js +++ b/lib/index.js @@ -7,6 +7,7 @@ * @typedef {import('vfile').VFileValue} VFileValue * * @typedef {import('../index.js').CompileResultMap} CompileResultMap + * @typedef {import('../index.js').Data} Data */ /** @@ -518,7 +519,7 @@ export class Processor extends CallableInstance { * * @deprecated * This is a private internal property and should not be used. - * @type {{settings?: Record} & Record} + * @type {Data} */ this.namespace = {} @@ -573,26 +574,28 @@ export class Processor extends CallableInstance { * > 👉 **Note**: setting information cannot occur on *frozen* processors. * > Call the processor first to create a new unfrozen processor. * + * @template {keyof Data} Key + * * @overload - * @returns {Record} + * @returns {Data} * * @overload - * @param {Record} dataset + * @param {Data} dataset * @returns {Processor} * * @overload - * @param {string} key - * @returns {unknown} + * @param {Key} key + * @returns {Data[Key]} * * @overload - * @param {string} key - * @param {unknown} value + * @param {Key} key + * @param {Data[Key]} value * @returns {Processor} * - * @param {Record | string} [key] + * @param {Data | Key} [key] * Key to get or set, or entire dataset to set, or nothing to get the * entire dataset (optional). - * @param {unknown} [value] + * @param {Data[Key]} [value] * Value to set (optional). * @returns {unknown} * The processor that `data` is called on when settings, the value at `key` diff --git a/test/data.js b/test/data.js index 671bdaab..6b8df6c2 100644 --- a/test/data.js +++ b/test/data.js @@ -18,6 +18,8 @@ test('`data`', async function (t) { }) await t.test('should not yield data prototypal fields', async function () { + // @ts-expect-error: `toString` is not a typed key of `Data`. + // But it exists on objects, so we test that here. assert.equal(unified().data('toString'), undefined) }) diff --git a/test/types.d.ts b/test/types.d.ts new file mode 100644 index 00000000..5c293242 --- /dev/null +++ b/test/types.d.ts @@ -0,0 +1,9 @@ +declare module 'unified' { + interface Data { + baz?: 'qux' | undefined + foo?: 'bar' | undefined + x?: boolean | undefined + } +} + +export {} // You may not need this, but it makes sure the file is a module. diff --git a/tsconfig.json b/tsconfig.json index ad1496e9..8e70d195 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,5 +11,5 @@ "target": "es2020" }, "exclude": ["coverage/", "node_modules/"], - "include": ["**/*.js", "index.d.ts"] + "include": ["**/*.js", "test/types.d.ts", "index.d.ts"] }