postcss-sorting
Advanced tools
Comparing version 1.6.1 to 1.7.0
@@ -5,2 +5,6 @@ # Change Log | ||
## 1.7.0 | ||
* Added `smacss` and `alphabetical` predefined configs. | ||
* Under the hood refactoring. | ||
## 1.6.1 | ||
@@ -7,0 +11,0 @@ * Fixed a regression in 1.6.0. Sort order with item like `@include media` didn't found rules like `@include media(">=desk") {}`. |
347
index.js
var postcss = require('postcss'); | ||
var objectAssign = require('object-assign'); | ||
var path = require('path'); | ||
var fs = require('fs'); | ||
module.exports = postcss.plugin('postcss-sorting', function (opts) { | ||
return function (css) { | ||
plugin(css, opts); | ||
}; | ||
}); | ||
function plugin(css, opts) { | ||
// Verify options and use defaults if not specified | ||
opts = verifyOptions(opts); | ||
var enableSorting = true; | ||
css.walk(function (node) { | ||
if (node.type === 'comment' && node.parent.type === 'root') { | ||
if (node.text === 'postcss-sorting: on') { | ||
enableSorting = true; | ||
} else if (node.text === 'postcss-sorting: off') { | ||
enableSorting = false; | ||
} | ||
} | ||
if (!enableSorting) { | ||
return; | ||
} | ||
// Process only rules and atrules with nodes | ||
if ((node.type === 'rule' || node.type === 'atrule') && node.nodes && node.nodes.length) { | ||
// Nodes for sorting | ||
var processed = []; | ||
// Add indexes to nodes | ||
node.each(function (childNode, index) { | ||
processed = processMostNodes(childNode, index, opts, processed); | ||
}); | ||
// Add last comments in the rule. Need this because last comments are not belonging to anything | ||
node.each(function (childNode, index) { | ||
processed = processLastComments(childNode, index, processed); | ||
}); | ||
// Sort declarations saved for sorting | ||
processed.sort(sortByIndexes); | ||
// Replace rule content with sorted one | ||
if (processed.length) { | ||
node.removeAll(); | ||
node.append(processed); | ||
} | ||
// Taking care of empty lines | ||
node.each(function (childNode) { | ||
formatNodes(childNode, opts); | ||
}); | ||
} | ||
}); | ||
} | ||
function verifyOptions(options) { | ||
@@ -10,10 +68,12 @@ if (options === null || typeof options !== 'object') { | ||
options['sort-order'] = options['sort-order'] || 'default'; | ||
options['empty-lines-between-children-rules'] = options['empty-lines-between-children-rules'] || 0; | ||
options['empty-lines-between-media-rules'] = options['empty-lines-between-media-rules'] || 0; | ||
options['preserve-empty-lines-between-children-rules'] = options['preserve-empty-lines-between-children-rules'] || false; | ||
options['empty-lines-before-comment'] = options['empty-lines-before-comment'] || 0; | ||
options['empty-lines-after-comment'] = options['empty-lines-after-comment'] || 0; | ||
var defaultOptions = { | ||
'sort-order': 'default', | ||
'empty-lines-between-children-rules': 0, | ||
'empty-lines-between-media-rules': 0, | ||
'preserve-empty-lines-between-children-rules': false, | ||
'empty-lines-before-comment': 0, | ||
'empty-lines-after-comment': 0, | ||
}; | ||
return options; | ||
return objectAssign({}, defaultOptions, options); | ||
} | ||
@@ -116,13 +176,13 @@ | ||
switch (node.type) { | ||
case 'decl': | ||
return (/^(\$|--)[\w-]+/).test(node.prop) ? '$variable' : node.prop; | ||
case 'decl': | ||
return (/^(\$|--)[\w-]+/).test(node.prop) ? '$variable' : node.prop; | ||
case 'atrule': | ||
return getAtruleSortName(node, order); | ||
case 'atrule': | ||
return getAtruleSortName(node, order); | ||
case 'rule': | ||
return '>child'; | ||
case 'rule': | ||
return '>child'; | ||
default: | ||
return null; | ||
default: | ||
return null; | ||
} | ||
@@ -228,170 +288,137 @@ } | ||
module.exports = postcss.plugin('postcss-sorting', function (opts) { | ||
// Verify options and use defaults if not specified | ||
opts = verifyOptions(opts); | ||
function processMostNodes(node, index, opts, processedNodes) { | ||
if (node.type === 'comment') { | ||
if (index === 0 && node.raws.before.indexOf('\n') === -1) { | ||
node.ruleComment = true; // need this flag to not append this comment twice | ||
return function (css) { | ||
var order = getSortOrderFromOptions(opts); | ||
var linesBetweenChildrenRules = getLinesBetweenRulesFromOptions('children', opts); | ||
var linesBetweenMediaRules = getLinesBetweenRulesFromOptions('media', opts); | ||
var preserveLinesBetweenChildren = opts['preserve-empty-lines-between-children-rules']; | ||
var linesBeforeComment = opts['empty-lines-before-comment']; | ||
var linesAfterComment = opts['empty-lines-after-comment']; | ||
var enableSorting = true; | ||
return processedNodes.concat(node); | ||
} | ||
css.walk(function (rule) { | ||
if (rule.type === 'comment' && rule.parent.type === 'root') { | ||
if (rule.text === 'postcss-sorting: on') { | ||
enableSorting = true; | ||
} else if (rule.text === 'postcss-sorting: off') { | ||
enableSorting = false; | ||
} | ||
} | ||
return processedNodes; | ||
} | ||
if (!enableSorting) { | ||
return; | ||
} | ||
var order = getSortOrderFromOptions(opts); | ||
// Process only rules and atrules with nodes | ||
if ((rule.type === 'rule' || rule.type === 'atrule') && rule.nodes && rule.nodes.length) { | ||
// Nodes for sorting | ||
var processed = []; | ||
node = addIndexesToNode(node, index, order); | ||
rule.each(function (node, index) { | ||
if (node.type === 'comment') { | ||
if (index === 0 && node.raws.before.indexOf('\n') === -1) { | ||
node.ruleComment = true; // need this flag to not append this comment twice | ||
// If comment on separate line before node, use node's indexes for comment | ||
var commentsBefore = fetchAllCommentsBeforeNode([], node.prev(), node); | ||
processed.push(node); | ||
} | ||
// If comment on same line with the node and node, use node's indexes for comment | ||
var commentsAfter = fetchAllCommentsAfterNode([], node.next(), node); | ||
return; | ||
} | ||
return processedNodes.concat(commentsBefore, node, commentsAfter); | ||
} | ||
node = addIndexesToNode(node, index, order); | ||
function processLastComments(node, index, processedNodes) { | ||
if (node.type === 'comment' && !node.hasOwnProperty('groupIndex') && !node.ruleComment) { | ||
node.groupIndex = Infinity; | ||
node.propertyIndex = Infinity; | ||
node.initialIndex = index; | ||
// If comment on separate line before node, use node's indexes for comment | ||
var commentsBefore = fetchAllCommentsBeforeNode([], node.prev(), node); | ||
return processedNodes.concat(node); | ||
} | ||
// If comment on same line with the node and node, use node's indexes for comment | ||
var commentsAfter = fetchAllCommentsAfterNode([], node.next(), node); | ||
return processedNodes; | ||
} | ||
processed = processed.concat(commentsBefore, node, commentsAfter); | ||
}); | ||
function sortByIndexes(a, b) { | ||
// If a's group index is higher than b's group index, in a sorted | ||
// list a appears after b: | ||
if (a.groupIndex !== b.groupIndex) { | ||
return a.groupIndex - b.groupIndex; | ||
} | ||
// Add last comments in the rule. Need this because last comments are not belonging to anything | ||
rule.each(function (node, index) { | ||
if (node.type === 'comment' && !node.hasOwnProperty('groupIndex') && !node.ruleComment) { | ||
node.groupIndex = Infinity; | ||
node.propertyIndex = Infinity; | ||
node.initialIndex = index; | ||
// If a and b have the same group index, and a's property index is | ||
// higher than b's property index, in a sorted list a appears after | ||
// b: | ||
if (a.propertyIndex !== b.propertyIndex) { | ||
return a.propertyIndex - b.propertyIndex; | ||
} | ||
processed.push(node); | ||
} | ||
}); | ||
// If a and b have the same group index and the same property index, | ||
// in a sorted list they appear in the same order they were in | ||
// original array: | ||
return a.initialIndex - b.initialIndex; | ||
} | ||
// Sort declarations saved for sorting: | ||
processed.sort(function (a, b) { | ||
// If a's group index is higher than b's group index, in a sorted | ||
// list a appears after b: | ||
if (a.groupIndex !== b.groupIndex) { | ||
return a.groupIndex - b.groupIndex; | ||
} | ||
function formatNodes(node, opts) { | ||
var linesBetweenChildrenRules = getLinesBetweenRulesFromOptions('children', opts); | ||
var linesBetweenMediaRules = getLinesBetweenRulesFromOptions('media', opts); | ||
var preserveLinesBetweenChildren = opts['preserve-empty-lines-between-children-rules']; | ||
var linesBeforeComment = opts['empty-lines-before-comment']; | ||
var linesAfterComment = opts['empty-lines-after-comment']; | ||
// If a and b have the same group index, and a's property index is | ||
// higher than b's property index, in a sorted list a appears after | ||
// b: | ||
if (a.propertyIndex !== b.propertyIndex) { | ||
return a.propertyIndex - b.propertyIndex; | ||
} | ||
// don't remove empty lines if they are should be preserved | ||
if ( | ||
!( | ||
preserveLinesBetweenChildren && | ||
(node.type === 'rule' || node.type === 'comment') && | ||
node.prev() && | ||
getApplicableNode('rule', node) | ||
) | ||
) { | ||
node = cleanLineBreaks(node); | ||
} | ||
// If a and b have the same group index and the same property index, | ||
// in a sorted list they appear in the same order they were in | ||
// original array: | ||
return a.initialIndex - b.initialIndex; | ||
}); | ||
var prevNode = node.prev(); | ||
if (processed.length) { | ||
rule.removeAll(); | ||
rule.append(processed); | ||
} | ||
if (prevNode && node.raws.before) { | ||
if (node.groupIndex > prevNode.groupIndex) { | ||
node.raws.before = createLineBreaks(1) + node.raws.before; | ||
} | ||
// Remove all empty lines and add empty lines between groups | ||
rule.each(function (node) { | ||
// don't remove empty lines if they are should be preserved | ||
if ( | ||
!( | ||
preserveLinesBetweenChildren && | ||
(node.type === 'rule' || node.type === 'comment') && | ||
node.prev() && | ||
getApplicableNode('rule', node) | ||
) | ||
) { | ||
node = cleanLineBreaks(node); | ||
} | ||
var applicableNode; | ||
var prevNode = node.prev(); | ||
// Insert empty lines between children classes | ||
if (node.type === 'rule' && linesBetweenChildrenRules > 0) { | ||
// between rules can be comments, so empty lines should be added to first comment between rules, rather than to rule | ||
applicableNode = getApplicableNode('rule', node); | ||
if (prevNode && node.raws.before) { | ||
if (node.groupIndex > prevNode.groupIndex) { | ||
node.raws.before = createLineBreaks(1) + node.raws.before; | ||
} | ||
if (applicableNode) { | ||
// add lines only if source empty lines not preserved, or if there are less empty lines then should be | ||
if ( | ||
!preserveLinesBetweenChildren || | ||
( | ||
preserveLinesBetweenChildren && | ||
countEmptyLines(applicableNode.raws.before) < linesBetweenChildrenRules | ||
) | ||
) { | ||
applicableNode.raws.before = createLineBreaks(linesBetweenChildrenRules - countEmptyLines(applicableNode.raws.before)) + applicableNode.raws.before; | ||
} | ||
} | ||
} | ||
var applicableNode; | ||
// Insert empty lines between media rules | ||
if (node.type === 'atrule' && node.name === 'media' && linesBetweenMediaRules > 0) { | ||
// between rules can be comments, so empty lines should be added to first comment between rules, rather than to rule | ||
applicableNode = getApplicableNode('atrule', node); | ||
// Insert empty lines between children classes | ||
if (node.type === 'rule' && linesBetweenChildrenRules > 0) { | ||
// between rules can be comments, so empty lines should be added to first comment between rules, rather than to rule | ||
applicableNode = getApplicableNode('rule', node); | ||
if (applicableNode) { | ||
applicableNode.raws.before = createLineBreaks(linesBetweenMediaRules - countEmptyLines(applicableNode.raws.before)) + applicableNode.raws.before; | ||
} | ||
} | ||
if (applicableNode) { | ||
// add lines only if source empty lines not preserved, or if there are less empty lines then should be | ||
if ( | ||
!preserveLinesBetweenChildren || | ||
( | ||
preserveLinesBetweenChildren && | ||
countEmptyLines(applicableNode.raws.before) < linesBetweenChildrenRules | ||
) | ||
) { | ||
applicableNode.raws.before = createLineBreaks(linesBetweenChildrenRules - countEmptyLines(applicableNode.raws.before)) + applicableNode.raws.before; | ||
} | ||
} | ||
} | ||
// Insert empty lines before comment | ||
if ( | ||
linesBeforeComment && | ||
node.type === 'comment' && | ||
(prevNode.type !== 'comment' || prevNode.raws.before.indexOf('\n') === -1) && // prevNode it's not a comment or it's an inline comment | ||
node.raws.before.indexOf('\n') >= 0 && // this isn't an inline comment | ||
countEmptyLines(node.raws.before) < linesBeforeComment | ||
) { | ||
node.raws.before = createLineBreaks(linesBeforeComment - countEmptyLines(node.raws.before)) + node.raws.before; | ||
} | ||
// Insert empty lines between media rules | ||
if (node.type === 'atrule' && node.name === 'media' && linesBetweenMediaRules > 0) { | ||
// between rules can be comments, so empty lines should be added to first comment between rules, rather than to rule | ||
applicableNode = getApplicableNode('atrule', node); | ||
if (applicableNode) { | ||
applicableNode.raws.before = createLineBreaks(linesBetweenMediaRules - countEmptyLines(applicableNode.raws.before)) + applicableNode.raws.before; | ||
} | ||
} | ||
// Insert empty lines before comment | ||
if ( | ||
linesBeforeComment && | ||
node.type === 'comment' && | ||
(prevNode.type !== 'comment' || prevNode.raws.before.indexOf('\n') === -1) && // prevNode it's not a comment or it's an inline comment | ||
node.raws.before.indexOf('\n') >= 0 && // this isn't an inline comment | ||
countEmptyLines(node.raws.before) < linesBeforeComment | ||
) { | ||
node.raws.before = createLineBreaks(linesBeforeComment - countEmptyLines(node.raws.before)) + node.raws.before; | ||
} | ||
// Insert empty lines after comment | ||
if ( | ||
linesAfterComment && | ||
node.type !== 'comment' && | ||
prevNode.type === 'comment' && | ||
prevNode.raws.before.indexOf('\n') >= 0 && // this isn't an inline comment | ||
countEmptyLines(node.raws.before) < linesAfterComment | ||
) { | ||
node.raws.before = createLineBreaks(linesAfterComment - countEmptyLines(node.raws.before)) + node.raws.before; | ||
} | ||
} | ||
}); | ||
} | ||
}); | ||
}; | ||
}); | ||
// Insert empty lines after comment | ||
if ( | ||
linesAfterComment && | ||
node.type !== 'comment' && | ||
prevNode.type === 'comment' && | ||
prevNode.raws.before.indexOf('\n') >= 0 && // this isn't an inline comment | ||
countEmptyLines(node.raws.before) < linesAfterComment | ||
) { | ||
node.raws.before = createLineBreaks(linesAfterComment - countEmptyLines(node.raws.before)) + node.raws.before; | ||
} | ||
} | ||
} |
{ | ||
"name": "postcss-sorting", | ||
"version": "1.6.1", | ||
"version": "1.7.0", | ||
"description": "PostCSS plugin to sort rules content with specified order.", | ||
@@ -24,9 +24,9 @@ "keywords": [ | ||
"dependencies": { | ||
"postcss": "^5.0.16" | ||
"object-assign": "^4.1.0", | ||
"postcss": "^5.1.2" | ||
}, | ||
"devDependencies": { | ||
"ava": "^0.13.0", | ||
"eslint": "^2.8.0", | ||
"postcss-less": "^0.8.0", | ||
"postcss-scss": "^0.1.7" | ||
"ava": "^0.16.0", | ||
"eslint": "^3.4.0", | ||
"postcss-scss": "^0.2.1" | ||
}, | ||
@@ -33,0 +33,0 @@ "scripts": { |
@@ -272,5 +272,7 @@ # PostCSS Sorting [![Build Status][ci-img]][ci] | ||
* `default` | ||
* `alphabetical` | ||
* `zen` | ||
* `csscomb` | ||
* `yandex` | ||
* `smacss` | ||
@@ -277,0 +279,0 @@ Example: `{ "sort-order": "zen" }` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
68417
3
11
2102
626
2
+ Addedobject-assign@^4.1.0
+ Addedobject-assign@4.1.1(transitive)
Updatedpostcss@^5.1.2