Skip to content

Commit 4cb2e9e

Browse files
committed
Add shared setting for pragma configuration (fixes jsx-eslint#228)
1 parent f1a5fe2 commit 4cb2e9e

12 files changed

+229
-55
lines changed

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ Add `plugins` section and specify ESLint-plugin-React as a plugin.
3131
}
3232
```
3333

34+
You can also specify some settings that will be shared across all the plugin rules.
35+
36+
```js
37+
{
38+
"settings": {
39+
"react": {
40+
"pragma": "React" // Pragma to use, default to "React"
41+
}
42+
}
43+
}
44+
```
45+
3446
If it is not already the case you must also configure `ESLint` to support JSX.
3547

3648
With ESLint 1.x.x:

docs/rules/jsx-uses-react.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ var Foo = require('foo');
4040
var Hello = <div>Hello {this.props.name}</div>;
4141
```
4242

43-
4443
## Rule Options
4544

4645
```js
@@ -51,6 +50,8 @@ var Hello = <div>Hello {this.props.name}</div>;
5150

5251
### `pragma`
5352

53+
**Deprecation notice**: This option is deprecated, please use the [shared settings](README.md#configuration) to specify a custom pragma.
54+
5455
As an alternative to specifying the above pragma in each source file, you can specify
5556
this configuration option:
5657

lib/rules/jsx-uses-react.js

+4-10
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,15 @@
55
'use strict';
66

77
var variableUtil = require('../util/variable');
8+
var pragmaUtil = require('../util/pragma');
89

910
// ------------------------------------------------------------------------------
1011
// Rule Definition
1112
// ------------------------------------------------------------------------------
1213

13-
var JSX_ANNOTATION_REGEX = /^\*\s*@jsx\s+([^\s]+)/;
14-
1514
module.exports = function(context) {
1615

17-
var config = context.options[0] || {};
18-
var id = config.pragma || 'React';
16+
var pragma = pragmaUtil.getFromContext(context);
1917

2018
// --------------------------------------------------------------------------
2119
// Public
@@ -24,15 +22,11 @@ module.exports = function(context) {
2422
return {
2523

2624
JSXOpeningElement: function() {
27-
variableUtil.markVariableAsUsed(context, id);
25+
variableUtil.markVariableAsUsed(context, pragma);
2826
},
2927

3028
BlockComment: function(node) {
31-
var matches = JSX_ANNOTATION_REGEX.exec(node.value);
32-
if (!matches) {
33-
return;
34-
}
35-
id = matches[1].split('.')[0];
29+
pragma = pragmaUtil.getFromNode(node) || pragma;
3630
}
3731

3832
};

lib/rules/no-deprecated.js

+45-28
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,14 @@
55
*/
66
'use strict';
77

8+
var pragmaUtil = require('../util/pragma');
9+
810
// ------------------------------------------------------------------------------
911
// Constants
1012
// ------------------------------------------------------------------------------
1113

1214
var DEPRECATED_MESSAGE = '{{oldMethod}} is deprecated since React {{version}}{{newMethod}}';
1315

14-
var DEPRECATED = {
15-
MemberExpression: {
16-
// 0.12.0
17-
'React.renderComponent': ['0.12.0', 'React.render'],
18-
'React.renderComponentToString': ['0.12.0', 'React.renderToString'],
19-
'React.renderComponentToStaticMarkup': ['0.12.0', 'React.renderToStaticMarkup'],
20-
'React.isValidComponent': ['0.12.0', 'React.isValidElement'],
21-
'React.PropTypes.component': ['0.12.0', 'React.PropTypes.element'],
22-
'React.PropTypes.renderable': ['0.12.0', 'React.PropTypes.node'],
23-
'React.isValidClass': ['0.12.0'],
24-
'this.transferPropsTo': ['0.12.0', 'spread operator ({...})'],
25-
// 0.13.0
26-
'React.addons.classSet': ['0.13.0', 'the npm module classnames'],
27-
'React.addons.cloneWithProps': ['0.13.0', 'React.cloneElement'],
28-
// 0.14.0
29-
'React.render': ['0.14.0', 'ReactDOM.render'],
30-
'React.unmountComponentAtNode': ['0.14.0', 'ReactDOM.unmountComponentAtNode'],
31-
'React.findDOMNode': ['0.14.0', 'ReactDOM.findDOMNode'],
32-
'React.renderToString': ['0.14.0', 'ReactDOMServer.renderToString'],
33-
'React.renderToStaticMarkup': ['0.14.0', 'ReactDOMServer.renderToStaticMarkup']
34-
}
35-
};
36-
3716
// ------------------------------------------------------------------------------
3817
// Rule Definition
3918
// ------------------------------------------------------------------------------
@@ -48,6 +27,37 @@ module.exports = function(context) {
4827
return Number(part);
4928
});
5029

30+
var pragma = pragmaUtil.getFromContext(context);
31+
32+
function getDeprecated() {
33+
var deprecated = {
34+
MemberExpression: {}
35+
};
36+
// 0.12.0
37+
deprecated.MemberExpression[pragma + '.renderComponent'] = ['0.12.0', pragma + '.render'];
38+
deprecated.MemberExpression[pragma + '.renderComponentToString'] = ['0.12.0', pragma + '.renderToString'];
39+
deprecated.MemberExpression[pragma + '.renderComponentToStaticMarkup'] = [
40+
'0.12.0',
41+
pragma + '.renderToStaticMarkup'
42+
];
43+
deprecated.MemberExpression[pragma + '.isValidComponent'] = ['0.12.0', pragma + '.isValidElement'];
44+
deprecated.MemberExpression[pragma + '.PropTypes.component'] = ['0.12.0', pragma + '.PropTypes.element'];
45+
deprecated.MemberExpression[pragma + '.PropTypes.renderable'] = ['0.12.0', pragma + '.PropTypes.node'];
46+
deprecated.MemberExpression[pragma + '.isValidClass'] = ['0.12.0'];
47+
deprecated.MemberExpression['this.transferPropsTo'] = ['0.12.0', 'spread operator ({...})'];
48+
// 0.13.0
49+
deprecated.MemberExpression[pragma + '.addons.classSet'] = ['0.13.0', 'the npm module classnames'];
50+
deprecated.MemberExpression[pragma + '.addons.cloneWithProps'] = ['0.13.0', pragma + '.cloneElement'];
51+
// 0.14.0
52+
deprecated.MemberExpression[pragma + '.render'] = ['0.14.0', 'ReactDOM.render'];
53+
deprecated.MemberExpression[pragma + '.unmountComponentAtNode'] = ['0.14.0', 'ReactDOM.unmountComponentAtNode'];
54+
deprecated.MemberExpression[pragma + '.findDOMNode'] = ['0.14.0', 'ReactDOM.findDOMNode'];
55+
deprecated.MemberExpression[pragma + '.renderToString'] = ['0.14.0', 'ReactDOMServer.renderToString'];
56+
deprecated.MemberExpression[pragma + '.renderToStaticMarkup'] = ['0.14.0', 'ReactDOMServer.renderToStaticMarkup'];
57+
58+
return deprecated;
59+
}
60+
5161
function checkVersion(methodVer) {
5262
methodVer = methodVer.split('.').map(function(part) {
5363
return Number(part);
@@ -60,10 +70,12 @@ module.exports = function(context) {
6070
}
6171

6272
function isDeprecated(type, method) {
73+
var deprecated = getDeprecated();
74+
6375
return (
64-
DEPRECATED[type] &&
65-
DEPRECATED[type][method] &&
66-
checkVersion(DEPRECATED[type][method][0])
76+
deprecated[type] &&
77+
deprecated[type][method] &&
78+
checkVersion(deprecated[type][method][0])
6779
);
6880
}
6981

@@ -78,11 +90,16 @@ module.exports = function(context) {
7890
if (!isDeprecated(node.type, method)) {
7991
return;
8092
}
93+
var deprecated = getDeprecated();
8194
context.report(node, DEPRECATED_MESSAGE, {
8295
oldMethod: method,
83-
version: DEPRECATED[node.type][method][0],
84-
newMethod: DEPRECATED[node.type][method][1] ? ', use ' + DEPRECATED[node.type][method][1] + ' instead' : ''
96+
version: deprecated[node.type][method][0],
97+
newMethod: deprecated[node.type][method][1] ? ', use ' + deprecated[node.type][method][1] + ' instead' : ''
8598
});
99+
},
100+
101+
BlockComment: function(node) {
102+
pragma = pragmaUtil.getFromNode(node) || pragma;
86103
}
87104

88105
};

lib/rules/react-in-jsx-scope.js

+5-10
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,31 @@
55
'use strict';
66

77
var variableUtil = require('../util/variable');
8+
var pragmaUtil = require('../util/pragma');
89

910
// -----------------------------------------------------------------------------
1011
// Rule Definition
1112
// -----------------------------------------------------------------------------
1213

13-
var JSX_ANNOTATION_REGEX = /^\*\s*@jsx\s+([^\s]+)/;
14-
1514
module.exports = function(context) {
1615

17-
var id = 'React';
16+
var pragma = pragmaUtil.getFromContext(context);
1817
var NOT_DEFINED_MESSAGE = '\'{{name}}\' must be in scope when using JSX';
1918

2019
return {
2120

2221
JSXOpeningElement: function(node) {
2322
var variables = variableUtil.variablesInScope(context);
24-
if (variableUtil.findVariable(variables, id)) {
23+
if (variableUtil.findVariable(variables, pragma)) {
2524
return;
2625
}
2726
context.report(node, NOT_DEFINED_MESSAGE, {
28-
name: id
27+
name: pragma
2928
});
3029
},
3130

3231
BlockComment: function(node) {
33-
var matches = JSX_ANNOTATION_REGEX.exec(node.value);
34-
if (!matches) {
35-
return;
36-
}
37-
id = matches[1].split('.')[0];
32+
pragma = pragmaUtil.getFromNode(node) || pragma;
3833
}
3934

4035
};

lib/util/Components.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
var util = require('util');
88
var variableUtil = require('./variable');
9+
var pragmaUtil = require('./pragma');
910

1011
/**
1112
* Components
@@ -104,6 +105,7 @@ Components.prototype.length = function() {
104105

105106
function componentRule(rule, context) {
106107

108+
var pragma = pragmaUtil.getFromContext(context);
107109
var sourceCode = context.getSourceCode();
108110
var components = new Components();
109111

@@ -120,7 +122,7 @@ function componentRule(rule, context) {
120122
if (!node.parent) {
121123
return false;
122124
}
123-
return /^(React\.)?createClass$/.test(sourceCode.getText(node.parent.callee));
125+
return new RegExp('^(' + pragma + '\\.)?createClass$').test(sourceCode.getText(node.parent.callee));
124126
},
125127

126128
/**
@@ -133,7 +135,7 @@ function componentRule(rule, context) {
133135
if (!node.superClass) {
134136
return false;
135137
}
136-
return /^(React\.)?Component$/.test(sourceCode.getText(node.superClass));
138+
return new RegExp('^(' + pragma + '\\.)?Component$').test(sourceCode.getText(node.superClass));
137139
},
138140

139141
/**
@@ -374,6 +376,10 @@ function componentRule(rule, context) {
374376
components.add(node, 0);
375377
},
376378

379+
BlockComment: function(node) {
380+
pragma = pragmaUtil.getFromNode(node) || pragma;
381+
},
382+
377383
ReturnStatement: function(node) {
378384
if (!utils.isReturningJSX(node)) {
379385
return;

lib/util/pragma.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* @fileoverview Utility functions for React pragma configuration
3+
* @author Yannick Croissant
4+
*/
5+
'use strict';
6+
7+
var JSX_ANNOTATION_REGEX = /^\*\s*@jsx\s+([^\s]+)/;
8+
9+
function getFromContext(context) {
10+
var pragma = 'React';
11+
// .eslintrc shared settings (http://eslint.org/docs/user-guide/configuring#adding-shared-settings)
12+
if (context.settings.react && context.settings.react.pragma) {
13+
pragma = context.settings.react.pragma;
14+
// Deprecated pragma option, here for backward compatibility
15+
} else if (context.options[0] && context.options[0].pragma) {
16+
pragma = context.options[0].pragma;
17+
}
18+
return pragma.split('.')[0];
19+
}
20+
21+
function getFromNode(node) {
22+
var matches = JSX_ANNOTATION_REGEX.exec(node.value);
23+
if (!matches) {
24+
return false;
25+
}
26+
return matches[1].split('.')[0];
27+
}
28+
29+
module.exports = {
30+
getFromContext: getFromContext,
31+
getFromNode: getFromNode
32+
};

tests/lib/rules/display-name.js

+38
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ var parserOptions = {
2121
}
2222
};
2323

24+
var settings = {
25+
react: {
26+
pragma: 'Foo'
27+
}
28+
};
29+
2430
// ------------------------------------------------------------------------------
2531
// Tests
2632
// ------------------------------------------------------------------------------
@@ -417,6 +423,38 @@ ruleTester.run('display-name', rule, {
417423
errors: [{
418424
message: 'Component definition is missing display name'
419425
}]
426+
}, {
427+
code: [
428+
'var Hello = Foo.createClass({',
429+
' _renderHello: function() {',
430+
' return <span>Hello {this.props.name}</span>;',
431+
' },',
432+
' render: function() {',
433+
' return <div>{this._renderHello()}</div>;',
434+
' }',
435+
'});'
436+
].join('\n'),
437+
parser: 'babel-eslint',
438+
settings: settings,
439+
errors: [{
440+
message: 'Component definition is missing display name'
441+
}]
442+
}, {
443+
code: [
444+
'/** @jsx Foo */',
445+
'var Hello = Foo.createClass({',
446+
' _renderHello: function() {',
447+
' return <span>Hello {this.props.name}</span>;',
448+
' },',
449+
' render: function() {',
450+
' return <div>{this._renderHello()}</div>;',
451+
' }',
452+
'});'
453+
].join('\n'),
454+
parser: 'babel-eslint',
455+
errors: [{
456+
message: 'Component definition is missing display name'
457+
}]
420458
}, {
421459
code: [
422460
'const Mixin = {',

tests/lib/rules/jsx-uses-react.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ var parserOptions = {
2020
}
2121
};
2222

23+
var settings = {
24+
react: {
25+
pragma: 'Foo'
26+
}
27+
};
28+
2329
// -----------------------------------------------------------------------------
2430
// Tests
2531
// -----------------------------------------------------------------------------
@@ -31,12 +37,15 @@ ruleTester.run('no-unused-vars', rule, {
3137
{code: '/*eslint jsx-uses-react:1*/ var React; <div />;', parserOptions: parserOptions},
3238
{code: '/*eslint jsx-uses-react:1*/ var React; (function () { <div /> })();', parserOptions: parserOptions},
3339
{code: '/*eslint jsx-uses-react:1*/ /** @jsx Foo */ var Foo; <div />;', parserOptions: parserOptions},
34-
{code: '/*eslint jsx-uses-react:[1,{"pragma":"Foo"}]*/ var Foo; <div />;', parserOptions: parserOptions}
40+
{code: '/*eslint jsx-uses-react:[1,{"pragma":"Foo"}]*/ var Foo; <div />;', parserOptions: parserOptions},
41+
{code: '/*eslint jsx-uses-react:1*/ var Foo; <div />;', settings: settings, parserOptions: parserOptions}
3542
],
3643
invalid: [
3744
{code: '/*eslint jsx-uses-react:1*/ var React;',
3845
errors: [{message: '"React" is defined but never used'}], parserOptions: parserOptions},
3946
{code: '/*eslint jsx-uses-react:1*/ /** @jsx Foo */ var React; <div />;',
40-
errors: [{message: '"React" is defined but never used'}], parserOptions: parserOptions}
47+
errors: [{message: '"React" is defined but never used'}], parserOptions: parserOptions},
48+
{code: '/*eslint jsx-uses-react:1*/ var React; <div />;',
49+
errors: [{message: '"React" is defined but never used'}], settings: settings, parserOptions: parserOptions}
4150
]
4251
});

0 commit comments

Comments
 (0)