├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.12 4 | - 4 5 | - stable 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.0.1 4 | 5 | - Loosen stylelint version range to allow for v4 (or anything higher than 3). 6 | 7 | ## 2.0.0 8 | 9 | - Add stylelint 3 compatibility. 10 | - Add `countNestedAtRules` option. 11 | - Rename `atRulesDontCount` option to `countAtRules`, defaulting to `true`. 12 | - Remove stylelint 2 compatibility. 13 | - Remove nesting block support, as it was removed from the spec draft. 14 | 15 | ## 1.0.0 16 | 17 | - Remove PostCSS 4 compatibility. 18 | - Add PostCSS 5 compatibility. 19 | 20 | ## 0.4.0 21 | 22 | - Add rules validation. 23 | 24 | ## 0.3.0 25 | 26 | - Add `atRulesDontCount` option. 27 | 28 | ## 0.2.1 29 | 30 | - Fix dependency typo. 31 | 32 | ## 0.2.0 33 | 34 | - Use `stylelint-rule-tester`. 35 | 36 | ## 0.1.0 37 | 38 | - Initial release. 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 David Clark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stylelint-statement-max-nesting-depth 2 | 3 | [![Build Status](https://travis-ci.org/davidtheclark/stylelint-statement-max-nesting-depth.svg)](https://travis-ci.org/davidtheclark/stylelint-statement-max-nesting-depth) 4 | 5 | **Deprecated: use stylelint's [`max-nesting-depth`](https://github.com/stylelint/stylelint/tree/master/lib/rules/max-nesting-depth) rule instead.** 6 | 7 | A [stylelint](https://github.com/stylelint/stylelint) custom rule to limit nesting depth. 8 | 9 | This rule will cause stylelint to warn you whenever a nested rule or at-rule exceeds your specified depth. 10 | 11 | ## Installation 12 | 13 | ``` 14 | npm install stylelint-statement-max-nesting-depth 15 | ``` 16 | 17 | v2+ is compatible with stylelint v3+. For older versions of stylelint, use older versions of this plugin. 18 | 19 | ## Details 20 | 21 | Preprocessers like Sass, Less, and Stylus have nesting. Nesting can be enabled via PostCSS with [postcss-nested](https://github.com/postcss/postcss-nested) or [postcss-nesting](https://github.com/jonathantneal/postcss-nesting). 22 | 23 | Here's how it works: 24 | 25 | ```css 26 | a { 27 | b { /* nesting level 1 */ 28 | .foo { /* nesting level 2 */ 29 | .bar { /* nesting level 3 */ 30 | .baz { /* nesting level 4 */ 31 | color: pink; 32 | } 33 | } 34 | } 35 | } 36 | } 37 | ``` 38 | 39 | ### Nesting depth ignores root-level at-rules 40 | 41 | Just like the heading said: root-level at-rules will not be included in the nesting depth calculation. 42 | 43 | So both of the following `.foo` rules have a nesting depth of 2, and will therefore pass if you `max` is less than or equal to 2: 44 | 45 | ```css 46 | a { 47 | b { 48 | .foo {} 49 | } 50 | } 51 | 52 | @media print { 53 | a { 54 | b { 55 | .foo {} 56 | } 57 | } 58 | } 59 | ``` 60 | 61 | Why? Because I think that's how most users would want this thing to work. If you disagree, file an issue. 62 | 63 | ### Options 64 | 65 | #### countAtRules 66 | 67 | Type `Boolean`; Default `true` 68 | 69 | If `false` *no* at-rules (root-level or nested) will affect the calculation of a statement's nesting depth. 70 | 71 | Both of the following `.foo` rules would have a nesting depth of `1`. 72 | 73 | ```css 74 | a { 75 | .foo {} 76 | } 77 | 78 | a { 79 | @media print { 80 | .foo {} 81 | } 82 | } 83 | ``` 84 | 85 | #### countNestedAtRules 86 | 87 | Type `Boolean`; Default `true` 88 | 89 | If `false`, nested at-rules will not affect the calculation of a statement's nesting depth. 90 | 91 | None of the following would involve a nesting depth greater than `1`. 92 | 93 | ```css 94 | a { 95 | .foo {} 96 | } 97 | 98 | a { 99 | @media print { 100 | .foo {} 101 | } 102 | } 103 | 104 | a { 105 | .foo { 106 | @media print { 107 | color: pink; 108 | } 109 | } 110 | } 111 | ``` 112 | 113 | ## Usage 114 | 115 | Add it to your stylelint config `plugins` array, then add '`statement-max-nesting-depth'` to your rules, specifying a max nesting depth as the primary option. 116 | 117 | Like so: 118 | 119 | ```js 120 | { 121 | "plugins": [ 122 | "stylelint-statement-max-nesting-depth" 123 | ], 124 | "rules": { 125 | // ... 126 | // The following settings = max nesting depth of 1, 127 | // with the option `countAtRules` set to `false` 128 | "statement-max-nesting-depth": [1, { countAtRules: false }], 129 | // ... 130 | }, 131 | }; 132 | ``` 133 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var assign = require('object-assign'); 2 | var stylelint = require('stylelint'); 3 | 4 | var ruleName = 'statement-max-nesting-depth'; 5 | 6 | var messages = stylelint.utils.ruleMessages(ruleName, { 7 | rejected: 'Nesting exceeds maximum nesting depth', 8 | }); 9 | 10 | module.exports = stylelint.createPlugin(ruleName, function(max, options) { 11 | // Set defaults 12 | options = assign({ 13 | countAtRules: true, 14 | countNestedAtRules: true, 15 | }, options); 16 | 17 | return function(root, result) { 18 | stylelint.utils.validateOptions({ 19 | ruleName: ruleName, 20 | result: result, 21 | actual: max, 22 | possible: [function(x) { return typeof x === 'number'; }], 23 | }); 24 | stylelint.utils.validateOptions({ 25 | ruleName: ruleName, 26 | result: result, 27 | actual: options, 28 | possible: { 29 | countAtRules: [true, false], 30 | countNestedAtRules: [true, false], 31 | }, 32 | }); 33 | 34 | root.walkRules(checkStatement); 35 | root.walkAtRules(checkStatement); 36 | 37 | function checkStatement(statement) { 38 | const depth = nestingDepth(statement); 39 | if (depth > max) { 40 | stylelint.utils.report({ 41 | ruleName: ruleName, 42 | result: result, 43 | node: statement, 44 | message: messages.rejected 45 | }); 46 | } 47 | } 48 | }; 49 | 50 | function nestingDepth(node, level) { 51 | level = level || 0; 52 | 53 | // The nesting depth level's computation has finished 54 | // when this function, recursively called, receives 55 | // a node that is not nested -- a direct child of the 56 | // root node 57 | if (node.parent.type === 'root') { 58 | return level; 59 | } 60 | 61 | if (node.parent.type === 'atrule') { 62 | // Conditions under which at-rules' children don't count 63 | if ( 64 | !options.countAtRules 65 | || node.parent.parent.type === 'root' 66 | ) { 67 | return nestingDepth(node.parent, level); 68 | } 69 | } 70 | 71 | if (node.parent.type === 'rule') { 72 | // Conditions under which rules' children don't count 73 | if ( 74 | (node.type === 'atrule' && !options.countNestedAtRules) 75 | ) { 76 | return nestingDepth(node.parent, level); 77 | } 78 | } 79 | 80 | // Unless any of the conditions above apply, we want to 81 | // add 1 to the nesting depth level and then check the parent, 82 | // continuing to add until we hit the root node 83 | return nestingDepth(node.parent, level + 1); 84 | } 85 | }); 86 | 87 | module.exports.ruleName = ruleName; 88 | module.exports.messages = messages; 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stylelint-statement-max-nesting-depth", 3 | "version": "2.0.1", 4 | "description": "A stylelint custom rule to limit nesting depth", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "tape test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/davidtheclark/stylelint-statement-max-nesting-depth.git" 12 | }, 13 | "keywords": [ 14 | "stylelint", 15 | "stylelint-plugin", 16 | "css", 17 | "nesting", 18 | "linter" 19 | ], 20 | "author": "David Clark", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/davidtheclark/stylelint-statement-max-nesting-depth/issues" 24 | }, 25 | "homepage": "https://github.com/davidtheclark/stylelint-statement-max-nesting-depth#readme", 26 | "devDependencies": { 27 | "tape": "^4.2.2" 28 | }, 29 | "dependencies": { 30 | "object-assign": "^4.0.1", 31 | "stylelint": ">=3.0.2", 32 | "stylelint-rule-tester": "^0.6.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var ruleTester = require('stylelint-rule-tester'); 2 | var statementMaxNestingDepth = require('..'); 3 | 4 | var messages = statementMaxNestingDepth.messages; 5 | var testRule = ruleTester(statementMaxNestingDepth.rule, statementMaxNestingDepth.ruleName); 6 | 7 | testRule(1, function(tr) { 8 | basics(tr); 9 | 10 | tr.ok('a { b { top: 0; }}'); 11 | tr.notOk('a { b { c { top: 0; }}}', messages.rejected); 12 | 13 | tr.ok('@media print { a { b { top: 0; }}}'); 14 | tr.notOk('@media print { a { b { c { top: 0; }}}}', messages.rejected); 15 | 16 | tr.ok('a { top: 0; b { top: 0; }}'); 17 | tr.notOk('a { top: 0; b { top: 0; c { top: 0; }}}', messages.rejected); 18 | tr.notOk('a { b { top: 0; c { top: 0; }} top: 0; }', messages.rejected); 19 | }); 20 | 21 | testRule(3, function(tr) { 22 | basics(tr); 23 | 24 | tr.ok('a { b { c { d { top: 0; }}}}'); 25 | tr.notOk('a { b { c { d { e { top: 0; }}}}}', messages.rejected); 26 | tr.ok('@media print { a { b { c { d { top: 0; }}}}}'); 27 | }); 28 | 29 | testRule(1, { countAtRules: false }, function(tr) { 30 | basics(tr); 31 | 32 | tr.ok('a { b { top: 0; }}'); 33 | tr.ok('a { @media print { b { top: 0; }}}'); 34 | tr.notOk('a { b { c { top: 0; }}}', messages.rejected); 35 | tr.notOk('a { @media print { b { c { top: 0; }}}}', messages.rejected); 36 | }); 37 | 38 | testRule(1, { countNestedAtRules: false }, function(tr) { 39 | basics(tr); 40 | 41 | tr.ok('a { @media print { b { top: 0; }}}'); 42 | tr.ok('a { b { @media print { top: 0; }}}'); 43 | tr.notOk('a { b { @media print { c { top: 0; } }}}', messages.rejected); 44 | tr.notOk('a { @media print { b { c { top: 0; }}}}', messages.rejected); 45 | }); 46 | 47 | function basics(tr) { 48 | tr.ok(''); 49 | tr.ok('a {}'); 50 | tr.ok('@import "foo.css";'); 51 | tr.ok('a { top: 0; }'); 52 | } 53 | --------------------------------------------------------------------------------