Skip to content

Commit

Permalink
Merge pull request #15 from travco/allowVariableNesting
Browse files Browse the repository at this point in the history
Allow variable nesting
  • Loading branch information
antyakushev authored Jul 26, 2016
2 parents 773ad9a + 93d73a4 commit 8d76d34
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 13 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules/
npm-debug.log
test/*.txt
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,32 @@ If you do want to use predefined range parameters though, consider using [postcs
}
```

Locality of variables in nested loops is supported:
```css
@for $x from 1 to 2 {
@for $y from 1 to $x {
@for $z from $y to $x {
.c-$(x)-$(z)-$(y) { padding: $(x)em $(z)em $(y)em; }
}
}
}
```

```css
.c-1-1-1 {
padding: 1em 1em 1em
}
.c-2-1-1 {
padding: 2em 1em 1em
}
.c-2-2-1 {
padding: 2em 2em 1em
}
.c-2-2-2 {
padding: 2em 2em 2em
}
```



See [PostCSS] docs for examples for your environment.
Expand Down
58 changes: 51 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,50 @@ module.exports = postcss.plugin('postcss-for', function (opts) {
opts = opts || {};
opts.nested = opts.nested || true;

var checkNumber, checkParams, processLoops, unrollLoop;
var parentsHaveIterator, manageIterStack, checkNumber, checkParams, processLoops, processOriginalLoops, unrollLoop;
var iterStack = [];

parentsHaveIterator = function (rule, param) {
if(rule.parent == null) { return false; }
if(rule.parent.type === 'root') { return false; }
if(rule.parent.params == null) { return false; }

var parentIterVar = list.space(rule.parent.params);

if (parentIterVar[0] == null) { return false; }
if (parentIterVar[0] === param) { return true; }
if ( iterStack.indexOf(param) !== -1) { return true; }
return parentsHaveIterator(rule.parent, param);
};

manageIterStack = function (rule) {
if (rule.parent.type !== 'root') {
var parentIterVar = list.space(rule.parent.params)[0];
if (iterStack.indexOf(parentIterVar) === -1) {
// If parent isn't in stack, wipe stack
iterStack.splice(0, iterStack.length);
} else {
// If parent is in stack, remove stack after parent
iterStack.splice(iterStack.indexOf(parentIterVar) + 1, iterStack.length - iterStack.indexOf(parentIterVar) - 1);
}
} else {
// If parent (root) isn't in stack, wipe stack
iterStack.splice(0, iterStack.length);
}
// Push current rule on stack regardless
iterStack.push( list.space(rule.params)[0] );
};

checkNumber = function (rule) {
return function (param) {
if (isNaN(parseInt(param)) || !param.match(/^-?\d+\.?\d*$/)) {

if (param.indexOf('$') !== -1) {
throw rule.error('Variable cannot be used as a range parameter', { plugin: 'postcss-for' });
if( !parentsHaveIterator(rule, param) ) {
throw rule.error('External variable (not from a parent for loop) cannot be used as a range parameter', { plugin: 'postcss-for' });
}
} else {
throw rule.error('Range parameter should be a number', { plugin: 'postcss-for' });
}

throw rule.error('Range parameter should be a number', { plugin: 'postcss-for' });
}
};
};
Expand Down Expand Up @@ -48,9 +81,9 @@ module.exports = postcss.plugin('postcss-for', function (opts) {
var value = {};
for ( var i = index; i * dir <= top * dir; i = i + by ) {
var content = rule.clone();
if (opts.nested) processLoops(content);
value[iterator] = i;
vars({ only: value })(content);
if (opts.nested) processLoops(content);
rule.parent.insertBefore(rule, content.nodes);
}
if ( rule.parent ) rule.remove();
Expand All @@ -64,7 +97,18 @@ module.exports = postcss.plugin('postcss-for', function (opts) {
});
};

processOriginalLoops = function (css) {
css.walkAtRules(function (rule) {
if ( rule.name === 'for' ) {
if (rule.parent) {
manageIterStack(rule);
}
unrollLoop(rule);
}
});
};

return function (css) {
processLoops(css);
processOriginalLoops(css);
};
});
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
"postcss-simple-vars": "^2.0.0"
},
"devDependencies": {
"gulp-eslint": "0.11.1",
"gulp-mocha": "2.1.3",
"chai": "3.2.0",
"gulp": "3.9.0"
"gulp": "3.9.0",
"gulp-eslint": "0.11.1",
"gulp-mocha": "2.1.3"
},
"scripts": {
"test": "gulp"
Expand Down
32 changes: 29 additions & 3 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,26 @@ describe('postcss-for', function () {
'.b-1-1 {}\n.b-1-2 {}\n.b-2-1 {}\n.b-2-2 {}');
});

it('it supports ranges with a variable from the parent for loop', function () {
test('@for $j from 1 to 3 { @for $i from 1 to $j {.b-$(i)-$(j) {} } }',
'.b-1-1 {}\n.b-1-2 {}\n.b-2-2 {}\n.b-1-3 {}\n.b-2-3 {}\n.b-3-3 {}');
});

it('it supports ranges with variables from any parent for loop', function () {
test('@for $w from 1 to 2 { @for $x from 1 to $w { @for $y from $x to $w { @for $z from $y to $w { .c-$(w)-$(z)-$(y)-$(x) {} }}}}',
'.c-1-1-1-1 {}\n.c-2-1-1-1 {}\n.c-2-2-1-1 {}\n.c-2-2-2-1 {}\n.c-2-2-2-2 {}');
});

it('it supports locality of variables in nested for loops', function () {
test('@for $w from 1 to 2 { @for $x from 1 to $w { \n@for $y from 1 to 2 { @for $z from $y to $w { .c-$(w)-$(z)-$(y)-$(x) {} }}\n@for $y from 1 to 3 { @for $z from $y to $w { .d-$(w)-$(z)-$(y)-$(x) {} }}\n}}',
'.c-1-1-1-1 {}\n.c-1-2-2-1 {}\n.c-1-1-2-1 {}\n.d-1-1-1-1 {}\n.d-1-2-2-1 {}\n.d-1-1-2-1 {}\n.d-1-3-3-1 {}\n.d-1-2-3-1 {}\n.d-1-1-3-1 {}\n.c-2-1-1-1 {}\n.c-2-2-1-1 {}\n.c-2-2-2-1 {}\n.d-2-1-1-1 {}\n.d-2-2-1-1 {}\n.d-2-2-2-1 {}\n.d-2-3-3-1 {}\n.d-2-2-3-1 {}\n.c-2-1-1-2 {}\n.c-2-2-1-2 {}\n.c-2-2-2-2 {}\n.d-2-1-1-2 {}\n.d-2-2-1-2 {}\n.d-2-2-2-2 {}\n.d-2-3-3-2 {}\n.d-2-2-3-2 {}');
});

it('it supports ranges with negative numbers', function () {
test('@for $i from -1 to 0 { .b-$i { width: $(i)px; } }',
'.b--1 {\n width: -1px\n}\n.b-0 {\n width: 0px\n}');
});


it('it throws an error on wrong syntax', function () {
expect(function () {
test('@for $i since 1 until 3 { .b-$i { width: $(i)px; } }');
Expand All @@ -47,10 +61,22 @@ describe('postcss-for', function () {
}).to.throw('<css input>:1:1: Range parameter should be a number');
});

it('it throws an error if range parameter is a variable', function () {
it('it throws an error if range parameter is an external variable', function () {
expect(function () {
test('@for $i from 1 to $columns { .b-$i { width: $(i)px; } }');
}).to.throw('<css input>:1:1: Variable cannot be used as a range parameter');
}).to.throw('<css input>:1:1: External variable (not from a parent for loop) cannot be used as a range parameter');
});

it('it throws an error if range parameter is from a previous non-parent for loop', function () {
expect(function () {
test('@for $w from 1 to 3 { @for $x from 1 to $w { \n@for $y from $x to $w { @for $z from $y to $w { .c-$(w)-$(z)-$(y)-$(x) {} }}\n@for $a from $y to $w { .d-$(w)-$(y)-$(a)-$(x) {} }}}');
}).to.throw('<css input>:3:1: External variable (not from a parent for loop) cannot be used as a range parameter');
});

it('it doesn\'t retain the stack after exiting multiple layers and throws an error for bad range parameters', function () {
expect(function () {
test('@for $w from 1 to 3 { @for $x from 1 to $w { @for $a from $x to $w { @for $b from $a to $w { .c-$(w)-$(b)-$(a)-$(x) {} }}}}\n@for $a from 1 to 3 { @for $b from $a to $w { .D-$(w)-$(b)-$(a)-$(x) {} }}');
}).to.throw('<css input>:2:23: External variable (not from a parent for loop) cannot be used as a range parameter');
});

});

0 comments on commit 8d76d34

Please sign in to comment.