Skip to content

Commit 1e6a73d

Browse files
authored
Merge pull request Intellicode#222 from tanhauhau/tanhauhau/autofix-sort-styles
autofix for 'sort-styles' rule
2 parents 89c3a11 + f6986cb commit 1e6a73d

File tree

3 files changed

+568
-53
lines changed

3 files changed

+568
-53
lines changed

lib/rules/sort-styles.js

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
const { astHelpers } = require('../util/stylesheet');
1313

1414
const {
15-
getStyleDeclarations,
15+
getStyleDeclarationsChunks,
16+
getPropertiesChunks,
1617
getStylePropertyIdentifier,
1718
isStyleSheetDeclaration,
19+
isEitherShortHand,
1820
} = astHelpers;
1921

2022
//------------------------------------------------------------------------------
@@ -28,13 +30,54 @@ module.exports = (context) => {
2830
const ignoreStyleProperties = options.ignoreStyleProperties;
2931
const isValidOrder = order === 'asc' ? (a, b) => a <= b : (a, b) => a >= b;
3032

31-
function report(type, node, prev, current) {
33+
const sourceCode = context.getSourceCode();
34+
35+
function sort(array) {
36+
return [].concat(array).sort((a, b) => {
37+
const identifierA = getStylePropertyIdentifier(a);
38+
const identifierB = getStylePropertyIdentifier(b);
39+
40+
let sortOrder = 0;
41+
if (isEitherShortHand(identifierA, identifierB)) {
42+
return a.range[0] - b.range[0];
43+
} else if (identifierA < identifierB) {
44+
sortOrder = -1;
45+
} else if (identifierA > identifierB) {
46+
sortOrder = 1;
47+
}
48+
return sortOrder * (order === 'asc' ? 1 : -1);
49+
});
50+
}
51+
52+
function report(array, type, node, prev, current) {
3253
const currentName = getStylePropertyIdentifier(current);
3354
const prevName = getStylePropertyIdentifier(prev);
55+
const hasComments = array
56+
.map(prop => sourceCode.getComments(prop))
57+
.reduce(
58+
(hasComment, comment) =>
59+
hasComment || comment.leading.length > 0 || comment.trailing > 0,
60+
false
61+
);
62+
3463
context.report({
3564
node,
3665
message: `Expected ${type} to be in ${order}ending order. '${currentName}' should be before '${prevName}'.`,
3766
loc: current.key.loc,
67+
fix: hasComments ? undefined : (fixer) => {
68+
const sortedArray = sort(array);
69+
return array
70+
.map((item, i) => {
71+
if (item !== sortedArray[i]) {
72+
return fixer.replaceText(
73+
item,
74+
sourceCode.getText(sortedArray[i])
75+
);
76+
}
77+
return null;
78+
})
79+
.filter(Boolean);
80+
},
3881
});
3982
}
4083

@@ -50,8 +93,15 @@ module.exports = (context) => {
5093
const prevName = getStylePropertyIdentifier(previous);
5194
const currentName = getStylePropertyIdentifier(current);
5295

96+
if (
97+
arrayName === 'style properties' &&
98+
isEitherShortHand(prevName, currentName)
99+
) {
100+
return;
101+
}
102+
53103
if (!isValidOrder(prevName, currentName)) {
54-
return report(arrayName, node, previous, current);
104+
return report(array, arrayName, node, previous, current);
55105
}
56106
}
57107
}
@@ -62,26 +112,33 @@ module.exports = (context) => {
62112
return;
63113
}
64114

65-
const classDefinitions = getStyleDeclarations(node);
115+
const classDefinitionsChunks = getStyleDeclarationsChunks(node);
66116

67117
if (!ignoreClassNames) {
68-
checkIsSorted(classDefinitions, 'class names', node);
118+
classDefinitionsChunks.forEach((classDefinitions) => {
119+
checkIsSorted(classDefinitions, 'class names', node);
120+
});
69121
}
70122

71123
if (ignoreStyleProperties) return;
72124

73-
classDefinitions.forEach((classDefinition) => {
74-
const styleProperties = classDefinition.value.properties;
75-
if (!styleProperties || styleProperties.length < 2) {
76-
return;
77-
}
78-
79-
checkIsSorted(styleProperties, 'style properties', node);
125+
classDefinitionsChunks.forEach((classDefinitions) => {
126+
classDefinitions.forEach((classDefinition) => {
127+
const styleProperties = classDefinition.value.properties;
128+
if (!styleProperties || styleProperties.length < 2) {
129+
return;
130+
}
131+
const stylePropertyChunks = getPropertiesChunks(styleProperties);
132+
stylePropertyChunks.forEach((stylePropertyChunk) => {
133+
checkIsSorted(stylePropertyChunk, 'style properties', node);
134+
});
135+
});
80136
});
81137
},
82138
};
83139
};
84140

