Skip to content

Instantly share code, notes, and snippets.

@Panya
Created November 12, 2014 17:33
Show Gist options
  • Save Panya/6b5c40d0830552c22f03 to your computer and use it in GitHub Desktop.
Save Panya/6b5c40d0830552c22f03 to your computer and use it in GitHub Desktop.
@extend !optional
diff --git a/docs/extend.md b/docs/extend.md
index a0e037f..92b18c6 100644
--- a/docs/extend.md
+++ b/docs/extend.md
@@ -185,3 +185,20 @@ Yielding:
}
Note that if the selector is not extended, it won't be in the resulting CSS, so it's a powerful way to create a library of extendable code. While you can insert code through mixins, they would insert the same code every time you use them, while extending placeholders would give you compact output.
+
+## Optional extending
+
+Sometimes it might be usefull to be able to extend something that might
+exists or not depending on the context. You can suffix any selector by `optional` to achieve this:
+
+ $specialDesign
+ color: #FFF
+
+ .btn
+ @extend .design !optional, $specialDesign !optional
+
+Yielding:
+
+ .btn {
+ color: #fff;
+ }
diff --git a/lib/nodes/selector.js b/lib/nodes/selector.js
index 46db498..14f697c 100644
--- a/lib/nodes/selector.js
+++ b/lib/nodes/selector.js
@@ -23,6 +23,7 @@ var Selector = module.exports = function Selector(segs){
Node.call(this);
this.inherits = true;
this.segments = segs;
+ this.optional = false;
};
/**
@@ -66,6 +67,7 @@ Selector.prototype.clone = function(parent){
clone.column = this.column;
clone.filename = this.filename;
clone.inherits = this.inherits;
+ clone.optional = this.optional;
clone.val = this.val;
clone.segments = this.segments.map(function(node){ return node.clone(parent, clone); });
return clone;
@@ -82,6 +84,7 @@ Selector.prototype.toJSON = function(){
return {
__type: 'Selector',
inherits: this.inherits,
+ optional: this.optional,
segments: this.segments,
val: this.val,
lineno: this.lineno,
diff --git a/lib/parser.js b/lib/parser.js
index efd024e..eb068e0 100644
--- a/lib/parser.js
+++ b/lib/parser.js
@@ -1073,12 +1073,26 @@ Parser.prototype = {
extend: function(){
var tok = this.expect('extend')
, selectors = []
+ , sel
+ , tok
, node
, arr;
do {
arr = this.selectorParts();
- if (arr.length) selectors.push(new nodes.Selector(arr));
+ if (arr.length) {
+ sel = new nodes.Selector(arr);
+
+ if ('!' == this.peek().type) {
+ tok = this.lookahead(2);
+ if ('ident' == tok.type && 'optional' == tok.val.name) {
+ this.skip(['!', 'ident']);
+ sel.optional = true;
+ }
+ }
+
+ selectors.push(sel);
+ }
} while(this.accept(','));
node = new nodes.Extend(selectors);
diff --git a/lib/visitor/evaluator.js b/lib/visitor/evaluator.js
index e2ea546..ee776fe 100644
--- a/lib/visitor/evaluator.js
+++ b/lib/visitor/evaluator.js
@@ -835,6 +835,7 @@ Evaluator.prototype.visitExtend = function(extend){
// Cloning the selector for when we are in a loop and don't want it to affect
// the selector nodes and cause the values to be different to expected
selector: this.interpolate(selector.clone()).trim(),
+ optional: selector.optional,
lineno: selector.lineno,
column: selector.column
});
diff --git a/lib/visitor/normalizer.js b/lib/visitor/normalizer.js
index e81b8eb..9ed77d4 100644
--- a/lib/visitor/normalizer.js
+++ b/lib/visitor/normalizer.js
@@ -378,6 +378,7 @@ Normalizer.prototype.extend = function(group, selectors){
group.block.node.extends.forEach(function(extend){
var groups = map[extend.selector];
if (!groups) {
+ if (extend.optional) return;
var err = new Error('Failed to @extend "' + extend.selector + '"');
err.lineno = extend.lineno;
err.column = extend.column;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment