Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

postcss-sorting

Package Overview
Dependencies
Maintainers
1
Versions
35
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

postcss-sorting - npm Package Compare versions

Comparing version 1.7.0 to 2.0.0

docs/at-rule-nested-empty-line-before.md

33

CHANGELOG.md

@@ -5,2 +5,35 @@ # Change Log

## 2.0.0
This release completely incompatible with the previous API. There is a lot new options. Please read the documentation.
[A migration guide](https://github.com/hudochenkov/postcss-sorting#migration-from-1x) is available.
### Added
* `sort-order` split into `order` and `properties-order`.
* Alphabetical order.
* At-rules can be checked if they have a block. E.g., `@include icon;` has no block.
* Custom properties and $-variables can be grouped separately.
* Empty lines for different node types:
* `rule-nested-empty-line-before`
* `at-rule-nested-empty-line-before`
* `declaration-empty-line-before`
* `custom-property-empty-line-before`
* `dollar-variable-empty-line-before`
* `comment-empty-line-before`
* `clean-empty-lines`: Remove all empty lines.
### Changed
* By default all options are disabled, and the plugin does nothing.
* Empty lines don't delete anymore if only “order” options are enabled.
* Droped support for Node <4.
### Removed
* Predefined configs.
* Command comments `/* postcss-sorting: on/off */`
* `preserve-empty-lines-between-children-rules`
* `empty-lines-between-children-rules`
* `empty-lines-between-media-rules`
* `empty-lines-before-comment`
* `empty-lines-after-comment`
## 1.7.0

@@ -7,0 +40,0 @@ * Added `smacss` and `alphabetical` predefined configs.

990

index.js

@@ -1,6 +0,31 @@

var postcss = require('postcss');
var objectAssign = require('object-assign');
var path = require('path');
var fs = require('fs');
'use strict';
const postcss = require('postcss');
const _ = require('lodash');
const isStandardSyntaxProperty = require('./lib/isStandardSyntaxProperty');
const isStandardSyntaxDeclaration = require('./lib/isStandardSyntaxDeclaration');
const isCustomProperty = require('./lib/isCustomProperty');
const isDollarVariable = require('./lib/isDollarVariable');
const isRuleWithNodes = require('./lib/isRuleWithNodes');
const validateOptions = require('./lib/validateOptions');
const createExpectedOrder = require('./lib/createExpectedOrder');
const createExpectedPropertiesOrder = require('./lib/createExpectedPropertiesOrder');
const processMostNodes = require('./lib/processMostNodes');
const processLastComments = require('./lib/processLastComments');
const getPropertiesOrderData = require('./lib/getPropertiesOrderData');
const sorting = require('./lib/sorting');
const getComments = require('./lib/getComments');
const cleanEmptyLines = require('./lib/cleanEmptyLines');
const emptyLineBeforeGroup = require('./lib/emptyLineBeforeGroup');
const isSingleLineBlock = require('./lib/isSingleLineBlock');
const isSingleLineString = require('./lib/isSingleLineString');
const hasEmptyLine = require('./lib/hasEmptyLine');
const createEmptyLines = require('./lib/createEmptyLines');
const isStandardSyntaxRule = require('./lib/isStandardSyntaxRule');
const hasBlock = require('./lib/hasBlock');
const hasNonSharedLineCommentBefore = require('./lib/hasNonSharedLineCommentBefore');
const hasSharedLineCommentBefore = require('./lib/hasSharedLineCommentBefore');
module.exports = postcss.plugin('postcss-sorting', function (opts) {

@@ -13,410 +38,757 @@ return function (css) {

function plugin(css, opts) {
// Verify options and use defaults if not specified
opts = verifyOptions(opts);
const validatedOptions = validateOptions(opts);
var enableSorting = true;
if (validatedOptions !== true) {
if (console && console.warn && _.isString(validatedOptions)) { // eslint-disable-line no-console
console.warn(validatedOptions); // eslint-disable-line no-console
}
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;
return;
}
// Having this option before `properties-order`, because later one can add empty lines by `emptyLineBefore`
if (opts['clean-empty-lines']) {
css.walk(function (node) {
if (isRuleWithNodes(node)) {
// Remove empty lines before every node
node.each(function (childNode) {
if (childNode.raws.before) {
childNode.raws.before = cleanEmptyLines(childNode.raws.before);
}
});
// Remove empty lines after the last node
if (node.raws.after) {
node.raws.after = cleanEmptyLines(node.raws.after);
}
}
}
});
}
if (!enableSorting) {
return;
}
if (opts.order) {
const expectedOrder = createExpectedOrder(opts.order);
// Process only rules and atrules with nodes
if ((node.type === 'rule' || node.type === 'atrule') && node.nodes && node.nodes.length) {
// Nodes for sorting
var processed = [];
css.walk(function (node) {
// Process only rules and atrules with nodes
if (isRuleWithNodes(node)) {
// Nodes for sorting
let processed = [];
// Add indexes to nodes
node.each(function (childNode, index) {
processed = processMostNodes(childNode, index, opts, processed);
});
// Add indexes to nodes
node.each(function (childNode, index) {
processed = processMostNodes(childNode, index, expectedOrder, 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);
});
// 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);
// Sort declarations saved for sorting
processed.sort(sorting.sortByIndexes);
// Replace rule content with sorted one
if (processed.length) {
// Enforce semicolon for the last node
node.raws.semicolon = true;
// Replace rule content with sorted one
node.removeAll();
node.append(processed);
}
});
}
// Taking care of empty lines
node.each(function (childNode) {
formatNodes(childNode, opts);
});
}
});
}
if (opts['properties-order']) {
const isAlphabetical = opts['properties-order'] === 'alphabetical';
const expectedOrder = isAlphabetical ? null : createExpectedPropertiesOrder(opts['properties-order']);
const unspecifiedPropertiesPosition = _.get(opts, ['unspecified-properties-position'], 'bottom');
function verifyOptions(options) {
if (options === null || typeof options !== 'object') {
options = {};
}
css.walk(function (node) {
// Process only rules and atrules with nodes
if (isRuleWithNodes(node)) {
const allRuleNodes = [];
let declarations = [];
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,
};
node.each(function (childNode, index) {
if (
childNode.type === 'decl' &&
isStandardSyntaxProperty(childNode.prop) &&
!isCustomProperty(childNode.prop)
) {
const unprefixedPropName = postcss.vendor.unprefixed(childNode.prop);
return objectAssign({}, defaultOptions, options);
}
const propData = {
name: childNode.prop,
unprefixedName: unprefixedPropName,
orderData: isAlphabetical ? null : getPropertiesOrderData(expectedOrder, unprefixedPropName),
node: childNode,
initialIndex: index,
unspecifiedPropertiesPosition,
};
function getSortOrderFromOptions(options) {
var sortOrder;
// add a marker
childNode.sortProperty = true;
if (Array.isArray(options['sort-order'])) {
sortOrder = options['sort-order'];
} else if (typeof options['sort-order'] === 'string') {
var configPath = path.join(__dirname, './configs/', options['sort-order']) + '.json';
// If comment on separate line before node, use node's indexes for comment
const commentsBefore = getComments.beforeDeclaration([], childNode.prev(), propData);
try {
sortOrder = fs.readFileSync(configPath);
sortOrder = JSON.parse(sortOrder);
sortOrder = sortOrder['sort-order'];
} catch (error) {
return {};
}
} else {
return {};
}
// If comment on same line with the node and node, use node's indexes for comment
const commentsAfter = getComments.afterDeclaration([], childNode.next(), propData);
// Add sorting indexes to order
var order = {};
declarations = declarations.concat(commentsBefore, propData, commentsAfter);
}
});
(typeof sortOrder[0] === 'string' ? [sortOrder] : sortOrder)
.forEach(function (group, groupIndex) {
group.forEach(function (prop, propIndex) {
order[prop] = {
group: groupIndex,
prop: propIndex
};
});
});
if (isAlphabetical) {
declarations.sort(sorting.sortDeclarationsAlphabetically);
} else {
declarations.sort(sorting.sortDeclarations);
}
return order;
}
// Process empty line before group
declarations.forEach(emptyLineBeforeGroup);
function getLinesBetweenRulesFromOptions(name, options) {
var lines = options['empty-lines-between-' + name + '-rules'];
let foundDeclarations = false;
if (typeof lines !== 'number' || isNaN(lines) || !isFinite(lines) || lines < 0 || Math.floor(lines) !== lines) {
throw new Error('Type of "empty-lines-between-' + name + '-rules" option must be integer with positive value.');
}
node.each(function (childNode) {
if (childNode.sortProperty) {
if (!foundDeclarations) {
foundDeclarations = true;
return lines;
}
declarations.forEach((item) => {
allRuleNodes.push(item.node);
});
}
} else {
allRuleNodes.push(childNode);
}
});
// Replace multiple line breaks with one
function cleanLineBreaks(node) {
if (node.raws.before) {
node.raws.before = node.raws.before.replace(/\r\n\s*\r\n/g, '\r\n').replace(/\n\s*\n/g, '\n');
node.removeAll();
node.append(allRuleNodes);
}
});
}
return node;
}
if (!_.isUndefined(opts['custom-property-empty-line-before'])) {
let customPropertyEmptyLineBefore = opts['custom-property-empty-line-before'];
function createLineBreaks(lineBreaksCount) {
return new Array(lineBreaksCount + 1).join('\n');
}
// Convert to common options format, e. g. `true` → `[true]`
if (!_.isArray(customPropertyEmptyLineBefore)) {
customPropertyEmptyLineBefore = [customPropertyEmptyLineBefore];
}
function getAtruleSortName(node, order) {
var atruleName = '@' + node.name;
var sortNameExtended;
var atruleParameter;
const optionName = 'custom-property-empty-line-before';
// If atRule has a parameter like `@mixin name` or `@include name`, sort by this parameter
if (node.params) {
atruleParameter = node.params;
sortNameExtended = atruleName + ' ' + atruleParameter;
css.walkDecls(function (decl) {
const prop = decl.prop;
const parent = decl.parent;
// check if there is a whole parameter in the config, e. g. `media("<=desk")`
if (order[sortNameExtended]) {
return sortNameExtended;
}
if (!isStandardSyntaxDeclaration(decl) || !isCustomProperty(prop)) {
return;
}
// check if there is a part of parameter in the config, e. g. `media` from `media("<=desk")`
atruleParameter = (/^[\w-]+/).exec(atruleParameter);
// Optionally ignore the node if a comment precedes it
if (
checkOption(optionName, 'ignore', 'after-comment')
&& hasNonSharedLineCommentBefore(decl)
) {
return;
}
if (atruleParameter && atruleParameter.length) {
sortNameExtended = atruleName + ' ' + atruleParameter[0];
// Optionally ignore nodes inside single-line blocks
if (
checkOption(optionName, 'ignore', 'inside-single-line-block')
&& isSingleLineBlock(parent)
) {
return;
}
if (order[sortNameExtended]) {
return sortNameExtended;
let expectEmptyLineBefore = customPropertyEmptyLineBefore[0];
// Optionally reverse the expectation for the first nested node
if (
checkOption(optionName, 'except', 'first-nested')
&& decl === parent.first
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
}
}
// If atrule with name is in order use the name
if (order[atruleName]) {
return atruleName;
}
// Optionally reverse the expectation if a comment precedes this node
if (
checkOption(optionName, 'except', 'after-comment')
&& hasNonSharedLineCommentBefore(decl)
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
return '@atrule';
}
// Optionally reverse the expectation if a custom property precedes this node
if (
checkOption(optionName, 'except', 'after-custom-property')
&& decl.prev()
&& (
(
decl.prev().prop
&& isCustomProperty(decl.prev().prop)
)
|| (
hasSharedLineCommentBefore(decl)
&& decl.prev().prev()
&& decl.prev().prev().prop
&& isCustomProperty(decl.prev().prev().prop)
)
)
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
function getSortName(node, order) {
switch (node.type) {
case 'decl':
return (/^(\$|--)[\w-]+/).test(node.prop) ? '$variable' : node.prop;
const hasEmptyLineBefore = hasEmptyLine(decl.raws.before);
case 'atrule':
return getAtruleSortName(node, order);
// Return if the expectation is met
if (expectEmptyLineBefore === hasEmptyLineBefore) {
return;
}
case 'rule':
return '>child';
if (expectEmptyLineBefore) {
if (decl.raws.before.indexOf('\n') === -1) {
decl.raws.before = `\n${decl.raws.before}`;
}
default:
return null;
decl.raws.before = createEmptyLines(1) + decl.raws.before;
} else {
decl.raws.before = cleanEmptyLines(decl.raws.before);
}
});
}
}
function getOrderProperty(node, order) {
var sortName = getSortName(node, order);
if (!_.isUndefined(opts['dollar-variable-empty-line-before'])) {
let dollarVariableEmptyLineBefore = opts['dollar-variable-empty-line-before'];
// Trying to get property indexes from order's list
var orderProperty = order[sortName];
// Convert to common options format, e. g. `true` → `[true]`
if (!_.isArray(dollarVariableEmptyLineBefore)) {
dollarVariableEmptyLineBefore = [dollarVariableEmptyLineBefore];
}
// If no property in the list and this property is prefixed then trying to get parameters for unprefixed property
if (!orderProperty && postcss.vendor.prefix(sortName)) {
sortName = postcss.vendor.unprefixed(sortName);
orderProperty = order[sortName];
}
const optionName = 'dollar-variable-empty-line-before';
return orderProperty;
}
css.walkDecls(function (decl) {
const prop = decl.prop;
const parent = decl.parent;
function addIndexesToNode(node, index, order) {
// Index to place the nodes that shouldn't be sorted
var lastGroupIndex = order['...'] ? order['...'].group : Infinity;
var lastPropertyIndex = order['...'] ? order['...'].prop : Infinity;
if (!isDollarVariable(prop)) {
return;
}
var orderProperty = getOrderProperty(node, order);
// Optionally ignore the node if a comment precedes it
if (
checkOption(optionName, 'ignore', 'after-comment')
&& hasNonSharedLineCommentBefore(decl)
) {
return;
}
// If the declaration's property is in order's list, save its
// group and property indexes. Otherwise set them to 10000, so
// declaration appears at the bottom of a sorted list:
node.groupIndex = orderProperty && orderProperty.group > -1 ? orderProperty.group : lastGroupIndex;
node.propertyIndex = orderProperty && orderProperty.prop > -1 ? orderProperty.prop : lastPropertyIndex;
node.initialIndex = index;
// Optionally ignore nodes inside single-line blocks
if (
checkOption(optionName, 'ignore', 'inside-single-line-block')
&& isSingleLineBlock(parent)
) {
return;
}
return node;
}
let expectEmptyLineBefore = dollarVariableEmptyLineBefore[0];
function fetchAllCommentsBeforeNode(comments, previousNode, node, currentInitialIndex) {
if (!previousNode || previousNode.type !== 'comment') {
return comments;
}
// Optionally reverse the expectation for the first nested node
if (
checkOption(optionName, 'except', 'first-nested')
&& decl === parent.first
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
if (!previousNode.raws.before || previousNode.raws.before.indexOf('\n') === -1) {
return comments;
}
// Optionally reverse the expectation if a comment precedes this node
if (
checkOption(optionName, 'except', 'after-comment')
&& hasNonSharedLineCommentBefore(decl)
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
currentInitialIndex = currentInitialIndex || node.initialIndex;
// Optionally reverse the expectation if a dollar variable precedes this node
if (
checkOption(optionName, 'except', 'after-dollar-variable')
&& decl.prev()
&& (
(
decl.prev().prop
&& isDollarVariable(decl.prev().prop)
)
|| (
hasSharedLineCommentBefore(decl)
&& decl.prev().prev()
&& decl.prev().prev().prop
&& isDollarVariable(decl.prev().prev().prop)
)
)
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
previousNode.groupIndex = node.groupIndex;
previousNode.propertyIndex = node.propertyIndex;
previousNode.initialIndex = currentInitialIndex - 0.0001;
const hasEmptyLineBefore = hasEmptyLine(decl.raws.before);
var newComments = [previousNode].concat(comments);
// Return if the expectation is met
if (expectEmptyLineBefore === hasEmptyLineBefore) {
return;
}
return fetchAllCommentsBeforeNode(newComments, previousNode.prev(), node, previousNode.initialIndex);
}
if (expectEmptyLineBefore) {
if (decl.raws.before.indexOf('\n') === -1) {
decl.raws.before = `\n${decl.raws.before}`;
}
function fetchAllCommentsAfterNode(comments, nextNode, node, currentInitialIndex) {
if (!nextNode || nextNode.type !== 'comment') {
return comments;
decl.raws.before = createEmptyLines(1) + decl.raws.before;
} else {
decl.raws.before = cleanEmptyLines(decl.raws.before);
}
});
}
if (!nextNode.raws.before || nextNode.raws.before.indexOf('\n') >= 0) {
return comments;
}
if (!_.isUndefined(opts['declaration-empty-line-before'])) {
let declarationEmptyLineBefore = opts['declaration-empty-line-before'];
currentInitialIndex = currentInitialIndex || node.initialIndex;
// Convert to common options format, e. g. `true` → `[true]`
if (!_.isArray(declarationEmptyLineBefore)) {
declarationEmptyLineBefore = [declarationEmptyLineBefore];
}
nextNode.groupIndex = node.groupIndex;
nextNode.propertyIndex = node.propertyIndex;
nextNode.initialIndex = currentInitialIndex + 0.0001;
const optionName = 'declaration-empty-line-before';
return fetchAllCommentsAfterNode(comments.concat(nextNode), nextNode.next(), node, nextNode.initialIndex);
}
css.walkDecls(function (decl) {
const prop = decl.prop;
const parent = decl.parent;
function getApplicableNode(lookFor, node) {
// find if there any rules before, and skip the comments
var prevNode = node.prev();
if (!isStandardSyntaxDeclaration(decl)) {
return;
}
if (prevNode) {
if (prevNode.type === lookFor) {
return node;
}
if (isCustomProperty(prop)) {
return;
}
if (prevNode.type === 'comment') {
return getApplicableNode(lookFor, prevNode);
}
}
// Optionally ignore the node if a comment precedes it
if (
checkOption(optionName, 'ignore', 'after-comment')
&& hasNonSharedLineCommentBefore(decl)
) {
return;
}
return false;
}
// Optionally ignore the node if a declaration precedes it
if (
checkOption(optionName, 'ignore', 'after-declaration')
&& decl.prev()
&& (
isDeclarationBefore(decl.prev())
|| (
hasSharedLineCommentBefore(decl)
&& isDeclarationBefore(decl.prev().prev())
)
)
) {
return;
}
function countEmptyLines(str) {
var lineBreaks = (str.match(/\n/g) || []).length;
// Optionally ignore nodes inside single-line blocks
if (
checkOption(optionName, 'ignore', 'inside-single-line-block')
&& isSingleLineBlock(parent)
) {
return;
}
if (lineBreaks > 0) {
lineBreaks -= 1;
}
let expectEmptyLineBefore = declarationEmptyLineBefore[0];
return lineBreaks;
}
// Optionally reverse the expectation for the first nested node
if (
checkOption(optionName, 'except', 'first-nested')
&& decl === parent.first
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
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
// Optionally reverse the expectation if a comment precedes this node
if (
checkOption(optionName, 'except', 'after-comment')
&& hasNonSharedLineCommentBefore(decl)
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
return processedNodes.concat(node);
}
// Optionally reverse the expectation if a declaration precedes this node
if (
checkOption(optionName, 'except', 'after-declaration')
&& decl.prev()
&& (
isDeclarationBefore(decl.prev())
|| (
hasSharedLineCommentBefore(decl)
&& isDeclarationBefore(decl.prev().prev())
)
)
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
return processedNodes;
const hasEmptyLineBefore = hasEmptyLine(decl.raws.before);
// Return if the expectation is met
if (expectEmptyLineBefore === hasEmptyLineBefore) {
return;
}
if (expectEmptyLineBefore) {
if (decl.raws.before.indexOf('\n') === -1) {
decl.raws.before = `\n${decl.raws.before}`;
}
decl.raws.before = createEmptyLines(1) + decl.raws.before;
} else {
decl.raws.before = cleanEmptyLines(decl.raws.before);
}
function isDeclarationBefore(targetDeclaration) {
return targetDeclaration
&& targetDeclaration.prop
&& isStandardSyntaxDeclaration(targetDeclaration)
&& !isCustomProperty(targetDeclaration.prop);
}
});
}
var order = getSortOrderFromOptions(opts);
if (!_.isUndefined(opts['rule-nested-empty-line-before'])) {
let ruleNestedEmptyLineBefore = opts['rule-nested-empty-line-before'];
node = addIndexesToNode(node, index, order);
// Convert to common options format, e. g. `true` → `[true]`
if (!_.isArray(ruleNestedEmptyLineBefore)) {
ruleNestedEmptyLineBefore = [ruleNestedEmptyLineBefore];
}
// If comment on separate line before node, use node's indexes for comment
var commentsBefore = fetchAllCommentsBeforeNode([], node.prev(), node);
const optionName = 'rule-nested-empty-line-before';
// If comment on same line with the node and node, use node's indexes for comment
var commentsAfter = fetchAllCommentsAfterNode([], node.next(), node);
css.walkRules(function (rule) {
if (!isStandardSyntaxRule(rule)) {
return;
}
return processedNodes.concat(commentsBefore, node, commentsAfter);
}
// Only attend to nested rule sets
if (rule.parent === css) {
return;
}
function processLastComments(node, index, processedNodes) {
if (node.type === 'comment' && !node.hasOwnProperty('groupIndex') && !node.ruleComment) {
node.groupIndex = Infinity;
node.propertyIndex = Infinity;
node.initialIndex = index;
// Optionally ignore the expectation if a non-shared-line comment precedes this node
if (
checkOption(optionName, 'ignore', 'after-comment')
&& hasNonSharedLineCommentBefore(rule)
) {
return;
}
return processedNodes.concat(node);
}
// Ignore if the expectation is for multiple and the rule is single-line
if (
(
_.isString(ruleNestedEmptyLineBefore[0])
&& ruleNestedEmptyLineBefore[0].indexOf('multi-line') !== -1
)
&& isSingleLineString(rule.toString())
) {
return;
}
return processedNodes;
}
let expectEmptyLineBefore = false;
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;
}
if (
(
_.isString(ruleNestedEmptyLineBefore[0])
&& ruleNestedEmptyLineBefore[0].indexOf('always') !== -1
)
|| ruleNestedEmptyLineBefore[0] === true
) {
expectEmptyLineBefore = true;
}
// 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;
}
// Optionally reverse the expectation for the first nested node
if (
checkOption(optionName, 'except', 'first-nested')
&& rule === rule.parent.first
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
// 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;
}
// Optionally reverse the expectation if a rule precedes this node
if (
checkOption(optionName, 'except', 'after-rule')
&& rule.prev()
&& (
rule.prev().type === 'rule'
|| (
hasSharedLineCommentBefore(rule)
&& rule.prev().prev()
&& rule.prev().prev().type === 'rule'
)
)
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
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'];
const hasEmptyLineBefore = hasEmptyLine(rule.raws.before);
// 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);
// Return if the expectation is met
if (expectEmptyLineBefore === hasEmptyLineBefore) {
return;
}
if (expectEmptyLineBefore) {
if (rule.raws.before.indexOf('\n') === -1) {
rule.raws.before = `\n${rule.raws.before}`;
}
rule.raws.before = createEmptyLines(1) + rule.raws.before;
} else {
rule.raws.before = cleanEmptyLines(rule.raws.before);
}
});
}
var prevNode = node.prev();
if (!_.isUndefined(opts['at-rule-nested-empty-line-before'])) {
let atRuleNestedEmptyLineBefore = opts['at-rule-nested-empty-line-before'];
if (prevNode && node.raws.before) {
if (node.groupIndex > prevNode.groupIndex) {
node.raws.before = createLineBreaks(1) + node.raws.before;
// Convert to common options format, e. g. `true` → `[true]`
if (!_.isArray(atRuleNestedEmptyLineBefore)) {
atRuleNestedEmptyLineBefore = [atRuleNestedEmptyLineBefore];
}
var applicableNode;
const optionName = 'at-rule-nested-empty-line-before';
// 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);
css.walkAtRules(function (atRule) {
// Only attend to nested at-rules
if (atRule.parent === css) {
return;
}
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;
// Return early if at-rule is to be ignored
if (checkOption(optionName, 'ignoreAtRules', atRule.name)) {
return;
}
// Optionally ignore the expectation if the node is blockless
if (
checkOption(optionName, 'ignore', 'blockless-after-blockless')
&& isBlocklessAfterBlockless()
) {
return;
}
const previousNode = atRule.prev();
// Optionally ignore the expection if the node is blockless
// and following another blockless at-rule with the same name
if (
checkOption(optionName, 'ignore', 'blockless-after-same-name-blockless')
&& isBlocklessAfterSameNameBlockless()
) {
return;
}
// Optionally ignore the expectation if a comment precedes this node
if (
checkOption(optionName, 'ignore', 'after-comment')
&& hasNonSharedLineCommentBefore(atRule)
) {
return;
}
let expectEmptyLineBefore = atRuleNestedEmptyLineBefore[0];
// Optionally reverse the expectation if any exceptions apply
if (
checkOption(optionName, 'except', 'first-nested')
&& isFirstNested()
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
if (
checkOption(optionName, 'except', 'blockless-after-blockless')
&& isBlocklessAfterBlockless()
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
if (
checkOption(optionName, 'except', 'blockless-after-same-name-blockless')
&& isBlocklessAfterSameNameBlockless()
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
if (
checkOption(optionName, 'except', 'after-same-name')
&& isAfterSameName()
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
const hasEmptyLineBefore = hasEmptyLine(atRule.raws.before);
// Return if the expectation is met
if (expectEmptyLineBefore === hasEmptyLineBefore) {
return;
}
if (expectEmptyLineBefore) {
if (atRule.raws.before.indexOf('\n') === -1) {
atRule.raws.before = `\n${atRule.raws.before}`;
}
atRule.raws.before = createEmptyLines(1) + atRule.raws.before;
} else {
atRule.raws.before = cleanEmptyLines(atRule.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);
function isBlocklessAfterBlockless() {
return !hasBlock(atRule)
&& atRule.prev()
&& (
(
atRule.prev().type === 'atrule'
&& !hasBlock(atRule.prev())
&& !hasBlock(atRule)
)
|| (
hasSharedLineCommentBefore(atRule)
&& atRule.prev().prev()
&& atRule.prev().prev().type === 'atrule'
&& !hasBlock(atRule.prev().prev())
)
);
}
if (applicableNode) {
applicableNode.raws.before = createLineBreaks(linesBetweenMediaRules - countEmptyLines(applicableNode.raws.before)) + applicableNode.raws.before;
function isBlocklessAfterSameNameBlockless() {
return !hasBlock(atRule)
&& previousNode
&& (
(
previousNode.type === 'atrule'
&& previousNode.name === atRule.name
&& !hasBlock(previousNode)
)
|| (
hasSharedLineCommentBefore(atRule)
&& previousNode.prev()
&& previousNode.prev().type === 'atrule'
&& previousNode.prev().name === atRule.name
&& !hasBlock(previousNode.prev())
)
);
}
}
// 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;
function isAfterSameName() {
return previousNode
&& (
(
previousNode.type === 'atrule'
&& previousNode.name === atRule.name
)
|| (
hasSharedLineCommentBefore(atRule)
&& previousNode.prev()
&& previousNode.prev().type === 'atrule'
&& previousNode.prev().name === atRule.name
)
);
}
function isFirstNested() {
return atRule === atRule.parent.first;
}
});
}
if (!_.isUndefined(opts['comment-empty-line-before'])) {
let commentEmptyLineBefore = opts['comment-empty-line-before'];
// Convert to common options format, e. g. `true` → `[true]`
if (!_.isArray(commentEmptyLineBefore)) {
commentEmptyLineBefore = [commentEmptyLineBefore];
}
// 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;
}
const optionName = 'comment-empty-line-before';
css.walk(function (node) {
// Process only rules and atrules with nodes
if (isRuleWithNodes(node)) {
node.walkComments((comment) => {
// Optionally ignore stylelint commands
if (
comment.text.indexOf('stylelint-') === 0
&& checkOption(optionName, 'ignore', 'stylelint-command')
) {
return;
}
// Optionally ignore newlines between comments
const prev = comment.prev();
if (
prev
&& prev.type === 'comment'
&& checkOption(optionName, 'ignore', 'after-comment')
) {
return;
}
if (
comment.raws.inline
|| comment.inline
) {
return;
}
const before = comment.raws.before || '';
// Ignore shared-line comments
if (before.indexOf('\n') === -1) {
return;
}
const hasEmptyLineBefore = hasEmptyLine(before);
let expectEmptyLineBefore = commentEmptyLineBefore[0];
// Optionally reverse the expectation if any exceptions apply
if (
checkOption(optionName, 'except', 'first-nested')
&& comment === comment.parent.first
) {
expectEmptyLineBefore = !expectEmptyLineBefore;
}
// Return if the expectation is met
if (expectEmptyLineBefore === hasEmptyLineBefore) {
return;
}
if (expectEmptyLineBefore) {
comment.raws.before = createEmptyLines(1) + comment.raws.before;
} else {
comment.raws.before = cleanEmptyLines(comment.raws.before);
}
});
}
});
}
function checkOption(primaryOption, secondaryOption, value) {
const secondaryOptionValues = _.get(opts[primaryOption][1], secondaryOption);
return _.includes(secondaryOptionValues, value);
}
}
The MIT License (MIT)
Copyright 2016 Aleks Hudochenkov <aleks@hudochenkov.com>
Copyright 2017 Aleks Hudochenkov <aleks@hudochenkov.com>

@@ -5,0 +5,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of

{
"name": "postcss-sorting",
"version": "1.7.0",
"description": "PostCSS plugin to sort rules content with specified order.",
"version": "2.0.0",
"description": "PostCSS plugin to keep rules and at-rules content in order.",
"keywords": [

@@ -20,19 +20,22 @@ "postcss",

"files": [
"configs",
"docs",
"lib",
"index.js"
],
"engines": {
"node": ">=4.0.0"
},
"dependencies": {
"object-assign": "^4.1.0",
"postcss": "^5.1.2"
"lodash": "^4.17.4",
"postcss": "^5.2.10"
},
"devDependencies": {
"ava": "^0.16.0",
"eslint": "^3.4.0",
"postcss-scss": "^0.2.1"
"ava": "^0.17.0",
"eslint": "^3.13.1"
},
"scripts": {
"test": "ava && eslint index.js test/test.js",
"test2": "ava",
"lint": "eslint index.js test/test.js"
"test": "ava && eslint index.js lib/*.js test/*.js",
"ava": "ava",
"lint": "eslint index.js lib/*.js test/*.js"
}
}
# PostCSS Sorting [![Build Status][ci-img]][ci]
[PostCSS] plugin to sort rules content with specified order. Heavily inspired by [CSSComb].
[PostCSS] plugin to keep rules and at-rules content in order.
Also available as [Sublime Text plugin], [Atom plugin], and [VS Code plugin].
Lint style sheets order with [stylelint-order].
## Features
* Plugin is sorting content for rules and at-rules.
* Sorting nested rules.
* Sorting at-rules, also by at-rule name and parameter.
* Sorting variables.
* Grouping content.
* Support CSS, SCSS (if [postcss-scss] parser is used), [PreCSS] and most likely any other syntax added by other PostCSS plugins.
* Sorts rules and at-rules content.
* Sorts properties.
* Sorts at-rules by different options.
* Groups properties, custom properties, dollar variables, nested rules, nested at-rules.
* Adds empty lines before different types of nodes.
* Supports CSS, SCSS (if [postcss-scss] parser is used), [PreCSS] and most likely any other syntax added by other PostCSS plugins.
## Table of Contents
* [Installation](#installation)
* [Options](#options)
* [Default options](#default-options)
* [`sort-order`](#sort-order)
* [Declarations](#declarations)
* [Prefixed properties](#prefixed-properties)
* [Grouping](#grouping)
* [@at-rules](#at-rules)
* [Nested rules](#nested-rules)
* [Variables](#variables)
* [Leftovers](#leftovers)
* [Predefined configs](#predefined-configs)
* [`empty-lines-between-children-rules`](#empty-lines-between-children-rules)
* [`empty-lines-between-media-rules`](#empty-lines-between-media-rules)
* [`preserve-empty-lines-between-children-rules`](#preserve-empty-lines-between-children-rules)
* [`empty-lines-before-comment`](#empty-lines-before-comment)
* [`empty-lines-after-comment`](#empty-lines-after-comment)
* [Disabling in style sheet](#disabling-in-style-sheet)
* [Migration from CSSComb](#migration-from-csscomb)
* [Usage](#usage)
* [Text editor](#text-editor)
* [Gulp](#gulp)
* [Grunt](#grunt)
* [Related tools](#related-tools)
## Installation

@@ -51,212 +26,105 @@

### Default options
The plugin has no default options. Everything is disabled by default.
```json
{
"sort-order": "default",
"empty-lines-between-children-rules": 0,
"empty-lines-between-media-rules": 0,
"preserve-empty-lines-between-children-rules": false
}
```
### Order
### `sort-order`
- [`order`](./docs/order.md): Specify the order of content within declaration blocks.
- [`properties-order`](./docs/properties-order.md): Specify the order of properties within declaration blocks. Can specify empty line before property groups.
- [`unspecified-properties-position`](./docs/unspecified-properties-position.md): Specify position for properties not specified in `properties-order`.
Set sort order. If no order is set, the plugin uses default config.
### Empty lines
**Note**: Use one of [predefined configs] as an example.
- [`clean-empty-lines`](./docs/clean-empty-lines.md): Remove all empty lines. Runs before all other rules.
- [`rule-nested-empty-line-before`](./docs/rule-nested-empty-line-before.md): Specify an empty line before nested rules.
- [`at-rule-nested-empty-line-before`](./docs/at-rule-nested-empty-line-before.md): Specify an empty line before nested at-rules.
- [`declaration-empty-line-before`](./docs/declaration-empty-line-before.md): Specify an empty line before declarations.
- [`custom-property-empty-line-before`](./docs/custom-property-empty-line-before.md): Specify an empty line before custom properties.
- [`dollar-variable-empty-line-before`](./docs/dollar-variable-empty-line-before.md): Specify an empty line before `$`-variable declarations.
- [`comment-empty-line-before`](./docs/comment-empty-line-before.md): Specify an empty line before comments.
Acceptable values:
## Handling comments
* `{Array}` of rules;
* `{Array}` of arrays of rules for groups separation;
* `{String}` with the name of predefined config.
Shared-line comments are comments which are located after a node and on the same line as a node.
#### Declarations
Example: `{ "sort-order": [ "margin", "padding" ] }`
```css
/* before */
p {
padding: 0;
margin: 0;
a {
/* regular comment */
color: pink; /* shared-line comment */
}
/* after */
p {
margin: 0;
padding: 0;
}
```
##### Prefixed properties
Shared-line comments are always ignored in all “empty lines before” options. The plugin always looks “through” these comments. For example:
Prefixed properties may not be in sort order. Plugin will look for unprefixed property and if it find one it will use that property order for the prefixed property. It would be better not to write prefixed properties in CSS at all and delegate this job to [Autoprefixer].
Example: `{ "sort-order": [ "position", "-webkit-box-sizing", "box-sizing", "width" ] }`
```css
/* before */
div {
-moz-box-sizing: border-box;
width: 100%;
box-sizing: border-box;
position: absolute;
-webkit-box-sizing: border-box;
```js
{
"declaration-empty-line-before": [true, {
except: "after-declaration"
}]
}
/* after */
div {
position: absolute;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
width: 100%;
}
```
#### Grouping
Technically there is a comment before `bottom`. But it's a shared line comment, so plugin looks before this comment and sees `top`:
Using an array of arrays for `sort-order` separate content into groups by an empty line.
Example: `{ "sort-order": [ [ "margin", "padding" ], [ "border", "background" ] ] }`
```css
/* before */
p {
background: none;
border: 0;
margin: 0;
padding: 0;
}
a {
--prop: pink;
/* after */
p {
margin: 0;
padding: 0;
border: 0;
background: none;
top: 5px; /* shared-line comment */
bottom: 15px;
}
```
#### @at-rules
For “order” options comments that are before node and on a separate line linked to that node. Shared-line comments are also linked to that node.
Any @at-rule inside another rule can be sorted. There is some keywords:
* `@atrule` — any at-rule.
* `@atrulename` — any at-rule with a specific name. Ex., `@media` or `@mixin`.
* `@atrulename parameter` — any at-rule with specific name and parameter. Ex., `@mixin clearfix`.
Example: `{ "sort-order": ["@atrule", "@mixin", "border", "@some-rule hello", "@mixin clearfix"] }`
```scss
/* before */
.block {
@some-rule hello;
border: none;
@mixin clearfix;
@media (min-width: 100px) {
display: none;
}
@mixin island;
```css
a {
top: 5px; /* shared-line comment belongs to `top` */
/* comment belongs to `bottom` */
/* comment belongs to `bottom` */
bottom: 15px; /* shared-line comment belongs to `bottom` */
}
/* after */
.block {
@media (min-width: 100px) {
display: none;
}
@mixin island;
border: none;
@some-rule hello;
@mixin clearfix;
}
```
#### Nested rules
## Migration from `1.x`
`>child` keyword for nested rules.
If you have been using [predefined configs], you can look at [migrated predefined configs].
Example: `{ "sort-order": [ ["position", "top", "width"], ['>child'] ] }`
`sort-order` was split into [`order`](./docs/order.md) and [`properties-order`](./docs/properties-order.md).
```scss
/* before */
.block {
position: absolute;
`properties-order` now uses an array of objects for grouping.
span {
display: inline-block;
}
`sort-order` keywords to new config conversion:
width: 50%;
| `1.x` | `2.x` |
| --- | --- |
| `@atrule` | `{ order: ["at-rules"] }` or `{ order: [{ type: "at-rule" }] }` |
| `@atrulename` | `{ order: [{ type: "at-rule", name: "atrulename" }] }` |
| `@atrulename parameter` | `{ order: [{ type: "at-rule", name: "atrulename", parameter: "parameter" }] }` |
| `>child` | `{ order: ["rules"] }` |
| `$variable` | `{ order: ["custom-properties", "dollar-variables"] }` |
| “leftovers” token `...` | `{ "unspecified-properties-position": "bottom" }` |
&__element {
display: none;
}
Config for `1.x`:
top: 0;
}
/* after */
.block {
position: absolute;
top: 0;
width: 50%;
span {
display: inline-block;
}
&__element {
display: none;
}
}
```
#### Variables
`$variable` keyword is using to sort variables like `$size`.
Example: `{ "sort-order": [ ["$variable"], ["position", "top", "width", "height"] ] }`
```scss
/* before */
.block {
position: absolute;
$width: 10px;
top: 0;
$height: 20px;
height: $height;
width: $width;
}
/* after */
.block {
$width: 10px;
$height: 20px;
position: absolute;
top: 0;
width: $width;
height: $height;
}
```
#### Leftovers
When there are properties that are not mentioned in the `sort-order` option, they are inserted after all the sorted properties in the new group in the same order they were in the source stylesheet.
You can override this by using a “leftovers” token: `...` — just place it either in its own group or near other properties in any other group and CSSComb would place all the properties that were not sorted where the `...` was in `sort-order`.
So, with this value:
``` json
```js
{
"sort-order": [
["$variable"],
["position"],
["...", "border"],
["@mixin"],
["font"]
[
"$variable"
],
[
"margin",
"padding"
],
[
"border",
"background"
],
[
'...',
"at-rule",
"@include",
"@include media",
">child"
]
]

@@ -266,275 +134,42 @@ }

everything would go into five groups: variables, then group with `position`, then group containing all the leftovers plus the `border`, then group with all mixins and then the `font`.
Config for `2.x`:
#### Predefined configs
[PostCSS Sorting] have [predefined configs]:
* `default`
* `alphabetical`
* `zen`
* `csscomb`
* `yandex`
* `smacss`
Example: `{ "sort-order": "zen" }`
### `empty-lines-between-children-rules`
Set a number of empty lines between nested children rules. By default there is no empty lines between `>child` rules.
Acceptable value: `{Number}` of empty lines
Example: `{ "empty-lines-between-children-rules": 1, "sort-order": [ ["..."], [">child"] ] }`
```scss
/* before */
.block {
position: absolute;
span {
display: inline-block;
}
&__element {
display: none;
}
&:hover {
top: 0;
}
```js
{
"order": [
"custom-properties",
"dollar-variables",
"declarations",
"at-rules",
{
"type": "at-rule",
"name": "include"
},
{
"type": "at-rule",
"name": "include",
"parameter": "icon"
},
"rules"
],
"properties-order": [
{
"emptyLineBefore": true,
"properties": [
"margin",
"padding"
]
},
{
"emptyLineBefore": true,
"properties": [
"border",
"background"
]
}
],
"unspecified-properties-position": "bottom"
}
/* after */
.block {
position: absolute;
span {
display: inline-block;
}
&__element {
display: none;
}
&:hover {
top: 0;
}
}
```
### `empty-lines-between-media-rules`
Set a number of empty lines between nested media rules. By default there is no empty lines between `@media` rules.
Acceptable value: `{Number}` of empty lines
Example: `{ "empty-lines-between-media-rules": 1, "sort-order": ["@media"] }`
```scss
/* before */
.block {
@media (min-width: 1px) {}
@media (min-width: 2px) {}
@media (min-width: 3px) {}
}
/* after */
.block {
@media (min-width: 1px) {}
@media (min-width: 2px) {}
@media (min-width: 3px) {}
}
```
### `preserve-empty-lines-between-children-rules`
Preserve empty lines between children rules and preserve empty lines for comments between children rules.
Acceptable value: `true`
Example: `{ "preserve-empty-lines-between-children-rules": true }`
```scss
/* before */
.block {
&:before {}
&:after {}
.element {}
/* comment */
.child {}
}
/* after (nothing changed) */
.block {
&:before {}
&:after {}
.element {}
/* comment */
.child {}
}
```
### `empty-lines-before-comment`
Set a number of empty lines before comment or comments group, which on separate lines. By default, there are no empty lines before comment.
Acceptable value: `{Number}` of empty lines
Example: `{ "empty-lines-before-comment": 2, "sort-order": [ "..." ] }`
```scss
/* before */
.hello {
display: inline-block;
/* upline comment 1 */
/* upline comment 2 */
font-style: italic;
border-bottom: 1px solid red; /* trololo 1 */ /* trololo 2 */
/* arrow */
&:before {
/* yeah */
content: "";
}
/* thing */
&:after {
/* joy */
display: none;
}
&__element {
/* sdfsf */
}
}
/* after */
.hello {
display: inline-block;
/* upline comment 1 */
/* upline comment 2 */
font-style: italic;
border-bottom: 1px solid red; /* trololo 1 */ /* trololo 2 */
/* arrow */
&:before {
/* yeah */
content: "";
}
/* thing */
&:after {
/* joy */
display: none;
}
&__element {
/* sdfsf */
}
}
```
### `empty-lines-after-comment`
Set a number of empty lines after comment or comments group, which on separate lines. By default, there are no empty lines after comment.
Acceptable value: `{Number}` of empty lines
Example: `{ "empty-lines-after-comment": 2, "sort-order": [ "..." ] }`
```scss
/* before */
.hello {
display: inline-block;
/* upline comment 1 */
/* upline comment 2 */
font-style: italic;
border-bottom: 1px solid red; /* trololo 1 */ /* trololo 2 */
/* arrow */
&:before {
/* yeah */
content: "";
}
/* thing */
&:after {
/* joy */
display: none;
}
&__element {
/* sdfsf */
}
}
/* after */
.hello {
display: inline-block;
/* upline comment 1 */
/* upline comment 2 */
font-style: italic;
border-bottom: 1px solid red; /* trololo 1 */ /* trololo 2 */
/* arrow */
&:before {
/* yeah */
content: "";
}
/* thing */
&:after {
/* joy */
display: none;
}
&__element {
/* sdfsf */
}
}
```
### Disabling in style sheet
The plugin can be temporarily turned off by using special comments.
```css
/* postcss-sorting: off */
.block1 {
width: 50px;
display: inline-block;
}
/* postcss-sorting: on */
```
Due to plugin nature only comments in the root of stylesheet will affect plugin processing. In this case comments will be treated like regular comments:
```css
.block5 {
/* postcss-sorting: off */
width: 20px;
display: inline-block;
/* postcss-sorting: on */
}
```
### Migration from CSSComb
If you used to use custom sorting order in [CSSComb] you can easily use this sorting order in PostCSS Sorting. `sort-order` option in this plugin is compatible with `sort-order` in CSSComb. Just copy `sort-order` value from CSSComb config to PostCSS Sorting config.
## Usage

@@ -544,7 +179,7 @@

#### Text editor
### Text editor
This plugin available as [Sublime Text plugin], [Atom plugin], and [VS Code plugin].
#### Gulp
### Gulp

@@ -569,3 +204,3 @@ Add [Gulp PostCSS] and PostCSS Sorting to your build tool:

).pipe(
gulp.dest('./css')
gulp.dest('./css/src')
);

@@ -575,3 +210,3 @@ });

#### Grunt
### Grunt

@@ -605,9 +240,9 @@ Add [Grunt PostCSS] and PostCSS Sorting to your build tool:

If you want format stylesheets, use [perfectionist] or [stylefmt], also a PostCSS-based tool.
[stylelint] and [stylelint-order] help lint style sheets and let know if style sheet order is correct.
Don't forget to lint stylesheets with [stylelint]!
If you want format style sheets, use [perfectionist] or [stylefmt], also a PostCSS-based tools.
## Thanks
This plugin is heavily inspired by [CSSComb]. Some code logic, tests, and documentation parts are taken from this tool.
This plugin is heavily inspired by [stylelint]. Some code logic, tests, and documentation parts are taken from this tool.

@@ -617,9 +252,8 @@ [PostCSS]: https://github.com/postcss/postcss

[ci]: https://travis-ci.org/hudochenkov/postcss-sorting
[PostCSS Sorting]: https://github.com/hudochenkov/postcss-sorting
[predefined configs]: https://github.com/hudochenkov/postcss-sorting/tree/master/configs
[Sublime Text plugin]: https://github.com/hudochenkov/sublime-postcss-sorting
[Atom plugin]: https://github.com/lysyi3m/atom-postcss-sorting
[VS Code plugin]: https://github.com/mrmlnc/vscode-postcss-sorting
[predefined configs]: https://github.com/hudochenkov/postcss-sorting/tree/ee71c3b61eea8fa11bc3aa2d26dd99a832df6d54/configs
[migrated predefined configs]: https://gist.github.com/hudochenkov/b7127590d3013a5982ed90ad63a85306
[CSSComb]: https://github.com/csscomb/csscomb.js
[Gulp PostCSS]: https://github.com/postcss/gulp-postcss

@@ -629,5 +263,5 @@ [Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss

[postcss-scss]: https://github.com/postcss/postcss-scss
[Autoprefixer]: https://github.com/postcss/autoprefixer
[perfectionist]: https://github.com/ben-eb/perfectionist
[stylefmt]: https://github.com/morishitter/stylefmt
[stylelint]: http://stylelint.io/
[stylelint-order]: https://github.com/hudochenkov/stylelint-order
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc