mirror of
https://github.com/hanzoai/postcss.git
synced 2026-09-24 17:01:56 +00:00
Rewrite code style detector
This commit is contained in:
+5
-19
@@ -9,23 +9,8 @@ class AtRule extends Container {
|
||||
super(defaults);
|
||||
}
|
||||
|
||||
// Different style for this.encoding and this.page at-rules.
|
||||
styleType() {
|
||||
return this.type + (this.childs ? '-body' : '-bodiless');
|
||||
}
|
||||
|
||||
defaultStyle(type) {
|
||||
if ( type == 'atrule-body' ) {
|
||||
return { between: ' ', after: this.defaultAfter() };
|
||||
} else {
|
||||
return { between: '' };
|
||||
}
|
||||
}
|
||||
|
||||
// Stringify at-rule
|
||||
stringify(builder, semicolon) {
|
||||
var style = this.style();
|
||||
|
||||
var name = '@' + this.name;
|
||||
var params = this.params ? this.stringifyRaw('params') : '';
|
||||
|
||||
@@ -36,12 +21,13 @@ class AtRule extends Container {
|
||||
}
|
||||
|
||||
if ( this.childs ) {
|
||||
this.stringifyBlock(builder, name + params + style.between + '{');
|
||||
this.stringifyBlock(builder, name + params);
|
||||
|
||||
} else {
|
||||
if ( this.before ) builder(this.before);
|
||||
var end = semicolon ? ';' : '';
|
||||
builder(name + params + style.between + end, this);
|
||||
var before = this.style('beforeRule');
|
||||
if ( before ) builder(before);
|
||||
var end = (this.between || '') + (semicolon ? ';' : '');
|
||||
builder(name + params + end, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-5
@@ -7,16 +7,19 @@ class Comment extends Node {
|
||||
super(defaults);
|
||||
}
|
||||
|
||||
// Default spaces for new node
|
||||
defaultStyle() {
|
||||
return { left: ' ', right: ' ' };
|
||||
styleMap() {
|
||||
return {
|
||||
commentLeft: this.left,
|
||||
commentRight: this.right
|
||||
};
|
||||
}
|
||||
|
||||
// Stringify declaration
|
||||
stringify(builder) {
|
||||
var style = this.style();
|
||||
if ( this.before ) builder(this.before);
|
||||
builder('/*' + style.left + this.text + style.right + '*/', this);
|
||||
var left = this.style('commentLeft');
|
||||
var right = this.style('commentRight');
|
||||
builder('/*' + left + this.text + right + '*/', this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+24
-20
@@ -3,6 +3,19 @@ var Declaration = require('./declaration');
|
||||
|
||||
// CSS node, that contain another nodes (like at-rules or rules with selectors)
|
||||
class Container extends Node {
|
||||
styleMap() {
|
||||
var style = {
|
||||
beforeRule: this.before,
|
||||
beforeOpen: this.between
|
||||
};
|
||||
if ( this.childs && this.childs.length ) {
|
||||
style.beforeClose = this.after;
|
||||
} else {
|
||||
style.emptyBody = this.after;
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
// Stringify container childs
|
||||
stringifyContent(builder) {
|
||||
if ( !this.childs ) return;
|
||||
@@ -18,31 +31,22 @@ class Container extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
// Generate default spaces before }
|
||||
defaultAfter() {
|
||||
if ( this.childs.length === 0 ) {
|
||||
return '';
|
||||
} else {
|
||||
var last = this.childs[0].before;
|
||||
if ( typeof(last) != 'undefined' && last.indexOf("\n") == -1 ) {
|
||||
return this.childs[0].before;
|
||||
} else {
|
||||
return "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stringify node with start (for example, selector) and brackets block
|
||||
// with child inside
|
||||
stringifyBlock(builder, start) {
|
||||
var style = this.style();
|
||||
var before = this.style('beforeRule');
|
||||
if ( before ) builder(before);
|
||||
builder(start + this.style('beforeOpen') + '{', this, 'start');
|
||||
|
||||
if ( this.before ) builder(this.before);
|
||||
builder(start, this, 'start');
|
||||
var after;
|
||||
if ( this.childs && this.childs.length ) {
|
||||
this.stringifyContent(builder);
|
||||
after = this.style('beforeClose');
|
||||
} else {
|
||||
after = this.style('emptyBody');
|
||||
}
|
||||
|
||||
this.stringifyContent(builder);
|
||||
|
||||
if ( style.after ) builder(style.after);
|
||||
if ( after ) builder(after);
|
||||
builder('}', this, 'end');
|
||||
}
|
||||
|
||||
|
||||
+9
-5
@@ -8,16 +8,20 @@ class Declaration extends Node {
|
||||
super(defaults);
|
||||
}
|
||||
|
||||
defaultStyle() {
|
||||
return { before: "\n ", between: ': ' };
|
||||
styleMap() {
|
||||
return {
|
||||
beforeDecl: this.before,
|
||||
colon: this.between
|
||||
};
|
||||
}
|
||||
|
||||
// Stringify declaration
|
||||
stringify(builder, semicolon) {
|
||||
var style = this.style();
|
||||
var before = this.style('beforeDecl');
|
||||
if ( before ) builder(before);
|
||||
|
||||
if ( style.before ) builder(style.before);
|
||||
var string = this.prop + style.between + this.stringifyRaw('value');
|
||||
var between = this.style('colon');
|
||||
var string = this.prop + between + this.stringifyRaw('value');
|
||||
|
||||
if ( this.important ) {
|
||||
string += this._important || ' !important';
|
||||
|
||||
+42
-61
@@ -21,22 +21,6 @@ var clone = function (obj, parent) {
|
||||
return cloned;
|
||||
};
|
||||
|
||||
// Is `obj` has all keys from `keys`. Return `false` of object with keys from
|
||||
// `keys` and values from `obj`.
|
||||
var keys = function (obj, keys) {
|
||||
var all = { };
|
||||
|
||||
for ( var key in keys ) {
|
||||
if ( typeof(obj[key]) == 'undefined' ) {
|
||||
return false;
|
||||
} else {
|
||||
all[key] = obj[key];
|
||||
}
|
||||
}
|
||||
|
||||
return all;
|
||||
};
|
||||
|
||||
// Some common methods for all CSS nodes
|
||||
class Node {
|
||||
constructor(defaults = { }) {
|
||||
@@ -116,61 +100,46 @@ class Node {
|
||||
return fixed;
|
||||
}
|
||||
|
||||
// Default code style
|
||||
defaultStyle() {
|
||||
styleMap() {
|
||||
return { };
|
||||
}
|
||||
|
||||
// Allow to split node with same type by other critera.
|
||||
// For example, to use different style for bodiless at-rules.
|
||||
styleType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
// Copy code style from first node with same type
|
||||
style() {
|
||||
var type = this.styleType();
|
||||
var defaults = this.defaultStyle(type);
|
||||
style(name) {
|
||||
// Already had
|
||||
var value = this.styleMap()[name];
|
||||
if ( typeof(value) != 'undefined' ) return value;
|
||||
|
||||
var all = keys(this, defaults);
|
||||
if ( all ) return all;
|
||||
var parent = this.parent;
|
||||
|
||||
var styled = defaults;
|
||||
if ( this.parent ) {
|
||||
|
||||
var root = this;
|
||||
while ( root.parent ) root = root.parent;
|
||||
|
||||
if ( !root.styleCache ) root.styleCache = { };
|
||||
if ( root.styleCache[type] ) {
|
||||
styled = root.styleCache[type];
|
||||
|
||||
} else {
|
||||
root.eachInside( (another) => {
|
||||
if ( another.styleType() != type ) return;
|
||||
if ( this == another ) return;
|
||||
|
||||
all = keys(another, styled);
|
||||
if ( all ) {
|
||||
styled = all;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
root.styleCache[type] = styled;
|
||||
}
|
||||
// Hack for first rule in CSS
|
||||
if ( !parent && (name == 'beforeRule' || name == 'beforeDecl') ) {
|
||||
return '';
|
||||
}
|
||||
if ( name == 'beforeRule' && !parent.parent && parent.first == this ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var merge = { };
|
||||
for ( var key in styled ) {
|
||||
if ( typeof(this[key]) == 'undefined' ) {
|
||||
merge[key] = styled[key];
|
||||
} else {
|
||||
merge[key] = this[key];
|
||||
}
|
||||
// Floating child without parent
|
||||
if ( !parent ) return this.defaultStyle[name];
|
||||
|
||||
// Detect style by other nodes
|
||||
var root = parent;
|
||||
while ( root.parent ) root = root.parent;
|
||||
|
||||
if ( !root.styleCache ) root.styleCache = { };
|
||||
if ( typeof(root.styleCache[name]) != 'undefined' ) {
|
||||
return root.styleCache[name];
|
||||
}
|
||||
|
||||
return merge;
|
||||
root.eachInside( (other) => {
|
||||
value = other.styleMap()[name];
|
||||
if ( typeof(value) != 'undefined' ) return false;
|
||||
});
|
||||
if ( typeof(value) == 'undefined' ) value = this.defaultStyle[name];
|
||||
|
||||
root.styleCache[name] = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
// Use raw value if origin was not changed
|
||||
@@ -185,4 +154,16 @@ class Node {
|
||||
}
|
||||
}
|
||||
|
||||
// Default code style
|
||||
Node.prototype.defaultStyle = {
|
||||
colon: ': ',
|
||||
beforeDecl: '\n ',
|
||||
commentLeft: ' ',
|
||||
commentRight: ' ',
|
||||
beforeRule: '\n',
|
||||
beforeOpen: ' ',
|
||||
beforeClose: '\n',
|
||||
emptyBody: ''
|
||||
};
|
||||
|
||||
module.exports = Node;
|
||||
|
||||
+1
-15
@@ -10,19 +10,6 @@ class Rule extends Container {
|
||||
super(defaults);
|
||||
}
|
||||
|
||||
// Different style for empty and non-empty rules
|
||||
styleType() {
|
||||
return this.type + (this.childs.length ? '-body' : '-empty');
|
||||
}
|
||||
|
||||
defaultStyle(type) {
|
||||
if ( type == 'rule-body' ) {
|
||||
return { between: ' ', after: this.defaultAfter() };
|
||||
} else {
|
||||
return { between: ' ', after: '' };
|
||||
}
|
||||
}
|
||||
|
||||
// Shortcut to get selectors as array
|
||||
|
||||
get selectors() {
|
||||
@@ -35,8 +22,7 @@ class Rule extends Container {
|
||||
|
||||
// Stringify rule
|
||||
stringify(builder) {
|
||||
this.stringifyBlock(builder,
|
||||
this.stringifyRaw('selector') + this.style().between + '{');
|
||||
this.stringifyBlock(builder, this.stringifyRaw('selector'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ describe('AtRule', () => {
|
||||
rule.toString().should.eql('@page 1 {}');
|
||||
});
|
||||
|
||||
it('clone spaces from another comment', () => {
|
||||
it('clone spaces from another at-rule', () => {
|
||||
var root = parse('@page{}');
|
||||
var rule = new AtRule({ name: 'page', params: 1, childs: [] });
|
||||
root.append(rule);
|
||||
|
||||
+4
-1
@@ -1,5 +1,6 @@
|
||||
var Declaration = require('../lib/declaration');
|
||||
var parse = require('../lib/parse');
|
||||
var Rule = require('../lib/rule');
|
||||
|
||||
var should = require('should');
|
||||
|
||||
@@ -17,7 +18,7 @@ describe('Declaration', () => {
|
||||
it('returns boolean', () => {
|
||||
var decl = new Declaration({ prop: 'color', value: 'black' });
|
||||
decl.important = true;
|
||||
decl.toString().should.eql('\n color: black !important');
|
||||
decl.toString().should.eql('color: black !important');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -46,6 +47,8 @@ describe('Declaration', () => {
|
||||
|
||||
it('inserts default spaces', () => {
|
||||
var decl = new Declaration({ prop: 'color', value: 'black' });
|
||||
var rule = new Rule({ selector: 'a' });
|
||||
rule.append(decl);
|
||||
decl.toString().should.eql("\n color: black");
|
||||
});
|
||||
|
||||
|
||||
+31
-21
@@ -50,7 +50,7 @@ describe('Node', () => {
|
||||
a.append( new Rule({ selector: 'b' }) );
|
||||
|
||||
root.first.replace(a);
|
||||
root.toString().should.eql('a {}b {}');
|
||||
root.toString().should.eql('a {}\nb {}');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -58,7 +58,7 @@ describe('Node', () => {
|
||||
describe('clone()', () => {
|
||||
|
||||
it('clones nodes', () => {
|
||||
var rule = new Rule({ selector: 'a' });
|
||||
var rule = new Rule({ selector: 'a', after: '' });
|
||||
rule.append({ prop: 'color', value: '/**/black', before: '' });
|
||||
|
||||
var clone = rule.clone();
|
||||
@@ -99,38 +99,48 @@ describe('Node', () => {
|
||||
|
||||
describe('style()', () => {
|
||||
|
||||
it('uses defaults without parent', () => {
|
||||
var rule = new Rule({ selector: 'a' });
|
||||
rule.style().should.eql({ between: ' ', after: '' });
|
||||
it('uses node style', () => {
|
||||
var rule = new Rule({ selector: 'a', before: ' ' });
|
||||
rule.style('beforeRule').should.eql(' ');
|
||||
});
|
||||
|
||||
it('uses defaults for artificial nodes', () => {
|
||||
it('hacks before for nodes without parent', () => {
|
||||
var rule = new Rule({ selector: 'a' });
|
||||
rule.style('beforeRule').should.eql('');
|
||||
});
|
||||
|
||||
it('hacks before for first node', () => {
|
||||
var root = new Root();
|
||||
root.append(new Rule({ selector: 'a' }));
|
||||
root.first.style().should.eql({ between: ' ', after: '' });
|
||||
root.first.style('beforeRule').should.eql('');
|
||||
});
|
||||
|
||||
it('uses nodes style', () => {
|
||||
it('hacks before for first decl', () => {
|
||||
var decl = new Declaration({ prop: 'color', value: 'black' });
|
||||
decl.style('beforeDecl').should.eql('');
|
||||
|
||||
var rule = new Rule({ selector: 'a' });
|
||||
rule.append(decl);
|
||||
decl.style('beforeDecl').should.eql('\n ');
|
||||
});
|
||||
|
||||
it('uses defaults without parent', () => {
|
||||
var rule = new Rule({ selector: 'a' });
|
||||
rule.style('beforeOpen').should.eql(' ');
|
||||
});
|
||||
|
||||
it('uses defaults for unique node', () => {
|
||||
var root = new Root();
|
||||
root.append( new Rule({ selector: 'a', between: '', after: '' }) );
|
||||
root.first.style().should.eql({ between: '', after: '' });
|
||||
root.append(new Rule({ selector: 'a' }));
|
||||
root.first.style('beforeOpen').should.eql(' ');
|
||||
});
|
||||
|
||||
it('clones style from first node', () => {
|
||||
var root = new Root();
|
||||
root.append( new Rule({ selector: 'a', between: '', after: ' ' }) );
|
||||
root.append( new Rule({ selector: 'a', between: '' }) );
|
||||
root.append( new Rule({ selector: 'b' }) );
|
||||
|
||||
root.last.style().should.eql({ between: '', after: ' ' });
|
||||
});
|
||||
|
||||
it('uses different style for different node style type', () => {
|
||||
var root = new Root();
|
||||
root.append( new AtRule({ name: 'page', childs: [] }) );
|
||||
root.append( new AtRule({ name: 'import' }) );
|
||||
|
||||
root.first.style().should.eql({ between: ' ', after: '' });
|
||||
root.last.style().should.eql( { between: '' });
|
||||
root.last.style('beforeOpen').should.eql('');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+2
-11
@@ -44,6 +44,8 @@ describe('Rule', () => {
|
||||
it('inserts default spaces', () => {
|
||||
var rule = new Rule({ selector: 'a' });
|
||||
rule.toString().should.eql('a {}');
|
||||
rule.append({ prop: 'color', value: 'black' });
|
||||
rule.toString().should.eql('a {\n color: black\n}');
|
||||
});
|
||||
|
||||
it('clone spaces from another rule', () => {
|
||||
@@ -65,17 +67,6 @@ describe('Rule', () => {
|
||||
rule.toString().should.eql("\nem {\n top: 0\n }");
|
||||
});
|
||||
|
||||
it('calculates after depends on childs', () => {
|
||||
var rule = new Rule({ selector: 'a' });
|
||||
rule.toString().should.eql('a {}');
|
||||
|
||||
rule.append({ prop: 'color', value: 'black', before: ' ' });
|
||||
rule.toString().should.eql('a { color: black }');
|
||||
|
||||
rule.first.before = "\n ";
|
||||
rule.toString().should.eql("a {\n color: black\n}");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user