UNPKG

perfectionist-dfd

Version:

Beautify and/or normalize CSS files. Fork and update of a fork and update of an archived project.

661 lines (595 loc) 19.7 kB
'use strict'; var postcss = require('postcss'); var module$1 = require('module'); var valueParser = require('postcss-value-parser'); var defined = require('defined'); require('string.prototype.repeat'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var postcss__default = /*#__PURE__*/_interopDefaultLegacy(postcss); var valueParser__default = /*#__PURE__*/_interopDefaultLegacy(valueParser); var defined__default = /*#__PURE__*/_interopDefaultLegacy(defined); function blockCommentRegex() { return /\/\*(.*?)\*\//gms; } const unit = valueParser__default["default"].unit; function isHex (node) { if (node.value[0] !== '#') { return false; } const range = node.value.slice(1); return ~[3, 4, 6, 8].indexOf(range.length) && !isNaN(parseInt(range, 16)); } function toShorthand (hex) { if ( hex.length === 7 && hex[1] === hex[2] && hex[3] === hex[4] && hex[5] === hex[6] ) { return '#' + hex[2] + hex[4] + hex[6]; } return hex; } function toLonghand (hex) { if (hex.length !== 4) { return hex; } const r = hex[1]; const g = hex[2]; const b = hex[3]; return '#' + r + r + g + g + b + b; } const lengths = [ 'px', 'em', 'rem', 'ex', 'ch', 'vh', 'vw', 'cm', 'mm', 'in', 'pt', 'pc', 'vmin', 'vmax', ]; function applyTransformFeatures (node, opts) { if (isHex(node)) { if (opts.colorCase === 'upper') { node.value = node.value.toUpperCase(); } if (opts.colorCase === 'lower') { node.value = node.value.toLowerCase(); } if (opts.colorShorthand === true) { node.value = toShorthand(node.value); } if (opts.colorShorthand === false) { node.value = toLonghand(node.value); } } const pair = unit(node.value); if (pair) { if ( opts.zeroLengthNoUnit === true && ~lengths.indexOf(pair.unit.toLowerCase()) && Number(pair.number) === 0 ) { node.value = '0'; return; } const parts = pair.number.split('.'); let pre = parts[0]; let post = parts.slice(1).join('.'); if (opts.trimLeadingZero === true && parts[1]) { pre = pre.replace(/^0+/, ''); } else if (opts.trimLeadingZero === false && !pre.length) { pre = 0; } if (opts.trimTrailingZeros === true && parts[1]) { const rounded = String(Number(pre + '.' + post)).split('.')[1]; post = rounded ? '.' + rounded : ''; } else if (opts.trimTrailingZeros === false && parts[1]) { post = '.' + parts[1]; } node.value = pre + post + pair.unit; } } function blank (value) { return defined__default["default"](value, ''); } function deeplyNested ({nodes}) { return nodes && nodes.some(({nodes: children}) => children); } function space (amount, indent = ' ') { return indent.repeat(amount); } function getIndent (node, indent = ' ', base = 4) { let level = 0; let {parent} = node; while (parent && parent.type !== 'root') { level++; parent = parent.parent; } return space(level * base, indent); } function isSassVariable ({parent, prop}) { return parent.type === 'root' && prop[0] === '$'; } function splitProperty (rule, prop, opts) { const {breakEvery, reindent, reduce, max} = { reindent: false, ...opts, }; const property = rule[prop]; if (!max || !property) { return; } const exploded = postcss.list.comma(property); if (property.length > max || reduce) { let indent = 0; if (typeof reindent === 'function') { indent = reindent(rule); } rule[prop] = exploded.reduce((lines, chunk) => { if (breakEvery) { lines.push(chunk); return lines; } if (lines[lines.length - 1].length + indent <= max) { const merged = `${lines[lines.length - 1]}, ${chunk}`; if (indent + merged.length <= max) { lines[lines.length - 1] = merged; return lines; } } lines.push(chunk); return lines; }, [exploded.shift()]).join(',\n' + space(indent)); } } function maxAtRuleLength (rule, {maxAtRuleLength: max}) { return splitProperty(rule, 'params', { max, breakEvery: true, reindent: function (r) { return r.name.length + 2; }, }); } function maxSelectorLength (rule, opts) { return splitProperty(rule, 'selector', { max: opts.maxSelectorLength, reduce: true, // where possible reduce to one line reindent: function (r) { return getIndent(r, opts.indentChar, opts.indentSize).length; }, }); } function maxValueLength (rule, {maxValueLength: max}) { if (rule.raws.value && rule.raws.value.raw) { rule.value = rule.raws.value.raw; } return splitProperty(rule, 'value', { max, breakEvery: true, reindent: function (r) { return getIndent(r).length + r.prop.length + 2; }, }); } function walk (parent, callback) { parent.nodes.forEach((node, index) => { const bubble = callback(node, index, parent); if (node.nodes && bubble !== false) { walk(node, callback); } }); } function applyCompact (css, opts) { css.walk(rule => { if (rule.type === 'decl') { if (rule.raws.value) { rule.value = rule.raws.value.raw.trim(); } // Format sass variable `$size: 30em;` if (isSassVariable(rule)) { rule.raws.before = ''; rule.raws.between = ': '; } const ast = valueParser__default["default"](rule.value); walk(ast, (node, index, parent) => { const next = parent.nodes[index + 1]; if (node.type === 'div' && node.value === ',') { node.before = ''; node.after = ' '; } if (node.type === 'function') { node.before = node.after = ' '; } if (node.type === 'space') { node.value = ' '; } if ( node.type === 'word' && node.value === '!' && parent.nodes[index + 2] && next.type === 'space' && parent.nodes[index + 2].type === 'word' ) { next.type = 'word'; next.value = ''; } if (node.type === 'word') { applyTransformFeatures(node, opts); } }); rule.value = ast.toString(); // Format `!important` if (rule.important) { rule.raws.important = ' !important'; } if (rule.raws.value) { rule.raws.value.raw = rule.value; } } opts.indentSize = 1; if (rule.type === 'comment') { if (rule.raws.inline) { rule.raws.inline = null; } let prev = rule.prev(); if (prev && prev.type === 'decl') { rule.raws.before = ' ' + blank(rule.raws.before); } if (rule.parent && rule.parent.type === 'root') { let next = rule.next(); if (next) { next.raws.before = '\n'; } if (rule !== css.first) { rule.raws.before = '\n'; } } return; } let indent = getIndent(rule, opts.indentChar, opts.indentSize); let deep = deeplyNested(rule); if (rule.type === 'rule' || rule.type === 'atrule') { if (!rule.nodes) { rule.raws.between = ''; } else { rule.raws.between = ' '; } rule.raws.after = ' '; rule.raws.before = indent + blank(rule.raws.before); rule.raws.semicolon = true; } if (rule.raws.selector && rule.raws.selector.raw) { rule.selector = rule.raws.selector.raw; } maxSelectorLength(rule, opts); if (rule.type === 'decl') { if (deeplyNested(rule.parent)) { let newline = rule === css.first ? '' : '\n'; rule.raws.before = newline + indent + blank(rule.raws.before); } else { rule.raws.before = ' ' + blank(rule.raws.before); } if (!blockCommentRegex().test(rule.raws.between)) { rule.raws.between = ': '; } } if ((deep || rule.nodes) && rule !== css.first) { rule.raws.before = '\n '; } if (deep) { rule.raws.after = '\n' + indent; } if (rule.parent && rule !== rule.parent.first && (rule.type === 'rule' || rule.type === 'atrule')) { rule.raws.before = '\n' + indent; } }); css.raws.after = '\n'; } function applyCompressed (css, opts) { css.walk(rule => { const {raws, type} = rule; rule.raws.semicolon = false; if (type === 'comment' && raws.inline) { rule.raws.inline = null; } if (type === 'rule' || type === 'atrule') { rule.raws.between = rule.raws.after = ''; } if (type === 'decl' && !blockCommentRegex().test(raws.between)) { rule.raws.between = ':'; } if (rule.type === 'decl') { if (raws.value) { rule.value = raws.value.raw.trim(); } const ast = valueParser__default["default"](rule.value); walk(ast, (node, index, parent) => { const next = parent.nodes[index + 1]; if (node.type === 'div' && node.value === ',' || node.type === 'function') { node.before = node.after = ''; } if (node.type === 'space') { node.value = ' '; if (next.type === 'word' && next.value[0] === '!') { node.value = ''; } } if ( node.type === 'word' && node.value === '!' && parent.nodes[index + 2] && next.type === 'space' && parent.nodes[index + 2].type === 'word' ) { next.type = 'word'; next.value = ''; } if (node.type === 'word') { applyTransformFeatures(node, opts); } }); rule.value = ast.toString(); if (isSassVariable(rule)) { rule.raws.before = ''; } // Format `!important` if (rule.important) { rule.raws.important = '!important'; } if (raws.value) { rule.raws.value.raw = rule.value; } } }); // Remove final newline css.raws.after = ''; } var longest = (a, b) => b.prop.length - a.prop.length; /** * List of vendor prefixes * * @type {string[]} */ const vendors = [ 'ah', 'apple', 'atsc', 'epub', 'hp', 'khtml', 'moz', 'ms', 'o', 'rim', 'ro', 'tc', 'wap', 'webkit', 'xv' ]; const prefixes = vendors.map(vendor => `-${vendor}-`); function prefixedDeclarations ({nodes}) { const prefix = node => prefixes.some(p => node.prop && !node.prop.indexOf(p)); return nodes.filter(prefix); } var sameLine = (a, b) => a.source.end.line === b.source.start.line; function unprefixed (prop) { return prop.replace(/^-\w+-/, ''); } function applyExpanded (css, opts) { css.walk(rule => { const {raws, type} = rule; if (type === 'decl') { if (raws.value) { rule.value = raws.value.raw.trim(); } // Format sass variable `$size: 30em;` if (isSassVariable(rule)) { if (rule !== css.first) { rule.raws.before = '\n'; } rule.raws.between = ': '; } const ast = valueParser__default["default"](rule.value); walk(ast, (node, index, parent) => { const next = parent.nodes[index + 1]; if (node.type === 'function') { node.before = node.after = ''; } if (node.type === 'div' && node.value === ',') { node.before = ''; node.after = ' '; } if (node.type === 'space') { node.value = ' '; } if ( node.type === 'word' && node.value === '!' && parent.nodes[index + 2] && next.type === 'space' && parent.nodes[index + 2].type === 'word' ) { next.type = 'word'; next.value = ''; } if (node.type === 'word') { applyTransformFeatures(node, opts); } }); rule.value = ast.toString(); // Format `!important` if (rule.important) { rule.raws.important = ' !important'; } if (raws.value) { rule.raws.value.raw = rule.value; } } let indent = getIndent(rule, opts.indentChar, opts.indentSize); if (type === 'comment') { let prev = rule.prev(); if (prev && prev.type === 'decl') { if (sameLine(prev, rule)) { rule.raws.before = ' ' + blank(rule.raws.before); } else { rule.raws.before = '\n' + indent + blank(rule.raws.before); } } if (!prev && rule !== css.first) { rule.raws.before = '\n' + indent + blank(rule.raws.before); } if (rule.parent && rule.parent.type === 'root') { let next = rule.next(); if (next) { next.raws.before = '\n\n'; } if (rule !== css.first) { rule.raws.before = '\n\n'; } } return; } rule.raws.before = indent + blank(rule.raws.before); if (type === 'rule' || type === 'atrule') { if (!rule.nodes) { rule.raws.between = ''; } else { rule.raws.between = ' '; } rule.raws.semicolon = true; if (rule.nodes) { rule.raws.after = '\n'; } } // visual cascade of vendor prefixed properties if (opts.cascade && type === 'rule' && rule.nodes.length > 1) { let props = []; let prefixed = prefixedDeclarations(rule).sort(longest).filter(({prop}) => { let base = unprefixed(prop); if (!~props.indexOf(base)) { return props.push(base); } return false; }); prefixed.forEach(prefix => { let base = unprefixed(prefix.prop); let vendor = prefix.prop.replace(base, '').length; rule.nodes.filter(({prop}) => prop && ~prop.indexOf(base)).forEach(decl => { let thisVendor = decl.prop.replace(base, '').length; let extraSpace = vendor - thisVendor; if (extraSpace > 0) { decl.raws.before = space(extraSpace) + blank(decl.raws.before); } }); }); } if (raws.selector && raws.selector.raw) { rule.selector = rule.raws.selector.raw; } maxSelectorLength(rule, opts); if (type === 'atrule') { if (rule.params) { rule.raws.afterName = ' '; } maxAtRuleLength(rule, opts); } if (type === 'decl') { if (!blockCommentRegex().test(rule.raws.between)) { rule.raws.between = ': '; } maxValueLength(rule, opts); } if (rule.parent && rule.parent.type !== 'root') { rule.raws.before = '\n' + blank(rule.raws.before); rule.raws.after = '\n' + indent; } if (rule.parent && rule !== rule.parent.first && (type === 'rule' || type === 'atrule')) { if (type === 'atrule' && !rule.nodes) { rule.raws.before = '\n' + indent; return; } rule.raws.before = '\n\n' + indent; } }); css.raws.after = '\n'; } const requireShim = module$1.createRequire((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('perfectionist-dfd.js', document.baseURI).href))); function checkOpts(defOpts, opts = {}) { var retOpts = opts; Object.keys(defOpts).forEach(key => { if (typeof opts[key] === 'undefined') { retOpts[key] = defOpts[key]; } }); return retOpts; } const defaultOpts = { cascade: true, colorCase: 'lower', colorShorthand: true, format: 'expanded', from: undefined, indentSize: 4, indentChar: ' ', maxAtRuleLength: 80, maxSelectorLength: 80, maxValueLength: 80, trimLeadingZero: true, trimTrailingZeros: true, zeroLengthNoUnit: true }; const perfectionistDFD = (opts = {}) => { opts = checkOpts(defaultOpts, opts); return { postcssPlugin: 'perfectionist-dfd', postcssVersion: '8.0.0', Once (css) { css.walk(node => { if (node.raws.before) { node.raws.before = node.raws.before.replace(/[;\s]/g, ''); } }); switch (opts.format) { case 'compact': applyCompact(css, opts); break; case 'compressed': applyCompressed(css, opts); break; case 'expanded': default: applyExpanded(css, opts); break; } } }; }; const perfectionistDFDProcess = (css, opts = {}) => { opts = checkOpts({ from: undefined, map: undefined, sourcemap: undefined, syntax: undefined, to: undefined }, opts); opts = checkOpts(defaultOpts, opts); opts.map = opts.map || (opts.sourcemap ? true : undefined); if (opts.syntax === 'scss') { opts.syntax = requireShim('postcss-scss'); } let processor = postcss__default["default"]([ perfectionistDFD(opts) ]); return processor.process(css, opts); }; perfectionistDFD.postcss = true; perfectionistDFD.process = perfectionistDFDProcess; module.exports = perfectionistDFD;