mirror of
https://github.com/hanzoai/postcss.git
synced 2026-09-24 17:01:56 +00:00
333 lines
9.2 KiB
JavaScript
333 lines
9.2 KiB
JavaScript
const defaultRaw = {
|
|
colon: ': ',
|
|
indent: ' ',
|
|
beforeDecl: '\n',
|
|
beforeRule: '\n',
|
|
beforeOpen: ' ',
|
|
beforeClose: '\n',
|
|
beforeComment: '\n',
|
|
after: '\n',
|
|
emptyBody: '',
|
|
commentLeft: ' ',
|
|
commentRight: ' '
|
|
};
|
|
|
|
function capitalize(str) {
|
|
return str[0].toUpperCase() + str.slice(1);
|
|
}
|
|
|
|
class Stringifier {
|
|
|
|
constructor(builder) {
|
|
this.builder = builder;
|
|
}
|
|
|
|
stringify(node, semicolon) {
|
|
this[node.type](node, semicolon);
|
|
}
|
|
|
|
root(node) {
|
|
this.body(node);
|
|
if ( node.raws.after ) this.builder(node.raws.after);
|
|
}
|
|
|
|
comment(node) {
|
|
let left = this.raw(node, 'left', 'commentLeft');
|
|
let right = this.raw(node, 'right', 'commentRight');
|
|
this.builder('/*' + left + node.text + right + '*/', node);
|
|
}
|
|
|
|
decl(node, semicolon) {
|
|
let between = this.raw(node, 'between', 'colon');
|
|
let string = node.prop + between + this.rawValue(node, 'value');
|
|
|
|
if ( node.important ) {
|
|
string += node.raws.important || ' !important';
|
|
}
|
|
|
|
if ( semicolon ) string += ';';
|
|
this.builder(string, node);
|
|
}
|
|
|
|
rule(node) {
|
|
this.block(node, this.rawValue(node, 'selector'));
|
|
if ( node.raws.ownSemicolon ) {
|
|
this.builder(node.raws.ownSemicolon, node, 'end');
|
|
}
|
|
}
|
|
|
|
atrule(node, semicolon) {
|
|
let name = '@' + node.name;
|
|
let params = node.params ? this.rawValue(node, 'params') : '';
|
|
|
|
if ( typeof node.raws.afterName !== 'undefined' ) {
|
|
name += node.raws.afterName;
|
|
} else if ( params ) {
|
|
name += ' ';
|
|
}
|
|
|
|
if ( node.nodes ) {
|
|
this.block(node, name + params);
|
|
} else {
|
|
let end = (node.raws.between || '') + (semicolon ? ';' : '');
|
|
this.builder(name + params + end, node);
|
|
}
|
|
}
|
|
|
|
body(node) {
|
|
let last = node.nodes.length - 1;
|
|
while ( last > 0 ) {
|
|
if ( node.nodes[last].type !== 'comment' ) break;
|
|
last -= 1;
|
|
}
|
|
|
|
let semicolon = this.raw(node, 'semicolon');
|
|
for ( let i = 0; i < node.nodes.length; i++ ) {
|
|
let child = node.nodes[i];
|
|
let before = this.raw(child, 'before');
|
|
if ( before ) this.builder(before);
|
|
this.stringify(child, last !== i || semicolon);
|
|
}
|
|
}
|
|
|
|
block(node, start) {
|
|
let between = this.raw(node, 'between', 'beforeOpen');
|
|
this.builder(start + between + '{', node, 'start');
|
|
|
|
let after;
|
|
if ( node.nodes && node.nodes.length ) {
|
|
this.body(node);
|
|
after = this.raw(node, 'after');
|
|
} else {
|
|
after = this.raw(node, 'after', 'emptyBody');
|
|
}
|
|
|
|
if ( after ) this.builder(after);
|
|
this.builder('}', node, 'end');
|
|
}
|
|
|
|
raw(node, own, detect) {
|
|
let value;
|
|
if ( !detect ) detect = own;
|
|
|
|
// Already had
|
|
if ( own ) {
|
|
value = node.raws[own];
|
|
if ( typeof value !== 'undefined' ) return value;
|
|
}
|
|
|
|
let parent = node.parent;
|
|
|
|
// Hack for first rule in CSS
|
|
if ( detect === 'before' ) {
|
|
if ( !parent || parent.type === 'root' && parent.first === node ) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
// Floating child without parent
|
|
if ( !parent ) return defaultRaw[detect];
|
|
|
|
// Detect style by other nodes
|
|
let root = node.root();
|
|
if ( !root.rawCache ) root.rawCache = { };
|
|
if ( typeof root.rawCache[detect] !== 'undefined' ) {
|
|
return root.rawCache[detect];
|
|
}
|
|
|
|
if ( detect === 'before' || detect === 'after' ) {
|
|
return this.beforeAfter(node, detect);
|
|
} else {
|
|
let method = 'raw' + capitalize(detect);
|
|
if ( this[method] ) {
|
|
value = this[method](root, node);
|
|
} else {
|
|
root.walk( i => {
|
|
value = i.raws[own];
|
|
if ( typeof value !== 'undefined' ) return false;
|
|
});
|
|
}
|
|
}
|
|
|
|
if ( typeof value === 'undefined' ) value = defaultRaw[detect];
|
|
|
|
root.rawCache[detect] = value;
|
|
return value;
|
|
}
|
|
|
|
rawSemicolon(root) {
|
|
let value;
|
|
root.walk( i => {
|
|
if ( i.nodes && i.nodes.length && i.last.type === 'decl' ) {
|
|
value = i.raws.semicolon;
|
|
if ( typeof value !== 'undefined' ) return false;
|
|
}
|
|
});
|
|
return value;
|
|
}
|
|
|
|
rawEmptyBody(root) {
|
|
let value;
|
|
root.walk( i => {
|
|
if ( i.nodes && i.nodes.length === 0 ) {
|
|
value = i.raws.after;
|
|
if ( typeof value !== 'undefined' ) return false;
|
|
}
|
|
});
|
|
return value;
|
|
}
|
|
|
|
rawIndent(root) {
|
|
if ( root.raws.indent ) return root.raws.indent;
|
|
let value;
|
|
root.walk( i => {
|
|
let p = i.parent;
|
|
if ( p && p !== root && p.parent && p.parent === root ) {
|
|
if ( typeof i.raws.before !== 'undefined' ) {
|
|
let parts = i.raws.before.split('\n');
|
|
value = parts[parts.length - 1];
|
|
value = value.replace(/[^\s]/g, '');
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
return value;
|
|
}
|
|
|
|
rawBeforeComment(root, node) {
|
|
let value;
|
|
root.walkComments( i => {
|
|
if ( typeof i.raws.before !== 'undefined' ) {
|
|
value = i.raws.before;
|
|
if ( value.indexOf('\n') !== -1 ) {
|
|
value = value.replace(/[^\n]+$/, '');
|
|
}
|
|
return false;
|
|
}
|
|
});
|
|
if ( typeof value === 'undefined' ) {
|
|
value = this.raw(node, null, 'beforeDecl');
|
|
} else if ( value ) {
|
|
value = value.replace(/[^\s]/g, '');
|
|
}
|
|
return value;
|
|
}
|
|
|
|
rawBeforeDecl(root, node) {
|
|
let value;
|
|
root.walkDecls( i => {
|
|
if ( typeof i.raws.before !== 'undefined' ) {
|
|
value = i.raws.before;
|
|
if ( value.indexOf('\n') !== -1 ) {
|
|
value = value.replace(/[^\n]+$/, '');
|
|
}
|
|
return false;
|
|
}
|
|
});
|
|
if ( typeof value === 'undefined' ) {
|
|
value = this.raw(node, null, 'beforeRule');
|
|
} else if ( value ) {
|
|
value = value.replace(/[^\s]/g, '');
|
|
}
|
|
return value;
|
|
}
|
|
|
|
rawBeforeRule(root) {
|
|
let value;
|
|
root.walk( i => {
|
|
if ( i.nodes && (i.parent !== root || root.first !== i) ) {
|
|
if ( typeof i.raws.before !== 'undefined' ) {
|
|
value = i.raws.before;
|
|
if ( value.indexOf('\n') !== -1 ) {
|
|
value = value.replace(/[^\n]+$/, '');
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
if ( value ) value = value.replace(/[^\s]/g, '');
|
|
return value;
|
|
}
|
|
|
|
rawBeforeClose(root) {
|
|
let value;
|
|
root.walk( i => {
|
|
if ( i.nodes && i.nodes.length > 0 ) {
|
|
if ( typeof i.raws.after !== 'undefined' ) {
|
|
value = i.raws.after;
|
|
if ( value.indexOf('\n') !== -1 ) {
|
|
value = value.replace(/[^\n]+$/, '');
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
if ( value ) value = value.replace(/[^\s]/g, '');
|
|
return value;
|
|
}
|
|
|
|
rawBeforeOpen(root) {
|
|
let value;
|
|
root.walk( i => {
|
|
if ( i.type !== 'decl' ) {
|
|
value = i.raws.between;
|
|
if ( typeof value !== 'undefined' ) return false;
|
|
}
|
|
});
|
|
return value;
|
|
}
|
|
|
|
rawColon(root) {
|
|
let value;
|
|
root.walkDecls( i => {
|
|
if ( typeof i.raws.between !== 'undefined' ) {
|
|
value = i.raws.between.replace(/[^\s:]/g, '');
|
|
return false;
|
|
}
|
|
});
|
|
return value;
|
|
}
|
|
|
|
beforeAfter(node, detect) {
|
|
let value;
|
|
if ( node.type === 'decl' ) {
|
|
value = this.raw(node, null, 'beforeDecl');
|
|
} else if ( node.type === 'comment' ) {
|
|
value = this.raw(node, null, 'beforeComment');
|
|
} else if ( detect === 'before' ) {
|
|
value = this.raw(node, null, 'beforeRule');
|
|
} else {
|
|
value = this.raw(node, null, 'beforeClose');
|
|
}
|
|
|
|
let buf = node.parent;
|
|
let depth = 0;
|
|
while ( buf && buf.type !== 'root' ) {
|
|
depth += 1;
|
|
buf = buf.parent;
|
|
}
|
|
|
|
if ( value.indexOf('\n') !== -1 ) {
|
|
let indent = this.raw(node, null, 'indent');
|
|
if ( indent.length ) {
|
|
for ( let step = 0; step < depth; step++ ) value += indent;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
rawValue(node, prop) {
|
|
let value = node[prop];
|
|
let raw = node.raws[prop];
|
|
if ( raw && raw.value === value ) {
|
|
return raw.raw;
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
export default Stringifier;
|