Skip to content

Commit eec5ad1

Browse files
committed
Refactoring: minimize spaghetti
1 parent 790ca4c commit eec5ad1

File tree

1 file changed

+186
-162
lines changed

1 file changed

+186
-162
lines changed

index.js

Lines changed: 186 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,63 @@ var objectAssign = require('object-assign');
33
var path = require('path');
44
var fs = require('fs');
55

6+
module.exports = postcss.plugin('postcss-sorting', function (opts) {
7+
return function (css) {
8+
plugin(css, opts);
9+
};
10+
});
11+
12+
function plugin(css, opts) {
13+
// Verify options and use defaults if not specified
14+
opts = verifyOptions(opts);
15+
16+
var enableSorting = true;
17+
18+
css.walk(function (node) {
19+
if (node.type === 'comment' && node.parent.type === 'root') {
20+
if (node.text === 'postcss-sorting: on') {
21+
enableSorting = true;
22+
} else if (node.text === 'postcss-sorting: off') {
23+
enableSorting = false;
24+
}
25+
}
26+
27+
if (!enableSorting) {
28+
return;
29+
}
30+
31+
// Process only rules and atrules with nodes
32+
if ((node.type === 'rule' || node.type === 'atrule') && node.nodes && node.nodes.length) {
33+
// Nodes for sorting
34+
var processed = [];
35+
36+
// Add indexes to nodes
37+
node.each(function (childNode, index) {
38+
processed = processMostNodes(childNode, index, opts, processed);
39+
});
40+
41+
// Add last comments in the rule. Need this because last comments are not belonging to anything
42+
node.each(function (childNode, index) {
43+
processed = processLastComments(childNode, index, processed);
44+
});
45+
46+
// Sort declarations saved for sorting
47+
processed.sort(sortByIndexes);
48+
49+
// Replace rule content with sorted one
50+
if (processed.length) {
51+
node.removeAll();
52+
node.append(processed);
53+
}
54+
55+
// Taking care of empty lines
56+
node.each(function (childNode) {
57+
formatNodes(childNode, opts);
58+
});
59+
}
60+
});
61+
}
62+
663
function verifyOptions(options) {
764
if (options === null || typeof options !== 'object') {
865
options = {};
@@ -226,172 +283,139 @@ function countEmptyLines(str) {
226283
return lineBreaks;
227284
}
228285

229-
module.exports = postcss.plugin('postcss-sorting', function (opts) {
230-
// Verify options and use defaults if not specified
231-
opts = verifyOptions(opts);
286+
function processMostNodes(node, index, opts, processedNodes) {
287+
if (node.type === 'comment') {
288+
if (index === 0 && node.raws.before.indexOf('\n') === -1) {
289+
node.ruleComment = true; // need this flag to not append this comment twice
232290

233-
return function (css) {
234-
var order = getSortOrderFromOptions(opts);
235-
var linesBetweenChildrenRules = getLinesBetweenRulesFromOptions('children', opts);
236-
var linesBetweenMediaRules = getLinesBetweenRulesFromOptions('media', opts);
237-
var preserveLinesBetweenChildren = opts['preserve-empty-lines-between-children-rules'];
238-
var linesBeforeComment = opts['empty-lines-before-comment'];
239-
var linesAfterComment = opts['empty-lines-after-comment'];
240-
var enableSorting = true;
241-
242-
css.walk(function (rule) {
243-
if (rule.type === 'comment' && rule.parent.type === 'root') {
244-
if (rule.text === 'postcss-sorting: on') {
245-
enableSorting = true;
246-
} else if (rule.text === 'postcss-sorting: off') {
247-
enableSorting = false;
291+
return processedNodes.concat(node);
292+
}
293+
294+
return processedNodes;
295+
}
296+
297+
var order = getSortOrderFromOptions(opts);
298+
299+
node = addIndexesToNode(node, index, order);
300+
301+
// If comment on separate line before node, use node's indexes for comment
302+
var commentsBefore = fetchAllCommentsBeforeNode([], node.prev(), node);
303+
304+
// If comment on same line with the node and node, use node's indexes for comment
305+
var commentsAfter = fetchAllCommentsAfterNode([], node.next(), node);
306+
307+
return processedNodes.concat(commentsBefore, node, commentsAfter);
308+
}
309+
310+
function processLastComments(node, index, processedNodes) {
311+
if (node.type === 'comment' && !node.hasOwnProperty('groupIndex') && !node.ruleComment) {
312+
node.groupIndex = Infinity;
313+
node.propertyIndex = Infinity;
314+
node.initialIndex = index;
315+
316+
return processedNodes.concat(node);
317+
}
318+
319+
return processedNodes;
320+
}
321+
322+
function sortByIndexes(a, b) {
323+
// If a's group index is higher than b's group index, in a sorted
324+
// list a appears after b:
325+
if (a.groupIndex !== b.groupIndex) {
326+
return a.groupIndex - b.groupIndex;
327+
}
328+
329+
// If a and b have the same group index, and a's property index is
330+
// higher than b's property index, in a sorted list a appears after
331+
// b:
332+
if (a.propertyIndex !== b.propertyIndex) {
333+
return a.propertyIndex - b.propertyIndex;
334+
}
335+
336+
// If a and b have the same group index and the same property index,
337+
// in a sorted list they appear in the same order they were in
338+
// original array:
339+
return a.initialIndex - b.initialIndex;
340+
}
341+
342+
function formatNodes(node, opts) {
343+
var linesBetweenChildrenRules = getLinesBetweenRulesFromOptions('children', opts);
344+
var linesBetweenMediaRules = getLinesBetweenRulesFromOptions('media', opts);
345+
var preserveLinesBetweenChildren = opts['preserve-empty-lines-between-children-rules'];
346+
var linesBeforeComment = opts['empty-lines-before-comment'];
347+
var linesAfterComment = opts['empty-lines-after-comment'];
348+
349+
// don't remove empty lines if they are should be preserved
350+
if (
351+
!(
352+
preserveLinesBetweenChildren &&
353+
(node.type === 'rule' || node.type === 'comment') &&
354+
node.prev() &&
355+
getApplicableNode('rule', node)
356+
)
357+
) {
358+
node = cleanLineBreaks(node);
359+
}
360+
361+
var prevNode = node.prev();
362+
363+
if (prevNode && node.raws.before) {
364+
if (node.groupIndex > prevNode.groupIndex) {
365+
node.raws.before = createLineBreaks(1) + node.raws.before;
366+
}
367+
368+
var applicableNode;
369+
370+
// Insert empty lines between children classes
371+
if (node.type === 'rule' && linesBetweenChildrenRules > 0) {
372+
// between rules can be comments, so empty lines should be added to first comment between rules, rather than to rule
373+
applicableNode = getApplicableNode('rule', node);
374+
375+
if (applicableNode) {
376+
// add lines only if source empty lines not preserved, or if there are less empty lines then should be
377+
if (
378+
!preserveLinesBetweenChildren ||
379+
(
380+
preserveLinesBetweenChildren &&
381+
countEmptyLines(applicableNode.raws.before) < linesBetweenChildrenRules
382+
)
383+
) {
384+
applicableNode.raws.before = createLineBreaks(linesBetweenChildrenRules - countEmptyLines(applicableNode.raws.before)) + applicableNode.raws.before;
248385
}
249386
}
387+
}
388+
389+
// Insert empty lines between media rules
390+
if (node.type === 'atrule' && node.name === 'media' && linesBetweenMediaRules > 0) {
391+
// between rules can be comments, so empty lines should be added to first comment between rules, rather than to rule
392+
applicableNode = getApplicableNode('atrule', node);
250393

251-
if (!enableSorting) {
252-
return;
394+
if (applicableNode) {
395+
applicableNode.raws.before = createLineBreaks(linesBetweenMediaRules - countEmptyLines(applicableNode.raws.before)) + applicableNode.raws.before;
253396
}
397+
}
254398

255-
// Process only rules and atrules with nodes
256-
if ((rule.type === 'rule' || rule.type === 'atrule') && rule.nodes && rule.nodes.length) {
257-
// Nodes for sorting
258-
var processed = [];
259-
260-
rule.each(function (node, index) {
261-
if (node.type === 'comment') {
262-
if (index === 0 && node.raws.before.indexOf('\n') === -1) {
263-
node.ruleComment = true; // need this flag to not append this comment twice
264-
265-
processed.push(node);
266-
}
267-
268-
return;
269-
}
270-
271-
node = addIndexesToNode(node, index, order);
272-
273-
// If comment on separate line before node, use node's indexes for comment
274-
var commentsBefore = fetchAllCommentsBeforeNode([], node.prev(), node);
275-
276-
// If comment on same line with the node and node, use node's indexes for comment
277-
var commentsAfter = fetchAllCommentsAfterNode([], node.next(), node);
278-
279-
processed = processed.concat(commentsBefore, node, commentsAfter);
280-
});
281-
282-
// Add last comments in the rule. Need this because last comments are not belonging to anything
283-
rule.each(function (node, index) {
284-
if (node.type === 'comment' && !node.hasOwnProperty('groupIndex') && !node.ruleComment) {
285-
node.groupIndex = Infinity;
286-
node.propertyIndex = Infinity;
287-
node.initialIndex = index;
288-
289-
processed.push(node);
290-
}
291-
});
292-
293-
// Sort declarations saved for sorting:
294-
processed.sort(function (a, b) {
295-
// If a's group index is higher than b's group index, in a sorted
296-
// list a appears after b:
297-
if (a.groupIndex !== b.groupIndex) {
298-
return a.groupIndex - b.groupIndex;
299-
}
300-
301-
// If a and b have the same group index, and a's property index is
302-
// higher than b's property index, in a sorted list a appears after
303-
// b:
304-
if (a.propertyIndex !== b.propertyIndex) {
305-
return a.propertyIndex - b.propertyIndex;
306-
}
307-
308-
// If a and b have the same group index and the same property index,
309-
// in a sorted list they appear in the same order they were in
310-
// original array:
311-
return a.initialIndex - b.initialIndex;
312-
});
313-
314-
if (processed.length) {
315-
rule.removeAll();
316-
rule.append(processed);
317-
}
399+
// Insert empty lines before comment
400+
if (
401+
linesBeforeComment &&
402+
node.type === 'comment' &&
403+
(prevNode.type !== 'comment' || prevNode.raws.before.indexOf('\n') === -1) && // prevNode it's not a comment or it's an inline comment
404+
node.raws.before.indexOf('\n') >= 0 && // this isn't an inline comment
405+
countEmptyLines(node.raws.before) < linesBeforeComment
406+
) {
407+
node.raws.before = createLineBreaks(linesBeforeComment - countEmptyLines(node.raws.before)) + node.raws.before;
408+
}
318409

319-
// Remove all empty lines and add empty lines between groups
320-
rule.each(function (node) {
321-
// don't remove empty lines if they are should be preserved
322-
if (
323-
!(
324-
preserveLinesBetweenChildren &&
325-
(node.type === 'rule' || node.type === 'comment') &&
326-
node.prev() &&
327-
getApplicableNode('rule', node)
328-
)
329-
) {
330-
node = cleanLineBreaks(node);
331-
}
332-
333-
var prevNode = node.prev();
334-
335-
if (prevNode && node.raws.before) {
336-
if (node.groupIndex > prevNode.groupIndex) {
337-
node.raws.before = createLineBreaks(1) + node.raws.before;
338-
}
339-
340-
var applicableNode;
341-
342-
// Insert empty lines between children classes
343-
if (node.type === 'rule' && linesBetweenChildrenRules > 0) {
344-
// between rules can be comments, so empty lines should be added to first comment between rules, rather than to rule
345-
applicableNode = getApplicableNode('rule', node);
346-
347-
if (applicableNode) {
348-
// add lines only if source empty lines not preserved, or if there are less empty lines then should be
349-
if (
350-
!preserveLinesBetweenChildren ||
351-
(
352-
preserveLinesBetweenChildren &&
353-
countEmptyLines(applicableNode.raws.before) < linesBetweenChildrenRules
354-
)
355-
) {
356-
applicableNode.raws.before = createLineBreaks(linesBetweenChildrenRules - countEmptyLines(applicableNode.raws.before)) + applicableNode.raws.before;
357-
}
358-
}
359-
}
360-
361-
// Insert empty lines between media rules
362-
if (node.type === 'atrule' && node.name === 'media' && linesBetweenMediaRules > 0) {
363-
// between rules can be comments, so empty lines should be added to first comment between rules, rather than to rule
364-
applicableNode = getApplicableNode('atrule', node);
365-
366-
if (applicableNode) {
367-
applicableNode.raws.before = createLineBreaks(linesBetweenMediaRules - countEmptyLines(applicableNode.raws.before)) + applicableNode.raws.before;
368-
}
369-
}
370-
371-
// Insert empty lines before comment
372-
if (
373-
linesBeforeComment &&
374-
node.type === 'comment' &&
375-
(prevNode.type !== 'comment' || prevNode.raws.before.indexOf('\n') === -1) && // prevNode it's not a comment or it's an inline comment
376-
node.raws.before.indexOf('\n') >= 0 && // this isn't an inline comment
377-
countEmptyLines(node.raws.before) < linesBeforeComment
378-
) {
379-
node.raws.before = createLineBreaks(linesBeforeComment - countEmptyLines(node.raws.before)) + node.raws.before;
380-
}
381-
382-
// Insert empty lines after comment
383-
if (
384-
linesAfterComment &&
385-
node.type !== 'comment' &&
386-
prevNode.type === 'comment' &&
387-
prevNode.raws.before.indexOf('\n') >= 0 && // this isn't an inline comment
388-
countEmptyLines(node.raws.before) < linesAfterComment
389-
) {
390-
node.raws.before = createLineBreaks(linesAfterComment - countEmptyLines(node.raws.before)) + node.raws.before;
391-
}
392-
}
393-
});
394-
}
395-
});
396-
};
397-
});
410+
// Insert empty lines after comment
411+
if (
412+
linesAfterComment &&
413+
node.type !== 'comment' &&
414+
prevNode.type === 'comment' &&
415+
prevNode.raws.before.indexOf('\n') >= 0 && // this isn't an inline comment
416+
countEmptyLines(node.raws.before) < linesAfterComment
417+
) {
418+
node.raws.before = createLineBreaks(linesAfterComment - countEmptyLines(node.raws.before)) + node.raws.before;
419+
}
420+
}
421+
}

0 commit comments

Comments
 (0)