import * as csstree from 'css-tree'; import { referencesProps } from './_collections.js'; /** * @typedef PrefixIdsParams * @property {boolean | string | ((node: import('../lib/types.js').XastElement, info: import('../lib/types.js').PluginInfo) => string)=} prefix * @property {string=} delim * @property {boolean=} prefixIds * @property {boolean=} prefixClassNames */ export const name = 'prefixIds'; export const description = 'prefix IDs'; /** * Extract basename from path. * * @param {string} path * @returns {string} */ const getBasename = (path) => { // extract everything after latest slash or backslash const matched = /[/\\]?([^/\\]+)$/.exec(path); if (matched) { return matched[1]; } return ''; }; /** * Escapes a string for being used as ID. * * @param {string} str * @returns {string} */ const escapeIdentifierName = (str) => { return str.replace(/[. ]/g, '_'); }; /** * @param {string} string * @returns {string} */ const unquote = (string) => { if ( (string.startsWith('"') && string.endsWith('"')) || (string.startsWith("'") && string.endsWith("'")) ) { return string.slice(1, -1); } return string; }; /** * Prefix the given string, unless it already starts with the generated prefix. * * @param {(id: string) => string} prefixGenerator Function to generate a prefix. * @param {string} body An arbitrary string. * @returns {string} The given string with a prefix prepended to it. */ const prefixId = (prefixGenerator, body) => { const prefix = prefixGenerator(body); if (body.startsWith(prefix)) { return body; } return prefix + body; }; /** * Insert the prefix in a reference string. A reference string is already * prefixed with #, so the prefix is inserted after the first character. * * @param {(id: string) => string} prefixGenerator Function to generate a prefix. * @param {string} reference An arbitrary string, should start with "#". * @returns {?string} The given string with a prefix inserted, or null if the string did not start with "#". */ const prefixReference = (prefixGenerator, reference) => { if (reference.startsWith('#')) { return '#' + prefixId(prefixGenerator, reference.slice(1)); } return null; }; /** * Generates a prefix for the given string. * * @param {string} body An arbitrary string. * @param {import('../lib/types.js').XastElement} node XML node that the identifier belongs to. * @param {import('../lib/types.js').PluginInfo} info * @param {((node: import('../lib/types.js').XastElement, info: import('../lib/types.js').PluginInfo) => string) | string | boolean | undefined} prefixGenerator Some way of obtaining a prefix. * @param {string} delim Content to insert between the prefix and original value. * @param {Map} history Map of previously generated prefixes to IDs. * @returns {string} A generated prefix. */ const generatePrefix = (body, node, info, prefixGenerator, delim, history) => { if (typeof prefixGenerator === 'function') { let prefix = history.get(body); if (prefix != null) { return prefix; } prefix = prefixGenerator(node, info) + delim; history.set(body, prefix); return prefix; } if (typeof prefixGenerator === 'string') { return prefixGenerator + delim; } if (prefixGenerator === false) { return ''; } if (info.path != null && info.path.length > 0) { return escapeIdentifierName(getBasename(info.path)) + delim; } return 'prefix' + delim; }; /** * Prefixes identifiers * * @author strarsis * @type {import('../lib/types.js').Plugin} */ export const fn = (_root, params, info) => { const { delim = '__', prefix, prefixIds = true, prefixClassNames = true, } = params; /** @type {Map} */ const prefixMap = new Map(); return { element: { enter: (node) => { /** * @param {string} id A node identifier or class. * @returns {string} Given string with a prefix inserted, or null if the string did not start with "#". */ const prefixGenerator = (id) => generatePrefix(id, node, info, prefix, delim, prefixMap); // prefix id/class selectors and url() references in styles if (node.name === 'style') { // skip empty