UNPKG

stylelint

Version:

A mighty CSS linter that helps you avoid errors and enforce conventions.

138 lines (109 loc) 4.19 kB
/** @typedef {Record<string, {prelude?: string | undefined, descriptors?: Record<string, string> | undefined}>} AtRuleDefinitions */ /** @typedef {Record<string, string>} PropertyDefinitions */ /** @typedef {Record<string, string>} TypeDefinitions */ /** @typedef {Array<string>} CSSWideKeywords */ /** @typedef {{atrules?: AtRuleDefinitions | undefined, properties?: PropertyDefinitions | undefined, types?: TypeDefinitions | undefined, cssWideKeywords?: CSSWideKeywords | undefined}} SyntaxDefinition */ /** * Merge multiple separate syntax definitions. * Individual definitions will override each other unless the second definition starts with a pipe symbol `|`. * * @param {...(SyntaxDefinition|undefined)} sources * @returns {SyntaxDefinition} */ export default function mergeSyntaxDefinitions(...sources) { /** @type {SyntaxDefinition|undefined} */ let target = sources[0] ?? {}; for (let i = 1; i < sources.length; i++) { const source = sources[i]; if (!source) continue; target = mergeTwoSyntaxDefinitions(target, source); } return target; } /** * Merge two separate syntax definitions. * Individual definitions will override each other unless the second definition starts with a pipe symbol `|`. * * @param {SyntaxDefinition} source1 * @param {SyntaxDefinition} source2 * @returns {SyntaxDefinition} */ function mergeTwoSyntaxDefinitions(source1, source2) { /** @type {SyntaxDefinition} */ const target = {}; target.atrules = mergeAtRuleDefinitions(source1.atrules, source2.atrules); target.properties = mergePropertyOrTypeDefinitions(source1.properties, source2.properties); target.types = mergePropertyOrTypeDefinitions(source1.types, source2.types); target.cssWideKeywords = mergeKeywords(source1.cssWideKeywords, source2.cssWideKeywords); return target; } /** * @param {AtRuleDefinitions | undefined} source1 * @param {AtRuleDefinitions | undefined} source2 * @returns {AtRuleDefinitions | undefined} */ function mergeAtRuleDefinitions(source1, source2) { if (typeof source1 === 'undefined' || typeof source2 === 'undefined') return source1 ?? source2 ?? {}; /** @type {AtRuleDefinitions} */ const target = structuredClone(source1); for (const [atRuleName, atRule] of Object.entries(source2)) { const targetAtRule = target[atRuleName] ?? {}; targetAtRule.prelude = mergePrelude(targetAtRule.prelude, atRule.prelude); targetAtRule.descriptors = mergePropertyOrTypeDefinitions( targetAtRule.descriptors, atRule.descriptors, ); target[atRuleName] = targetAtRule; } return target; } /** * @param {Record<string, string> | undefined} source1 * @param {Record<string, string> | undefined} source2 * @returns {Record<string, string> | undefined} */ function mergePropertyOrTypeDefinitions(source1, source2) { if (typeof source1 === 'undefined' || typeof source2 === 'undefined') return source1 ?? source2 ?? {}; /** @type {Record<string, string>} */ const target = { ...source1, }; for (const [name, syntax] of Object.entries(source2)) { target[name] = mergeSyntax(target[name], syntax); } return target; } /** * @param {Array<string> | undefined} source1 * @param {Array<string> | undefined} source2 * @returns {Array<string> | undefined} */ function mergeKeywords(source1, source2) { if (typeof source1 === 'undefined' || typeof source2 === 'undefined') return source1 ?? source2 ?? []; return Array.from(new Set([...source1, ...source2])); } /** * @param {string | undefined} source1 * @param {string} source2 * @returns {string} */ function mergeSyntax(source1, source2) { if (typeof source1 === 'undefined') return source2; const trimmedSource2 = source2.trimStart(); if (!trimmedSource2.startsWith('|')) return source2; return `${source1} ${trimmedSource2}`; } /** * @param {string | undefined} source1 * @param {string | undefined} source2 * @returns {string | undefined} */ function mergePrelude(source1, source2) { if (typeof source1 === 'undefined' || typeof source2 === 'undefined') return source1 ?? source2; const trimmedSource2 = source2.trimStart(); if (!trimmedSource2.startsWith('|')) return source2; return `${source1} ${trimmedSource2}`; }