import { dirname } from "node:path" import * as prettierMarkdown from "prettier/plugins/markdown" import type { Parser, Plugin } from "prettier" import type { Root } from "mdast" import { cosmiconfig } from "cosmiconfig" import { HTML_ONLY_OPTION_KEYS } from "./html.js" import { MARKDOWN_ONLY_OPTION_KEYS } from "./markdown.js" import { remarkPunctilio, type RemarkPunctilioOptions } from "./remark.js" import { omitKeys } from "./utils.js" const upstreamMarkdown = prettierMarkdown.parsers.markdown as Parser const explorer = cosmiconfig("punctilio") // A config file may be shared with the CLI, so it can carry markdown-only // keys (prettier controls stringification itself) and HTML-only keys. Strip // those; unknown keys still fail loudly inside remarkPunctilio. const INAPPLICABLE_CONFIG_KEYS: readonly string[] = [ ...MARKDOWN_ONLY_OPTION_KEYS, ...HTML_ONLY_OPTION_KEYS, ] async function loadPunctilioConfig(searchFrom: string): Promise { const result = await explorer.search(searchFrom) if (!result || result.isEmpty) return {} return omitKeys(result.config as RemarkPunctilioOptions, INAPPLICABLE_CONFIG_KEYS) } async function runRemarkPunctilio(opts: RemarkPunctilioOptions, ast: Root): Promise { // The unified `Transformer` signature requires a VFile and a callback, // but `remarkPunctilio` only ever reads the tree. Narrow to the actual // shape here so the cast lives in one well-commented place. const transformer = remarkPunctilio(opts) as (tree: Root) => void | Promise await transformer(ast) } const plugin: Plugin = { parsers: { markdown: { ...upstreamMarkdown, async parse(text, options) { const ast = await upstreamMarkdown.parse(text, options) const searchFrom = typeof options.filepath === "string" ? dirname(options.filepath) : process.cwd() const opts = await loadPunctilioConfig(searchFrom) await runRemarkPunctilio(opts, ast) return ast }, }, }, } export default plugin