Skip to content

Commit 24c409f

Browse files
authored
Merge branch 'master' into greenkeeper/initial
2 parents 8fdeaa5 + 7e33ed0 commit 24c409f

22 files changed

+5841
-551
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ build/Release
2525
# Dependency directory
2626
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
2727
node_modules
28+
29+
.idea

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
language: node_js
22
node_js:
3-
- 5
4-
- 6
3+
- 8
4+
- 10
55
after_success:
66
- npm run coveralls

README.md

Lines changed: 21 additions & 6 deletions
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

@@ -13,20 +13,20 @@ React Native specific linting rules for ESLint. This repository is structured li
1313
Install [ESLint](https://www.github.com/eslint/eslint) either locally or globally.
1414

1515
```sh
16-
$ npm install eslint
16+
$ npm install --save-dev eslint
1717
```
1818

1919
To make most use of this plugin, its recommended to install [eslint-plugin-react](http://github.com/yannickcr/eslint-plugin-react) in addition to [ESLint](https://www.github.com/eslint/eslint). If you installed `ESLint` globally, you have to install eslint-plugin-react globally too. Otherwise, install it locally.
2020

2121
```sh
22-
$ npm install eslint-plugin-react
22+
$ npm install --save-dev eslint-plugin-react
2323
```
2424

2525
Similarly, install eslint-plugin-react-native
2626

2727

2828
```sh
29-
$ npm install eslint-plugin-react-native
29+
$ npm install --save-dev eslint-plugin-react-native
3030
```
3131

3232
# Configuration
@@ -46,8 +46,20 @@ If it is not already the case you must also configure `ESLint` to support JSX.
4646

4747
```json
4848
{
49-
"ecmaFeatures": {
50-
"jsx": true
49+
"parserOptions": {
50+
"ecmaFeatures": {
51+
"jsx": true
52+
}
53+
}
54+
}
55+
```
56+
57+
In order to whitelist all *browser-like* globals, add `react-native/react-native` to your config.
58+
59+
```json
60+
{
61+
"env": {
62+
"react-native/react-native": true
5163
}
5264
}
5365
```
@@ -61,16 +73,19 @@ Finally, enable all of the rules that you would like to use.
6173
"react-native/split-platform-components": 2,
6274
"react-native/no-inline-styles": 2,
6375
"react-native/no-color-literals": 2,
76+
"react-native/no-raw-text": 2,
6477
}
6578
}
6679
```
6780

6881
# List of supported rules
6982

7083
* [no-unused-styles](docs/rules/no-unused-styles.md): Detect `StyleSheet` rules which are not used in your React components
84+
* [sort-styles](docs/rules/sort-styles.md): Require style definitions to be sorted alphabetically
7185
* [split-platform-components](docs/rules/split-platform-components.md): Enforce using platform specific filenames when necessary
7286
* [no-inline-styles](docs/rules/no-inline-styles.md): Detect JSX components with inline styles that contain literal values
7387
* [no-color-literals](docs/rules/no-color-literals.md): Detect `StyleSheet` rules and inline styles containing color literals instead of variables
88+
* [no-raw-text](docs/rules/no-raw-text.md): Detect raw text outside of `Text` component
7489

7590
[npm-url]: https://npmjs.org/package/eslint-plugin-react-native
7691
[npm-image]: http://img.shields.io/npm/v/eslint-plugin-react-native.svg?style=flat-square

docs/rules/no-raw-text.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Detect raw text outside of Text component
2+
All strings in React Native should be wrapped with a Text component.
3+
4+
## Rule Details
5+
6+
The following patterns are considered warnings:
7+
8+
```js
9+
<View>some text</View>
10+
```
11+
12+
```js
13+
const text = 'some text';
14+
<View>{`${text}`}</View>
15+
```
16+
17+
The following patterns are not considered warnings:
18+
19+
```js
20+
<View><Text>some text</Text></View>
21+
```
22+
23+
```js
24+
const text = 'some text';
25+
<View><Text>{`${text}`}</Text></View>
26+
```
27+
28+
#### This rule has an object option:
29+
30+
- "skip" – allow to skip checking for the array of custom components

docs/rules/no-unused-styles.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,4 @@ const styles = StyleSheet.create({
120120
text: {}
121121
});
122122
```
123-
There should be atleast one component, so centralized `StyleSheets` are not checked for unused rules.
123+
There should be at least one component, so centralized `StyleSheets` are not checked for unused rules.

