From e793e81e929f494a7f3ff058a4781663f1028aad Mon Sep 17 00:00:00 2001 From: hokaccha Date: Mon, 5 Dec 2011 23:19:13 +0900 Subject: [PATCH] Add Sass like extend --- lib/less/index.js | 14 +++++----- lib/less/parser.js | 20 ++++++++++++- lib/less/tree/extend.js | 51 ++++++++++++++++++++++++++++++++++ lib/less/tree/ruleset.js | 7 +++++ test/css/extend-clearfix.css | 15 ++++++++++ test/css/extend-nest.css | 15 ++++++++++ test/css/extend.css | 24 ++++++++++++++++ test/less/extend-clearfix.less | 19 +++++++++++++ test/less/extend-nest.less | 22 +++++++++++++++ test/less/extend.less | 27 ++++++++++++++++++ 10 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 lib/less/tree/extend.js create mode 100644 test/css/extend-clearfix.css create mode 100644 test/css/extend-nest.css create mode 100644 test/css/extend.css create mode 100644 test/less/extend-clearfix.less create mode 100644 test/less/extend-nest.less create mode 100644 test/less/extend.less diff --git a/lib/less/index.js b/lib/less/index.js index a5fe7a8e..56ac5d62 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -85,13 +85,13 @@ var less = { } }; -['color', 'directive', 'operation', 'dimension', - 'keyword', 'variable', 'ruleset', 'element', - 'selector', 'quoted', 'expression', 'rule', - 'call', 'url', 'alpha', 'import', - 'mixin', 'comment', 'anonymous', 'value', - 'javascript', 'assignment', 'condition', 'paren', - 'media', 'ratio', 'unicode-descriptor' +['color', 'directive', 'operation', 'dimension', + 'keyword', 'variable', 'ruleset', 'element', + 'selector', 'quoted', 'expression', 'rule', + 'call', 'url', 'alpha', 'import', + 'mixin', 'comment', 'anonymous', 'value', + 'javascript', 'assignment', 'condition', 'paren', + 'media', 'ratio', 'unicode-descriptor', 'extend' ].forEach(function (n) { require('./tree/' + n); }); diff --git a/lib/less/parser.js b/lib/less/parser.js index d1d4e716..77c982bc 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -526,7 +526,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); } @@ -803,6 +804,23 @@ less.Parser = function Parser(env) { if ((a = $(this.entity)) && $('/') && (b = $(this.entity))) { return new(tree.Shorthand)(a, b); } + }, + + // + // 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); + } restore(); }, diff --git a/lib/less/tree/extend.js b/lib/less/tree/extend.js new file mode 100644 index 00000000..191ae0ff --- /dev/null +++ b/lib/less/tree/extend.js @@ -0,0 +1,51 @@ +(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) { + 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 a198617c..69152a3e 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -23,6 +23,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 || ruleset.allowImports || !ruleset.strictImports) { ruleset.evalImports(env); @@ -59,6 +65,7 @@ tree.Ruleset.prototype = { // Pop the stack env.frames.shift(); + env.selectors.shift(); if (env.mediaBlocks) { for(var i = mediaBlockCount; i < env.mediaBlocks.length; i++) { diff --git a/test/css/extend-clearfix.css b/test/css/extend-clearfix.css new file mode 100644 index 00000000..292f057d --- /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 00000000..7defeef1 --- /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 00000000..75b2e396 --- /dev/null +++ b/test/css/extend.css @@ -0,0 +1,24 @@ +.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; +} diff --git a/test/less/extend-clearfix.less b/test/less/extend-clearfix.less new file mode 100644 index 00000000..f496de9e --- /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 00000000..5b64da0b --- /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 00000000..66fc0ae1 --- /dev/null +++ b/test/less/extend.less @@ -0,0 +1,27 @@ +.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; +}