diff --git a/lib/less/index.js b/lib/less/index.js index 9bf4b7c54..0832f4a10 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -78,7 +78,7 @@ var less = { 'selector', 'quoted', 'expression', 'rule', 'call', 'url', 'alpha', 'import', 'mixin', 'comment', 'anonymous', 'value', - 'javascript', 'assignment' + 'javascript', 'assignment', 'extend' ].forEach(function (n) { require('./tree/' + n); }); diff --git a/lib/less/parser.js b/lib/less/parser.js index 7464441a3..7fb2953f7 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -435,7 +435,8 @@ less.Parser = function Parser(env) { var node, root = []; while ((node = $(this.mixin.definition) || $(this.rule) || $(this.ruleset) || - $(this.mixin.call) || $(this.comment) || $(this.directive)) + $(this.mixin.call) || $(this.comment) || $(this.directive) || + $(this.extend)) || $(/^[\s\n]+/)) { node && root.push(node); } @@ -672,6 +673,23 @@ less.Parser = function Parser(env) { } }, + // + // extend + // + extend: function() { + var elements = [], e, c, args, index = i, s = input.charAt(i); + + if (s !== '+') { return } + + while (e = $(/^\+[#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/)) { + elements.push(new(tree.Element)(c, e.slice(1), i)); + } + + if (elements.length > 0 && ($(';') || peek('}'))) { + return new(tree.Extend)(elements, index); + } + }, + // // Mixins // diff --git a/lib/less/tree/extend.js b/lib/less/tree/extend.js new file mode 100644 index 000000000..4f6947df6 --- /dev/null +++ b/lib/less/tree/extend.js @@ -0,0 +1,56 @@ +(function (tree) { + +tree.Extend = function Extend(elements, index) { + this.selector = new(tree.Selector)(elements); + this.index = index; +}; + +tree.Extend.prototype.eval = function Extend_eval(env) { + var selfSelectors = findSelfSelectors(env.selectors), + targetValue = this.selector.elements[0].value; + + env.frames.forEach(function(frame) { + frame.rulesets().forEach(function(rule) { + rule.selectors.forEach(function(selector) { + selector.elements.forEach(function(element, idx) { + if (element.value === targetValue) { + selfSelectors.forEach(function(_selector) { + _selector.elements[0] = new tree.Element( + element.combinator, + _selector.elements[0].value, + _selector.elements[0].index + ); + rule.selectors.push(new tree.Selector( + selector.elements + .slice(0, idx) + .concat(_selector.elements) + .concat(selector.elements.slice(idx + 1)) + )); + }); + } + }); + }); + }); + }); + return this; +}; + +function findSelfSelectors(selectors) { + var ret = []; + + (function loop(elem, i) { + if (selectors[i] && selectors[i].length) { + selectors[i].forEach(function(s) { + loop(s.elements.concat(elem), i + 1); + }); + } + else { + ret.push({ elements: elem }); + } + })([], 0); + + return ret; +} + + +})(require('../tree')); diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 6ac025e58..44e65ec36 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -14,6 +14,12 @@ tree.Ruleset.prototype = { // push the current ruleset to the frames stack env.frames.unshift(ruleset); + // currrent selectors + if (!env.selectors) { + env.selectors = []; + } + env.selectors.unshift(this.selectors); + // Evaluate imports if (ruleset.root) { for (var i = 0; i < ruleset.rules.length; i++) { @@ -51,6 +57,7 @@ tree.Ruleset.prototype = { // Pop the stack env.frames.shift(); + env.selectors.shift(); return ruleset; }, diff --git a/test/css/extend-clearfix.css b/test/css/extend-clearfix.css new file mode 100644 index 000000000..292f057d2 --- /dev/null +++ b/test/css/extend-clearfix.css @@ -0,0 +1,15 @@ +.clearfix, .foo, .bar { + *zoom: 1; +} +.clearfix:after, .foo:after, .bar:after { + content: ''; + display: block; + clear: both; + height: 0; +} +.foo { + color: red; +} +.bar { + color: blue; +} diff --git a/test/css/extend-nest.css b/test/css/extend-nest.css new file mode 100644 index 000000000..7defeef1f --- /dev/null +++ b/test/css/extend-nest.css @@ -0,0 +1,15 @@ +.sidebar, .sidebar2, .type1 .sidebar3 { + width: 300px; + background: red; +} +.sidebar .box, .sidebar2 .box, .type1 .sidebar3 .box { + background: #FFF; + border: 1px solid #000; + margin: 10px 0; +} +.sidebar2 { + background: blue; +} +.type1 .sidebar3 { + background: green; +} diff --git a/test/css/extend.css b/test/css/extend.css new file mode 100644 index 000000000..5e14ad695 --- /dev/null +++ b/test/css/extend.css @@ -0,0 +1,30 @@ +.error, .badError { + border: 1px #f00; + background: #fdd; +} +.error.intrusion, .badError.intrusion { + font-size: 1.3em; + font-weight: bold; +} +.intrusion .error, .intrusion .badError { + display: none; +} +.badError { + border-width: 3px; +} +.foo .bar, +.foo .baz, +.ext1 .ext2 .bar, +.ext1 .ext2 .baz, +.ext3 .bar, +.ext4 .bar, +.ext3 .baz, +.ext4 .baz { + display: none; +} +div.ext5, +.ext6 > .ext5, +div.ext7, +.ext6 > .ext7 { + width: 100px; +} diff --git a/test/less/extend-clearfix.less b/test/less/extend-clearfix.less new file mode 100644 index 000000000..f496de9e8 --- /dev/null +++ b/test/less/extend-clearfix.less @@ -0,0 +1,19 @@ +.clearfix { + *zoom: 1; + &:after { + content: ''; + display: block; + clear: both; + height: 0; + } +} + +.foo { + +.clearfix; + color: red; +} + +.bar { + +.clearfix; + color: blue; +} diff --git a/test/less/extend-nest.less b/test/less/extend-nest.less new file mode 100644 index 000000000..5b64da0b9 --- /dev/null +++ b/test/less/extend-nest.less @@ -0,0 +1,22 @@ +.sidebar { + width: 300px; + background: red; + + .box { + background: #FFF; + border: 1px solid #000; + margin: 10px 0; + } +} + +.sidebar2 { + +.sidebar; + background: blue; +} + +.type1 { + .sidebar3 { + +.sidebar; + background: green; + } +} diff --git a/test/less/extend.less b/test/less/extend.less new file mode 100644 index 000000000..60fdcd4a3 --- /dev/null +++ b/test/less/extend.less @@ -0,0 +1,35 @@ +.error { + border: 1px #f00; + background: #fdd; +} +.error.intrusion { + font-size: 1.3em; + font-weight: bold; +} +.intrusion .error { + display: none; +} +.badError { + +.error; + border-width: 3px; +} + +.foo .bar, .foo .baz { + display: none; +} + +.ext1 .ext2 { + +.foo; +} + +.ext3, .ext4 { + +.foo; +} + +div.ext5, .ext6 > .ext5 { + width: 100px; +} + +.ext7 { + +.ext5; +}