docs/rules/sort-styles.md

Lines changed: 132 additions & 0 deletions
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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ 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'),
11+
'no-raw-text': require('./lib/rules/no-raw-text'),
1012
};
1113

1214
function configureAsError(rules) {
@@ -29,7 +31,14 @@ module.exports = {
2931
'no-unused-styles': 0,
3032
'no-inline-styles': 0,
3133
'no-color-literals': 0,
34+
'sort-styles': 0,
3235
'split-platform-components': 0,
36+
'no-raw-text': 0,
37+
},
38+
environments: {
39+
'react-native': {
40+
globals: require('eslint-plugin-react-native-globals').environments.all.globals,
41+
},
3342
},
3443
configs: {
3544
all: {

lib/rules/no-color-literals.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ module.exports = Components.detect((context) => {
3232

3333
return {
3434
VariableDeclarator: (node) => {
35-
if (astHelpers.isStyleSheetDeclaration(node)) {
35+
if (astHelpers.isStyleSheetDeclaration(node, context.settings)) {
3636
const styles = astHelpers.getStyleDeclarations(node);
3737

3838
if (styles) {

lib/rules/no-raw-text.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* @fileoverview Detects raw text outside of Text component
3+
* @author Alex Zhukov
4+
*/
5+
6+
'use strict';
7+
8+
module.exports = (context) => {
9+
const options = context.options[0] || {};
10+
11+
const elementName = node => (
12+
node.openingElement &&
13+
node.openingElement.name &&
14+
node.openingElement.name.type === 'JSXIdentifier' &&
15+
node.openingElement.name.name
16+
);
17+
18+
const report = (node) => {
19+
const errorValue = node.type === 'TemplateLiteral'
20+
? `TemplateLiteral: ${node.expressions[0].name}`
21+
: node.value.trim();
22+
23+
const formattedErrorValue = errorValue.length > 0
24+
? `Raw text (${errorValue})`
25+
: 'Whitespace(s)';
26+
27+
context.report({
28+
node,
29+
message: `${formattedErrorValue} cannot be used outside of a <Text> tag`,
30+
});
31+
};
32+
33+
const skippedElements = options.skip ? options.skip : [];
34+
const allowedElements = ['Text', 'TSpan', 'StyledText'].concat(skippedElements);
35+
36+
const getValidation = node => !allowedElements.includes(elementName(node.parent));
37+
38+
return {
39+
Literal(node) {
40+
const parentType = node.parent.type;
41+
const onlyFor = ['JSXExpressionContainer', 'JSXElement'];
42+
if (typeof node.value !== 'string' ||
43+
/^[\r\n\t\f\v]+$/.test(node.value.replace(/ /g, '')) ||
44+
!onlyFor.includes(parentType) ||
45+
(node.parent.parent && node.parent.parent.type === 'JSXAttribute')
46+
) return;
47+
48+
const isStringLiteral = parentType === 'JSXExpressionContainer';
49+
if (getValidation(isStringLiteral ? node.parent : node)) {
50+
report(node);
51+
}
52+
},
53+
54+
JSXText(node) {
55+
if (getValidation(node)) {
56+
report(node);
57+
}
58+
},
59+
60+
TemplateLiteral(node) {
61+
if (
62+
node.parent.type !== 'JSXExpressionContainer' ||
63+
(node.parent.parent && node.parent.parent.type === 'JSXAttribute')
64+
) return;
65+
66+
if (getValidation(node.parent)) {
67+
report(node);
68+
}
69+
},
70+
};
71+
};
72+
73+
module.exports.schema = [
74+
{
75+
type: 'object',
76+
properties: {
77+
skip: {
78+
type: 'array',
79+
items: {
80+
type: 'string',
81+
},
82+
},
83+
},
84+
additionalProperties: false,
85+
},
86+
];

lib/rules/no-unused-styles.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ module.exports = Components.detect((context, components) => {
4242
},
4343

4444
VariableDeclarator: function (node) {
45-
if (astHelpers.isStyleSheetDeclaration(node)) {
45+
if (astHelpers.isStyleSheetDeclaration(node, context.settings)) {
4646
const styleSheetName = astHelpers.getStyleSheetName(node);
4747
const styles = astHelpers.getStyleDeclarations(node);
4848

0 commit comments

Comments
 (0)