Skip to content

Commit 56b8078

Browse files
authored
Merge pull request #211 from draperunner/sort-styles
Add sort-styles rule
2 parents 1d18f84 + 03a982d commit 56b8078

File tree

6 files changed

+802
-397
lines changed

6 files changed

+802
-397
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ ESLint plugin for React Native
44

55
[![Greenkeeper badge](https://badges.greenkeeper.io/Intellicode/eslint-plugin-react-native.svg)](https://greenkeeper.io/)
66

7-
[![Maintenance Status][status-image]][status-url] [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][deps-image]][deps-url] [![Coverage Status][coverage-image]][coverage-url] [![Code Climate][climate-image]][climate-url] [![BCH compliance][bettercode-image]][bettercode-url]
7+
[![Maintenance Status][status-image]][status-url] [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][deps-image]][deps-url] [![Coverage Status][coverage-image]][coverage-url] [![Code Climate][climate-image]][climate-url] [![BCH compliance][bettercode-image]][bettercode-url]
88

99
React Native specific linting rules for ESLint. This repository is structured like (and contains code from) the excellent [eslint-plugin-react](http://github.com/yannickcr/eslint-plugin-react).
1010

@@ -79,6 +79,7 @@ Finally, enable all of the rules that you would like to use.
7979
# List of supported rules
8080

8181
* [no-unused-styles](docs/rules/no-unused-styles.md): Detect `StyleSheet` rules which are not used in your React components
82+
* [sort-styles](docs/rules/sort-styles.md): Require style definitions to be sorted alphabetically
8283
* [split-platform-components](docs/rules/split-platform-components.md): Enforce using platform specific filenames when necessary
8384
* [no-inline-styles](docs/rules/no-inline-styles.md): Detect JSX components with inline styles that contain literal values
8485
* [no-color-literals](docs/rules/no-color-literals.md): Detect `StyleSheet` rules and inline styles containing color literals instead of variables

docs/rules/sort-styles.md

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Require StyleSheet keys to be sorted
2+
It's like [sort-keys](https://eslint.org/docs/rules/sort-keys), but just for React Native styles.
3+
4+
Keeping your style definitions sorted is a common convention that helps with readability. This rule lets you enforce an ascending (default) or descending alphabetical order for both "class names" and style properties.
5+
6+
## Rule Details
7+
8+
The following patterns are considered warnings:
9+
10+
```js
11+
const styles = StyleSheet.create({
12+
button: {
13+
width: 100,
14+
color: 'green',
15+
},
16+
});
17+
```
18+
19+
```js
20+
const styles = StyleSheet.create({
21+
button: {},
22+
anchor: {},
23+
});
24+
```
25+
26+
The following patterns are not considered warnings:
27+
28+
```js
29+
const styles = StyleSheet.create({
30+
button: {
31+
color: 'green',
32+
width: 100,
33+
},
34+
});
35+
```
36+
37+
```js
38+
const styles = StyleSheet.create({
39+
anchor: {},
40+
button: {},
41+
});
42+
```
43+
44+
## Options
45+
46+
```
47+
{
48+
"react-native/sort-styles": ["error", "asc", { "ignoreClassNames": false, "ignoreStyleProperties": false }]
49+
}
50+
```
51+
52+
The 1st option is "asc" or "desc".
53+
54+
* `"asc"` (default) - enforce properties to be in ascending order.
55+
* `"desc"` - enforce properties to be in descending order.
56+
57+
The 2nd option is an object which has 2 properties.
58+
59+
* `ignoreClassNames` - if `true`, order will not be enforced on the class name level. Default is `false`.
60+
* `ignoreStyleProperties` - if `true`, order will not be enforced on the style property level. Default is `false`.
61+
62+
### desc
63+
64+
`/* eslint react-native/sort-styles: ["error", "desc"] */`
65+
66+
The following patterns are considered warnings:
67+
68+
```js
69+
const styles = StyleSheet.create({
70+
button: {
71+
color: 'green',
72+
width: 100,
73+
},
74+
});
75+
```
76+
77+
```js
78+
const styles = StyleSheet.create({
79+
anchor: {},
80+
button: {},
81+
});
82+
```
83+
84+
The following patterns are not considered warnings:
85+
86+
```js
87+
const styles = StyleSheet.create({
88+
button: {
89+
width: 100,
90+
color: 'green',
91+
},
92+
});
93+
```
94+
95+
```js
96+
const styles = StyleSheet.create({
97+
button: {},
98+
anchor: {},
99+
});
100+
```
101+
102+
### ignoreClassNames
103+
104+
`/* eslint react-native/sort-styles: ["error", "asc", { "ignoreClassNames": true }] */`
105+
106+
The following patterns are not considered warnings:
107+
108+
```js
109+
const styles = StyleSheet.create({
110+
button: {
111+
color: 'green',
112+
width: 100,
113+
},
114+
anchor: {},
115+
});
116+
```
117+
118+
# ignoreStyleProperties
119+
120+
`/* eslint react-native/sort-styles: ["error", "asc", { "ignoreStyleProperties": true }] */`
121+
122+
The following patterns are not considered warnings:
123+
124+
```js
125+
const styles = StyleSheet.create({
126+
anchor: {},
127+
button: {
128+
width: 100,
129+
color: 'green',
130+
},
131+
});
132+
```

index.js

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const allRules = {
66
'no-unused-styles': require('./lib/rules/no-unused-styles'),
77
'no-inline-styles': require('./lib/rules/no-inline-styles'),
88
'no-color-literals': require('./lib/rules/no-color-literals'),
9+
'sort-styles': require('./lib/rules/sort-styles'),
910
'split-platform-components': require('./lib/rules/split-platform-components'),
1011
'no-raw-text': require('./lib/rules/no-raw-text'),
1112
};
@@ -30,6 +31,7 @@ module.exports = {
3031
'no-unused-styles': 0,
3132
'no-inline-styles': 0,
3233
'no-color-literals': 0,
34+
'sort-styles': 0,
3335
'split-platform-components': 0,
3436
'no-raw-text': 0,
3537
},

lib/rules/sort-styles.js

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/**
2+
* @fileoverview Rule to require StyleSheet object keys to be sorted
3+
* @author Mats Byrkjeland
4+
*/
5+
6+
'use strict';
7+
8+
//------------------------------------------------------------------------------
9+
// Requirements
10+
//------------------------------------------------------------------------------
11+
12+
const { astHelpers } = require('../util/stylesheet');
13+
14+
const { getStyleDeclarations, isStyleSheetDeclaration } = astHelpers;
15+
16+
//------------------------------------------------------------------------------
17+
// Rule Definition
18+
//------------------------------------------------------------------------------
19+
20+
module.exports = (context) => {
21+
const order = context.options[0] || 'asc';
22+
const options = context.options[1] || {};
23+
const ignoreClassNames = options.ignoreClassNames;
24+
const ignoreStyleProperties = options.ignoreStyleProperties;
25+
const isValidOrder = order === 'asc' ? (a, b) => a <= b : (a, b) => a >= b;
26+
27+
function report(type, node, prev, current) {
28+
const currentName = current.key.name;
29+
const prevName = prev.key.name;
30+
context.report({
31+
node,
32+
message: `Expected ${type} to be in ${order}ending order. '${currentName}' should be before '${prevName}'.`,
33+
loc: current.key.loc,
34+
});
35+
}
36+
37+
function checkIsSorted(array, arrayName, node) {
38+
for (let i = 1; i < array.length; i += 1) {
39+
const previous = array[i - 1];
40+
const current = array[i];
41+
42+
if (previous.type !== 'Property' || current.type !== 'Property') {
43+
return;
44+
}
45+
46+
if (!isValidOrder(previous.key.name, current.key.name)) {
47+
return report(arrayName, node, previous, current);
48+
}
49+
}
50+
}
51+
52+
return {
53+
VariableDeclarator: function (node) {
54+
if (!isStyleSheetDeclaration(node, context.settings)) {
55+
return;
56+
}
57+
58+
const classDefinitions = getStyleDeclarations(node);
59+
60+
if (!ignoreClassNames) {
61+
checkIsSorted(classDefinitions, 'class names', node);
62+
}
63+
64+
if (ignoreStyleProperties) return;
65+
66+
classDefinitions.forEach((classDefinition) => {
67+
const styleProperties = classDefinition.value.properties;
68+
if (!styleProperties || styleProperties.length < 2) {
69+
return;
70+
}
71+
72+
checkIsSorted(styleProperties, 'style properties', node);
73+
});
74+
},
75+
};
76+
};
77+
78+
module.exports.schema = [
79+
{
80+
enum: ['asc', 'desc'],
81+
},
82+
{
83+
type: 'object',
84+
properties: {
85+
ignoreClassNames: {
86+
type: 'boolean',
87+
},
88+
ignoreStyleProperties: {
89+
type: 'boolean',
90+
},
91+
},
92+
additionalProperties: false,
93+
},
94+
];

0 commit comments

Comments
 (0)