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
JavaScript
;
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;