diff --git a/README.md b/README.md
index 437406b571..d8b38fd571 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,18 @@ Add `plugins` section and specify ESLint-plugin-React as a plugin.
}
```
+You can also specify some settings that will be shared across all the plugin rules.
+
+```js
+{
+ "settings": {
+ "react": {
+ "pragma": "React" // Pragma to use, default to "React"
+ }
+ }
+}
+```
+
If it is not already the case you must also configure `ESLint` to support JSX.
With ESLint 1.x.x:
diff --git a/docs/rules/jsx-uses-react.md b/docs/rules/jsx-uses-react.md
index 7ff269a4a6..a454bd1004 100644
--- a/docs/rules/jsx-uses-react.md
+++ b/docs/rules/jsx-uses-react.md
@@ -40,7 +40,6 @@ var Foo = require('foo');
var Hello =
Hello {this.props.name}
;
```
-
## Rule Options
```js
@@ -51,6 +50,8 @@ var Hello = Hello {this.props.name}
;
### `pragma`
+**Deprecation notice**: This option is deprecated, please use the [shared settings](README.md#configuration) to specify a custom pragma.
+
As an alternative to specifying the above pragma in each source file, you can specify
this configuration option:
diff --git a/lib/rules/jsx-uses-react.js b/lib/rules/jsx-uses-react.js
index 2e4839801d..c5fd74204c 100644
--- a/lib/rules/jsx-uses-react.js
+++ b/lib/rules/jsx-uses-react.js
@@ -5,17 +5,15 @@
'use strict';
var variableUtil = require('../util/variable');
+var pragmaUtil = require('../util/pragma');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
-var JSX_ANNOTATION_REGEX = /^\*\s*@jsx\s+([^\s]+)/;
-
module.exports = function(context) {
- var config = context.options[0] || {};
- var id = config.pragma || 'React';
+ var pragma = pragmaUtil.getFromContext(context);
// --------------------------------------------------------------------------
// Public
@@ -24,15 +22,11 @@ module.exports = function(context) {
return {
JSXOpeningElement: function() {
- variableUtil.markVariableAsUsed(context, id);
+ variableUtil.markVariableAsUsed(context, pragma);
},
BlockComment: function(node) {
- var matches = JSX_ANNOTATION_REGEX.exec(node.value);
- if (!matches) {
- return;
- }
- id = matches[1].split('.')[0];
+ pragma = pragmaUtil.getFromNode(node) || pragma;
}
};
diff --git a/lib/rules/react-in-jsx-scope.js b/lib/rules/react-in-jsx-scope.js
index de92fce142..550fddc3b0 100644
--- a/lib/rules/react-in-jsx-scope.js
+++ b/lib/rules/react-in-jsx-scope.js
@@ -5,36 +5,31 @@
'use strict';
var variableUtil = require('../util/variable');
+var pragmaUtil = require('../util/pragma');
// -----------------------------------------------------------------------------
// Rule Definition
// -----------------------------------------------------------------------------
-var JSX_ANNOTATION_REGEX = /^\*\s*@jsx\s+([^\s]+)/;
-
module.exports = function(context) {
- var id = 'React';
+ var pragma = pragmaUtil.getFromContext(context);
var NOT_DEFINED_MESSAGE = '\'{{name}}\' must be in scope when using JSX';
return {
JSXOpeningElement: function(node) {
var variables = variableUtil.variablesInScope(context);
- if (variableUtil.findVariable(variables, id)) {
+ if (variableUtil.findVariable(variables, pragma)) {
return;
}
context.report(node, NOT_DEFINED_MESSAGE, {
- name: id
+ name: pragma
});
},
BlockComment: function(node) {
- var matches = JSX_ANNOTATION_REGEX.exec(node.value);
- if (!matches) {
- return;
- }
- id = matches[1].split('.')[0];
+ pragma = pragmaUtil.getFromNode(node) || pragma;
}
};
diff --git a/lib/util/Components.js b/lib/util/Components.js
index c6823b569f..4222a36192 100644
--- a/lib/util/Components.js
+++ b/lib/util/Components.js
@@ -6,6 +6,7 @@
var util = require('util');
var variableUtil = require('./variable');
+var pragmaUtil = require('./pragma');
/**
* Components
@@ -104,6 +105,7 @@ Components.prototype.length = function() {
function componentRule(rule, context) {
+ var pragma = pragmaUtil.getFromContext(context);
var sourceCode = context.getSourceCode();
var components = new Components();
@@ -120,7 +122,7 @@ function componentRule(rule, context) {
if (!node.parent) {
return false;
}
- return /^(React\.)?createClass$/.test(sourceCode.getText(node.parent.callee));
+ return new RegExp('^(' + pragma + '\\.)?createClass$').test(sourceCode.getText(node.parent.callee));
},
/**
@@ -133,7 +135,7 @@ function componentRule(rule, context) {
if (!node.superClass) {
return false;
}
- return /^(React\.)?Component$/.test(sourceCode.getText(node.superClass));
+ return new RegExp('^(' + pragma + '\\.)?Component$').test(sourceCode.getText(node.superClass));
},
/**
@@ -374,6 +376,10 @@ function componentRule(rule, context) {
components.add(node, 0);
},
+ BlockComment: function(node) {
+ pragma = pragmaUtil.getFromNode(node) || pragma;
+ },
+
ReturnStatement: function(node) {
if (!utils.isReturningJSX(node)) {
return;
diff --git a/lib/util/pragma.js b/lib/util/pragma.js
new file mode 100644
index 0000000000..479257f47f
--- /dev/null
+++ b/lib/util/pragma.js
@@ -0,0 +1,32 @@
+/**
+ * @fileoverview Utility functions for React pragma configuration
+ * @author Yannick Croissant
+ */
+'use strict';
+
+var JSX_ANNOTATION_REGEX = /^\*\s*@jsx\s+([^\s]+)/;
+
+function getFromContext(context) {
+ var pragma = 'React';
+ // .eslintrc shared settings (http://eslint.org/docs/user-guide/configuring#adding-shared-settings)
+ if (context.settings.react && context.settings.react.pragma) {
+ pragma = context.settings.react.pragma;
+ // Deprecated pragma option, here for backward compatibility
+ } else if (context.options[0] && context.options[0].pragma) {
+ pragma = context.options[0].pragma;
+ }
+ return pragma.split('.')[0];
+}
+
+function getFromNode(node) {
+ var matches = JSX_ANNOTATION_REGEX.exec(node.value);
+ if (!matches) {
+ return false;
+ }
+ return matches[1].split('.')[0];
+}
+
+module.exports = {
+ getFromContext: getFromContext,
+ getFromNode: getFromNode
+};
diff --git a/tests/lib/rules/display-name.js b/tests/lib/rules/display-name.js
index 0490397f02..5033a0fa96 100644
--- a/tests/lib/rules/display-name.js
+++ b/tests/lib/rules/display-name.js
@@ -21,6 +21,12 @@ var parserOptions = {
}
};
+var settings = {
+ react: {
+ pragma: 'Foo'
+ }
+};
+
// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------
@@ -417,6 +423,38 @@ ruleTester.run('display-name', rule, {
errors: [{
message: 'Component definition is missing display name'
}]
+ }, {
+ code: [
+ 'var Hello = Foo.createClass({',
+ ' _renderHello: function() {',
+ ' return Hello {this.props.name};',
+ ' },',
+ ' render: function() {',
+ ' return {this._renderHello()}
;',
+ ' }',
+ '});'
+ ].join('\n'),
+ parser: 'babel-eslint',
+ settings: settings,
+ errors: [{
+ message: 'Component definition is missing display name'
+ }]
+ }, {
+ code: [
+ '/** @jsx Foo */',
+ 'var Hello = Foo.createClass({',
+ ' _renderHello: function() {',
+ ' return Hello {this.props.name};',
+ ' },',
+ ' render: function() {',
+ ' return {this._renderHello()}
;',
+ ' }',
+ '});'
+ ].join('\n'),
+ parser: 'babel-eslint',
+ errors: [{
+ message: 'Component definition is missing display name'
+ }]
}, {
code: [
'const Mixin = {',
diff --git a/tests/lib/rules/jsx-uses-react.js b/tests/lib/rules/jsx-uses-react.js
index a8d7db2633..709cfdd879 100644
--- a/tests/lib/rules/jsx-uses-react.js
+++ b/tests/lib/rules/jsx-uses-react.js
@@ -20,6 +20,12 @@ var parserOptions = {
}
};
+var settings = {
+ react: {
+ pragma: 'Foo'
+ }
+};
+
// -----------------------------------------------------------------------------
// Tests
// -----------------------------------------------------------------------------
@@ -31,12 +37,15 @@ ruleTester.run('no-unused-vars', rule, {
{code: '/*eslint jsx-uses-react:1*/ var React; ;', parserOptions: parserOptions},
{code: '/*eslint jsx-uses-react:1*/ var React; (function () { })();', parserOptions: parserOptions},
{code: '/*eslint jsx-uses-react:1*/ /** @jsx Foo */ var Foo; ;', parserOptions: parserOptions},
- {code: '/*eslint jsx-uses-react:[1,{"pragma":"Foo"}]*/ var Foo; ;', parserOptions: parserOptions}
+ {code: '/*eslint jsx-uses-react:[1,{"pragma":"Foo"}]*/ var Foo; ;', parserOptions: parserOptions},
+ {code: '/*eslint jsx-uses-react:1*/ var Foo; ;', settings: settings, parserOptions: parserOptions}
],
invalid: [
{code: '/*eslint jsx-uses-react:1*/ var React;',
errors: [{message: '"React" is defined but never used'}], parserOptions: parserOptions},
{code: '/*eslint jsx-uses-react:1*/ /** @jsx Foo */ var React; ;',
- errors: [{message: '"React" is defined but never used'}], parserOptions: parserOptions}
+ errors: [{message: '"React" is defined but never used'}], parserOptions: parserOptions},
+ {code: '/*eslint jsx-uses-react:1*/ var React; ;',
+ errors: [{message: '"React" is defined but never used'}], settings: settings, parserOptions: parserOptions}
]
});
diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js
index bb20dfe4ac..63d5bc2364 100644
--- a/tests/lib/rules/prop-types.js
+++ b/tests/lib/rules/prop-types.js
@@ -19,6 +19,12 @@ var parserOptions = {
}
};
+var settings = {
+ react: {
+ pragma: 'Foo'
+ }
+};
+
require('babel-eslint');
// ------------------------------------------------------------------------------
@@ -1426,6 +1432,42 @@ ruleTester.run('prop-types', rule, {
errors: [
{message: '\'lastname\' is missing in props validation'}
]
+ }, {
+ code: [
+ 'class Test extends Foo.Component {',
+ ' render() {',
+ ' return (',
+ ' {this.props.firstname} {this.props.lastname}
',
+ ' );',
+ ' }',
+ '}',
+ 'Test.propTypes = {',
+ ' firstname: React.PropTypes.string',
+ '};'
+ ].join('\n'),
+ parser: 'babel-eslint',
+ settings: settings,
+ errors: [
+ {message: '\'lastname\' is missing in props validation'}
+ ]
+ }, {
+ code: [
+ '/** @jsx Foo */',
+ 'class Test extends Foo.Component {',
+ ' render() {',
+ ' return (',
+ ' {this.props.firstname} {this.props.lastname}
',
+ ' );',
+ ' }',
+ '}',
+ 'Test.propTypes = {',
+ ' firstname: React.PropTypes.string',
+ '};'
+ ].join('\n'),
+ parser: 'babel-eslint',
+ errors: [
+ {message: '\'lastname\' is missing in props validation'}
+ ]
}
]
});
diff --git a/tests/lib/rules/react-in-jsx-scope.js b/tests/lib/rules/react-in-jsx-scope.js
index 33f8df2ce5..4dbb28c2bd 100644
--- a/tests/lib/rules/react-in-jsx-scope.js
+++ b/tests/lib/rules/react-in-jsx-scope.js
@@ -20,6 +20,12 @@ var parserOptions = {
}
};
+var settings = {
+ react: {
+ pragma: 'Foo'
+ }
+};
+
// -----------------------------------------------------------------------------
// Tests
// -----------------------------------------------------------------------------
@@ -46,7 +52,8 @@ ruleTester.run('react-in-jsx-scope', rule, {
'});',
'export default Button;'
].join('\n'),
- parserOptions: parserOptions}
+ parserOptions: parserOptions},
+ {code: 'var Foo, App; ;', settings: settings, parserOptions: parserOptions}
],
invalid: [
{code: 'var App, a = ;',
@@ -58,6 +65,8 @@ ruleTester.run('react-in-jsx-scope', rule, {
{code: '/** @jsx React.DOM */ var a =
;',
errors: [{message: '\'React\' must be in scope when using JSX'}], parserOptions: parserOptions},
{code: '/** @jsx Foo.bar */ var React, a =
;',
- errors: [{message: '\'Foo\' must be in scope when using JSX'}], parserOptions: parserOptions}
+ errors: [{message: '\'Foo\' must be in scope when using JSX'}], parserOptions: parserOptions},
+ {code: 'var React, a =
;',
+ errors: [{message: '\'Foo\' must be in scope when using JSX'}], settings: settings, parserOptions: parserOptions}
]
});