postcss-nested-ancestors
Advanced tools
Comparing version 0.0.1 to 0.1.0
@@ -0,5 +1,11 @@ | ||
## 0.1.0 | ||
* Add experimental `replaceDeclarations` option, to process declaration props and values, too | ||
* Cast warning when nestingLevel >= parentsStack.length | ||
* Move `spacesAndAmpersandRegex` regex into a reusable regex | ||
* Add a failing test case documenting issues when complex nesting | ||
## 0.0.1 | ||
* Fix levelSymbol and parentSymbol options not being used. | ||
* Fix `levelSymbol` and `parentSymbol` options not being used | ||
## 0.0.0 | ||
* Initial release. | ||
* Initial release |
81
index.js
@@ -7,3 +7,4 @@ var postcss = require('postcss'), | ||
opts = assign({ | ||
placeholder: '^&' | ||
placeholder: '^&', | ||
replaceDeclarations: false | ||
}, opts); | ||
@@ -18,12 +19,13 @@ | ||
var parentsStack = [], | ||
// Get all ancestors placeholder recurrencies: ^&, ^^&, ^^^&, [...] | ||
placeholderRegex = new RegExp( | ||
/** | ||
* Get all ancestors placeholder recurrencies: | ||
* ^&, ^^&, ^^^&, [...] | ||
*/ | ||
// eslint-disable-next-line max-len | ||
'(' + escRgx(opts.levelSymbol) + ')+(' + escRgx(opts.parentSymbol) + ')', | ||
'g' | ||
); | ||
), | ||
// Get any space preceding an ampersand | ||
spacesAndAmpersandRegex = /\s&/g; | ||
/** | ||
@@ -33,15 +35,21 @@ * Walk current ancestor stack and | ||
* | ||
* @param {Number} ancestor nesting depth ( 0 = &, 1 = grandparent, ...) | ||
* @param {Number} ancestor nesting depth (0 = &, 1 = ^& = grandparent...) | ||
* @param {Object} a PostCSS Rule object | ||
* @param {Object} a PostCSS Result object | ||
* @return {String} ancestor selector | ||
*/ | ||
function getParentSelectorAtLevel(nestingLevel) { | ||
function getParentSelectorAtLevel(nestingLevel, rule, result) { | ||
nestingLevel = nestingLevel || 1; | ||
// @TODO add warning when nestingLevel >= parentsStack.length | ||
// Set a warning when nestingLevel >= parentsStack.length | ||
if ( nestingLevel >= parentsStack.length ) { | ||
rule.warn(result, 'Parent selector exceeds current stack.'); | ||
} | ||
return parentsStack.filter( function (rule, index) { | ||
// Create an array of matching parent selectors | ||
return parentsStack.filter( function (selector, index) { | ||
return index < parentsStack.length - nestingLevel; | ||
}) | ||
.join(' ') | ||
.replace(/\s&/g, ''); // Remove empty spaces before "&" | ||
.replace(spacesAndAmpersandRegex, ''); // Remove " &" | ||
} | ||
@@ -54,9 +62,14 @@ | ||
* | ||
* @param {String} placeholder eg.^^& | ||
* @param {String} placeholder (eg.^^&) | ||
* @param {Object} a PostCSS Rule object | ||
* @param {Object} a PostCSS Result object | ||
* @return {String} string ancestor selector fragment | ||
*/ | ||
function placeholderReplacer(placeholder) { | ||
function placeholderReplacer(placeholder, rule, result) { | ||
return getParentSelectorAtLevel( | ||
// Get how many level symbols ("^") has current placeholder | ||
placeholder.split(opts.levelSymbol).length - 1 | ||
placeholder.split(opts.levelSymbol).length - 1, | ||
rule, | ||
result | ||
); | ||
@@ -66,37 +79,49 @@ } | ||
/** | ||
* Replace any ancestor placeholder into a given selector. | ||
* Replace any ancestor placeholder into a given selector/string. | ||
* | ||
* @param {String} selector | ||
* @param {String} CSS selector / string | ||
* @param {Object} a PostCSS Rule object | ||
* @param {Object} a PostCSS Result object | ||
* @return {String} selector | ||
*/ | ||
function replacePlaceholders(selector) { | ||
function replacePlaceholders(selector, rule, result) { | ||
// Find placeholders and replace them with matching parent selector | ||
return selector.replace( | ||
placeholderRegex, | ||
placeholderReplacer | ||
function (placeholder) { | ||
return placeholderReplacer(placeholder, rule, result); | ||
} | ||
); | ||
} | ||
var process = function (node) { | ||
var process = function (node, result) { | ||
node.each( function (rule) { | ||
if (rule.type === 'rule') { | ||
// Add current parent selector to current parent stack | ||
if (rule.parent.type === 'rule') { | ||
parentsStack.push(rule.parent.selector); | ||
} | ||
// Replace parents placeholders in rule selectors | ||
rule.selector = replacePlaceholders(rule.selector, rule, result); | ||
// Replace parents placeholders in rule selector | ||
rule.selectors = rule.selectors.map(replacePlaceholders); | ||
// Add current selector to current parent stack | ||
parentsStack.push(rule.selector); | ||
// Process child rules | ||
process(rule); | ||
process(rule, result); | ||
} | ||
// Optionally replace parents placeholders into declarations | ||
// eslint-disable-next-line brace-style | ||
else if (opts.replaceDeclarations && rule.type === 'decl') { | ||
rule.value = replacePlaceholders(rule.value, rule, result); | ||
rule.prop = replacePlaceholders(rule.prop, rule, result); | ||
} | ||
}); | ||
// Remove current parent stack item at the end of each child iteration | ||
parentsStack.pop(); | ||
}; | ||
return process; | ||
return function (root, result) { | ||
process(root, result); | ||
}; | ||
}); |
{ | ||
"name": "postcss-nested-ancestors", | ||
"version": "0.0.1", | ||
"version": "0.1.0", | ||
"description": "PostCSS plugin to reference any ancestor selector in nested CSS", | ||
@@ -27,3 +27,3 @@ "keywords": [ | ||
"devDependencies": { | ||
"ava": "^0.14.0", | ||
"ava": "^0.16.0", | ||
"eslint": "^2.10.0", | ||
@@ -39,4 +39,10 @@ "eslint-config-postcss": "^2.0.2" | ||
"eslintConfig": { | ||
"extends": "eslint-config-postcss/es5" | ||
"extends": "eslint-config-postcss/es5", | ||
"rules": { | ||
"max-len": [ | ||
"error", | ||
90 | ||
] | ||
} | ||
} | ||
} |
@@ -15,3 +15,3 @@ # PostCSS Nested ancestors [![Build Status][ci-img]][ci] | ||
**PostCSS Nested ancestors** introduce `^&` selector which let you reference **any parent ancestor selector** with an easy and customizable interface. | ||
**PostCSS Nested ancestors** introduces `^&` selector which let you reference **any parent ancestor selector** with an easy and customizable interface. | ||
@@ -78,3 +78,3 @@ This plugin should be used **before** a POSTCSS rules unwrapper like [postcss-nested]. | ||
[postcss-nested-ancestors] instead replaces special ancestor selectors, makes no use of variable assignment and produces an output ready to be unwrapped with [postcss-nested]. | ||
**PostCSS Nested ancestors** instead replaces special ancestor selectors, makes no use of variable assignment and produces an output ready to be unwrapped with [postcss-nested]. | ||
@@ -116,3 +116,55 @@ ## Installation | ||
### replaceDeclarations (experimental) | ||
Type: `boolean` | ||
Default: `false` | ||
If this is true then this plugin will look through your declaration values/properties for the placeholder symbol and replace them with specified selector. | ||
An use case for this if enabling [postcss-ref](https://github.com/morishitter/postcss-ref) to work with dynamic `@ref` selectors. Read discussion [here](https://github.com/toomuchdesign/postcss-nested-ancestors/pull/3). | ||
```css | ||
/* Before */ | ||
.foo { | ||
&:last-child { | ||
border-top: ref(^&, border-bottom); | ||
} | ||
} | ||
/* After PostCSS Nested ancestors and PostCSS Nested */ | ||
.foo { | ||
} | ||
.foo:last-child { | ||
border-top: ref(.foo, border-bottom); | ||
} | ||
``` | ||
## Known issues | ||
### Complex nesting | ||
**PostCSS Nested ancestors** is currently not able to resolve complex nesting cases like multiple selector declarations. It might break badly. | ||
```css | ||
/* Before */ | ||
.foo, | ||
.bar { | ||
&:hover { | ||
^&-moo { | ||
} | ||
} | ||
} | ||
/* After :-( */ | ||
.foo, | ||
.bar { | ||
&:hover { | ||
.foo,.bar-moo { | ||
} | ||
} | ||
} | ||
``` | ||
## Todo's | ||
- Add warning when nestingLevel >= parentsStack.length | ||
* Find a smart way to resolve complex nesting issues |
11342
103
168