141+
module.exports.fixable = 'code';
85142
module.exports.schema = [
86143
{
87144
enum: ['asc', 'desc'],

lib/util/stylesheet.js

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -127,32 +127,72 @@ const astHelpers = {
127127
},
128128

129129
getStyleSheetName: function (node) {
130+
if (node && node.id) {
131+
return node.id.name;
132+
}
133+
},
134+
135+
getStyleDeclarations: function (node) {
130136
if (
131137
node &&
132-
node.id
138+
node.init &&
139+
node.init.arguments &&
140+
node.init.arguments[0] &&
141+
node.init.arguments[0].properties
133142
) {
134-
return node.id.name;
143+
return node.init.arguments[0].properties.filter(property => property.type === 'Property');
135144
}
145+
146+
return [];
136147
},
137148

138-
getStyleDeclarations: function (node) {
149+
getStyleDeclarationsChunks: function (node) {
139150
if (
140151
node &&
141152
node.init &&
142153
node.init.arguments &&
143154
node.init.arguments[0] &&
144155
node.init.arguments[0].properties
145156
) {
146-
return node
147-
.init
148-
.arguments[0]
149-
.properties
150-
.filter(property => property.type === 'Property');
157+
const properties = node.init.arguments[0].properties;
158+
const result = [];
159+
let chunk = [];
160+
for (let i = 0; i < properties.length; i += 1) {
161+
const property = properties[i];
162+
if (property.type === 'Property') {
163+
chunk.push(property);
164+
} else if (chunk.length) {
165+
result.push(chunk);
166+
chunk = [];
167+
}
168+
}
169+
if (chunk.length) {
170+
result.push(chunk);
171+
}
172+
return result;
151173
}
152174

153175
return [];
154176
},
155177

178+
getPropertiesChunks: function (properties) {
179+
const result = [];
180+
let chunk = [];
181+
for (let i = 0; i < properties.length; i += 1) {
182+
const property = properties[i];
183+
if (property.type === 'Property') {
184+
chunk.push(property);
185+
} else if (chunk.length) {
186+
result.push(chunk);
187+
chunk = [];
188+
}
189+
}
190+
if (chunk.length) {
191+
result.push(chunk);
192+
}
193+
return result;
194+
},
195+
156196
getExpressionIdentifier: function (node) {
157197
if (node) {
158198
switch (node.type) {
@@ -437,6 +477,16 @@ const astHelpers = {
437477
return [node.object.name, node.property.name].join('.');
438478
}
439479
},
480+
481+
isEitherShortHand: function (property1, property2) {
482+
const shorthands = ['margin', 'padding', 'border', 'flex'];
483+
if (shorthands.includes(property1)) {
484+
return property2.startsWith(property1);
485+
} else if (shorthands.includes(property2)) {
486+
return property1.startsWith(property2);
487+
}
488+
return false;
489+
},
440490
};
441491

442492
module.exports.astHelpers = astHelpers;

0 commit comments

Comments
 (0)