Skip to content

Commit

Permalink
Add Data, Settings types to augment shared data
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Aug 14, 2023
1 parent 40f0329 commit a44db46
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ node_modules/
*.log
yarn.lock
!/index.d.ts
!/test/types.d.ts
55 changes: 55 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type {
Compiler,
CompilerClass,
CompilerFunction,
// `Data` is typed and exposed below.
Pluggable,
PluggableList,
Plugin,
Expand All @@ -18,6 +19,7 @@ export type {
ProcessCallback,
Processor,
RunCallback,
// `Settings` is typed and exposed below.
TransformCallback,
Transformer
} from './lib/index.js'
Expand All @@ -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.
Expand All @@ -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<string> | 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 {}
21 changes: 12 additions & 9 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* @typedef {import('vfile').VFileValue} VFileValue
*
* @typedef {import('../index.js').CompileResultMap} CompileResultMap
* @typedef {import('../index.js').Data} Data
*/

/**
Expand Down Expand Up @@ -518,7 +519,7 @@ export class Processor extends CallableInstance {
*
* @deprecated
* This is a private internal property and should not be used.
* @type {{settings?: Record<string, unknown>} & Record<string, unknown>}
* @type {Data}
*/
this.namespace = {}

Expand Down Expand Up @@ -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<string, unknown>}
* @returns {Data}
*
* @overload
* @param {Record<string, unknown>} dataset
* @param {Data} dataset
* @returns {Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
*
* @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<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
*
* @param {Record<string, unknown> | 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`
Expand Down
2 changes: 2 additions & 0 deletions test/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})

Expand Down
9 changes: 9 additions & 0 deletions test/types.d.ts
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
"target": "es2020"
},
"exclude": ["coverage/", "node_modules/"],
"include": ["**/*.js", "index.d.ts"]
"include": ["**/*.js", "test/types.d.ts", "index.d.ts"]
}

0 comments on commit a44db46

Please sign in to comment.