diff --git a/node_modules/csscomb/lib/options/lines-between-rulesets.js b/node_modules/csscomb/lib/options/lines-between-rulesets.js new file mode 100644 index 0000000..269125f --- /dev/null +++ b/node_modules/csscomb/lib/options/lines-between-rulesets.js @@ -0,0 +1,255 @@ +'use strict'; + +let gonzales = require('gonzales-pe'); + +let option = { + newLinesString: '', + newLinesNode: null, + + /** + * Option's name as it's used in config. + * @type {String} + */ + get name() { + return 'lines-between-rulesets'; + }, + + /** + * Name of option that must run after this option. + * @type {String} + */ + get runBefore() { + return 'block-indent'; + }, + + /** + * List of syntaxes that are supported by this option. + * @type {Array} + */ + get syntax() { + return ['css', 'less', 'sass', 'scss']; + }, + + /** + * Types of values this option accepts in config. + * @type {Object} + */ + get accepts() { + return { + number: true + }; + }, + + /** + * @param {number} value + * @returns {number} + */ + /* + ** Still need to override, as the core implementation of setValue doesn't + ** pass numbers through, but creates a string of spaces of the same length. + */ + setValue(value) { + let valueType = typeof value; + + if (valueType !== 'number') { + throw new Error('Value must be a number.'); + } + + return value; + }, + + buildSpacing(syntax, value) { + let spacing = ''; + let numNewLines = 0; + let newLinesOffset = 1; + + if (syntax === 'sass') { + newLinesOffset = 0; + } + + numNewLines = Math.round(value) + newLinesOffset; + + for (var i = 0; i < numNewLines; i++) { + spacing += '\n'; + } + + return spacing; + }, + + /** + * Processes ast and fixes found code style errors. + * @param {Node} ast + */ + process(ast) { + var _this = option; + var value = this.getValue('lines-between-rulesets'); + // console.log(value,_this) + _this.newLinesString = _this.buildSpacing(ast.syntax, value); + _this.newLinesNode = gonzales.createNode({ + type: 'space', + content: _this.newLinesString + }); + _this.processBlock(ast); + }, + + processBlock(x) { + var _this = option; + if (x.is('stylesheet')) { + // Check all @rules + _this.processAtRules(x); + + // Check all rulesets + _this.processRuleSets(x); + } + + x.forEach((node) => { + if (!node.is('block')) { + return _this.processBlock(node); + } + + // Check all @rules + _this.processAtRules(node); + + // Check all rulesets + _this.processRuleSets(node); + + _this.processBlock(node); + }); + }, + + processAtRules(node) { + node.forEach('atrule', (atRuleNode, index) => { + option.insertNewlines(node, index); + }); + }, + + processRuleSets(node) { + node.forEach('ruleset', (ruleSetNode, index) => { + option.insertNewlines(node, index); + }); + }, + + isComment(node) { + if (!node) { + return false; + } + return (node.is('singlelineComment') || node.is('multilineComment')); + }, + + isNewline(node) { + if (!node) { + return false; + } + return (node.content === '\n'); + }, + + prevLineIsComment(parent, index) { + let indexThreshold = 2; + let prevChild; + let prevMinusOneChild; + let prevMinusTwoChild; + let parentSyntax = parent ? parent.syntax : null; + var _this = option; + // Sass is troublesome because newlines are counted as separate nodes + if (parentSyntax === 'sass') { + indexThreshold = 3; + } + + if (!parent || index < indexThreshold) { + return false; + } + + prevChild = parent.get(index - 1); + prevMinusOneChild = parent.get(index - 2); + + if (parentSyntax === 'sass') { + prevMinusTwoChild = parent.get(index - 3); + return ( + _this.isComment(prevMinusTwoChild) && + _this.isNewline(prevMinusOneChild) && + prevChild.is('space') + ); + } + + return (_this.isComment(prevMinusOneChild) && prevChild.is('space')); + }, + + /* + ** Find the latest previous child that isn't a comment, and return its index. + */ + findLatestNonCommentNode(parent, index) { + let prevChild; + let lastNonCommentIndex = -1; + let currentIndex = index; + let jumpSize = 2; + var _this = option; + if (parent.syntax === 'sass') { + jumpSize = 3; + } + + while (currentIndex >= 0) { + if (_this.prevLineIsComment(parent, currentIndex)) { + currentIndex -= jumpSize; + continue; + } + + prevChild = parent.get(currentIndex - 1); + + if (!_this.isComment(prevChild)) { + lastNonCommentIndex = currentIndex - 1; + break; + } + + currentIndex--; + } + + return lastNonCommentIndex; + }, + + insertNewlinesAsString(node) { + let content = node.content; + let lastNewline = content.lastIndexOf('\n'); + let newContent; + + if (lastNewline > -1) { + content = content.substring(lastNewline + 1); + } + + newContent = option.newLinesString + content; + node.content = newContent; + }, + + insertNewlinesAsNode(node) { + node.insert(node.length, option.newLinesNode); + }, + + insertNewlines(node, index) { + let prevChild = node.get(index - 1); + let shouldInsert = false; + + // Check for previous nodes that are not a space + // Do not insert if the ruleset is the first item + for (var i = 0; i < index; i++) { + if (!node.get(i).is('space')) { + shouldInsert = true; + break; + } + } + + if (prevChild && shouldInsert) { + var _this = option; + if (_this.prevLineIsComment(node, index) || _this.isComment(prevChild)) { + let lastNonCommentIndex = _this.findLatestNonCommentNode(node, index); + prevChild = node.get(lastNonCommentIndex); + } + + if (prevChild.is('space')) { + _this.insertNewlinesAsString(prevChild); + } else { + _this.insertNewlinesAsNode(prevChild); + } + } + } +}; + +module.exports = option;