Skip to content

Commit

Permalink
Add shared setting for pragma configuration (fixes #228)
Browse files Browse the repository at this point in the history
  • Loading branch information
yannickcr committed Dec 29, 2015
1 parent f1a5fe2 commit 02ecd33
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 27 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion docs/rules/jsx-uses-react.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ var Foo = require('foo');
var Hello = <div>Hello {this.props.name}</div>;
```


## Rule Options

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

### `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:

Expand Down
14 changes: 4 additions & 10 deletions lib/rules/jsx-uses-react.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}

};
Expand Down
15 changes: 5 additions & 10 deletions lib/rules/react-in-jsx-scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

};
Expand Down
10 changes: 8 additions & 2 deletions lib/util/Components.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

var util = require('util');
var variableUtil = require('./variable');
var pragmaUtil = require('./pragma');

/**
* Components
Expand Down Expand Up @@ -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();

Expand All @@ -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));
},

/**
Expand All @@ -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));
},

/**
Expand Down Expand Up @@ -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;
Expand Down
32 changes: 32 additions & 0 deletions lib/util/pragma.js
Original file line number Diff line number Diff line change
@@ -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
};
38 changes: 38 additions & 0 deletions tests/lib/rules/display-name.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ var parserOptions = {
}
};

var settings = {
react: {
pragma: 'Foo'
}
};

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -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 <span>Hello {this.props.name}</span>;',
' },',
' render: function() {',
' return <div>{this._renderHello()}</div>;',
' }',
'});'
].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 <span>Hello {this.props.name}</span>;',
' },',
' render: function() {',
' return <div>{this._renderHello()}</div>;',
' }',
'});'
].join('\n'),
parser: 'babel-eslint',
errors: [{
message: 'Component definition is missing display name'
}]
}, {
code: [
'const Mixin = {',
Expand Down
13 changes: 11 additions & 2 deletions tests/lib/rules/jsx-uses-react.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ var parserOptions = {
}
};

var settings = {
react: {
pragma: 'Foo'
}
};

// -----------------------------------------------------------------------------
// Tests
// -----------------------------------------------------------------------------
Expand All @@ -31,12 +37,15 @@ ruleTester.run('no-unused-vars', rule, {
{code: '/*eslint jsx-uses-react:1*/ var React; <div />;', parserOptions: parserOptions},
{code: '/*eslint jsx-uses-react:1*/ var React; (function () { <div /> })();', parserOptions: parserOptions},
{code: '/*eslint jsx-uses-react:1*/ /** @jsx Foo */ var Foo; <div />;', parserOptions: parserOptions},
{code: '/*eslint jsx-uses-react:[1,{"pragma":"Foo"}]*/ var Foo; <div />;', parserOptions: parserOptions}
{code: '/*eslint jsx-uses-react:[1,{"pragma":"Foo"}]*/ var Foo; <div />;', parserOptions: parserOptions},
{code: '/*eslint jsx-uses-react:1*/ var Foo; <div />;', 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; <div />;',
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; <div />;',
errors: [{message: '"React" is defined but never used'}], settings: settings, parserOptions: parserOptions}
]
});
42 changes: 42 additions & 0 deletions tests/lib/rules/prop-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ var parserOptions = {
}
};

var settings = {
react: {
pragma: 'Foo'
}
};

require('babel-eslint');

// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -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 (',
' <div>{this.props.firstname} {this.props.lastname}</div>',
' );',
' }',
'}',
'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 (',
' <div>{this.props.firstname} {this.props.lastname}</div>',
' );',
' }',
'}',
'Test.propTypes = {',
' firstname: React.PropTypes.string',
'};'
].join('\n'),
parser: 'babel-eslint',
errors: [
{message: '\'lastname\' is missing in props validation'}
]
}
]
});
13 changes: 11 additions & 2 deletions tests/lib/rules/react-in-jsx-scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ var parserOptions = {
}
};

var settings = {
react: {
pragma: 'Foo'
}
};

// -----------------------------------------------------------------------------
// Tests
// -----------------------------------------------------------------------------
Expand All @@ -46,7 +52,8 @@ ruleTester.run('react-in-jsx-scope', rule, {
'});',
'export default Button;'
].join('\n'),
parserOptions: parserOptions}
parserOptions: parserOptions},
{code: 'var Foo, App; <App />;', settings: settings, parserOptions: parserOptions}
],
invalid: [
{code: 'var App, a = <App />;',
Expand All @@ -58,6 +65,8 @@ ruleTester.run('react-in-jsx-scope', rule, {
{code: '/** @jsx React.DOM */ var a = <img />;',
errors: [{message: '\'React\' must be in scope when using JSX'}], parserOptions: parserOptions},
{code: '/** @jsx Foo.bar */ var React, a = <img />;',
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 = <img />;',
errors: [{message: '\'Foo\' must be in scope when using JSX'}], settings: settings, parserOptions: parserOptions}
]
});

0 comments on commit 02ecd33

Please sign in to comment.