Rewrite code style detector

This commit is contained in:
Andrey Sitnik
2014-11-06 13:22:04 +03:00
parent 1c09df2f68
commit fdda048b2a
10 changed files with 127 additions and 159 deletions
+5 -19
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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}");
});
});
});